mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16:15 -08:00
feat: improvements in xtream code module
This commit is contained in:
1 parent
b7975dba8b
commit
28b4e64de3
13 files changed
+175
-83
No files matched your search
@@ -52,6 +52,10 @@ import { FilterPipe } from '../../shared/pipes/filter.pipe';
|
||||
display: block;
|
||||
}
|
||||
|
||||
mat-card-content {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.no-content {
|
||||
text-align: center;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.container {
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: 340px auto;
|
||||
grid-template-columns: 350px 450px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
.image {
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.image img {
|
||||
@@ -26,7 +26,7 @@
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@media (max-width: 800px) {
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -38,6 +38,10 @@
|
||||
.details {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.details {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<h2 mat-dialog-title>Title</h2>
|
||||
<mat-dialog-content class="mat-typography">
|
||||
<h2 mat-dialog-title>{{ title }}</h2>
|
||||
<mat-dialog-content>
|
||||
<app-vjs-player
|
||||
[options]="{
|
||||
sources: [
|
||||
{
|
||||
src: streamUrl,
|
||||
type: 'application/x-mpegURL'
|
||||
}
|
||||
]
|
||||
}"
|
||||
*ngIf="player === 'videojs'"
|
||||
[options]="vjsOptions"
|
||||
></app-vjs-player>
|
||||
<app-html-video-player *ngIf="player === 'html5'" [channel]="channel">
|
||||
</app-html-video-player>
|
||||
</mat-dialog-content>
|
||||
@@ -1,17 +1,49 @@
|
||||
import { NgIf } from '@angular/common';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { getExtensionFromUrl } from '../../../../shared/playlist.utils';
|
||||
import { HtmlVideoPlayerComponent } from '../../player/components/html-video-player/html-video-player.component';
|
||||
import { VjsPlayerComponent } from '../../player/components/vjs-player/vjs-player.component';
|
||||
import { VideoPlayer } from '../../settings/settings.interface';
|
||||
|
||||
interface DialogData {
|
||||
streamUrl: string;
|
||||
player: VideoPlayer;
|
||||
title: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-player-dialog',
|
||||
templateUrl: './player-dialog.component.html',
|
||||
standalone: true,
|
||||
imports: [VjsPlayerComponent, MatDialogModule],
|
||||
imports: [
|
||||
HtmlVideoPlayerComponent,
|
||||
MatDialogModule,
|
||||
NgIf,
|
||||
VjsPlayerComponent,
|
||||
],
|
||||
})
|
||||
export class PlayerDialogComponent {
|
||||
streamUrl: string;
|
||||
channel = {};
|
||||
vjsOptions = {};
|
||||
player: VideoPlayer;
|
||||
title: string;
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) streamUrl: any) {
|
||||
this.streamUrl = streamUrl;
|
||||
constructor(@Inject(MAT_DIALOG_DATA) data: DialogData) {
|
||||
this.player = data.player;
|
||||
this.title = data.title;
|
||||
|
||||
const extension = getExtensionFromUrl(data.streamUrl);
|
||||
const mimeType =
|
||||
extension === 'm3u' || extension === 'm3u8' || extension === 'ts'
|
||||
? 'application/x-mpegURL'
|
||||
: 'video/mp4';
|
||||
|
||||
this.vjsOptions = {
|
||||
sources: [{ src: data.streamUrl, type: mimeType }],
|
||||
};
|
||||
this.channel = {
|
||||
url: data.streamUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,14 @@
|
||||
|
||||
<ng-template #episodesView>
|
||||
<mat-card class="episode-item" (click)="selectedSeason = undefined">
|
||||
<mat-card-title>< Back to seasons</mat-card-title>
|
||||
<div class="episode-title">< Back to seasons</div>
|
||||
</mat-card>
|
||||
<mat-card
|
||||
class="episode-item"
|
||||
*ngFor="let episode of seasons[selectedSeason]"
|
||||
(click)="selectEpisode(episode)"
|
||||
>
|
||||
<mat-card-content
|
||||
<mat-card-content *ngIf="episode.info.movie_image"
|
||||
><img [src]="episode.info.movie_image" class="episode-cover"
|
||||
/></mat-card-content>
|
||||
<div class="episode-title">
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
|
||||
.season-item,
|
||||
.episode-item {
|
||||
min-height: 100px;
|
||||
justify-content: center;
|
||||
width: 150px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.episode-item {
|
||||
|
||||
@@ -45,5 +45,5 @@
|
||||
|
||||
<app-season-container
|
||||
[seasons]="item.episodes"
|
||||
(episodeClicked)="playEpisode($event)"
|
||||
(episodeClicked)="playClicked.emit($event)"
|
||||
/>
|
||||
@@ -1,12 +1,13 @@
|
||||
import { JsonPipe, NgIf } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { OPEN_MPV_PLAYER } from '../../../../shared/ipc-commands';
|
||||
import { XtreamSerieDetails } from '../../../../shared/xtream-serie-details.interface';
|
||||
import { DataService } from '../../services/data.service';
|
||||
import {
|
||||
XtreamSerieDetails,
|
||||
XtreamSerieEpisode,
|
||||
} from '../../../../shared/xtream-serie-details.interface';
|
||||
import { selectCurrentPlaylist } from '../../state/selectors';
|
||||
import { PlayerDialogComponent } from '../player-dialog/player-dialog.component';
|
||||
import { SeasonContainerComponent } from '../season-container/season-container.component';
|
||||
|
||||
@Component({
|
||||
@@ -14,25 +15,19 @@ import { SeasonContainerComponent } from '../season-container/season-container.c
|
||||
templateUrl: './serial-details.component.html',
|
||||
styleUrls: ['../detail-view.scss'],
|
||||
standalone: true,
|
||||
imports: [JsonPipe, MatButtonModule, NgIf, SeasonContainerComponent],
|
||||
imports: [
|
||||
JsonPipe,
|
||||
MatButtonModule,
|
||||
NgIf,
|
||||
SeasonContainerComponent,
|
||||
PlayerDialogComponent,
|
||||
],
|
||||
})
|
||||
export class SerialDetailsComponent {
|
||||
@Input({ required: true }) item: XtreamSerieDetails;
|
||||
|
||||
@Output() playClicked = new EventEmitter();
|
||||
@Output() playClicked = new EventEmitter<XtreamSerieEpisode>();
|
||||
|
||||
dialog = inject(MatDialog);
|
||||
store = inject(Store);
|
||||
dataService = inject(DataService);
|
||||
currentPlaylist = this.store.selectSignal(selectCurrentPlaylist);
|
||||
|
||||
playEpisode(episode: any) {
|
||||
const { serverUrl, username, password } = this.currentPlaylist();
|
||||
|
||||
const streamUrl = `${serverUrl}/series/${username}/${password}/${episode.id}.${episode.container_extension}`;
|
||||
/* this.dialog.open(PlayerDialogComponent, {
|
||||
data: streamUrl,
|
||||
}); */
|
||||
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, { url: streamUrl });
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,11 @@
|
||||
{{ item.info.duration }}
|
||||
</label>
|
||||
<div class="action-buttons">
|
||||
<button mat-flat-button color="accent" (click)="playClicked.emit()">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="accent"
|
||||
(click)="playClicked.emit(item)"
|
||||
>
|
||||
Play
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -13,5 +13,5 @@ import { XtreamVodDetails } from '../../../../shared/xtream-vod-details.interfac
|
||||
export class VodDetailsComponent {
|
||||
@Input({ required: true }) item: XtreamVodDetails;
|
||||
|
||||
@Output() playClicked = new EventEmitter<void>();
|
||||
@Output() playClicked = new EventEmitter<XtreamVodDetails>();
|
||||
}
|
||||
@@ -6,28 +6,34 @@
|
||||
/>
|
||||
|
||||
<div class="container">
|
||||
<app-category-view
|
||||
*ngIf="currentLayout === 'category'"
|
||||
(categoryClicked)="categoryClicked($event)"
|
||||
[items]="items"
|
||||
/>
|
||||
<ng-container *ngIf="isLoading; else contentLoaded">
|
||||
<mat-spinner />
|
||||
</ng-container>
|
||||
<ng-template #contentLoaded>
|
||||
<app-category-view
|
||||
*ngIf="currentLayout === 'category'"
|
||||
(categoryClicked)="categoryClicked($event)"
|
||||
[items]="items"
|
||||
/>
|
||||
|
||||
<app-category-content-view
|
||||
*ngIf="currentLayout === 'category_content'"
|
||||
[items]="items"
|
||||
(itemClicked)="itemClicked($event)"
|
||||
/>
|
||||
<app-category-content-view
|
||||
*ngIf="currentLayout === 'category_content'"
|
||||
[items]="items"
|
||||
(itemClicked)="itemClicked($event)"
|
||||
/>
|
||||
|
||||
<app-vod-details
|
||||
*ngIf="currentLayout === 'vod-details'"
|
||||
[item]="vodDetails"
|
||||
(playClicked)="playVod()"
|
||||
/>
|
||||
<app-vod-details
|
||||
*ngIf="currentLayout === 'vod-details'"
|
||||
[item]="vodDetails"
|
||||
(playClicked)="playVod($event)"
|
||||
/>
|
||||
|
||||
<app-serial-details
|
||||
*ngIf="currentLayout === 'serie-details'"
|
||||
[item]="vodDetails"
|
||||
/>
|
||||
<app-serial-details
|
||||
*ngIf="currentLayout === 'serie-details'"
|
||||
[item]="vodDetails"
|
||||
(playClicked)="playEpisode($event)"
|
||||
/>
|
||||
|
||||
<app-player-view *ngIf="currentLayout === 'player'" />
|
||||
<app-player-view *ngIf="currentLayout === 'player'" />
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -1,4 +1,10 @@
|
||||
.container {
|
||||
height: calc(100vh - 140px);
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
import { JsonPipe, KeyValuePipe, NgFor, NgIf, NgSwitch } from '@angular/common';
|
||||
import { Component, NgZone, OnInit, effect, inject } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
NgZone,
|
||||
OnInit,
|
||||
Signal,
|
||||
effect,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
@@ -28,7 +35,17 @@ import { NavigationBarComponent } from './navigation-bar/navigation-bar.componen
|
||||
import { PlayerViewComponent } from './player-view/player-view.component';
|
||||
import { VodDetailsComponent } from './vod-details/vod-details.component';
|
||||
|
||||
import { XtreamSerieDetails } from '../../../shared/xtream-serie-details.interface';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import {
|
||||
XtreamSerieDetails,
|
||||
XtreamSerieEpisode,
|
||||
} from '../../../shared/xtream-serie-details.interface';
|
||||
import { Settings, VideoPlayer } from '../settings/settings.interface';
|
||||
import { STORE_KEY } from '../shared/enums/store-keys.enum';
|
||||
import { PlayerDialogComponent } from './player-dialog/player-dialog.component';
|
||||
import { SerialDetailsComponent } from './serial-details/serial-details.component';
|
||||
|
||||
const ContentTypes = {
|
||||
@@ -85,12 +102,16 @@ type LayoutView =
|
||||
PlayerViewComponent,
|
||||
CategoryContentViewComponent,
|
||||
SerialDetailsComponent,
|
||||
PlayerDialogComponent,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
})
|
||||
export class XtreamMainContainerComponent implements OnInit {
|
||||
dataService = inject(DataService);
|
||||
dialog = inject(MatDialog);
|
||||
ngZone = inject(NgZone);
|
||||
snackBar = inject(MatSnackBar);
|
||||
storage = inject(StorageMap);
|
||||
store = inject(Store);
|
||||
currentPlaylist = this.store.selectSignal(selectCurrentPlaylist);
|
||||
|
||||
@@ -100,6 +121,10 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
selectedContentType = ContentType.VODS;
|
||||
currentLayout: LayoutView = 'category';
|
||||
vodDetails!: XtreamVodDetails | XtreamSerieDetails;
|
||||
settings = toSignal(
|
||||
this.storage.get(STORE_KEY.Settings)
|
||||
) as Signal<Settings>;
|
||||
isLoading = true;
|
||||
|
||||
commandsList = [
|
||||
new IpcCommand(XTREAM_RESPONSE, (response: XtreamResponse) =>
|
||||
@@ -139,7 +164,6 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
}
|
||||
|
||||
handleResponse(response: XtreamResponse) {
|
||||
console.log(response.payload);
|
||||
switch (response.action) {
|
||||
case XtreamCodeActions.GetSeriesCategories:
|
||||
case XtreamCodeActions.GetVodCategories:
|
||||
@@ -160,6 +184,7 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
getCategories(contentType: ContentType = this.selectedContentType) {
|
||||
@@ -169,7 +194,6 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
this.sendRequest({ action });
|
||||
}
|
||||
|
||||
// TODO: MAKE IT ABSTRACT SOMEHOW
|
||||
ngOnDestroy(): void {
|
||||
if (this.dataService.isElectron) {
|
||||
this.commandsList.forEach((command) =>
|
||||
@@ -213,7 +237,7 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
title: item.name,
|
||||
action: XtreamCodeActions.GetLiveStreams,
|
||||
});
|
||||
this.playStream(item);
|
||||
this.playLiveStream(item);
|
||||
} else if (item.series_id) {
|
||||
action = XtreamCodeActions.GetSeriesInfo;
|
||||
this.breadcrumbs.push({ title: item.name, action });
|
||||
@@ -221,31 +245,47 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
playStream(item: XtreamLiveStream) {
|
||||
this.currentLayout = 'player';
|
||||
playLiveStream(item: XtreamLiveStream) {
|
||||
const { serverUrl, username, password } = this.currentPlaylist();
|
||||
|
||||
const streamUrl = `${serverUrl}/${item.stream_type}/${username}/${password}/${item.stream_id}.ts`;
|
||||
|
||||
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, {
|
||||
url: streamUrl,
|
||||
});
|
||||
this.openPlayer(streamUrl, item.name);
|
||||
}
|
||||
|
||||
playVod() {
|
||||
this.currentLayout = 'player';
|
||||
openPlayer(streamUrl: string, title: string) {
|
||||
const player = this.settings().player;
|
||||
if (player === VideoPlayer.MPV) {
|
||||
this.currentLayout = 'player';
|
||||
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, {
|
||||
url: streamUrl,
|
||||
});
|
||||
} else {
|
||||
this.dialog.open(PlayerDialogComponent, {
|
||||
data: { streamUrl, player, title },
|
||||
width: '80%',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
playVod(vodItem: XtreamVodDetails) {
|
||||
const { serverUrl, username, password } = this.currentPlaylist();
|
||||
|
||||
this.items = [];
|
||||
const streamUrl = `${serverUrl}/movie/${username}/${password}/${
|
||||
(this.vodDetails as XtreamVodDetails).movie_data.stream_id
|
||||
}.${
|
||||
(this.vodDetails as XtreamVodDetails).movie_data.container_extension
|
||||
}`;
|
||||
const streamUrl = `${serverUrl}/movie/${username}/${password}/${vodItem.movie_data.stream_id}.${vodItem.movie_data.container_extension}`;
|
||||
|
||||
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, {
|
||||
url: streamUrl,
|
||||
});
|
||||
this.openPlayer(streamUrl, vodItem.info.name);
|
||||
}
|
||||
|
||||
playEpisode(episode: XtreamSerieEpisode) {
|
||||
const { serverUrl, username, password } = this.currentPlaylist();
|
||||
const player = this.settings().player;
|
||||
const streamUrl = `${serverUrl}/series/${username}/${password}/${episode.id}.${episode.container_extension}`;
|
||||
if (player === VideoPlayer.MPV) {
|
||||
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, { url: streamUrl });
|
||||
} else {
|
||||
this.dialog.open(PlayerDialogComponent, {
|
||||
data: { streamUrl, player, title: episode.title },
|
||||
width: '80%',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeContentType(contentType: ContentType) {
|
||||
@@ -261,6 +301,7 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
null,
|
||||
{ duration: 4000 }
|
||||
);
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,6 +354,7 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
}
|
||||
|
||||
sendRequest(params: Record<string, string | number>) {
|
||||
this.isLoading = true;
|
||||
const { serverUrl, username, password } = this.currentPlaylist();
|
||||
this.dataService.sendIpcEvent(XTREAM_REQUEST, {
|
||||
url: serverUrl,
|
||||
|
||||
Reference in new issue
Block a user