// ==UserScript== // @name 百度网盘换播放器 // @namespace https://example.com // @version 1.1.0 // @description 修复双播放器、按钮点击、音画同步问题 // @match https://pan.baidu.com/s/* // @match http*://yun.baidu.com/s/* // @require https://unpkg.com/hls.js@1.6.16/dist/hls.min.js // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @run-at document-end // ==/UserScript== (function () { 'use strict'; const state = { share_uk: '', shareid: '', sign: '', timestamp: '', jsToken: '', file: null, filelist: [], quality: [], adToken: '', hls: null, video: null, currentQuality: '480', playerContainer: null, }; /* ============================================================ * 工具函数 * ============================================================ */ function waitForLocals(callback) { if (unsafeWindow.locals && unsafeWindow.locals.get) { unsafeWindow.locals.get("file_list", function (list) { if (list && list.length) callback(); else setTimeout(() => waitForLocals(callback), 500); }); } else { setTimeout(() => waitForLocals(callback), 300); } } function buildStreamUrl(type) { return "/share/streaming?channel=chunlei" + "&uk=" + state.share_uk + "&fid=" + state.file.fs_id + "&sign=" + state.sign + "×tamp=" + state.timestamp + "&shareid=" + state.shareid + "&type=" + type + "&vip=0&jsToken=" + state.jsToken + (state.adToken ? "&adToken=" + encodeURIComponent(state.adToken) : ""); } function fetchAdToken() { return fetch(buildStreamUrl("M3U8_AUTO_480")) .then(r => r.json()) .then(data => { if (data.errno === 133 && data.adToken) state.adToken = data.adToken; return state.adToken; }); } function buildQualityList() { const resolution = state.file.resolution || ""; const match = resolution.match(/width:(\d+),height:(\d+)/); const pixels = match ? (+match[1]) * (+match[2]) : 0; const levels = [480, 360]; if (pixels > 409920) levels.unshift(720); if (pixels > 921600) levels.unshift(1080); const labels = { 1080: "超清 1080P", 720: "高清 720P", 480: "流畅 480P", 360: "省流 360P" }; state.quality = levels.map(q => ({ level: q, label: labels[q], type: "M3U8_AUTO_" + q })); } function getShareId() { return (/baidu.com\/(?:s\/1|(?:share|wap)\/init\?surl=)([\w-]{5,25})/.exec(location.href) || [])[1] || ""; } function buildFileList() { const cached = JSON.parse(sessionStorage.getItem(getShareId()) || "[]"); let list = cached.length ? cached.filter(i => i.category === 1) : []; if (state.file && !list.find(i => i.fs_id === state.file.fs_id)) list.unshift(state.file); state.filelist = list.map(item => ({ fs_id: item.fs_id, name: item.server_filename || item.name || "未知视频", isCurrent: item.fs_id === state.file.fs_id, })); state.filelist.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }) ); } function observeFileList() { try { const ctx = unsafeWindow.require('system-core:context/context.js'); const currentList = ctx.instanceForSystem.list.getCurrentList(); if (currentList && currentList.length) { sessionStorage.setItem(getShareId(), JSON.stringify(currentList)); } } catch (e) { } } /* ============================================================ * ============================================================ */ function destroyNativePlayer() { // 1. 调用原生播放器实例的 dispose,停止播放并释放资源 try { if (unsafeWindow.require && unsafeWindow.require.async) { unsafeWindow.require.async("file-widget-1:videoPlay/context.js", function (context) { let count = 0; const id = setInterval(function () { const playerInstance = context && context.getContext && context.getContext().playerInstance; if (playerInstance && playerInstance.player) { clearInterval(id); try { playerInstance.player.dispose(); } catch (e) { } playerInstance.player = false; } else if (++count > 60) { clearInterval(id); } }, 300); }); } } catch (e) { } // 2. 停止页面上所有原生 video 标签 document.querySelectorAll('video').forEach(v => { try { v.pause(); v.src = ''; v.load(); } catch (e) { } }); } /* ============================================================ * ============================================================ */ function createPlayerUI() { const nativeWrap = document.querySelector("#video-wrap"); if (!nativeWrap) { setTimeout(createPlayerUI, 400); return; } // 先销毁原生播放器实例 destroyNativePlayer(); // 删除原生容器后面的兄弟节点(百度经常在旁边挂广告/推荐层) while (nativeWrap.nextSibling) { nativeWrap.parentNode.removeChild(nativeWrap.nextSibling); } // 创建我们自己的容器,替换掉原生 #video-wrap const container = document.createElement("div"); container.id = "custom-bd-player"; container.style.cssText = "width:100%;height:100%;position:relative;background:#000;"; nativeWrap.parentNode.replaceChild(container, nativeWrap); // 关键:父容器 z-index 设为 auto,避免被其他层盖住 container.parentNode.style.cssText += 'z-index:auto;position:relative;'; state.playerContainer = container; // --- 视频元素 --- const video = document.createElement('video'); video.id = 'custom-bd-video'; video.controls = true; video.playsInline = true; video.style.cssText = 'width:100%;height:100%;object-fit:contain;background:#000;display:block;'; container.appendChild(video); state.video = video; // --- 顶部工具栏(z-index 拉满 + pointer-events 确保可点击)--- const toolbar = document.createElement('div'); toolbar.style.cssText = ` position:absolute;top:12px;right:12px;z-index:99999; display:flex;gap:8px;align-items:center;pointer-events:auto; `; container.appendChild(toolbar); // 倍速 const savedRate = GM_getValue('bd_playback_rate', '1.0x'); const speedBtn = createDropdown(savedRate, ['0.5x', '0.75x', '1.0x', '1.25x', '1.5x', '2.0x', '3.0x'], (val) => { video.playbackRate = parseFloat(val); GM_setValue('bd_playback_rate', val); } ); toolbar.appendChild(speedBtn); // 清晰度 const qualityBtn = createDropdown('清晰度', state.quality.map(q => q.label), (label) => { const q = state.quality.find(item => item.label === label); if (q) switchQuality(q.level); } ); toolbar.appendChild(qualityBtn); // 播放列表 const listBtn = document.createElement('button'); listBtn.textContent = '列表'; listBtn.style.cssText = btnStyle(); listBtn.onclick = (e) => { e.stopPropagation(); togglePlaylist(); }; toolbar.appendChild(listBtn); // --- 播放列表抽屉 --- const playlist = document.createElement('div'); playlist.id = 'custom-bd-playlist'; playlist.style.cssText = ` position:absolute;top:0;right:0;width:280px;height:100%; background:rgba(0,0,0,0.95);color:#fff;z-index:99998; transform:translateX(100%);transition:transform .3s; overflow-y:auto;font-size:13px;pointer-events:auto; `; renderPlaylistItems(playlist); container.appendChild(playlist); } /* ============================================================ * ============================================================ */ function createDropdown(currentLabel, options, onSelect) { const wrap = document.createElement('div'); wrap.style.cssText = 'position:relative;'; const btn = document.createElement('button'); btn.className = 'dd-label'; btn.textContent = currentLabel; btn.style.cssText = btnStyle(); wrap.appendChild(btn); const menu = document.createElement('div'); menu.className = 'dd-menu'; menu.style.cssText = ` position:absolute;top:calc(100% + 6px);right:0; background:rgba(20,20,20,0.95);border-radius:6px; min-width:90px;display:none;z-index:100000; box-shadow:0 4px 16px rgba(0,0,0,0.5); border:1px solid rgba(255,255,255,0.1); `; options.forEach(opt => { const item = document.createElement('div'); item.textContent = opt; item.style.cssText = ` padding:8px 14px;cursor:pointer;color:#fff;white-space:nowrap; font-size:12px; `; item.onmouseenter = () => item.style.background = 'rgba(255,255,255,0.12)'; item.onmouseleave = () => item.style.background = 'transparent'; item.onclick = (e) => { e.stopPropagation(); onSelect(opt); btn.textContent = opt; menu.style.display = 'none'; }; menu.appendChild(item); }); wrap.appendChild(menu); // 点击按钮切换菜单 btn.onclick = (e) => { e.stopPropagation(); // 先关闭所有其他菜单 document.querySelectorAll('.dd-menu').forEach(m => { if (m !== menu) m.style.display = 'none'; }); menu.style.display = menu.style.display === 'block' ? 'none' : 'block'; }; // 点击页面其他地方关闭菜单 document.addEventListener('click', (e) => { if (!wrap.contains(e.target)) menu.style.display = 'none'; }); return wrap; } function btnStyle() { return ` padding:5px 12px;background:rgba(0,0,0,0.65);color:#fff; border:1px solid rgba(255,255,255,0.25);border-radius:6px; cursor:pointer;font-size:12px;pointer-events:auto; backdrop-filter:blur(6px);user-select:none; `; } function renderPlaylistItems(container) { container.innerHTML = ''; const header = document.createElement('div'); header.textContent = '播放列表(' + state.filelist.length + ')'; header.style.cssText = 'padding:14px;font-weight:bold;border-bottom:1px solid rgba(255,255,255,0.1);position:sticky;top:0;background:rgba(0,0,0,0.95);'; container.appendChild(header); state.filelist.forEach(item => { const row = document.createElement('div'); row.textContent = item.name; row.style.cssText = ` padding:11px 14px;cursor:pointer; border-bottom:1px solid rgba(255,255,255,0.05); ${item.isCurrent ? 'color:#4fc3f7;background:rgba(79,195,247,0.12);' : 'color:#ccc;'} `; row.onclick = () => { location.href = "https://pan.baidu.com" + location.pathname + "?fid=" + item.fs_id; }; container.appendChild(row); }); } function togglePlaylist() { const pl = document.getElementById('custom-bd-playlist'); if (!pl) return; pl.style.transform = pl.style.transform === 'translateX(0px)' ? 'translateX(100%)' : 'translateX(0px)'; } /* ============================================================ * ============================================================ */ function loadVideo(qualityLevel) { state.currentQuality = qualityLevel; const q = state.quality.find(item => item.level === qualityLevel) || state.quality[0]; const url = buildStreamUrl(q.type); const currentTime = state.video ? state.video.currentTime : 0; const wasPlaying = state.video && !state.video.paused; // 彻底销毁旧实例 if (state.hls) { state.hls.destroy(); state.hls = null; } if (Hls.isSupported()) { state.hls = new Hls({ enableWorker: true, // 启用 Worker,解码不卡主线程 lowLatencyMode: false, // 关闭低延迟模式(点播场景不需要,避免音画不同步) backBufferLength: 90, // 增大回溯缓冲 maxBufferLength: 30, // 前向缓冲 30 秒 maxMaxBufferLength: 60, // 最大缓冲 60 秒 startLevel: -1, // 自动选择起始码率 capLevelToPlayerSize: true,// 根据播放器尺寸限制码率 }); state.hls.loadSource(url); state.hls.attachMedia(state.video); state.hls.on(Hls.Events.MANIFEST_PARSED, () => { state.video.currentTime = currentTime; if (wasPlaying) state.video.play().catch(() => { }); }); // 错误恢复 state.hls.on(Hls.Events.ERROR, (event, data) => { if (data.fatal) { switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: state.hls.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: state.hls.recoverMediaError(); break; default: state.hls.destroy(); break; } } }); } else { state.video.src = url; state.video.currentTime = currentTime; if (wasPlaying) state.video.play().catch(() => { }); } } function switchQuality(level) { loadVideo(level); } /* ============================================================ * 入口 * ============================================================ */ function main() { waitForLocals(function () { unsafeWindow.locals.get( "file_list", "share_uk", "shareid", "sign", "timestamp", function (file_list, share_uk, shareid, sign, timestamp) { if (!file_list || !file_list.length) return; state.share_uk = share_uk; state.shareid = shareid; state.sign = sign; state.timestamp = timestamp; state.jsToken = unsafeWindow.jsToken || ''; if (file_list.length === 1 && file_list[0].category === 1) { state.file = file_list[0]; startPlayback(); } else { observeFileList(); document.addEventListener('click', () => setTimeout(observeFileList, 600), true); } } ); }); } function startPlayback() { fetchAdToken().then(() => { buildQualityList(); buildFileList(); createPlayerUI(); // 等待 video 元素挂载 const waitVideo = setInterval(() => { if (state.video) { clearInterval(waitVideo); loadVideo(state.currentQuality); showToast("增强播放器已就绪"); } }, 200); }); } function showToast(msg) { const tip = document.createElement('div'); tip.textContent = msg; tip.style.cssText = ` position:fixed;top:24px;left:50%;transform:translateX(-50%); background:rgba(0,0,0,0.85);color:#fff;padding:10px 24px; border-radius:6px;z-index:999999;font-size:14px;pointer-events:none; `; document.body.appendChild(tip); setTimeout(() => tip.remove(), 2500); } main(); })();