mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
feat: add support for additional HTTP headers in player and Stalker requests
This commit is contained in:
1 parent
2fdb8f409f
commit
e76466e250
9 files changed
+354
-98
No files matched your search
@@ -99,7 +99,8 @@ export class ElectronService extends DataService {
|
||||
data.referer ?? undefined,
|
||||
data.origin ?? undefined,
|
||||
data.contentInfo,
|
||||
data.startTime
|
||||
data.startTime,
|
||||
data.headers ?? undefined
|
||||
);
|
||||
/* thumbnail: data.thumbnail ?? '', */
|
||||
} catch (error: any) {
|
||||
@@ -124,7 +125,8 @@ export class ElectronService extends DataService {
|
||||
data.referer ?? undefined,
|
||||
data.origin ?? undefined,
|
||||
data.contentInfo,
|
||||
data.startTime
|
||||
data.startTime,
|
||||
data.headers ?? undefined
|
||||
);
|
||||
} catch (error: any) {
|
||||
const errorMessage = error?.message || String(error);
|
||||
|
||||
@@ -31,7 +31,8 @@ export class PlayerService {
|
||||
referer?: string,
|
||||
origin?: string,
|
||||
contentInfo?: any,
|
||||
startTime?: number
|
||||
startTime?: number,
|
||||
headers?: Record<string, string>
|
||||
) {
|
||||
const player = this.settingsStore.player() ?? VideoPlayer.VideoJs;
|
||||
|
||||
@@ -46,6 +47,7 @@ export class PlayerService {
|
||||
'user-agent': userAgent,
|
||||
referer: referer,
|
||||
origin: origin,
|
||||
headers,
|
||||
contentInfo,
|
||||
startTime,
|
||||
});
|
||||
@@ -60,6 +62,7 @@ export class PlayerService {
|
||||
'user-agent': userAgent,
|
||||
referer: referer,
|
||||
origin: origin,
|
||||
headers,
|
||||
contentInfo,
|
||||
startTime,
|
||||
});
|
||||
|
||||
@@ -3,14 +3,18 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { signalStoreFeature, withMethods } from '@ngrx/signals';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DataService, PlaylistsService, StalkerSessionService } from 'services';
|
||||
import { Playlist, STALKER_REQUEST, StalkerPortalActions } from 'shared-interfaces';
|
||||
import {
|
||||
Playlist,
|
||||
STALKER_REQUEST,
|
||||
StalkerPortalActions,
|
||||
} from 'shared-interfaces';
|
||||
import { PlayerService } from '../../../services/player.service';
|
||||
import { createLogger } from '../../../shared/utils/logger';
|
||||
import { StalkerContentTypes } from '../../stalker-content-types';
|
||||
import {
|
||||
normalizeStalkerEntityId,
|
||||
normalizeStalkerEntityIdAsNumber,
|
||||
} from '../../stalker-vod.utils';
|
||||
import { createLogger } from '../../../shared/utils/logger';
|
||||
|
||||
type StalkerContentType = 'itv' | 'vod' | 'series';
|
||||
|
||||
@@ -61,6 +65,30 @@ export function withStalkerPlayer() {
|
||||
cmd: string,
|
||||
series?: number
|
||||
) => {
|
||||
const normalizeCmdValue = (value: string): string => {
|
||||
const trimmed = String(value ?? '').trim();
|
||||
if (!trimmed) return '';
|
||||
|
||||
// Some portals prepend transport wrappers like:
|
||||
// "ffmpeg http://...", "ffrt http://...", etc.
|
||||
const splitAt = trimmed.indexOf(' ');
|
||||
if (splitAt > 0) {
|
||||
const candidate = trimmed
|
||||
.slice(splitAt + 1)
|
||||
.trim();
|
||||
if (
|
||||
candidate.startsWith('http://') ||
|
||||
candidate.startsWith('https://') ||
|
||||
candidate.startsWith('/') ||
|
||||
candidate.startsWith('?')
|
||||
) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
const selectedContentType =
|
||||
storeState.selectedContentType();
|
||||
const type = series ? 'vod' : selectedContentType;
|
||||
@@ -69,7 +97,8 @@ export function withStalkerPlayer() {
|
||||
// The server adds the required token for playback authorization
|
||||
// Note: cmd is already transformed during item processing (has_files items)
|
||||
const params = {
|
||||
action: StalkerContentTypes[selectedContentType].getLink,
|
||||
action: StalkerContentTypes[selectedContentType]
|
||||
.getLink,
|
||||
cmd: cmd,
|
||||
type,
|
||||
disable_ad: '0',
|
||||
@@ -83,10 +112,11 @@ export function withStalkerPlayer() {
|
||||
let response: StalkerResponse;
|
||||
if (playlist?.isFullStalkerPortal) {
|
||||
// Full stalker portal - use authenticated request with retry
|
||||
response = await stalkerSession.makeAuthenticatedRequest(
|
||||
playlist,
|
||||
params
|
||||
);
|
||||
response =
|
||||
await stalkerSession.makeAuthenticatedRequest(
|
||||
playlist,
|
||||
params
|
||||
);
|
||||
} else {
|
||||
// Simple stalker portal - no auth needed
|
||||
response = await dataService.sendIpcEvent(
|
||||
@@ -106,16 +136,13 @@ export function withStalkerPlayer() {
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
let url = response.js.cmd as string;
|
||||
let url = normalizeCmdValue(response.js.cmd as string);
|
||||
|
||||
// If cmd is empty, the content is not available
|
||||
if (!url) {
|
||||
throw new Error('nothing_to_play');
|
||||
}
|
||||
|
||||
if (url.startsWith('ffmpeg')) {
|
||||
url = url.split(' ')[1];
|
||||
}
|
||||
// Handle incomplete URLs - some portals return just query params or relative paths
|
||||
if (
|
||||
url &&
|
||||
@@ -136,7 +163,8 @@ export function withStalkerPlayer() {
|
||||
pathParts[i] === 'portal'
|
||||
) {
|
||||
basePath =
|
||||
'/' + pathParts.slice(1, i + 1).join('/');
|
||||
'/' +
|
||||
pathParts.slice(1, i + 1).join('/');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -144,9 +172,17 @@ export function withStalkerPlayer() {
|
||||
// If url starts with ?, it's just query params
|
||||
// Combine with the original cmd path to form the complete streaming URL
|
||||
if (url.startsWith('?')) {
|
||||
const normalizedCmd = normalizeCmdValue(cmd);
|
||||
// The streaming URL is: portal origin + base path + original cmd path + token query
|
||||
// e.g., http://portal.com + /stalker_portal + /media/12345.mpg + ?token=xxx
|
||||
url = `${portalUrlObj.origin}${basePath}${cmd}${url}`;
|
||||
if (
|
||||
normalizedCmd.startsWith('http://') ||
|
||||
normalizedCmd.startsWith('https://')
|
||||
) {
|
||||
url = `${normalizedCmd}${url}`;
|
||||
} else {
|
||||
url = `${portalUrlObj.origin}${basePath}${normalizedCmd}${url}`;
|
||||
}
|
||||
} else if (url.startsWith('/')) {
|
||||
// Relative path - prepend origin and base path
|
||||
url = `${portalUrlObj.origin}${basePath}${url}`;
|
||||
@@ -173,10 +209,11 @@ export function withStalkerPlayer() {
|
||||
|
||||
let response: StalkerResponse;
|
||||
if (playlist.isFullStalkerPortal) {
|
||||
response = await stalkerSession.makeAuthenticatedRequest(
|
||||
playlist,
|
||||
queryParams
|
||||
);
|
||||
response =
|
||||
await stalkerSession.makeAuthenticatedRequest(
|
||||
playlist,
|
||||
queryParams
|
||||
);
|
||||
} else {
|
||||
response = await dataService.sendIpcEvent(
|
||||
STALKER_REQUEST,
|
||||
@@ -205,8 +242,10 @@ export function withStalkerPlayer() {
|
||||
) => {
|
||||
const playlistId = storeState.currentPlaylist()?._id;
|
||||
if (!playlistId) return;
|
||||
const recentlyViewedItem: { id: string; title: string } &
|
||||
Record<string, unknown> = {
|
||||
const recentlyViewedItem: {
|
||||
id: string;
|
||||
title: string;
|
||||
} & Record<string, unknown> = {
|
||||
...item,
|
||||
id: normalizeStalkerEntityId(item?.id),
|
||||
cmd,
|
||||
|
||||
@@ -64,42 +64,51 @@ export function withStalkerPortal() {
|
||||
},
|
||||
})
|
||||
),
|
||||
withMethods((store, dataService = inject(DataService)) => ({
|
||||
async setCurrentPlaylist(playlist: PlaylistMeta | undefined) {
|
||||
patchState(store, { currentPlaylist: playlist });
|
||||
withMethods(
|
||||
(
|
||||
store,
|
||||
dataService = inject(DataService),
|
||||
stalkerSession = inject(StalkerSessionService)
|
||||
) => ({
|
||||
async setCurrentPlaylist(playlist: PlaylistMeta | undefined) {
|
||||
stalkerSession.setActiveWatchdogPlaylist(
|
||||
(playlist as Playlist) || undefined
|
||||
);
|
||||
patchState(store, { currentPlaylist: playlist });
|
||||
|
||||
// Ensure Stalker playlist exists in SQLite for playback positions
|
||||
// Only sync if this is actually a Stalker playlist (has macAddress and portalUrl)
|
||||
if (
|
||||
playlist &&
|
||||
dataService.isElectron &&
|
||||
playlist._id &&
|
||||
playlist.macAddress &&
|
||||
playlist.portalUrl
|
||||
) {
|
||||
try {
|
||||
const playlistId = String(playlist._id);
|
||||
// Check if playlist exists in SQLite
|
||||
const existing =
|
||||
await window.electron.dbGetPlaylist(playlistId);
|
||||
if (!existing) {
|
||||
// Create playlist in SQLite
|
||||
await window.electron.dbCreatePlaylist({
|
||||
id: playlistId,
|
||||
name: playlist.title || '',
|
||||
macAddress: playlist.macAddress || '',
|
||||
url: playlist.portalUrl || '',
|
||||
type: 'stalker',
|
||||
});
|
||||
// Ensure Stalker playlist exists in SQLite for playback positions
|
||||
// Only sync if this is actually a Stalker playlist (has macAddress and portalUrl)
|
||||
if (
|
||||
playlist &&
|
||||
dataService.isElectron &&
|
||||
playlist._id &&
|
||||
playlist.macAddress &&
|
||||
playlist.portalUrl
|
||||
) {
|
||||
try {
|
||||
const playlistId = String(playlist._id);
|
||||
// Check if playlist exists in SQLite
|
||||
const existing =
|
||||
await window.electron.dbGetPlaylist(playlistId);
|
||||
if (!existing) {
|
||||
// Create playlist in SQLite
|
||||
await window.electron.dbCreatePlaylist({
|
||||
id: playlistId,
|
||||
name: playlist.title || '',
|
||||
macAddress: playlist.macAddress || '',
|
||||
url: playlist.portalUrl || '',
|
||||
type: 'stalker',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
'Error syncing Stalker playlist to SQLite',
|
||||
error
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
'Error syncing Stalker playlist to SQLite',
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}))
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
Vendored
+5
-2
@@ -34,7 +34,8 @@ declare global {
|
||||
referer?: string,
|
||||
origin?: string,
|
||||
contentInfo?: any,
|
||||
startTime?: number
|
||||
startTime?: number,
|
||||
headers?: Record<string, string>
|
||||
) => void;
|
||||
openInVlc: (
|
||||
url: string,
|
||||
@@ -43,7 +44,8 @@ declare global {
|
||||
referer?: string,
|
||||
origin?: string,
|
||||
contentInfo?: any,
|
||||
startTime?: number
|
||||
startTime?: number,
|
||||
headers?: Record<string, string>
|
||||
) => void;
|
||||
autoUpdatePlaylists: (playlists: any[]) => Promise<any[]>;
|
||||
fetchEpg: (
|
||||
@@ -80,6 +82,7 @@ declare global {
|
||||
macAddress: string;
|
||||
params: Record<string, string>;
|
||||
token?: string;
|
||||
serialNumber?: string;
|
||||
}) => Promise<any>;
|
||||
xtreamRequest: (payload: {
|
||||
url: string;
|
||||
|
||||
Reference in new issue
Block a user