mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
fix(xtream): key catchup probes by output formats
This commit is contained in:
1 parent
ca81bc552f
commit
007ae7028d
3 files changed
+96
-16
No files matched your search
@@ -89,8 +89,12 @@ supports these catch-up variants:
|
||||
with optional `extension=ts` or `extension=m3u8`.
|
||||
|
||||
Electron probes concrete catch-up variants before caching a playlist-level
|
||||
choice. The probe uses a short range `GET`, follows only validated redirects,
|
||||
and accepts only `200` or `206` as playable. MPEG-TS is preferred before HLS
|
||||
when the provider allows it because some portals return a valid HLS manifest
|
||||
while the first media segment fails in Chromium/video.js. PWA fallback keeps the
|
||||
REST MPEG-TS URL when no Electron probe API is available.
|
||||
choice. The cache key includes the playlist id and the normalized
|
||||
`allowed_output_formats` advertised by the provider, so a catch-up variant
|
||||
detected before account capabilities are known cannot force stale MPEG-TS URLs
|
||||
after the portal later reports HLS-only playback. The probe uses a short range
|
||||
`GET`, follows only validated redirects, and accepts only `200` or `206` as
|
||||
playable. MPEG-TS is preferred before HLS when the provider allows it because
|
||||
some portals return a valid HLS manifest while the first media segment fails in
|
||||
Chromium/video.js. PWA fallback keeps the REST MPEG-TS URL when no Electron
|
||||
probe API is available.
|
||||
@@ -188,11 +188,57 @@ describe('XtreamUrlService', () => {
|
||||
expect(secondUrl).toBe(firstUrl);
|
||||
expect(xtreamProbeUrl).toHaveBeenCalledTimes(2);
|
||||
expect(databaseService.setAppState).toHaveBeenCalledWith(
|
||||
'xtream-catchup-variant:v3:playlist-1',
|
||||
'xtream-catchup-variant:v4:playlist-1:formats:ts',
|
||||
'legacy:ts'
|
||||
);
|
||||
});
|
||||
|
||||
it('redetects catchup variants when provider output formats become known', async () => {
|
||||
const xtreamProbeUrl = jest.fn(async (url: string) => ({
|
||||
status:
|
||||
url.includes('/streaming/timeshift.php?') &&
|
||||
url.includes('extension=ts')
|
||||
? 206
|
||||
: url.includes('/streaming/timeshift.php?') &&
|
||||
url.includes('extension=m3u8')
|
||||
? 200
|
||||
: 0,
|
||||
}));
|
||||
window.electron = {
|
||||
xtreamProbeUrl,
|
||||
} as typeof window.electron;
|
||||
|
||||
const initialUrl = await service.resolveCatchupUrl(
|
||||
'playlist-format-refresh',
|
||||
credentials,
|
||||
101,
|
||||
1775296800,
|
||||
1775300400
|
||||
);
|
||||
const refreshedUrl = await service.resolveCatchupUrl(
|
||||
'playlist-format-refresh',
|
||||
{
|
||||
...credentials,
|
||||
allowedOutputFormats: ['m3u8'],
|
||||
},
|
||||
101,
|
||||
1775296800,
|
||||
1775300400
|
||||
);
|
||||
|
||||
expect(initialUrl).toContain('extension=ts');
|
||||
expect(refreshedUrl).toContain('extension=m3u8');
|
||||
expect(xtreamProbeUrl).toHaveBeenCalledTimes(4);
|
||||
expect(databaseService.setAppState).toHaveBeenCalledWith(
|
||||
'xtream-catchup-variant:v4:playlist-format-refresh:formats:unknown',
|
||||
'legacy:ts'
|
||||
);
|
||||
expect(databaseService.setAppState).toHaveBeenCalledWith(
|
||||
'xtream-catchup-variant:v4:playlist-format-refresh:formats:m3u8',
|
||||
'legacy:m3u8'
|
||||
);
|
||||
});
|
||||
|
||||
it('prefers playable legacy MPEG-TS catchup before HLS for video.js compatible playlists', async () => {
|
||||
const xtreamProbeUrl = jest.fn(async (url: string) => ({
|
||||
status:
|
||||
@@ -227,7 +273,7 @@ describe('XtreamUrlService', () => {
|
||||
'GET'
|
||||
);
|
||||
expect(databaseService.setAppState).toHaveBeenCalledWith(
|
||||
'xtream-catchup-variant:v3:playlist-hls',
|
||||
'xtream-catchup-variant:v4:playlist-hls:formats:m3u8,ts',
|
||||
'legacy:ts'
|
||||
);
|
||||
});
|
||||
@@ -259,7 +305,7 @@ describe('XtreamUrlService', () => {
|
||||
expect(catchupUrl).toContain('/streaming/timeshift.php?');
|
||||
expect(catchupUrl).toContain('extension=m3u8');
|
||||
expect(databaseService.setAppState).toHaveBeenCalledWith(
|
||||
'xtream-catchup-variant:v3:playlist-hls-only',
|
||||
'xtream-catchup-variant:v4:playlist-hls-only:formats:m3u8,ts',
|
||||
'legacy:m3u8'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ type XtreamProbeApi = {
|
||||
) => Promise<{ status: number }>;
|
||||
};
|
||||
|
||||
const XTREAM_CATCHUP_VARIANT_KEY_PREFIX = 'xtream-catchup-variant:v3:';
|
||||
const XTREAM_CATCHUP_VARIANT_KEY_PREFIX = 'xtream-catchup-variant:v4:';
|
||||
|
||||
/**
|
||||
* Service for constructing Xtream stream URLs.
|
||||
@@ -230,7 +230,10 @@ export class XtreamUrlService {
|
||||
stopTimestamp: number,
|
||||
serverTimezone?: string
|
||||
): Promise<XtreamCatchupVariant> {
|
||||
const cacheKey = `${XTREAM_CATCHUP_VARIANT_KEY_PREFIX}${playlistId}`;
|
||||
const cacheKey = this.getCatchupVariantCacheKey(
|
||||
playlistId,
|
||||
credentials
|
||||
);
|
||||
const cached = this.catchupSchemeCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return this.normalizeCatchupVariant(cached);
|
||||
@@ -348,9 +351,8 @@ export class XtreamUrlService {
|
||||
private getPreferredCatchupExtensions(
|
||||
credentials: XtreamCredentials
|
||||
): XtreamCatchupExtension[] {
|
||||
const allowedFormats = credentials.allowedOutputFormats
|
||||
?.map((format) => format.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
const allowedFormats =
|
||||
this.getNormalizedAllowedOutputFormats(credentials);
|
||||
const formats =
|
||||
allowedFormats && allowedFormats.length > 0
|
||||
? allowedFormats
|
||||
@@ -369,6 +371,35 @@ export class XtreamUrlService {
|
||||
: [XTREAM_CATCHUP_EXTENSIONS.TS];
|
||||
}
|
||||
|
||||
private getCatchupVariantCacheKey(
|
||||
playlistId: string,
|
||||
credentials: XtreamCredentials
|
||||
): string {
|
||||
const allowedFormats =
|
||||
this.getNormalizedAllowedOutputFormats(credentials);
|
||||
const formatSignature =
|
||||
allowedFormats && allowedFormats.length > 0
|
||||
? [...new Set(allowedFormats)]
|
||||
.sort()
|
||||
.map((format) => encodeURIComponent(format))
|
||||
.join(',')
|
||||
: 'unknown';
|
||||
|
||||
return `${XTREAM_CATCHUP_VARIANT_KEY_PREFIX}${playlistId}:formats:${formatSignature}`;
|
||||
}
|
||||
|
||||
private getNormalizedAllowedOutputFormats(
|
||||
credentials: XtreamCredentials
|
||||
): string[] | undefined {
|
||||
const allowedFormats = credentials.allowedOutputFormats
|
||||
?.map((format) => format.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
|
||||
return allowedFormats && allowedFormats.length > 0
|
||||
? allowedFormats
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private normalizeCatchupVariant(
|
||||
scheme: XtreamCatchupScheme | XtreamCatchupVariant
|
||||
): XtreamCatchupVariant {
|
||||
@@ -436,9 +467,8 @@ export class XtreamUrlService {
|
||||
requestedFormat: string
|
||||
): string {
|
||||
const requested = requestedFormat.trim().toLowerCase();
|
||||
const allowedFormats = credentials.allowedOutputFormats
|
||||
?.map((format) => format.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
const allowedFormats =
|
||||
this.getNormalizedAllowedOutputFormats(credentials);
|
||||
|
||||
if (!requested || requested === StreamFormat.AutoStreamFormat) {
|
||||
return this.resolveAutoLiveStreamFormat(allowedFormats);
|
||||
|
||||
Reference in new issue
Block a user