fix: Hls.cast dead code, double-patch interceptors, manifest URL patterns
CI / test (pull_request) Successful in 32s

Hls.cast() is a TypeScript identity cast in hls.js — returns input unchanged,
NOT a reverse-lookup. Replaced with scanning video element properties for
HLS instance references real players store as custom properties.

Fixed double-patching bug in interceptors.js and expanded URL pattern
matching to cover /hls/, /playlist/, /manifest/, .hlsvod paths.

83/83 tests pass, clean build (9 modules).
This commit is contained in:
robot committed 2026-08-12 21:21:03 +00:00
1 parent 542c5786be
commit 3cf402d3c7
3 files changed
+90 -50

No files matched your search

+65 -26
View File
@@ -104,21 +104,45 @@ function m3u8DeepProbeFn() {
const manifests = [];
const seen = new Set();
// Helpers available in the MAIN world context
function resolveUrl(url) {
if (!url) return null;
try {
if (url.startsWith('//')) return 'https:' + url;
if (url.startsWith('http')) return url;
return new URL(url, window.location.origin).href;
} catch { return null; }
}
function isManifestUrl(url) {
if (!url) return false;
const u = url.toLowerCase();
return u.includes('.m3u8') || u.includes('.hlsvod') ||
(u.includes('/hls/') && u.includes('http')) ||
(u.includes('/playlist') && u.includes('http')) ||
(u.includes('/manifest') && u.includes('http'));
}
function walk(obj, path, depth) {
if (depth > 5) return;
if (typeof obj === 'string') {
const match = obj.match(/https?:\/\/[^"'\s`)]+\.m3u8[^"'\s`)]*/gi);
const match = obj.match(/https?:\/\/[^"'`\s)]+?(?:\.m3u8|\.hlsvod|\/hls\/|\/playlist[^"'`\s]*|\/manifest[^"'`\s]*)[^"'`\s)]*/gi);
if (match) {
for (const u of match) {
if (!seen.has(u)) { seen.add(u); manifests.push({ url: u, player: path }); }
const resolved = resolveUrl(u);
if (resolved && isManifestUrl(resolved) && !seen.has(resolved)) {
seen.add(resolved); manifests.push({ url: resolved, player: path });
}
}
}
return;
}
if (!obj || typeof obj !== 'object') return;
for (const k of Object.keys(obj)) {
walk(obj[k], `${path}.${k}`, depth + 1);
}
try {
for (const k of Object.keys(obj)) {
walk(obj[k], `${path}.${k}`, depth + 1);
}
} catch { /* avoid circular ref errors */ }
}
// Strategy A: Known player globals (shallow + deep)
@@ -127,28 +151,37 @@ function m3u8DeepProbeFn() {
'__hls', '__hls_player', 'player', 'videoPlayer', 'mainPlayer',
'fhplayer', '__pj', 'playerInstance',
'PHPlayer', 'fh', '__phPlayer', '__ph', 'phPlayer', 'phPlayerInstance',
'bitgravity', 'bgPlayer', 'FG', 'Fhls',
];
for (const name of playerGlobals) {
const obj = window[name];
if (!obj || typeof obj === 'function') continue;
for (const key of ['url', '_url', 'src', 'manifestUrl', 'hlsUrl', 'playlistUrl']) {
if (typeof obj[key] === 'string' && obj[key].includes('.m3u8')) {
if (!seen.has(obj[key])) { seen.add(obj[key]); manifests.push({ url: obj[key], player: `${name}.${key}` }); }
for (const key of ['url', '_url', 'src', 'manifestUrl', 'hlsUrl', 'playlistUrl', 'hls_url', 'mp4_url']) {
if (typeof obj[key] === 'string' && isManifestUrl(obj[key])) {
const resolved = resolveUrl(obj[key]);
if (resolved && !seen.has(resolved)) { seen.add(resolved); manifests.push({ url: resolved, player: `${name}.${key}` }); }
}
}
walk(obj, name, 0);
}
// Strategy B: Video element HLS instances
// B1: Use Hls.cast(video) — the official hls.js API to get the bound instance
if (typeof Hls !== 'undefined' && typeof Hls.cast === 'function') {
for (const v of document.querySelectorAll('video')) {
try {
const hlsInst = Hls.cast(v);
if (hlsInst && hlsInst.url) {
if (!seen.has(hlsInst.url)) { seen.add(hlsInst.url); manifests.push({ url: hlsInst.url, player: 'Hls.cast' }); }
}
} catch { /* ignore */ }
// B1: Scan video elements for HLS instance references stored by players.
// NOTE: Hls.cast() in hls.js is a TypeScript type-cast utility (identity function),
// NOT a reverse-lookup. It returns its input unchanged — Hls.cast(video) just
// returns the <video> element with no .url property. Real players store the
// HLS instance directly on the video element as a custom property.
for (const v of document.querySelectorAll('video')) {
for (const key of Object.keys(v)) {
const obj = v[key];
if (!obj || typeof obj !== 'object') continue;
// Check for common HLS instance patterns on video elements
if (typeof obj === 'object' && typeof obj.url === 'string' && isManifestUrl(obj.url)) {
const resolved = resolveUrl(obj.url);
if (resolved && !seen.has(resolved)) { seen.add(resolved); manifests.push({ url: resolved, player: `video.${key}.url` }); }
}
// Walk deep into player objects attached to video elements
walk(obj, `video.${key}`, 0);
}
}
@@ -248,16 +281,22 @@ function scrapeFn(hlsManifests) {
mediaType = 'hls';
src = hlsManifest;
}
// Priority 3: Hls.cast(video) — official hls.js API (works when Hls is loaded)
else if (typeof Hls !== 'undefined' && typeof Hls.cast === 'function') {
try {
const hlsInst = Hls.cast(v);
if (hlsInst && hlsInst.url) {
hlsManifest = hlsInst.url;
mediaType = 'hls';
src = hlsManifest;
// Priority 3: Scan video element properties for HLS instance references.
// Hls.cast() is a TypeScript identity cast in hls.js — NOT a reverse-lookup.
// Real players store the HLS instance on the video element as custom properties.
if (!hlsManifest) {
for (const key of Object.keys(v)) {
const obj = v[key];
if (obj && typeof obj === 'object' && typeof obj.url === 'string') {
const u = obj.url.toLowerCase();
if (u.includes('.m3u8') || u.includes('.hlsvod') || u.includes('/hls/') || u.includes('/playlist')) {
hlsManifest = obj.url;
mediaType = 'hls';
src = hlsManifest;
break;
}
}
} catch { /* ignore */ }
}
}
// Priority 4: Direct HTTP source (non-blob)
if (!hlsManifest && rawSrc.startsWith('http') && !rawSrc.startsWith('blob:')) {