feat: epg integration

This commit is contained in:
4gray committed 2021-01-31 23:16:23 +01:00
1 parent 1dc3c22fd4
commit 2e861b6a1f
6 files changed
+93 -31

No files matched your search

+1 -10
View File
@@ -1,7 +1,5 @@
import { Component, NgZone } from '@angular/core'; import { Component, NgZone } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { AppConfig } from '../environments/environment';
import { akitaDevtools } from '@datorama/akita';
import { Titlebar, Color } from 'custom-electron-titlebar'; import { Titlebar, Color } from 'custom-electron-titlebar';
import { ElectronService } from './services/electron.service'; import { ElectronService } from './services/electron.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -33,11 +31,7 @@ export class AppComponent {
private snackBar: MatSnackBar, private snackBar: MatSnackBar,
private storage: StorageMap private storage: StorageMap
) { ) {
if (!AppConfig.production) {
akitaDevtools(ngZone);
}
this.translate.setDefaultLang('en'); this.translate.setDefaultLang('en');
console.log('AppConfig', AppConfig);
if (electronService.isElectron) { if (electronService.isElectron) {
console.log(process.env); console.log(process.env);
@@ -61,10 +55,7 @@ export class AppComponent {
this.electronService.ipcRenderer.on(EPG_FETCH_DONE, () => { this.electronService.ipcRenderer.on(EPG_FETCH_DONE, () => {
this.ngZone.run(() => { this.ngZone.run(() => {
this.channelStore.update((store) => ({ this.channelStore.setEpgAvailableFlag(true);
...store,
epgAvailable: true,
}));
this.snackBar.open('EPG was successfully downloaded!', null, { this.snackBar.open('EPG was successfully downloaded!', null, {
duration: 2000, duration: 2000,
verticalPosition: 'bottom', verticalPosition: 'bottom',
@@ -1,27 +1,42 @@
<ng-container *ngIf="channel; else noEpg"> <ng-container *ngIf="channel; else noEpg">
<div id="channel-info" fxLayoutAlign="row"> <div fxFlex="80px" id="channel-info" fxLayoutAlign="row" fxLayoutGap="10px">
<img <img *ngIf="channel?.icon" [src]="channel.icon" width="48" />
fxFlex="48px" <div>
*ngIf="channel?.icon" <div class="channel-name">{{ channel?.name[0]?.value }}</div>
[src]="channel.icon" <ng-container>
width="48" <div class="playing-now">Playing now:</div>
/> <div class="epg-program-name">
<div fxFlex="100" fxLayoutAlign="start center" class="channel-name"> {{
{{ channel?.name[0]?.value }} playingNow ? playingNow.title[0]?.value : 'Live stream'
}}
</div>
</ng-container>
</div> </div>
</div> </div>
<mat-list> <mat-selection-list fxFlex [multiple]="false">
<mat-list-item <mat-list-option
*ngFor="let item of items" *ngFor="let item of items"
[value]="item"
[class.active]="timeNow >= item.start && timeNow <= item.stop" [class.active]="timeNow >= item.start && timeNow <= item.stop"
(click)="
setEpgProgram(
item,
timeNow >= item.start && timeNow <= item.stop
)
"
> >
<div matLine> <div matLine>
<span class="time">{{ item.start }} - {{ item.stop }}</span> <span class="time">{{ item.start }} - {{ item.stop }}</span>
<span
*ngIf="timeNow >= item.start && timeNow <= item.stop"
class="live-badge"
>live</span
>
</div> </div>
<p matLine>{{ item?.title[0]?.value }}</p> <p matLine>{{ item?.title[0]?.value }}</p>
<mat-divider></mat-divider> <mat-divider></mat-divider>
</mat-list-item> </mat-list-option>
</mat-list> </mat-selection-list>
</ng-container> </ng-container>
<ng-template #noEpg> <ng-template #noEpg>
<mat-list> <mat-list>
@@ -1,8 +1,21 @@
.active { :host {
background-color: #ddd; display: flex;
flex-direction: column;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
height: 100%;
} }
.time { .active {
border-bottom: 1px dashed #666;
background: linear-gradient(to top, #d2d2d2 0%, #d6d6d6 20%, rgba(255, 255, 255, 0) 100%)
}
.time, .playing-now {
color: #666; color: #666;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
font-size: 0.8em; font-size: 0.8em;
@@ -10,16 +23,41 @@
margin-bottom: 5px; margin-bottom: 5px;
} }
.epg-program-name {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
font-size: 0.9em;
}
#channel-info { #channel-info {
padding:10px; padding:10px;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
background-color: #ccc;
} }
.channel-name { .channel-name {
padding-left: 10px font-weight: 800;
} }
mat-list { mat-list {
height: calc(100vh - 110px); height: calc(100vh - 110px);
overflow: auto; overflow: auto;
}
mat-selection-list {
overflow: auto;
}
.live-badge {
background-color: red;
border-radius: 2px;
padding: 2px 6px;
color: #fff;
text-transform: uppercase;
margin-left: 6px;
font-size: 0.8em;
} }
@@ -4,6 +4,7 @@ import { EpgProgram } from '../../models/epg-program.model';
import * as moment from 'moment'; import * as moment from 'moment';
import { ElectronService } from '../../../services/electron.service'; import { ElectronService } from '../../../services/electron.service';
import { EPG_GET_PROGRAM_DONE } from '../../../shared/ipc-commands'; import { EPG_GET_PROGRAM_DONE } from '../../../shared/ipc-commands';
import { ChannelStore } from 'app/state';
interface EpgData { interface EpgData {
channel: EpgChannel; channel: EpgChannel;
@@ -30,6 +31,9 @@ export class EpgListComponent {
payload: EpgData; payload: EpgData;
}; };
/** EPG selected program */
playingNow: EpgProgram;
/** Current time as formatted string */ /** Current time as formatted string */
timeNow: string; timeNow: string;
@@ -39,6 +43,7 @@ export class EpgListComponent {
* @param ngZone * @param ngZone
*/ */
constructor( constructor(
private channelStore: ChannelStore,
private electronService: ElectronService, private electronService: ElectronService,
private ngZone: NgZone private ngZone: NgZone
) { ) {
@@ -81,6 +86,17 @@ export class EpgListComponent {
} }
} }
/**
* Sets the provided epg program as active and starts to play
* @param program
*/
setEpgProgram(program: EpgProgram, isLive?: boolean): void {
isLive
? this.channelStore.resetActiveEpgProgram()
: this.channelStore.setActiveEpgProgram(program);
this.playingNow = program;
}
/** /**
* Removes all ipc renderer listeners after destroy * Removes all ipc renderer listeners after destroy
*/ */
@@ -41,16 +41,18 @@ export class HtmlVideoPlayerComponent implements OnChanges, OnDestroy {
* @param channel given channel object * @param channel given channel object
*/ */
playChannel(channel: Channel): void { playChannel(channel: Channel): void {
const url = channel.url + channel.epgParams;
if (Hls.isSupported()) { if (Hls.isSupported()) {
console.log('... switching channel to ', channel.name, channel.url); console.log('... switching channel to ', channel.name, url);
this.hls.loadSource(channel.url); this.hls.loadSource(url);
this.hls.attachMedia(this.videoPlayer.nativeElement); this.hls.attachMedia(this.videoPlayer.nativeElement);
this.videoPlayer.nativeElement.play();
} else if ( } else if (
this.videoPlayer.nativeElement.canPlayType( this.videoPlayer.nativeElement.canPlayType(
'application/vnd.apple.mpegurl' 'application/vnd.apple.mpegurl'
) )
) { ) {
this.videoPlayer.nativeElement.src = channel.url; this.videoPlayer.nativeElement.src = url;
this.videoPlayer.nativeElement.addEventListener( this.videoPlayer.nativeElement.addEventListener(
'loadedmetadata', 'loadedmetadata',
() => { () => {
@@ -55,7 +55,7 @@
[options]="{ [options]="{
sources: [ sources: [
{ {
src: activeChannel.url, src: activeChannel.url + activeChannel.epgParams,
type: 'application/x-mpegURL' type: 'application/x-mpegURL'
} }
] ]