133 lines
4.3 KiB
JavaScript
133 lines
4.3 KiB
JavaScript
// 点击"下载视频"时执行:优先从页面内嵌数据 RENDER_DATA 精确提取当前视频地址,
|
|
// 其次才用嗅探列表兜底。
|
|
(function () {
|
|
try {
|
|
var pageUrl = location.href;
|
|
|
|
// 从 URL 提取视频 ID
|
|
var videoId = "";
|
|
var m = pageUrl.match(/[?&]modal_id=(\d+)/);
|
|
if (m) videoId = m[1];
|
|
if (!videoId) {
|
|
var m0 = pageUrl.match(/[?&]aweme_id=(\d+)/);
|
|
if (m0) videoId = m0[1];
|
|
}
|
|
if (!videoId) {
|
|
var m2 = pageUrl.match(/\/(?:video|note)\/(\d+)/);
|
|
if (m2) videoId = m2[1];
|
|
}
|
|
|
|
// === 方案一:从 RENDER_DATA 里精确找当前视频的播放地址 ===
|
|
var renderVideoUrl = "";
|
|
var renderAudioUrl = "";
|
|
try {
|
|
var rd = document.getElementById("RENDER_DATA");
|
|
if (rd && rd.textContent) {
|
|
var decoded = decodeURIComponent(rd.textContent);
|
|
// 在 JSON 里找 aweme_id 等于 videoId 的节点,提取其 video.play_addr 或 play_addr 列表
|
|
// 用正则粗匹配,避免完整 JSON 解析的复杂性
|
|
var idx = decoded.indexOf(videoId);
|
|
while (idx >= 0) {
|
|
// 在这个 ID 附近找 playApi / play_addr / url_list
|
|
var chunk = decoded.substring(Math.max(0, idx - 200), idx + 5000);
|
|
// 找 DASH 格式的视频轨和音频轨
|
|
var vMatches = chunk.match(/https?:\/\/[^"\\]+media-video[^"\\]*/gi);
|
|
var aMatches = chunk.match(/https?:\/\/[^"\\]+media-audio[^"\\]*/gi);
|
|
if (vMatches && vMatches.length) {
|
|
renderVideoUrl = vMatches[vMatches.length - 1];
|
|
if (aMatches && aMatches.length) {
|
|
renderAudioUrl = aMatches[aMatches.length - 1];
|
|
}
|
|
break;
|
|
}
|
|
// 找普通 mp4 直链
|
|
var mp4Matches = chunk.match(/https?:\/\/[^"\\]*\.mp4[^"\\]*/gi);
|
|
if (mp4Matches && mp4Matches.length) {
|
|
renderVideoUrl = mp4Matches[mp4Matches.length - 1];
|
|
break;
|
|
}
|
|
idx = decoded.indexOf(videoId, idx + 1);
|
|
}
|
|
}
|
|
} catch (e) { /* RENDER_DATA 解析失败就走嗅探兜底 */ }
|
|
|
|
// === 方案二:选正在播放的 <video> 元素 ===
|
|
var vids = Array.prototype.slice.call(document.querySelectorAll("video"));
|
|
var best = null;
|
|
for (var i = 0; i < vids.length; i++) {
|
|
var v = vids[i];
|
|
if (!v.paused && v.currentTime > 0) { best = v; break; }
|
|
}
|
|
if (!best) {
|
|
for (var j = 0; j < vids.length; j++) {
|
|
var v2 = vids[j];
|
|
if (v2.videoWidth > 0 || (!isNaN(v2.duration) && v2.duration > 0 && v2.currentTime > 0)) {
|
|
best = v2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!best) best = vids[0];
|
|
|
|
var current = "";
|
|
var playable = true;
|
|
if (best) {
|
|
current = best.currentSrc || best.src || "";
|
|
if (!current) {
|
|
var s = best.querySelector && best.querySelector("source");
|
|
if (s) current = s.src || s.getAttribute("src") || "";
|
|
}
|
|
if (best.networkState === 0) playable = false;
|
|
}
|
|
|
|
// === 方案三:嗅探列表兜底 ===
|
|
var all = window.__mediaUrls || [];
|
|
var videoTracks = [];
|
|
var audioTracks = [];
|
|
var otherMedia = [];
|
|
for (var k = 0; k < all.length; k++) {
|
|
var u = all[k];
|
|
if (/media-video/i.test(u)) videoTracks.push(u);
|
|
else if (/media-audio/i.test(u)) audioTracks.push(u);
|
|
else otherMedia.push(u);
|
|
}
|
|
|
|
function pickLast(arr) {
|
|
return arr.length ? arr[arr.length - 1] : "";
|
|
}
|
|
|
|
var dashVideo = pickLast(videoTracks);
|
|
var dashAudio = pickLast(audioTracks);
|
|
|
|
// 如果 RENDER_DATA 找到了,优先用它
|
|
if (renderVideoUrl) {
|
|
dashVideo = renderVideoUrl;
|
|
if (renderAudioUrl) dashAudio = renderAudioUrl;
|
|
}
|
|
|
|
var title = document.title || "";
|
|
var h1 = document.querySelector("h1");
|
|
if (h1 && h1.innerText) title = h1.innerText;
|
|
|
|
return JSON.stringify({
|
|
current: current || "",
|
|
playable: playable,
|
|
usesMse: !!window.__usesMse || /^blob:/i.test(current),
|
|
title: title,
|
|
pageUrl: pageUrl,
|
|
videoId: videoId,
|
|
sniffed: otherMedia.slice(-60),
|
|
dashVideo: dashVideo,
|
|
dashAudio: dashAudio,
|
|
m3u8Blobs: (window.__m3u8Blobs || []).slice(-1),
|
|
});
|
|
} catch (e) {
|
|
return JSON.stringify({
|
|
current: "",
|
|
title: document.title || "",
|
|
pageUrl: location.href,
|
|
error: String(e),
|
|
});
|
|
}
|
|
})();
|