fix: Hls.cast dead code, double-patch interceptors, manifest URL patterns
CI / test (pull_request) Successful in 32s
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:
1 parent
542c5786be
commit
3cf402d3c7
3 files changed
+90
-50
No files matched your search
+65
-26
@@ -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:')) {
|
||||
|
||||
Reference in new issue
Block a user