mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 10:06:15 -08:00
feat: epg integration
This commit is contained in:
6 files changed
+93
-31
No files matched your search
@@ -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'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in new issue
Block a user