mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor: tauri support for mpv/vlc
This commit is contained in:
1 parent
4d8bac9645
commit
9f1b123078
17 files changed
+389
-42
No files matched your search
@@ -1,8 +1,92 @@
|
||||
use log::info;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
#[tauri::command]
|
||||
async fn open_in_mpv(url: String, path: String) -> Result<(), String> {
|
||||
info!("Custom MVP path: {}", path);
|
||||
let mpv_paths = if cfg!(target_os = "windows") {
|
||||
vec![
|
||||
r"C:\Program Files\mpv\mpv.exe",
|
||||
r"C:\Program Files (x86)\mpv\mpv.exe",
|
||||
]
|
||||
} else if cfg!(target_os = "linux") {
|
||||
vec!["/usr/bin/mpv", "/usr/local/bin/mpv", "/snap/bin/mpv"]
|
||||
} else {
|
||||
vec![
|
||||
"/Applications/mpv.app/Contents/MacOS/mpv",
|
||||
"/opt/homebrew/bin/mpv",
|
||||
"/usr/local/bin/mpv",
|
||||
]
|
||||
};
|
||||
|
||||
let mpv_path = if !path.is_empty() && Path::new(&path).exists() {
|
||||
path
|
||||
} else {
|
||||
mpv_paths
|
||||
.iter()
|
||||
.find(|&path| Path::new(path).exists())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "mpv".to_string())
|
||||
};
|
||||
|
||||
info!("Using the following MPV player path: {}", mpv_path);
|
||||
|
||||
Command::new(mpv_path)
|
||||
.arg(&url)
|
||||
.spawn()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn open_in_vlc(url: String, path: String) -> Result<(), String> {
|
||||
info!("Custom MVP path: {}", path);
|
||||
let vlc_paths = if cfg!(target_os = "windows") {
|
||||
vec![
|
||||
r"C:\Program Files\VideoLAN\VLC\vlc.exe",
|
||||
r"C:\Program Files (x86)\VideoLAN\VLC\vlc.exe",
|
||||
]
|
||||
} else if cfg!(target_os = "linux") {
|
||||
vec!["/usr/bin/vlc", "/usr/local/bin/vlc", "/snap/bin/vlc"]
|
||||
} else {
|
||||
vec![
|
||||
"/Applications/VLC.app/Contents/MacOS/VLC",
|
||||
"/opt/homebrew/bin/vlc",
|
||||
"/usr/local/bin/vlc",
|
||||
]
|
||||
};
|
||||
|
||||
let vlc_path = if !path.is_empty() && Path::new(&path).exists() {
|
||||
path
|
||||
} else {
|
||||
vlc_paths
|
||||
.iter()
|
||||
.find(|&path| Path::new(path).exists())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "vlc".to_string())
|
||||
};
|
||||
|
||||
info!("Using VLC player path: {}", vlc_path);
|
||||
|
||||
Command::new(vlc_path)
|
||||
.arg(&url)
|
||||
.spawn()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::new().build())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.invoke_handler(tauri::generate_handler![open_in_mpv, open_in_vlc])
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
|
||||
@@ -24,6 +24,7 @@ import { dbConfig } from './indexed-db.config';
|
||||
import { DataService } from './services/data.service';
|
||||
import { ElectronService } from './services/electron.service';
|
||||
import { PwaService } from './services/pwa.service';
|
||||
import { TauriService } from './services/tauri.service';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { PlaylistEffects } from './state/effects';
|
||||
import { playlistReducer } from './state/reducers';
|
||||
@@ -40,15 +41,20 @@ function isElectron() {
|
||||
return !!(window && window.process && (window.process as any).type);
|
||||
}
|
||||
|
||||
function isTauri() {
|
||||
return '__TAURI_INTERNALS__' in window || '__TAURI__' in window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally imports the necessary service based on the current environment
|
||||
* @param dbService indexed db service
|
||||
* @returns
|
||||
*/
|
||||
export function DataFactory(dbService: NgxIndexedDBService, http: HttpClient) {
|
||||
export function DataFactory(http: HttpClient) {
|
||||
if (isElectron()) {
|
||||
return new ElectronService();
|
||||
}
|
||||
if (isTauri()) {
|
||||
return new TauriService();
|
||||
}
|
||||
return new PwaService(http);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
.recent-playlists {
|
||||
height: calc(100vh - 106px);
|
||||
height: calc(100vh - 100px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-nav-list style="overflow: hidden">
|
||||
<mat-nav-list id="all-channels">
|
||||
<cdk-virtual-scroll-viewport
|
||||
itemSize="50"
|
||||
class="scroll-viewport"
|
||||
|
||||
+7
-3
@@ -5,7 +5,7 @@
|
||||
}
|
||||
|
||||
.scroll-viewport {
|
||||
min-height: calc(100vh - 185px);
|
||||
min-height: calc(100vh - 159px);
|
||||
}
|
||||
|
||||
.search-panel {
|
||||
@@ -21,7 +21,11 @@
|
||||
|
||||
mat-nav-list {
|
||||
overflow: scroll;
|
||||
max-height: calc(100vh - 193px);
|
||||
}
|
||||
|
||||
#all-channels {
|
||||
overflow: hidden;
|
||||
max-height: calc(100vh - 167px);
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
@@ -61,7 +65,7 @@ mat-nav-list {
|
||||
|
||||
#groups-list,
|
||||
#favorites-list {
|
||||
height: calc(100vh - 120px);
|
||||
height: calc(100vh - 105px);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 599px) {
|
||||
|
||||
@@ -10,10 +10,11 @@ export abstract class DataService {
|
||||
return this.isElectron ? window.require('electron').ipcRenderer : null;
|
||||
}
|
||||
abstract getAppVersion(): string;
|
||||
abstract sendIpcEvent(type: string, payload?: unknown): void;
|
||||
abstract sendIpcEvent(type: string, payload?: unknown);
|
||||
abstract removeAllListeners(type: string): void;
|
||||
abstract listenOn(
|
||||
command: string,
|
||||
callback: (...args: any[]) => void
|
||||
): void;
|
||||
abstract getAppEnvironment(): string;
|
||||
}
|
||||
@@ -45,4 +45,8 @@ export class ElectronService extends DataService {
|
||||
listenOn(command: string, callback: (...args: any[]) => void): void {
|
||||
this.ipcRenderer.on(command, callback);
|
||||
}
|
||||
|
||||
getAppEnvironment(): string {
|
||||
return 'electron';
|
||||
}
|
||||
}
|
||||
@@ -234,4 +234,8 @@ export class PwaService extends DataService {
|
||||
listenOn(_command: string, callback: (...args: any[]) => void): void {
|
||||
window.addEventListener('message', callback);
|
||||
}
|
||||
|
||||
getAppEnvironment(): string {
|
||||
return 'pwa';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
import { parse } from 'iptv-playlist-parser';
|
||||
import {
|
||||
ERROR,
|
||||
PLAYLIST_PARSE_BY_URL,
|
||||
PLAYLIST_PARSE_RESPONSE,
|
||||
XTREAM_RESPONSE,
|
||||
} from '../../../shared/ipc-commands';
|
||||
import { Playlist } from '../../../shared/playlist.interface';
|
||||
import { createPlaylistObject } from '../../../shared/playlist.utils';
|
||||
import { AppConfig } from '../../environments/environment';
|
||||
import { DataService } from './data.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class TauriService extends DataService {
|
||||
constructor() {
|
||||
super();
|
||||
console.log('Tauri service initialized...');
|
||||
}
|
||||
|
||||
getAppVersion(): string {
|
||||
return AppConfig.version;
|
||||
}
|
||||
|
||||
async sendIpcEvent(type: string, payload?: unknown) {
|
||||
if (type === PLAYLIST_PARSE_BY_URL) {
|
||||
this.fetchFromUrl(payload);
|
||||
} else if (type === 'PLAYLIST_UPDATE') {
|
||||
console.log('PLAYLIST_UPDATE');
|
||||
} else if (type === 'XTREAM_REQUEST') {
|
||||
this.forwardXtreamRequest(
|
||||
payload as { url: string; params: Record<string, string> }
|
||||
);
|
||||
} else if (type === 'STALKER_REQUEST') {
|
||||
console.log('STALKER_REQUEST');
|
||||
this.fetchStalkerData(
|
||||
payload as {
|
||||
url: string;
|
||||
macAddress: string;
|
||||
params: Record<string, string>;
|
||||
}
|
||||
);
|
||||
} else if (type === 'OPEN_MPV_PLAYER') {
|
||||
return invoke('open_in_mpv', {
|
||||
url: (payload as any).url,
|
||||
path: (payload as any).mpvPlayerPath || '',
|
||||
}).catch((error) => {
|
||||
window.postMessage({
|
||||
type: ERROR,
|
||||
message: `Error launching MPV: ${error}`,
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
} else if (type === 'OPEN_VLC_PLAYER') {
|
||||
return invoke('open_in_vlc', {
|
||||
url: (payload as any).url,
|
||||
path: (payload as any).vlcPlayerPath || '',
|
||||
}).catch((error) => {
|
||||
window.postMessage({
|
||||
type: ERROR,
|
||||
message: `Error launching VLC: ${error}`,
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
} else {
|
||||
console.log('Unknown type', type);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchStalkerData(payload: {
|
||||
url: string;
|
||||
macAddress: string;
|
||||
params: Record<string, string>;
|
||||
}) {
|
||||
try {
|
||||
console.log(payload);
|
||||
const url = new URL(payload.url);
|
||||
|
||||
Object.entries(payload.params).forEach(([key, value]) => {
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Cookie: `mac=${payload.macAddress}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Error: ${response.statusText} (Status: ${response.status})`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log(result);
|
||||
window.postMessage({
|
||||
type: 'STALKER_RESPONSE',
|
||||
payload: result,
|
||||
action: payload.params.action,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
window.postMessage({
|
||||
type: ERROR,
|
||||
message: err.message ?? 'Error: not found',
|
||||
status: err.status ?? 404,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fetchFromUrl(payload: Partial<Playlist>) {
|
||||
try {
|
||||
const response = await fetch(payload.url);
|
||||
const responseBody = await response.text();
|
||||
const parsedPlaylist = parse(responseBody);
|
||||
|
||||
const playlist = createPlaylistObject(
|
||||
'tests',
|
||||
parsedPlaylist,
|
||||
payload.url,
|
||||
'URL'
|
||||
);
|
||||
|
||||
window.postMessage({
|
||||
type: PLAYLIST_PARSE_RESPONSE,
|
||||
payload: { ...playlist, isTemporary: payload.isTemporary },
|
||||
});
|
||||
} catch (error) {
|
||||
window.postMessage({
|
||||
type: ERROR,
|
||||
message: this.getErrorMessageByStatusCode(error.status),
|
||||
status: error.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getErrorMessageByStatusCode(status: number) {
|
||||
let message = 'Something went wrong';
|
||||
switch (status) {
|
||||
case 0:
|
||||
message = 'The backend is not reachable';
|
||||
break;
|
||||
case 413:
|
||||
message =
|
||||
'This file is too big. Use standalone or self-hosted version of the app.';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
async forwardXtreamRequest(payload: {
|
||||
url: string;
|
||||
params: Record<string, string>;
|
||||
}) {
|
||||
const url = new URL(`${payload.url}/player_api.php`);
|
||||
Object.entries(payload.params).forEach(([key, value]) => {
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
|
||||
const responseBody = await response.json();
|
||||
if (!responseBody) {
|
||||
window.postMessage({
|
||||
type: ERROR,
|
||||
status: response.status,
|
||||
message: responseBody.message ?? 'Unknown error',
|
||||
});
|
||||
} else {
|
||||
window.postMessage({
|
||||
type: XTREAM_RESPONSE,
|
||||
payload: responseBody,
|
||||
action: payload.params.action,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeAllListeners(type: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
listenOn(command: string, callback: (...args: any[]) => void): void {
|
||||
window.addEventListener('message', callback);
|
||||
}
|
||||
|
||||
getAppEnvironment(): string {
|
||||
return 'tauri';
|
||||
}
|
||||
}
|
||||
@@ -199,24 +199,30 @@
|
||||
></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
{{ 'SETTINGS.REMOTE_CONTROL' | translate }}
|
||||
<p>{{ 'SETTINGS.REMOTE_CONTROL_DESCRIPTION' | translate }}</p>
|
||||
@if (isPwa || isElectron) {
|
||||
<mat-divider></mat-divider>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
{{ 'SETTINGS.REMOTE_CONTROL' | translate }}
|
||||
<p>
|
||||
{{ 'SETTINGS.REMOTE_CONTROL_DESCRIPTION' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column" style="margin-right: 10px">
|
||||
<mat-checkbox
|
||||
class="column"
|
||||
formControlName="remoteControl"
|
||||
></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column" style="margin-right: 10px">
|
||||
<mat-checkbox
|
||||
class="column"
|
||||
formControlName="remoteControl"
|
||||
></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<mat-divider></mat-divider>
|
||||
<div class="row" *ngIf="settingsForm.value.remoteControl === true">
|
||||
<div class="column">
|
||||
{{ 'SETTINGS.REMOTE_CONTROL_PORT' | translate }}
|
||||
<p>{{ 'SETTINGS.REMOTE_CONTROL_PORT_DESCRIPTION' | translate }}</p>
|
||||
<p>
|
||||
{{ 'SETTINGS.REMOTE_CONTROL_PORT_DESCRIPTION' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column" style="margin-right: 10px">
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
@@ -294,7 +300,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" *ngIf="!isElectron">
|
||||
<div class="row" *ngIf="isPwa">
|
||||
{{ 'SETTINGS.EPG_NOTE' | translate }}
|
||||
<a href="https://github.com/4gray/iptvnator/releases">{{
|
||||
'SETTINGS.EPG_NOTE_URL_TEXT' | translate
|
||||
|
||||
@@ -67,6 +67,8 @@ export class SettingsComponent implements OnInit {
|
||||
/** Flag that indicates whether the app runs in electron environment */
|
||||
isElectron = this.electronService.isElectron;
|
||||
|
||||
isPwa = this.electronService.getAppEnvironment() === 'pwa';
|
||||
|
||||
electronPlayers = [
|
||||
{
|
||||
id: VideoPlayer.MPV,
|
||||
@@ -88,7 +90,8 @@ export class SettingsComponent implements OnInit {
|
||||
id: VideoPlayer.VideoJs,
|
||||
label: 'VideoJs Player',
|
||||
},
|
||||
...(this.isElectron ? this.electronPlayers : []),
|
||||
...this.electronPlayers,
|
||||
/* ...(this.isElectron ? this.electronPlayers : []), */
|
||||
];
|
||||
|
||||
/** Current version of the app */
|
||||
@@ -113,7 +116,7 @@ export class SettingsComponent implements OnInit {
|
||||
mpvPlayerPath: '',
|
||||
vlcPlayerPath: '',
|
||||
remoteControl: false,
|
||||
remoteControlPort: 3000
|
||||
remoteControlPort: 3000,
|
||||
});
|
||||
|
||||
/** Form array with epg sources */
|
||||
@@ -125,7 +128,7 @@ export class SettingsComponent implements OnInit {
|
||||
*/
|
||||
constructor(
|
||||
private dialogService: DialogService,
|
||||
private electronService: DataService,
|
||||
public electronService: DataService,
|
||||
private epgService: EpgService,
|
||||
private formBuilder: FormBuilder,
|
||||
private playlistsService: PlaylistsService,
|
||||
@@ -165,7 +168,8 @@ export class SettingsComponent implements OnInit {
|
||||
mpvPlayerPath: settings.mpvPlayerPath ?? '',
|
||||
vlcPlayerPath: settings.vlcPlayerPath ?? '',
|
||||
remoteControl: settings.remoteControl ?? false,
|
||||
remoteControlPort: settings.remoteControlPort ?? 3000
|
||||
remoteControlPort:
|
||||
settings.remoteControlPort ?? 3000,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
@@ -255,7 +259,10 @@ export class SettingsComponent implements OnInit {
|
||||
this.applyChangedSettings();
|
||||
});
|
||||
|
||||
this.electronService.sendIpcEvent(SETTINGS_UPDATE, this.settingsForm.value);
|
||||
this.electronService.sendIpcEvent(
|
||||
SETTINGS_UPDATE,
|
||||
this.settingsForm.value
|
||||
);
|
||||
|
||||
this.electronService.sendIpcEvent(
|
||||
SET_MPV_PLAYER_PATH,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { NgxWhatsNewModule } from 'ngx-whats-new';
|
||||
import { HomeComponent } from '../../../home/home.component';
|
||||
import { DataService } from '../../../services/data.service';
|
||||
@@ -56,6 +57,9 @@ export class HeaderComponent implements OnInit {
|
||||
/** Environment flag */
|
||||
isElectron = this.dataService.isElectron;
|
||||
|
||||
/** Environment flag for Tauri */
|
||||
isTauri = this.dataService.getAppEnvironment() === 'tauri';
|
||||
|
||||
/** Visibility flag of the "what is new" modal dialog */
|
||||
isDialogVisible$ = this.whatsNewService.dialogState$;
|
||||
|
||||
@@ -121,8 +125,12 @@ export class HeaderComponent implements OnInit {
|
||||
* Opens the provided URL string in new browser window
|
||||
* @param url url to open
|
||||
*/
|
||||
openUrl(url: string): void {
|
||||
window.open(url, '_blank');
|
||||
async openUrl(url: string): Promise<void> {
|
||||
if (this.isTauri) {
|
||||
await open(url);
|
||||
} else {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -376,12 +376,14 @@ export class StalkerMainContainerComponent implements OnInit {
|
||||
this.dialog.open(ExternalPlayerInfoDialogComponent);
|
||||
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, {
|
||||
url: streamUrl,
|
||||
mpvPlayerPath: this.settings()?.mpvPlayerPath,
|
||||
});
|
||||
} else if (player === VideoPlayer.VLC) {
|
||||
if (!this.hideExternalInfoDialog())
|
||||
this.dialog.open(ExternalPlayerInfoDialogComponent);
|
||||
this.dataService.sendIpcEvent(OPEN_VLC_PLAYER, {
|
||||
url: streamUrl,
|
||||
vlcPlayerPath: this.settings()?.vlcPlayerPath,
|
||||
});
|
||||
} else {
|
||||
this.dialog.open<PlayerDialogComponent, PlayerDialogData>(
|
||||
|
||||
@@ -85,6 +85,7 @@ export class PlaylistEffects {
|
||||
url:
|
||||
activeChannel.url +
|
||||
(activeChannel.epgParams ?? ''),
|
||||
mpvPlayerPath: settings?.mpvPlayerPath,
|
||||
});
|
||||
else if (
|
||||
settings &&
|
||||
@@ -95,6 +96,7 @@ export class PlaylistEffects {
|
||||
url:
|
||||
activeChannel.url +
|
||||
(activeChannel.epgParams ?? ''),
|
||||
vlcPlayerPath: settings?.vlcPlayerPath,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { XtreamVodDetails } from '../../../../shared/xtream-vod-details.interface';
|
||||
@@ -20,7 +21,15 @@ import { SafePipe } from './safe.pipe';
|
||||
templateUrl: './vod-details.component.html',
|
||||
styleUrls: ['../detail-view.scss'],
|
||||
standalone: true,
|
||||
imports: [MatButtonModule, NgIf, MatIconModule, SafePipe, TranslateModule],
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatIcon,
|
||||
NgIf,
|
||||
MatIconModule,
|
||||
SafePipe,
|
||||
TranslateModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
})
|
||||
export class VodDetailsComponent implements OnInit {
|
||||
@Input({ required: true }) item: XtreamVodDetails;
|
||||
@@ -34,6 +43,7 @@ export class VodDetailsComponent implements OnInit {
|
||||
private portalId = this.route.snapshot.paramMap.get('id');
|
||||
|
||||
isFavorite = false;
|
||||
isLoading = false;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.checkFavoriteStatus();
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
currentLayout === 'category' || currentLayout === 'category_content'
|
||||
"
|
||||
[sortVisible]="currentLayout === 'category_content'"
|
||||
[favoriteVisible]="currentLayout === 'category_content' || currentLayout === 'live-stream-favorites'"
|
||||
[favoriteVisible]="
|
||||
currentLayout === 'category_content' ||
|
||||
currentLayout === 'live-stream-favorites'
|
||||
"
|
||||
[contentTypeNavigationItems]="navigationContentTypes"
|
||||
[activeLiveStream]="activeLiveStream"
|
||||
[favoritesLiveStream$]="favoritesLiveStream$"
|
||||
|
||||
@@ -21,10 +21,10 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { IpcCommand } from '../../../shared/ipc-command.class';
|
||||
import {
|
||||
REMOTE_CONTROL_CHANGE_CHANNEL,
|
||||
ERROR,
|
||||
OPEN_MPV_PLAYER,
|
||||
OPEN_VLC_PLAYER,
|
||||
REMOTE_CONTROL_CHANGE_CHANNEL,
|
||||
XTREAM_REQUEST,
|
||||
XTREAM_RESPONSE,
|
||||
} from '../../../shared/ipc-commands';
|
||||
@@ -195,9 +195,12 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
new IpcCommand(ERROR, (response: { message: string; status: number }) =>
|
||||
this.showErrorAsNotification(response)
|
||||
),
|
||||
new IpcCommand(REMOTE_CONTROL_CHANGE_CHANNEL, (response: { type: 'up' | 'down' }) => {
|
||||
this.remoteControlChangeChannel(response.type);
|
||||
})
|
||||
new IpcCommand(
|
||||
REMOTE_CONTROL_CHANGE_CHANNEL,
|
||||
(response: { type: 'up' | 'down' }) => {
|
||||
this.remoteControlChangeChannel(response.type);
|
||||
}
|
||||
),
|
||||
];
|
||||
|
||||
constructor() {
|
||||
@@ -207,16 +210,16 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
this.favorites$ = this.playlistService.getPortalFavorites(
|
||||
this.currentPlaylist()._id
|
||||
);
|
||||
this.favoritesLiveStream$ = this.playlistService.getPortalLiveStreamFavorites(
|
||||
this.currentPlaylist()._id
|
||||
);
|
||||
this.favoritesLiveStream$ =
|
||||
this.playlistService.getPortalLiveStreamFavorites(
|
||||
this.currentPlaylist()._id
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setInitialBreadcrumb();
|
||||
|
||||
this.commandsList.forEach((command) => {
|
||||
if (this.dataService.isElectron) {
|
||||
this.dataService.listenOn(command.id, (_event, response) =>
|
||||
@@ -274,6 +277,7 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
this.currentLayout = 'serie-details';
|
||||
this.vodDetails = response.payload as XtreamSerieDetails;
|
||||
break;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
||||
case 'get_short_epg':
|
||||
this.epgItems = (
|
||||
(response.payload as any).epg_listings as EpgItem[]
|
||||
@@ -368,12 +372,14 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
this.dialog.open(ExternalPlayerInfoDialogComponent);
|
||||
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, {
|
||||
url: streamUrl,
|
||||
mpvPlayerPath: this.settings()?.mpvPlayerPath,
|
||||
});
|
||||
} else if (this.player === VideoPlayer.VLC) {
|
||||
if (!this.hideExternalInfoDialog())
|
||||
this.dialog.open(ExternalPlayerInfoDialogComponent);
|
||||
this.dataService.sendIpcEvent(OPEN_VLC_PLAYER, {
|
||||
url: streamUrl,
|
||||
vlcPlayerPath: this.settings()?.vlcPlayerPath,
|
||||
});
|
||||
} else {
|
||||
if (this.selectedContentType !== ContentType.ITV) {
|
||||
@@ -548,7 +554,10 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
}
|
||||
|
||||
remoteControlChangeChannel(type: 'up' | 'down') {
|
||||
if (this.currentLayout === 'category_content' && this.activeLiveStream) {
|
||||
if (
|
||||
this.currentLayout === 'category_content' &&
|
||||
this.activeLiveStream
|
||||
) {
|
||||
let nextItem;
|
||||
const index = this.activeLiveStream.num - 1;
|
||||
if (type === 'up') {
|
||||
|
||||
Reference in new issue
Block a user