feat(xtream): update live streams layout

This commit is contained in:
4gray committed 2024-04-01 23:32:40 +02:00
1 parent b4954d370c
commit 2c3a06b6f9
5 files changed
+168 -13

No files matched your search

@@ -0,0 +1,40 @@
<div
[class]="
player === 'videojs' || player === 'html5'
? 'itv-container'
: 'itv-container-epg'
"
>
<div class="sidebar">
<div class="sidebar-content">
<mat-selection-list
[multiple]="false"
[hideSingleSelectionIndicator]="true"
>
@for (item of channels; track $index) {
<mat-list-option (click)="itemClicked.emit(item)">
<img
matListItemAvatar
class="stream-icon"
*ngIf="item.stream_icon"
[src]="item.stream_icon"
/>
<div class="title">{{ item.name }}</div>
</mat-list-option>
}
</mat-selection-list>
</div>
</div>
@if (player === 'videojs' || player === 'html5') {
<div class="video-player">
@if (streamUrl) {
<app-web-player-view [streamUrl]="streamUrl" />
}
</div>
}
<div class="epg">
<div class="epg-content">
<app-epg-view [epgItems]="epgItems" />
</div>
</div>
</div>
@@ -0,0 +1,47 @@
:host {
width: 100%;
}
.itv-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
gap: 0 0;
grid-auto-flow: row;
grid-template-areas:
'sidebar video-player video-player'
'sidebar epg epg'
'sidebar epg epg';
height: 100%;
}
.itv-container-epg {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
gap: 0 0;
grid-auto-flow: row;
grid-template-areas:
'sidebar epg epg'
'sidebar epg epg'
'sidebar epg epg';
height: 100%;
}
.sidebar { grid-area: sidebar; }
.video-player { grid-area: video-player; }
.epg { grid-area: epg; }
.epg-content {
height: 100%;
overflow-y: auto;
border-right: #111 1px solid;
}
.sidebar-content {
height: 100%;
overflow-y: auto;
border-right: #111 1px solid;
}
@@ -0,0 +1,24 @@
import { NgIf } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatListModule } from '@angular/material/list';
import { XtreamItem } from '../../../../shared/xtream-item.interface';
import { VideoPlayer } from '../../settings/settings.interface';
import { EpgItem } from '../../xtream/epg-item.interface';
import { EpgViewComponent } from '../epg-view/epg-view.component';
import { WebPlayerViewComponent } from '../web-player-view/web-player-view.component';
@Component({
standalone: true,
selector: 'app-live-stream-layout',
templateUrl: './live-stream-layout.component.html',
styleUrls: ['./live-stream-layout.component.scss'],
imports: [EpgViewComponent, MatListModule, NgIf, WebPlayerViewComponent],
})
export class LiveStreamLayoutComponent {
@Input({ required: true }) channels: XtreamItem[];
@Input({ required: true }) player: VideoPlayer = VideoPlayer.VideoJs;
@Input() epgItems: EpgItem[];
@Input() streamUrl: string;
@Output() itemClicked = new EventEmitter<XtreamItem>();
}
@@ -30,10 +30,20 @@
/>
}
@case ('category_content') {
<app-category-content-view
[items]="items"
(itemClicked)="itemClicked($event)"
/>
@if (selectedContentType === 'itv') {
<app-live-stream-layout
[channels]="items"
[player]="player"
[streamUrl]="streamUrl"
[epgItems]="epgItems"
(itemClicked)="itemClicked($event)"
/>
} @else {
<app-category-content-view
[items]="items"
(itemClicked)="itemClicked($event)"
/>
}
}
@case ('vod-details') {
<app-vod-details
@@ -13,7 +13,6 @@ import {
effect,
inject,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
@@ -38,6 +37,7 @@ import { selectCurrentPlaylist } from '../state/selectors';
import { CategoryContentViewComponent } from './category-content-view/category-content-view.component';
import { CategoryViewComponent } from './category-view/category-view.component';
import { ContentType } from './content-type.enum';
import { EpgItem } from './epg-item.interface';
import { NavigationBarComponent } from './navigation-bar/navigation-bar.component';
import { VodDetailsComponent } from './vod-details/vod-details.component';
@@ -52,6 +52,7 @@ import {
XtreamSerieDetails,
XtreamSerieEpisode,
} from '../../../shared/xtream-serie-details.interface';
import { LiveStreamLayoutComponent } from '../portals/live-stream-layout/live-stream-layout.component';
import { DialogService } from '../services/dialog.service';
import { PlaylistsService } from '../services/playlists.service';
import { Settings, VideoPlayer } from '../settings/settings.interface';
@@ -64,6 +65,16 @@ import { PlayerDialogComponent } from './player-dialog/player-dialog.component';
import { PortalStore } from './portal.store';
import { SerialDetailsComponent } from './serial-details/serial-details.component';
function b64DecodeUnicode(str: string) {
return decodeURIComponent(
Array.prototype.map
.call(atob(str), function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join('')
);
}
const ContentTypes = {
[ContentType.ITV]: {
getContentAction: XtreamCodeActions.GetLiveStreams,
@@ -116,6 +127,7 @@ type LayoutView =
RouterLink,
PlaylistErrorViewComponent,
TranslateModule,
LiveStreamLayoutComponent,
],
})
export class XtreamMainContainerComponent implements OnInit {
@@ -146,6 +158,7 @@ export class XtreamMainContainerComponent implements OnInit {
},
];
player: VideoPlayer;
favorites$: Observable<any>;
breadcrumbs: Breadcrumb[] = [];
items = [];
@@ -160,6 +173,8 @@ export class XtreamMainContainerComponent implements OnInit {
searchPhrase = this.portalStore.searchPhrase();
contentId: number;
errorViewInfo = { title: '', message: '' };
streamUrl: string;
epgItems = [];
commandsList = [
new IpcCommand(XTREAM_RESPONSE, (response: XtreamResponse) =>
@@ -233,6 +248,15 @@ export class XtreamMainContainerComponent implements OnInit {
this.currentLayout = 'serie-details';
this.vodDetails = response.payload as XtreamSerieDetails;
break;
case 'get_short_epg':
this.epgItems = (
(response.payload as any).epg_listings as EpgItem[]
).map((i) => ({
...i,
title: b64DecodeUnicode(i.title).trim(),
description: b64DecodeUnicode(i.description).trim(),
}));
break;
default:
break;
}
@@ -287,6 +311,11 @@ export class XtreamMainContainerComponent implements OnInit {
this.contentId = item.stream_id;
this.sendRequest({ action, vod_id: item.stream_id });
} else if (item.stream_type && item.stream_type === 'live') {
this.sendRequest({
action: 'get_short_epg',
stream_id: item.stream_id,
limit: 10,
});
this.playLiveStream(item);
} else if (item.series_id) {
this.items = [];
@@ -304,22 +333,25 @@ export class XtreamMainContainerComponent implements OnInit {
}
openPlayer(streamUrl: string, title: string) {
const player = this.settings().player;
if (player === VideoPlayer.MPV) {
this.player = this.settings().player ?? VideoPlayer.VideoJs;
if (this.player === VideoPlayer.MPV) {
this.dialog.open(ExternalPlayerInfoDialogComponent);
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, {
url: streamUrl,
});
} else if (player === VideoPlayer.VLC) {
} else if (this.player === VideoPlayer.VLC) {
this.dialog.open(ExternalPlayerInfoDialogComponent);
this.dataService.sendIpcEvent(OPEN_VLC_PLAYER, {
url: streamUrl,
});
} else {
this.dialog.open(PlayerDialogComponent, {
data: { streamUrl, player, title },
width: '80%',
});
this.streamUrl = streamUrl;
if (this.selectedContentType !== ContentType.ITV) {
this.dialog.open(PlayerDialogComponent, {
data: { streamUrl, player: this.player, title },
width: '80%',
});
}
}
}
@@ -427,7 +459,9 @@ export class XtreamMainContainerComponent implements OnInit {
}
sendRequest(params: Record<string, string | number>) {
this.isLoading = true;
if (params.action !== 'get_short_epg') {
this.isLoading = true;
}
const { serverUrl, username, password } = this.currentPlaylist();
this.dataService.sendIpcEvent(XTREAM_REQUEST, {
url: serverUrl,