fix(m3u): unify DASH URL detection with playback extension normalization

isDashStreamUrl() used the simpler getStreamExtensionFromUrl(), so URLs
the player engines classify as DASH (stream.MPD, ?ext=mpd, ?format=mpd)
were not routed to the Shaka-capable inline player and lost their
ClearKey metadata with Video.js or external players configured
(Codex P2). The normalized getPlaybackMediaExtensionFromUrl() now lives
in @iptvnator/shared/m3u-utils (re-exported unchanged from the playback
lib) and both routing and engine selection share it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-23 23:40:40 +02:00
1 parent 48d191b0d7
commit 95bd7f7fe0
5 files changed
+145 -122

No files matched your search

@@ -1,27 +1,17 @@
import { getExtensionFromUrl } from '@iptvnator/shared/m3u-utils';
import {
UNSUPPORTED_CONTAINER_EXTENSIONS,
getPlaybackMediaExtensionFromUrl,
} from '@iptvnator/shared/m3u-utils';
import type {
PlaybackSourceMetadata,
PlaybackSourceMetadataInput,
} from './playback-diagnostics.model';
const UNSUPPORTED_CONTAINER_EXTENSIONS = new Set([
'avi',
'asf',
'divx',
'flv',
'm2ts',
'm4v',
'mkv',
'mov',
'mpeg',
'mpg',
'rm',
'rmvb',
'ts',
'vob',
'wmv',
]);
// Media-extension normalization lives in @iptvnator/shared/m3u-utils so M3U
// routing (isDashChannel) and engine selection always agree; re-exported here
// to keep the playback-lib import surface stable.
export { getPlaybackMediaExtensionFromUrl };
const UNSUPPORTED_CONTAINER_NAMES = new Set([
...UNSUPPORTED_CONTAINER_EXTENSIONS,
@@ -31,42 +21,6 @@ const UNSUPPORTED_CONTAINER_NAMES = new Set([
'x-msvideo',
]);
const EXPLICIT_MEDIA_EXTENSION_QUERY_KEYS = ['extension', 'ext'];
const DECLARED_MEDIA_FORMAT_QUERY_KEYS = ['format', 'container'];
const DECLARED_MEDIA_EXTENSION_ALIASES = new Map([
['hls', 'm3u8'],
['mpegts', 'ts'],
['mpeg-ts', 'ts'],
]);
const DECLARED_MEDIA_EXTENSIONS = new Set([
...UNSUPPORTED_CONTAINER_EXTENSIONS,
'aac',
'flac',
'm3u',
'm3u8',
'm4s',
'mp3',
'mp4',
'mpd',
'oga',
'ogg',
'ogv',
'webm',
]);
const NON_MEDIA_URL_EXTENSIONS = new Set([
'asp',
'aspx',
'cgi',
'jsp',
'mpv',
'php',
'pl',
]);
const BROWSER_LIMITED_CODEC_PATTERNS: ReadonlyArray<{
readonly label: string;
readonly pattern: RegExp;
@@ -95,35 +49,6 @@ export function createPlaybackSourceMetadata(
};
}
export function getPlaybackMediaExtensionFromUrl(url: string): string {
const explicitQueryExtension = getMediaExtensionFromQuery(
url,
EXPLICIT_MEDIA_EXTENSION_QUERY_KEYS
);
if (explicitQueryExtension) {
return explicitQueryExtension;
}
const pathExtension = normalizeExtensionToken(getExtensionFromUrl(url));
if (DECLARED_MEDIA_EXTENSIONS.has(pathExtension)) {
return pathExtension;
}
const formatQueryExtension = getMediaExtensionFromQuery(
url,
DECLARED_MEDIA_FORMAT_QUERY_KEYS
);
if (formatQueryExtension) {
return formatQueryExtension;
}
if (NON_MEDIA_URL_EXTENSIONS.has(pathExtension)) {
return '';
}
return pathExtension;
}
export function getLikelyBrowserUnsupportedCodecLabels(
metadata: Pick<PlaybackSourceMetadata, 'audioCodecs' | 'videoCodecs'>
): string[] {
@@ -188,40 +113,3 @@ function normalizeCodecs(codecs: readonly string[] | undefined): string[] {
function normalizeToken(value: string | undefined): string {
return value?.trim().toLowerCase() ?? '';
}
function normalizeExtensionToken(value: string | undefined): string {
return normalizeToken(value).replace(/^\.+/, '');
}
function getMediaExtensionFromQuery(
url: string,
queryKeys: readonly string[]
): string {
try {
const parsedUrl = new URL(url, 'http://iptvnator.local');
for (const key of queryKeys) {
const declaredExtension = normalizeDeclaredMediaExtension(
parsedUrl.searchParams.get(key) ?? undefined
);
if (declaredExtension) {
return declaredExtension;
}
}
return '';
} catch {
return '';
}
}
function normalizeDeclaredMediaExtension(value: string | undefined): string {
const extension = normalizeExtensionToken(value);
const normalized =
DECLARED_MEDIA_EXTENSION_ALIASES.get(extension) ?? extension;
if (!normalized || NON_MEDIA_URL_EXTENSIONS.has(normalized)) {
return '';
}
return DECLARED_MEDIA_EXTENSIONS.has(normalized) ? normalized : '';
}