mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(playback): route non-HLS containers to the native source path (#1510)
The HTML5 player chose its engine by exclusion: anything that was not mpd/ts/mp4 went to hls.js, so .mkv (the default Xtream series/VOD container), .webm, .avi, .mov and .m4v were fed to hls.js as manifests. hls.js raised a manifest error and the player showed the network/provider diagnostic over media Chromium plays natively. Add resolvePlaybackUrlSourceKind() to @iptvnator/playback/util as the one URL-to-engine rule (mpd -> dash, m3u8/m3u -> hls, ts/m2ts/extension-less -> mpegts, everything else -> native) and read it from both the HTML5 player and ArtPlayer's getArtPlayerVideoType(), so the two engines agree. ArtPlayer serves every native container through a single ART_PLAYER_NATIVE_SOURCE_TYPE custom type, keeping the source session the owner of teardown and controls binding. The HTML5 native <source> carries the video/mp4 hint only for MP4-family files: a hint the browser's canPlayType() rejects makes it skip the source, and Chromium demuxes containers it does not advertise there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
0a2f6121f8
commit
5e51c1ea5d
15 files changed
+257
-35
No files matched your search
@@ -6,4 +6,5 @@ export type * from './lib/diagnostics/shaka-error.types';
|
||||
export * from './lib/playback-recommendation.model';
|
||||
export * from './lib/playback-recommendation-policy';
|
||||
export * from './lib/playback-session-key';
|
||||
export * from './lib/playback-source-routing';
|
||||
export * from './lib/playback-target-capabilities';
|
||||
@@ -0,0 +1,43 @@
|
||||
import { PlaybackSourceKind } from './playback-recommendation.model';
|
||||
import { resolvePlaybackUrlSourceKind } from './playback-source-routing';
|
||||
|
||||
describe('resolvePlaybackUrlSourceKind', () => {
|
||||
it.each([
|
||||
['https://example.test/live/playlist.m3u8', PlaybackSourceKind.Hls],
|
||||
[
|
||||
'https://example.test/live/playlist.M3U8?token=signed',
|
||||
PlaybackSourceKind.Hls,
|
||||
],
|
||||
['https://example.test/live/list.m3u', PlaybackSourceKind.Hls],
|
||||
['https://example.test/play?format=hls', PlaybackSourceKind.Hls],
|
||||
['https://example.test/live.mpd', PlaybackSourceKind.Dash],
|
||||
[
|
||||
'https://example.test/stream.MPD?token=signed#frag',
|
||||
PlaybackSourceKind.Dash,
|
||||
],
|
||||
['https://example.test/raw.ts', PlaybackSourceKind.MpegTs],
|
||||
['https://example.test/disc.m2ts', PlaybackSourceKind.MpegTs],
|
||||
['https://example.test/live/user/pass/1', PlaybackSourceKind.MpegTs],
|
||||
['https://example.test/live.php?id=7', PlaybackSourceKind.MpegTs],
|
||||
['https://example.test/play?ext=mpegts', PlaybackSourceKind.MpegTs],
|
||||
])('routes %s to the %s engine', (url, kind) => {
|
||||
expect(resolvePlaybackUrlSourceKind(url)).toBe(kind);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'https://example.test/movie/user/pass/2000000.mp4',
|
||||
'https://example.test/series/user/pass/80000.mkv',
|
||||
'https://example.test/movie.MKV?token=signed',
|
||||
'https://example.test/play?extension=mkv',
|
||||
'https://example.test/movie.webm',
|
||||
'https://example.test/movie.avi',
|
||||
'https://example.test/movie.mov',
|
||||
'https://example.test/movie.m4v',
|
||||
'https://example.test/track.mp3',
|
||||
'https://example.test/movie.bin',
|
||||
])('hands the non-HLS container %s to the native element', (url) => {
|
||||
expect(resolvePlaybackUrlSourceKind(url)).toBe(
|
||||
PlaybackSourceKind.Native
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { getPlaybackMediaExtensionFromUrl } from '@iptvnator/shared/m3u-utils';
|
||||
import { PlaybackSourceKind } from './playback-recommendation.model';
|
||||
|
||||
/**
|
||||
* Engine family a playback URL is routed to BEFORE any engine runs.
|
||||
*
|
||||
* `resolvePlaybackSourceKind()` answers the opposite question — which family
|
||||
* a diagnostic came out of after an engine failed. Every built-in web player
|
||||
* selects its source engine through this function, so the HTML5 player and
|
||||
* ArtPlayer cannot disagree on what a URL is. A URL never yields `unknown`:
|
||||
* an unrecognized container is exactly what the native `<video>` element is
|
||||
* for.
|
||||
*/
|
||||
export type PlaybackUrlSourceKind = Exclude<
|
||||
PlaybackSourceKind,
|
||||
typeof PlaybackSourceKind.Unknown
|
||||
>;
|
||||
|
||||
const DASH_MANIFEST_EXTENSION = 'mpd';
|
||||
const HLS_MANIFEST_EXTENSIONS: ReadonlySet<string> = new Set(['m3u', 'm3u8']);
|
||||
/**
|
||||
* `.m2ts` (BDAV, 192-byte packets) rides with `.ts`: mpegts.js probes the
|
||||
* packet size itself, so its `mpegts` player type covers both.
|
||||
*/
|
||||
const MPEG_TS_EXTENSIONS: ReadonlySet<string> = new Set(['ts', 'm2ts']);
|
||||
|
||||
export function resolvePlaybackUrlSourceKind(
|
||||
url: string
|
||||
): PlaybackUrlSourceKind {
|
||||
const extension = getPlaybackMediaExtensionFromUrl(url);
|
||||
if (extension === DASH_MANIFEST_EXTENSION) {
|
||||
return PlaybackSourceKind.Dash;
|
||||
}
|
||||
if (HLS_MANIFEST_EXTENSIONS.has(extension)) {
|
||||
return PlaybackSourceKind.Hls;
|
||||
}
|
||||
// Extension-less IPTV proxy/script URLs (`/live/user/pass/1`, `.php`)
|
||||
// are predominantly raw MPEG-TS and keep the established engine.
|
||||
if (!extension || MPEG_TS_EXTENSIONS.has(extension)) {
|
||||
return PlaybackSourceKind.MpegTs;
|
||||
}
|
||||
// Every other container — mkv, webm, mp4, avi, mov, m4v, audio files —
|
||||
// is handed to the browser. hls.js only ever receives HLS manifests: fed
|
||||
// an .mkv it raised a manifest error over media Chromium plays natively.
|
||||
return PlaybackSourceKind.Native;
|
||||
}
|
||||
Reference in new issue
Block a user