mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 05:06:16 -08:00
merge: resolve master conflicts for pwa hardening
- merge origin/master into PR #964 and keep embedded MPV test on the isolated playback sub-entrypoint - centralize EPG capability through DataService.supportsEpg and update PWA web-e2e expectations - split BrowserAccessError copy between Electron and PWA diagnostics
This commit is contained in:
commit
36bce47764
102 files changed
+1894
-439
No files matched your search
@@ -49,14 +49,16 @@ export class AppComponent implements OnInit {
|
||||
private readonly DEFAULT_LANG = Language.ENGLISH;
|
||||
|
||||
constructor() {
|
||||
const electronProcess = this.dataService.remote?.process;
|
||||
if (
|
||||
((this.dataService.isElectron &&
|
||||
this.dataService?.remote?.process.platform === 'linux') ||
|
||||
this.dataService?.remote?.process.platform === 'win32') &&
|
||||
this.dataService.remote.process.argv.length > 2
|
||||
this.dataService.isElectron &&
|
||||
electronProcess &&
|
||||
(electronProcess.platform === 'linux' ||
|
||||
electronProcess.platform === 'win32') &&
|
||||
electronProcess.argv.length > 2
|
||||
) {
|
||||
const filePath = this.dataService.remote.process.argv.find(
|
||||
(filepath) =>
|
||||
const filePath = electronProcess.argv.find(
|
||||
(filepath: string) =>
|
||||
filepath.endsWith('.m3u') || filepath.endsWith('.m3u8')
|
||||
);
|
||||
if (filePath) {
|
||||
@@ -102,7 +104,7 @@ export class AppComponent implements OnInit {
|
||||
*/
|
||||
initSettings(): void {
|
||||
this.settingsService
|
||||
.getValueFromLocalStorage(STORE_KEY.Settings)
|
||||
.getValueFromLocalStorage<Settings>(STORE_KEY.Settings)
|
||||
.subscribe((settings: Settings) => {
|
||||
if (settings && Object.keys(settings).length > 0) {
|
||||
// No need to send settings to Electron on init
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
EmbeddedMpvOverlayVisibilityService,
|
||||
EmbeddedMpvPlayerComponent,
|
||||
EmbeddedMpvSessionController,
|
||||
} from '@iptvnator/ui/playback';
|
||||
} from '@iptvnator/ui/playback/embedded-mpv-player';
|
||||
|
||||
@Component({
|
||||
imports: [EmbeddedMpvPlayerComponent],
|
||||
|
||||
@@ -117,7 +117,7 @@ export class ElectronService extends DataService {
|
||||
payload?: unknown
|
||||
): Promise<T> {
|
||||
if (type === PLAYLIST_PARSE_BY_URL) {
|
||||
this.fetchM3uPlaylistFromUrl(payload);
|
||||
this.fetchM3uPlaylistFromUrl(payload as Partial<Playlist>);
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ export class ElectronService extends DataService {
|
||||
this.translateService.instant(
|
||||
'HOME.PLAYLISTS.AUTO_REFRESH_UPDATE_SUCCESS'
|
||||
),
|
||||
null,
|
||||
undefined,
|
||||
{ duration: 2000 }
|
||||
);
|
||||
return playlists as T;
|
||||
@@ -266,6 +266,10 @@ export class ElectronService extends DataService {
|
||||
}
|
||||
|
||||
private async fetchM3uPlaylistFromUrl(payload: Partial<Playlist>) {
|
||||
if (!payload.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = payload.title?.trim() || undefined;
|
||||
|
||||
window.electron
|
||||
@@ -353,7 +357,7 @@ export class ElectronService extends DataService {
|
||||
this.translateService.instant(
|
||||
'HOME.PLAYLISTS.PLAYLIST_UPDATE_SUCCESS'
|
||||
),
|
||||
null,
|
||||
undefined,
|
||||
{ duration: 2000 }
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -5,7 +5,14 @@ import { SwUpdate } from '@angular/service-worker';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PlaylistActions } from '@iptvnator/m3u-state';
|
||||
import { catchError, firstValueFrom, from, switchMap, throwError } from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
firstValueFrom,
|
||||
from,
|
||||
Observable,
|
||||
switchMap,
|
||||
throwError,
|
||||
} from 'rxjs';
|
||||
import { DataService } from '@iptvnator/services';
|
||||
import {
|
||||
ERROR,
|
||||
@@ -108,12 +115,12 @@ export class PwaService extends DataService {
|
||||
*/
|
||||
sendIpcEvent<T = unknown>(type: string, payload?: unknown): T {
|
||||
if (type === PLAYLIST_PARSE_BY_URL) {
|
||||
this.fetchFromUrl(payload);
|
||||
this.fetchFromUrl(payload as Partial<Playlist>);
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
if (type === PLAYLIST_UPDATE) {
|
||||
this.refreshPlaylist(payload);
|
||||
this.refreshPlaylist(payload as Partial<Playlist & { id: string }>);
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
@@ -138,6 +145,12 @@ export class PwaService extends DataService {
|
||||
}
|
||||
|
||||
refreshPlaylist(payload: Partial<Playlist & { id: string }>) {
|
||||
if (!payload.url || !payload.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const playlistId = payload.id;
|
||||
|
||||
this.getPlaylistFromUrl(payload.url)
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
@@ -155,7 +168,7 @@ export class PwaService extends DataService {
|
||||
this.store.dispatch(
|
||||
PlaylistActions.updatePlaylist({
|
||||
playlist,
|
||||
playlistId: payload.id,
|
||||
playlistId,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -163,7 +176,7 @@ export class PwaService extends DataService {
|
||||
this.translateService.instant(
|
||||
'HOME.PLAYLISTS.PLAYLIST_UPDATE_SUCCESS'
|
||||
),
|
||||
null,
|
||||
undefined,
|
||||
{ duration: 2000 }
|
||||
);
|
||||
});
|
||||
@@ -193,13 +206,19 @@ export class PwaService extends DataService {
|
||||
* @param payload playlist payload
|
||||
*/
|
||||
fetchFromUrl(payload: Partial<Playlist>): void {
|
||||
if (!payload.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = payload.title?.trim() || undefined;
|
||||
|
||||
this.getPlaylistFromUrl(payload.url)
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
this.snackBar.open(
|
||||
this.getErrorMessageByStatusCode(error.status),
|
||||
this.getErrorMessageByStatusCode(
|
||||
this.extractHttpStatusCode(error)
|
||||
),
|
||||
'Close',
|
||||
{
|
||||
duration: 5000,
|
||||
@@ -226,7 +245,7 @@ export class PwaService extends DataService {
|
||||
});
|
||||
}
|
||||
|
||||
getErrorMessageByStatusCode(status: number) {
|
||||
getErrorMessageByStatusCode(status: number | null | undefined) {
|
||||
let messageKey = 'HOME.URL_UPLOAD.ERROR_FETCH_FAILED';
|
||||
switch (status) {
|
||||
case 413:
|
||||
@@ -524,10 +543,10 @@ export class PwaService extends DataService {
|
||||
}
|
||||
}
|
||||
|
||||
getPlaylistFromUrl(url: string) {
|
||||
getPlaylistFromUrl(url: string): Observable<Playlist> {
|
||||
return from(this.getProviderTargetId(url)).pipe(
|
||||
switchMap((targetId) =>
|
||||
this.http.get(`${this.corsProxyUrl}/parse`, {
|
||||
this.http.get<Playlist>(`${this.corsProxyUrl}/parse`, {
|
||||
params: { targetId },
|
||||
})
|
||||
)
|
||||
|
||||
@@ -115,7 +115,10 @@ export class SettingsService {
|
||||
const mediaQuery = this.systemThemeMediaQuery as LegacyMediaQueryList;
|
||||
|
||||
if (mediaQuery.addEventListener) {
|
||||
mediaQuery.addEventListener('change', this.systemThemeChangeHandler);
|
||||
mediaQuery.addEventListener(
|
||||
'change',
|
||||
this.systemThemeChangeHandler
|
||||
);
|
||||
} else {
|
||||
mediaQuery.addListener?.(this.systemThemeChangeHandler);
|
||||
}
|
||||
@@ -147,8 +150,8 @@ export class SettingsService {
|
||||
* @param key key to get
|
||||
* @returns returns the value of the given key
|
||||
*/
|
||||
getValueFromLocalStorage(key: STORE_KEY) {
|
||||
return this.storage.get(key);
|
||||
getValueFromLocalStorage<T = unknown>(key: STORE_KEY): Observable<T> {
|
||||
return this.storage.get(key) as Observable<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,9 +178,9 @@ export class SettingsService {
|
||||
*/
|
||||
getAppVersion() {
|
||||
return this.http
|
||||
.get<{ created_at: string; name: string }[]>(
|
||||
'https://api.github.com/repos/4gray/iptvnator/releases'
|
||||
)
|
||||
.get<
|
||||
{ created_at: string; name: string }[]
|
||||
>('https://api.github.com/repos/4gray/iptvnator/releases')
|
||||
.pipe(
|
||||
map((response) => {
|
||||
// Filter out pre-release versions (beta, alpha, rc, etc.)
|
||||
@@ -215,10 +218,7 @@ export class SettingsService {
|
||||
* @param latestVersion latest stable version from GitHub
|
||||
* @returns true if current version is outdated
|
||||
*/
|
||||
isVersionOutdated(
|
||||
currentVersion: string,
|
||||
latestVersion: string
|
||||
): boolean {
|
||||
isVersionOutdated(currentVersion: string, latestVersion: string): boolean {
|
||||
const cleanCurrent = parseVersion(currentVersion);
|
||||
const cleanLatest = parseVersion(latestVersion);
|
||||
|
||||
|
||||
@@ -494,7 +494,10 @@ describe('SettingsComponent', () => {
|
||||
.some((player) => player.id === VideoPlayer.EmbeddedMpv)
|
||||
).toBe(false);
|
||||
|
||||
resolveSupport!({
|
||||
if (!resolveSupport) {
|
||||
throw new Error('Expected embedded MPV support resolver');
|
||||
}
|
||||
resolveSupport({
|
||||
supported: true,
|
||||
platform: 'darwin',
|
||||
});
|
||||
@@ -982,4 +985,39 @@ describe('SettingsComponent', () => {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves saved EPG settings when saving from the web settings form', async () => {
|
||||
fixture.destroy();
|
||||
window.electron = undefined as unknown as typeof window.electron;
|
||||
|
||||
const mockStore = settingsStore as unknown as MockSettingsStore;
|
||||
mockStore._setSettings({
|
||||
epgUrl: ['https://example.com/guide.xml'],
|
||||
preferUploadedEpgOverXtream: true,
|
||||
});
|
||||
mockStore.updateSettings.mockResolvedValue(undefined);
|
||||
|
||||
const webFixture = TestBed.createComponent(SettingsComponent);
|
||||
const webComponent = webFixture.componentInstance;
|
||||
webComponent.checkAppVersion = jest.fn();
|
||||
webComponent.fetchLocalIpAddresses = jest
|
||||
.fn()
|
||||
.mockResolvedValue(undefined);
|
||||
webFixture.detectChanges();
|
||||
await webFixture.whenStable();
|
||||
|
||||
webComponent.settingsForm.patchValue({ theme: Theme.DarkTheme });
|
||||
webComponent.onSubmit();
|
||||
await webFixture.whenStable();
|
||||
|
||||
expect(mockStore.updateSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
epgUrl: ['https://example.com/guide.xml'],
|
||||
preferUploadedEpgOverXtream: true,
|
||||
theme: Theme.DarkTheme,
|
||||
})
|
||||
);
|
||||
|
||||
webFixture.destroy();
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import {
|
||||
FormArray,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
@@ -48,6 +49,7 @@ import {
|
||||
CoverSize,
|
||||
Language,
|
||||
normalizeExternalPlayerArguments,
|
||||
Settings,
|
||||
StartupBehavior,
|
||||
StreamFormat,
|
||||
Theme,
|
||||
@@ -162,10 +164,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
]);
|
||||
|
||||
/** Current version of the app */
|
||||
version: string;
|
||||
version = '';
|
||||
|
||||
/** Update message to show */
|
||||
updateMessage: string;
|
||||
updateMessage = '';
|
||||
|
||||
/** EPG availability flag */
|
||||
epgAvailable$ = this.store.select(selectIsEpgAvailable);
|
||||
@@ -178,7 +180,9 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
/** Settings form object */
|
||||
settingsForm = this.formBuilder.group({
|
||||
player: [VideoPlayer.VideoJs],
|
||||
...(this.isDesktop ? { epgUrl: new FormArray([]) } : {}),
|
||||
...(this.isDesktop
|
||||
? { epgUrl: new FormArray<FormControl<string | null>>([]) }
|
||||
: {}),
|
||||
streamFormat: StreamFormat.M3u8StreamFormat,
|
||||
openStreamOnDoubleClick: false,
|
||||
language: Language.ENGLISH,
|
||||
@@ -468,21 +472,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
* the indexed db store
|
||||
*/
|
||||
onSubmit(): void {
|
||||
const settings = {
|
||||
...this.settingsForm.value,
|
||||
mpvPlayerPath: this.normalizeExternalPlayerPath(
|
||||
this.settingsForm.value.mpvPlayerPath
|
||||
),
|
||||
vlcPlayerPath: this.normalizeExternalPlayerPath(
|
||||
this.settingsForm.value.vlcPlayerPath
|
||||
),
|
||||
mpvPlayerArguments: normalizeExternalPlayerArguments(
|
||||
this.settingsForm.value.mpvPlayerArguments
|
||||
),
|
||||
vlcPlayerArguments: normalizeExternalPlayerArguments(
|
||||
this.settingsForm.value.vlcPlayerArguments
|
||||
),
|
||||
};
|
||||
const settings = this.createSettingsFromFormValue();
|
||||
|
||||
this.settingsStore.updateSettings(settings).then(() => {
|
||||
this.applyChangedSettings();
|
||||
@@ -505,6 +495,51 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
return playerPath?.trim() ?? '';
|
||||
}
|
||||
|
||||
private createSettingsFromFormValue(): Settings {
|
||||
const currentSettings = this.settingsStore.getSettings();
|
||||
const value = this.settingsForm.value;
|
||||
const epgUrl = Array.isArray(value.epgUrl)
|
||||
? value.epgUrl.filter(
|
||||
(url): url is string => typeof url === 'string'
|
||||
)
|
||||
: (currentSettings.epgUrl ?? []);
|
||||
|
||||
return {
|
||||
player: value.player ?? VideoPlayer.VideoJs,
|
||||
streamFormat: value.streamFormat ?? StreamFormat.M3u8StreamFormat,
|
||||
openStreamOnDoubleClick: value.openStreamOnDoubleClick ?? false,
|
||||
language: value.language ?? Language.ENGLISH,
|
||||
showCaptions: value.showCaptions ?? false,
|
||||
showDashboard: value.showDashboard ?? true,
|
||||
startupBehavior: value.startupBehavior ?? StartupBehavior.FirstView,
|
||||
showExternalPlaybackBar: value.showExternalPlaybackBar ?? true,
|
||||
theme: value.theme ?? Theme.SystemTheme,
|
||||
mpvPlayerPath: this.normalizeExternalPlayerPath(
|
||||
value.mpvPlayerPath
|
||||
),
|
||||
mpvPlayerArguments: normalizeExternalPlayerArguments(
|
||||
value.mpvPlayerArguments
|
||||
),
|
||||
mpvReuseInstance: value.mpvReuseInstance ?? false,
|
||||
vlcPlayerPath: this.normalizeExternalPlayerPath(
|
||||
value.vlcPlayerPath
|
||||
),
|
||||
vlcPlayerArguments: normalizeExternalPlayerArguments(
|
||||
value.vlcPlayerArguments
|
||||
),
|
||||
vlcReuseInstance: value.vlcReuseInstance ?? false,
|
||||
remoteControl: value.remoteControl ?? false,
|
||||
remoteControlPort: Number(value.remoteControlPort ?? 8765),
|
||||
recordingFolder: value.recordingFolder ?? '',
|
||||
coverSize: value.coverSize ?? 'medium',
|
||||
epgUrl,
|
||||
preferUploadedEpgOverXtream:
|
||||
value.preferUploadedEpgOverXtream ??
|
||||
currentSettings.preferUploadedEpgOverXtream ??
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the changed settings to the app
|
||||
*/
|
||||
@@ -516,14 +551,19 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
if (!Array.isArray(epgUrls)) {
|
||||
epgUrls = [epgUrls];
|
||||
}
|
||||
epgUrls = epgUrls.filter((url) => url !== '');
|
||||
if (epgUrls.length > 0) {
|
||||
const validEpgUrls = epgUrls.filter(
|
||||
(url): url is string =>
|
||||
typeof url === 'string' && url !== ''
|
||||
);
|
||||
if (validEpgUrls.length > 0) {
|
||||
// Fetch all EPG URLs at once
|
||||
this.epgService.fetchEpg(epgUrls);
|
||||
this.epgService.fetchEpg(validEpgUrls);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.translate.use(this.settingsForm.value.language);
|
||||
this.translate.use(
|
||||
this.settingsForm.value.language ?? Language.ENGLISH
|
||||
);
|
||||
this.settingsService.changeTheme(
|
||||
this.settingsForm.value.theme ?? Theme.SystemTheme
|
||||
);
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "Браузерный плеер не получил доступ к потоку",
|
||||
"DESCRIPTION": "Запрос может быть заблокирован CORS, mixed content или требованиями провайдера/прокси к заголовкам. Скопируйте URL потока и откройте его во внешнем плеере, например MPV, VLC или IINA."
|
||||
"DESCRIPTION": "Запрос может быть заблокирован CORS, mixed content или требованиями провайдера/прокси к заголовкам. MPV или VLC часто обходят такие ограничения браузерного плеера.",
|
||||
"PWA_DESCRIPTION": "Запрос может быть заблокирован CORS, mixed content или требованиями провайдера/прокси к заголовкам. Скопируйте URL потока и откройте его вручную во внешнем плеере, например MPV, VLC или IINA."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "Поток может быть зашифрован или защищен DRM",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
@@ -407,7 +407,8 @@
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. Copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA to bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
|
||||
Vendored
+4
-3
@@ -19,6 +19,7 @@ import {
|
||||
PlaylistRefreshPayload,
|
||||
PortalDebugEvent,
|
||||
ResolvedPortalPlayback,
|
||||
Settings,
|
||||
XtreamBackupFavoriteItem,
|
||||
XtreamBackupHiddenCategory,
|
||||
XtreamBackupRecentlyViewedItem,
|
||||
@@ -71,7 +72,7 @@ declare global {
|
||||
url: string,
|
||||
title: string,
|
||||
thumbnail: string,
|
||||
userAgent: string,
|
||||
userAgent: string | undefined,
|
||||
referer?: string,
|
||||
origin?: string,
|
||||
contentInfo?: unknown,
|
||||
@@ -82,7 +83,7 @@ declare global {
|
||||
url: string,
|
||||
title: string,
|
||||
thumbnail: string,
|
||||
userAgent: string,
|
||||
userAgent: string | undefined,
|
||||
referer?: string,
|
||||
origin?: string,
|
||||
contentInfo?: unknown,
|
||||
@@ -119,7 +120,7 @@ declare global {
|
||||
searchTerm: string,
|
||||
limit?: number
|
||||
) => Promise<EpgProgram[]>;
|
||||
updateSettings: (settings: JsonObject) => Promise<void>;
|
||||
updateSettings: (settings: Partial<Settings>) => Promise<void>;
|
||||
getAiSettings: () => Promise<{
|
||||
aiProvider: string;
|
||||
aiModelName: string;
|
||||
|
||||
Reference in new issue
Block a user