// 资源嗅探脚本:在安装页面播放器脚本之前接管 fetch / XMLHttpRequest / createObjectURL, // 把真正被下载的媒体地址(mp4 / m3u8 等)回报给宿主程序,用于"下载视频"按钮。 (function () { if (window.__mediaSniffInstalled) return; window.__mediaSniffInstalled = true; window.__mediaUrls = []; window.__m3u8Blobs = []; var MEDIA_EXT = /\.(mp4|flv|m3u8|mpd|mkv|mov|m4v|webm|f4v|3gp|mpg|mpeg|ts)(\?|#|$)/i; var PLAIN_EXT = /\.(jpg|jpeg|png|gif|webp|ico|svg|bmp|css|js|json|woff2?|ttf|map|txt)(\?|#|$)/i; function isMediaUrl(u) { if (!u) return false; u = String(u); if (u.indexOf("blob:") === 0 || u.indexOf("data:") === 0) return false; if (PLAIN_EXT.test(u)) return false; return MEDIA_EXT.test(u) || /\/(video|vod|playback|media)[\/-]/i.test(u); } function absUrl(u) { try { return new URL(String(u), location.href).href; } catch (e) { return String(u); } } function post(msg) { try { if ( window.chrome && window.chrome.webview && window.chrome.webview.postMessage ) { window.chrome.webview.postMessage(JSON.stringify(msg)); } } catch (e) {} } function remember(text) { if (!text || typeof text !== "string") return; if (text.indexOf("#EXTM3U") < 0) return; if (window.__m3u8Blobs.indexOf(text) >= 0) return; window.__m3u8Blobs.push(text); post({ type: "hlsText", text: text }); } function report(u) { try { var full = absUrl(u); if (!isMediaUrl(full)) return; if (window.__mediaUrls.indexOf(full) >= 0) return; window.__mediaUrls.push(full); // base 是发出这个请求的文档地址:播放器常在跨域 iframe 里, // 宿主必须用它当 Referer,用顶层页面地址会被防盗链判为非法来源 post({ type: "media", url: full, base: location.href }); } catch (e) {} } window.__reportMedia = report; window.__rememberHlsText = remember; // 抖音等站点会用 atob(base64) 把播放地址还原出来,这里顺手抓一份解码结果 try { var origAtob = window.atob; if (origAtob) { window.atob = function (s) { var r = origAtob.apply(this, arguments); try { if (r && typeof r === "string") { if (r.indexOf("#EXTM3U") >= 0) remember(r); else if (isMediaUrl(r)) report(r); } } catch (e) {} return r; }; } } catch (e) {} try { var origFetch = window.fetch; if (origFetch) { window.fetch = function (input, init) { try { var u = typeof input === "string" || input instanceof URL ? input : input && input.url; if (u) report(u); } catch (e) {} return origFetch.apply(this, arguments); }; } } catch (e) {} try { var xhrOpen = XMLHttpRequest.prototype.open; var xhrSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, u) { try { if (u) report(u); } catch (e) {} return xhrOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function () { var xhr = this; try { xhr.addEventListener("load", function () { try { if (xhr.responseType === "" || xhr.responseType === "text") { remember(xhr.responseText); } else if ( xhr.responseType === "blob" && xhr.response && xhr.response.text ) { xhr.response.text().then(remember); } } catch (e) {} }); } catch (e) {} return xhrSend.apply(this, arguments); }; } catch (e) {} // MSE 播放时 video.src 只能是 blob:,抓不到 http 地址,所以靠上面的分片嗅探兜底; // 这里额外记录 createObjectURL 的调用,方便识别"确实在用 MSE"。 try { var ocu = window.URL && window.URL.createObjectURL; if (ocu) { window.URL.createObjectURL = function (obj) { var u = ocu.apply(this, arguments); try { window.__usesMse = true; if (obj && typeof obj.text === "function") obj.text().then(remember); } catch (e) {} return u; }; } } catch (e) {} })();