// ==UserScript==
// @name 大魔王视频助手<移动版>
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 悬浮播放器显示、识别横屏与竖屏视频显示、可拖拽位置并且记忆位置,自动接管播放网页视频,长按倍速播放支持,双击切换全屏,双指长按弹出控制面板。手势调节播放进度、亮度、声音,精准智能去除m3u8广告片段,强力分析诊断工具。
// @author bug大魔王
// @match *://*/*
// @connect *
// @require https://unpkg.com/hls.js@1.5.12/dist/hls.min.js
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_notification
// @grant GM_info
// @run-at document-start
// @icon https://n.sinaimg.cn/sinacn10/576/w700h676/20181001/87c2-hkvrhpr8368697.jpg
// ==/UserScript==
(function () {
'use strict';
const Hls = window.Hls, SCRIPT_NAME = '大魔王视频助手', SCRIPT_VERSION = '1.0', IS_TOP_FRAME = window.self === window.top;
const MSG = { M3U8: 'M3U8_PLAYER_COMMAND_V2_FINAL', SANDBOX: 'SANDBOX_URL_FOUND_V1', RAW: 'RAW_SIGNAL_FORWARDED_V1', LOG: 'ACTION_LOG_FORWARDED_V1', NEUTRALIZE: 'DMZ_NEUTRALIZE_COMMAND_V1', AUTOPLAY: 'DMZ_ACTIVATE_AUTOPLAY_V1', QUERY: 'DMZ_QUERY_IS_MAIN_PLAYER_V1', CLICK: 'DMZ_IFRAME_TRIGGER_CLICK_V1', RESTORE: 'DMZ_RESTORE_COMMAND_V1' };
const C = { ROOT: 'dmz-host-container', PLAYER: 'dmz-custom-player', SETTINGS: 'dmz-settings-panel', INFO: 'dmz-info-panel', MAX_DEPTH: 5, H_PX: '36px', WRAP: 'dmz-video-wrapper', CLOSE: 'dmz-close-button', DRAG: 'dmz-drag-handle', SCREW: 'screw-effect', IND: 'indicator', STYLE: 'dmz-player-styles', GRID: 'settings-grid', CARD: 'settings-card', FULL: 'full-width', TITLE: 'settings-title', INFO_C: 'settings-card-info', ITEM: 'option-item', COL: 'option-item-col', FOOT: 'settings-footer', BTN: 'settings-btn', B_CLOSE: 'close', B_SAVE: 'save', B_RESET: 'reset', B_ACT: 'action', SW: 'switch', SL: 'slider', TARGETS: ['aliplayer', 'DPlayer', 'TCPlayer', 'xgplayer', 'Chimee', 'videojs', 'player'], LONG_PRESS: 400 };
const _ic = (d, c = '', v = '0 0 24 24') => ``;
const ICONS = {
BRIGHTNESS: _ic('M20 15.31L23.31 12 20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69zM12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z'),
VOLUME_SIDE: _ic('M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z'),
BIG_PLAY: _ic('M8 5v14l11-7z', 'icon-play'),
BIG_PAUSE: _ic('M6 19h4V5H6v14zm8-14v14h4V5h-4z', 'icon-pause', '0 0 24 24" style="display: none;'),
FORWARD: _ic('M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z', 'icon-progress', '0 0 24 24" style="display:none;'),
REWIND: _ic('M11 18V6l-8.5 6L11 18zM20 6l-8.5 6 8.5 6V6z', 'icon-rewind', '0 0 24 24" style="display:none;'),
VOLUME_ON: _ic('M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z', 'icon-volume-on'),
VOLUME_OFF: _ic('M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z', 'icon-volume-off', '0 0 24 24" style="display: none;'),
FS_ENTER: _ic('M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z', 'icon-fullscreen-enter'),
FS_EXIT: _ic('M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z', 'icon-fullscreen-exit', '0 0 24 24" style="display: none;'),
MORE: _ic('M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'),
PIP: _ic('M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z'),
PLAYBACK_RATE: _ic('M10 8v8l6-4-6-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z')
};
const LOG = { INFO: 'ℹ️ INFO', MODULE: '📡 模块', CONFIG: '⚙️ 配置', ERROR: '❌ ERROR', WARN: '⚠️ WARN', TERM: '🛡️ TERMINATOR', PLAYER: '📺 播放器', LOCK: '🔒 播放器锁定', REVEAL: '✨ 播放器渲染', STREAM: '📶 流选择', SWITCH: '🔄 播放切换', HLS: '📽️ HLS', CORE: '👑 指挥中心', SLICE: '✂️ 广告过滤', URL: '🔗 URL解析', EXEC: '⚡️ 指令执行', IFRAME: '📦 沙箱', NEUT: '🔇 原生中和', SCAN: '🔍 扫描', SCAN_W: '🔍 扫描警告', HOOK: '🎣 播放器劫持', HOOK_F: '🎣 劫持失败', NAV: '🧭 导航', UI: '🎨 UI', INIT: '🛠️ 部署', LIFE: '♻️ LIFECYCLE', COMM: '📡 iFrame通信', FATAL: '📸 FATAL_SNAPSHOT', ATT: '🕵️♂️ 情报分析', SUCC: '🔊 接管成功', FAIL: '💀 接管失败', DECRYPT: '🗝️ 主动解混淆劫持' };
const SRC = { DOM: '页面元素扫描', ATTR: '页面元素扫描(data-*)', XHR: '网络拦截(XHR)', FETCH: '网络拦截(Fetch)', HOOK: (n) => `播放器接口劫持(${n})`, FRAME: 'iFrame 信使', ATOB: '主动解混淆劫持(atob)', JSON: '主动解混淆劫持(JSON)', SET: '主动解混淆劫持(attr)' };
const PLAYER_CSS = `:host{all:initial;position:fixed!important;background:transparent;overflow:visible;z-index:2147483646!important;display:flex;flex-direction:column;gap:0;padding:0;box-sizing:border-box;pointer-events:none;opacity:0;transition:opacity .3s ease-out;will-change:transform}:host(.dmz-visible){opacity:1}:host(.dmz-no-transition){transition:none!important}.${C.WRAP}{cursor:pointer;width:100%;flex-grow:1;display:flex;justify-content:center;align-items:center;background:#000;border-radius:20px;position:relative;overflow:hidden;box-shadow:0 12px 32px rgba(0,0,0,.25),0 2px 6px rgba(0,0,0,.1);max-height:0;flex-grow:0;-webkit-tap-highlight-color:transparent;pointer-events:auto;transition:max-height 1.2s cubic-bezier(.4,0,.2,1);will-change:max-height;touch-action:none}#${C.PLAYER}{width:100%;height:100%;object-fit:contain;background:#000;border-radius:20px;opacity:0;transition:opacity .3s ease;pointer-events:none}.${C.CLOSE}{position:absolute;top:12px;right:12px;background:rgba(0,0,0,.4);color:#fff;width:26px;height:26px;border-radius:50%;display:flex;justify-content:center;align-items:center;font-size:20px;line-height:1;font-weight:700;cursor:pointer;z-index:30;border:1px solid rgba(255,255,255,.1);outline:none;-webkit-tap-highlight-color:transparent;opacity:0;visibility:hidden;pointer-events:none;transition:background .2s ease,transform .2s ease,opacity .3s ease,visibility .3s ease}.${C.WRAP}.dmz-controls-visible .${C.CLOSE}{opacity:1;visibility:visible;pointer-events:auto}.${C.CLOSE}:hover{background:rgba(230,50,90,.9);transform:scale(1.1)}.${C.CLOSE}:active{background-color:rgba(128,128,128,.5)}.dmz-control-button{display:flex;justify-content:center;align-items:center;width:30px;height:30px;background:transparent;border:none;padding:0;cursor:pointer;flex-shrink:0;color:#fff;outline:none;-webkit-tap-highlight-color:transparent;border-radius:50%;transition:background-color .2s ease}.dmz-control-button:active{background-color:rgba(128,128,128,.4)}.dmz-control-button svg{width:22px;height:22px;fill:currentColor;pointer-events:none}.dmz-big-play-button-container{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;pointer-events:none;z-index:22;opacity:0;transition:opacity .3s ease}.${C.WRAP}.dmz-controls-visible .dmz-big-play-button-container{opacity:1}.dmz-big-play-button{width:64px;height:64px;background:rgba(28,28,30,.5);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:2px solid rgba(255,255,255,.8);border-radius:50%;display:flex;justify-content:center;align-items:center;padding:0;cursor:pointer;pointer-events:auto;color:#fff;outline:none;-webkit-tap-highlight-color:transparent;transition:background .2s ease,transform .2s ease}.dmz-big-play-button:hover{transform:scale(1.1);background:rgba(28,28,30,.7)}.dmz-big-play-button:active{background:rgba(80,80,80,.6);transform:scale(1.05)}.dmz-big-play-button svg{width:32px;height:32px;fill:currentColor}.${C.DRAG}{width:calc(100% - 20px);margin:0 auto;height:18px;flex-shrink:0;cursor:move;display:flex;justify-content:center;align-items:center;position:relative;background:transparent;border-radius:9px;box-shadow:none;-webkit-tap-highlight-color:transparent;overflow:hidden;touch-action:none;pointer-events:auto}.${C.DRAG} .screw-effect{position:absolute;top:0;height:100%;width:calc(50% - 69px);background-image:none}.${C.DRAG} .indicator{width:80px;height:5px;background-color:#e0e0e0;border-radius:2.5px;box-shadow:0 0 3px rgba(0,0,0,.2);opacity:0;transition:opacity .3s ease,background-color .3s ease}.${C.DRAG}.dmz-indicator-visible .indicator{opacity:1}@media(prefers-color-scheme:dark){.${C.DRAG} .indicator{background-color:#424242;box-shadow:0 0 3px rgba(0,0,0,.5)}}.dmz-controls-bar{position:absolute;bottom:3px;left:10px;right:10px;width:auto;height:auto;background:rgba(28,28,30,.6);backdrop-filter:blur(15px);-webkit-backdrop-filter:blur(15px);border-radius:10px;display:flex;flex-direction:column;align-items:center;padding:2px 10px;box-sizing:border-box;opacity:1;visibility:visible;transition:opacity .3s ease,visibility .3s ease;z-index:25;pointer-events:auto;touch-action:none}.dmz-controls-bar.hidden{opacity:0;visibility:hidden;pointer-events:none}.dmz-time-display{color:#fff;font-size:13px;text-shadow:0 0 3px rgba(0,0,0,.8);margin:0 8px;font-family:sans-serif}.dmz-time-separator{color:#ccc;margin:0 4px;font-size:13px}.dmz-bottom-controls{width:100%;height:32px;display:flex;align-items:center}.dmz-spacer{flex-grow:1}.dmz-progress-container{width:100%;height:15px;display:flex;align-items:center}.dmz-progress-bar{flex-grow:1;height:100%;display:flex;align-items:center;padding:0;cursor:pointer;-webkit-tap-highlight-color:transparent}.dmz-progress-rail{width:100%;height:4px;background:rgba(255,255,255,.3);position:relative;border-radius:2px}.dmz-progress-buffer{position:absolute;left:0;top:0;height:100%;width:0;background:rgba(255,255,255,.6);border-radius:2px;transition:width .1s linear}.dmz-progress-played{position:absolute;left:0;top:0;height:100%;width:0;background:#38b6ff;border-radius:2px}.dmz-progress-handle{position:absolute;top:50%;left:0;width:12px;height:12px;background:#fff;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 0 5px rgba(0,0,0,.5);pointer-events:none}.dmz-more-menu-container{position:relative}.dmz-more-menu{position:absolute;bottom:calc(100% + 5px);right:0;background:rgba(28,28,30,.8);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-radius:8px;width:180px;z-index:30;list-style:none;margin:0;padding:5px;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease;transform-origin:bottom right}.dmz-more-menu.visible{opacity:1;visibility:visible}.dmz-menu-item{display:flex;align-items:center;padding:8px 12px;color:#fff;font-size:14px;cursor:pointer;border-radius:6px}.dmz-menu-item:hover{background-color:rgba(255,255,255,.1)}.dmz-menu-item.disabled{color:#777;cursor:not-allowed;background:transparent!important}.dmz-menu-item svg{width:20px;height:20px;margin-right:10px;fill:currentColor;pointer-events:none}.dmz-menu-item .dmz-menu-item-label{flex-grow:1;pointer-events:none}.dmz-menu-item .dmz-menu-item-value{color:#aaa;pointer-events:none}.dmz-playback-rates{display:none}.dmz-playback-rates.visible{display:block}.dmz-playback-rates .dmz-menu-item.active{color:#38b6ff;font-weight:700}.dmz-gesture-indicator-wrapper{position:absolute;top:40px;left:50%;transform:translateX(-50%);background:rgba(28,28,30,.7);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:#fff;padding:6px 12px;border-radius:18px;font-size:13px;font-family:sans-serif;display:flex;align-items:center;gap:6px;z-index:20;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;pointer-events:none}.dmz-gesture-indicator-wrapper.visible{opacity:1;visibility:visible}.dmz-indicator-icon{display:flex;justify-content:center;align-items:center;width:24px;height:24px;color:#fff}.dmz-indicator-icon svg{width:100%;height:100%;fill:currentColor}.dmz-indicator-text{min-width:120px;text-align:center}.dmz-brightness-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:#000;opacity:0;pointer-events:none;z-index:15;transition:opacity .1s ease}.dmz-side-indicator-container{position:absolute;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;align-items:center;gap:10px;opacity:0;transition:opacity .3s ease;pointer-events:none;z-index:20}.dmz-side-indicator-container.visible{opacity:1}.dmz-side-indicator-container.left{left:30px}.dmz-side-indicator-container.right{right:30px}.dmz-side-indicator-bar-wrapper{width:6px;height:120px;background-color:rgba(28,28,30,.7);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-radius:3px;display:flex;align-items:flex-end;overflow:hidden;border:1px solid rgba(255,255,255,.15);box-sizing:border-box}.dmz-side-indicator-fill{width:100%;height:0;background-color:#38b6ff;border-radius:3px}.dmz-side-indicator-fill.transition-active{transition:height .1s linear}.dmz-side-indicator-icon{width:24px;height:24px}.dmz-side-indicator-icon svg{width:100%;height:100%;fill:#fff;filter:drop-shadow(0 0 2px rgba(0,0,0,.5))}`;
const UNIFIED_PANEL_CSS = `:host{all:initial;box-sizing:border-box;position:fixed;top:0;left:0;width:100%;height:100%;z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.5);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);--s-bg:rgba(28,28,34,.95);--s-border:rgba(56,182,255,.5);--s-shadow:rgba(0,0,0,.4);--s-text-main:#e5e5ea;--s-text-title:#fff;--s-text-label:#fff;--s-text-value:#ccc;--s-text-dim:#aaa;--s-card-bg:rgba(40,40,50,.8);--s-card-border:rgba(255,255,255,.1);--s-textarea-bg:#111;--s-input-bg:rgba(20,20,30,.8);--s-input-border:rgba(56,182,255,.5);--s-switch-bg:#58585a;--s-switch-handle:#fff;--s-switch-checked:#34c759;--s-info:#61dafb;--s-nav-btn-bg:rgba(255,255,255,0.08);--s-nav-btn-bg-active:var(--s-info)}:host(.visible){display:flex}@media(prefers-color-scheme:light){:host{--s-bg:rgba(252,252,252,.95);--s-border:rgba(0,122,255,.4);--s-shadow:rgba(0,0,0,.15);--s-text-main:#333;--s-text-title:#000;--s-text-label:#111;--s-text-value:#555;--s-text-dim:#666;--s-card-bg:rgba(255,255,255,.8);--s-card-border:rgba(0,0,0,.1);--s-textarea-bg:#f0f0f0;--s-input-bg:rgb(229,229,234);--s-input-border:rgba(0,0,0,.2);--s-switch-bg:#ccc;--s-info:#007aff;--s-nav-btn-bg:rgba(0,0,0,0.05)}}.dmz-unified-panel-wrapper{width:90%;max-width:550px;max-height:95%;background:var(--s-bg);border:1px solid var(--s-border);border-radius:12px;box-shadow:0 8px 32px var(--s-shadow);color:var(--s-text-main);display:flex;flex-direction:column;margin-bottom:env(safe-area-inset-bottom,20px)}.dmz-unified-panel-wrapper *{box-sizing:border-box}.panel-header{flex-shrink:0;padding:15px}.dmz-panel-nav{display:flex;justify-content:center;gap:10px;padding:0 15px 15px;border-bottom:1px solid var(--s-card-border)}.dmz-unified-panel-content{flex-grow:1;overflow-y:auto;overscroll-behavior:contain}.dmz-panel-pane{display:none;padding:15px;gap:15px;flex-direction:column}.dmz-panel-pane.active{display:flex}.dmz-unified-panel-footer{display:none;align-items:center;justify-content:flex-end;border-top:1px solid var(--s-card-border);gap:10px;flex-shrink:0;padding:15px;min-height:60px}.dmz-unified-panel-footer.active{display:flex}.title-bar{display:flex;justify-content:space-between;align-items:center}.title-bar h3{margin:0;font-size:18px;color:var(--s-text-title);text-shadow:0 0 5px rgba(56,182,255,.7);font-weight:600}.title-bar .close-btn{background:transparent;border:none;color:rgba(255,82,82,.7);font-size:26px;cursor:pointer;padding:0;font-weight:700;width:30px;height:30px;display:flex;align-items:center;justify-content:center;line-height:1}.title-bar .close-btn:hover{color:rgba(255,82,82,1)}.dmz-nav-btn{flex-grow:1;padding:5px;border-radius:6px;border:none;background:var(--s-nav-btn-bg);color:var(--s-text-dim);font-size:14px;font-weight:600;cursor:pointer;transition:background-color 0.2s,color 0.2s}.dmz-nav-btn.active{background:var(--s-nav-btn-bg-active);color:#fff}.copy-report-btn{display:block;width:100%;margin:0 0 10px;background:rgba(56,182,255,.15);border:1px solid rgba(56,182,255,.3);color:var(--s-info);padding:8px;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;text-align:center}.copy-report-btn:hover{background:rgba(56,182,255,.3)}.status-banner{padding:10px 15px;border-radius:8px;background:rgba(56,182,255,.15);border:1px solid rgba(56,182,255,.3);display:flex;align-items:center;gap:12px;font-size:16px;font-weight:600}.status-banner-icon{font-size:22px;width:22px;height:22px;display:flex;align-items:center;justify-content:center}.cards-container{display:grid;grid-template-columns:1fr 1fr;gap:15px}.info-card{background:var(--s-card-bg);padding:12px 15px;border-radius:10px;border:1px solid var(--s-card-border);display:flex;flex-direction:column;gap:10px}.info-card.full-width{grid-column:1 / -1}.card-title{font-size:12px;color:var(--s-text-dim);font-weight:600;margin:0 0 5px;padding-bottom:5px;border-bottom:1px solid var(--s-card-border)}.card-content-item{display:flex;flex-direction:column;gap:8px;font-size:14px;align-items:flex-start}.label-wrapper{display:flex;justify-content:space-between;align-items:center;width:100%}.label{font-weight:600;color:var(--s-text-label)}.card-content-item .value{font-size:13px;color:var(--s-text-value);word-break:break-all}.card-content-item .value.success{color:#28a745}.card-content-item .value.warning{color:#ffc107}.card-content-item .value.error{color:#dc3545}.card-content-item .value ul{padding-left:18px;margin:5px 0 0}.card-content-item .value code{background:rgba(0,0,0,.05);padding:2px 5px;border-radius:4px;font-family:monospace;border:1px solid rgba(0,0,0,.05)}.copy-btn{background:rgba(0,122,255,.1);border:1px solid rgba(0,122,255,.2);color:#007aff;padding:3px 10px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;transition:background .2s ease,color .2s ease}.copy-btn:hover{background:rgba(0,122,255,.2);color:#0056b3}.details-toggle{cursor:pointer;color:var(--s-info);margin-top:10px;text-align:center;font-weight:600;padding:8px;background:rgba(0,122,255,.08);border-radius:8px}textarea{width:100%;box-sizing:border-box;background:var(--s-textarea-bg);color:var(--s-text-value);border:1px solid var(--s-card-border);border-radius:6px;font-family:monospace;font-size:10px;resize:vertical;padding:8px;margin-top:8px}textarea[data-info="m3u8-raw"],textarea[data-info="m3u8-processed"]{min-height:50px}.dmz-log-container{width:100%;box-sizing:border-box;background:var(--s-textarea-bg);color:var(--s-text-value);border:1px solid var(--s-card-border);border-radius:6px;font-family:monospace;font-size:10px;resize:vertical;padding:8px;margin-top:8px;min-height:300px;max-height:300px;overflow-y:auto}.dmz-log-container pre{margin:0;white-space:pre-wrap;word-break:break-all}.log-entry{line-height:1.4}.log-collapsible{cursor:pointer;padding:4px 8px;background-color:rgba(56,182,255,.15);border-radius:4px;margin:4px 0;user-select:none;transition:background-color 0.2s}.log-collapsible:hover{background-color:rgba(56,182,255,.3)}.log-collapsed-content .log-entry{padding-left:15px;border-left:2px solid rgba(255,255,255,.2)}.${C.GRID}{display:flex;flex-direction:column;gap:15px}.${C.CARD}{background:var(--s-card-bg);padding:12px 15px;border-radius:10px;border:1px solid var(--s-card-border)}h3.settings-title{color:var(--s-info);margin:0 0 10px;padding-bottom:10px;border-bottom:1px solid var(--s-card-border);font-size:16px;font-weight:600}.${C.INFO_C}{background:rgba(56,182,255,.1);border:1px solid rgba(56,182,255,.25);padding:12px;border-radius:8px;color:var(--s-text-dim);margin-top:0;font-size:13px}.${C.INFO_C} b{font-weight:700;color:var(--s-text-main)}.${C.INFO_C} code{background:rgba(0,0,0,.05);padding:2px 5px;border-radius:4px;font-family:monospace;border:1px solid rgba(0,0,0,.08)}.${C.ITEM},.${C.COL}{display:flex;justify-content:space-between;align-items:center;padding:8px 0;color:var(--s-text-main);font-size:15px}.${C.COL}{flex-direction:column;align-items:flex-start;gap:8px}.${C.COL} label{font-weight:600}.${C.COL} input,.${C.COL} textarea{width:100%;box-sizing:border-box;background:var(--s-input-bg);border:1px solid var(--s-input-border);border-radius:6px;padding:10px;color:var(--s-text-main);font-family:sans-serif;font-size:14px}#site-blacklist-input{resize:vertical;min-height:120px;font-family:monospace}.${C.BTN}.${C.B_ACT}{background-color:rgba(0,122,255,.2);color:var(--s-info);margin-top:10px;width:100%;flex:none;border:1px solid rgba(0,122,255,.3)}.${C.BTN}.${C.B_ACT}:hover{background-color:rgba(0,122,255,.3)}.${C.BTN}{flex:0 1 auto;padding:8px 20px;border:none;border-radius:8px;cursor:pointer;color:#fff;font-size:15px;font-weight:600;box-shadow:0 2px 5px rgba(0,0,0,.1);transition:transform .1s ease,background-color .2s ease}.${C.BTN}:active{transform:scale(0.97)}.${C.BTN}.${C.B_CLOSE}{background-color:#555}.${C.BTN}.${C.B_SAVE}{background-color:#007aff}.${C.BTN}.${C.B_RESET}{background-color:#007aff}.${C.SW}{position:relative;display:inline-block;width:51px;height:31px}.${C.SW} input{opacity:0;width:0;height:0}.${C.SL}{position:absolute;cursor:pointer;inset:0;background-color:var(--s-switch-bg);transition:.4s;border-radius:31px}.${C.SL}:before{position:absolute;content:"";height:27px;width:27px;left:2px;bottom:2px;background-color:var(--s-switch-handle);transition:.4s;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.2)}input:checked+.${C.SL}{background-color:var(--s-switch-checked)}input:checked+.${C.SL}:before{transform:translateX(20px)}`;
const _ce = (t, o = {}, c = []) => { const e = document.createElement(t); Object.entries(o).forEach(([k, v]) => { if (k === 'style' && typeof v === 'object') Object.assign(e.style, v); else if (k in e) e[k] = v; else e.setAttribute(k, v); }); c.forEach(n => e.append(n)); return e; };
const _el = (e, t, f, o) => e.addEventListener(t, f, o);
const ComponentHelper = { create: _ce, toggleIcon(s, h = []) { if (s) s.style.display = 'block'; h.forEach(i => { if (i) i.style.display = 'none'; }); } };
class EventManager { constructor() { this.listeners = []; } add(e, t, h, o) { e?.addEventListener(t, h, o); this.listeners.push({ e, t, h, o }); } removeAll() { for (const { e, t, h, o } of this.listeners) e?.removeEventListener(t, h, o); this.listeners = []; } }
const Utils = {
debounce(f, w) { let t; return function (...a) { clearTimeout(t); t = setTimeout(() => f.apply(this, a), w); }; },
createSwitchableModule(m) { return { ...m, enable(c) { if (!this.isActive && this.activate) { this.activate(c); c.actionLogger.log(`${m.name ?? ''} 已启用。`, LOG.MODULE); } }, disable(c) { if (this.isActive && this.deactivate) { this.deactivate(c); c?.actionLogger.log(`${m.name ?? ''} 已禁用。`, LOG.MODULE); } } }; },
wildcardToRegex(p) { return new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')}$`); },
isM3U8(u) { return typeof u === 'string' && (u.toLowerCase().includes('.m3u8') || u.toLowerCase().includes('.m3u')); },
copyToClipboard(t, cb) { if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(t).then(cb).catch(e => console.error(`${SCRIPT_NAME}: Clipboard API failed`, e)); else { let a = _ce("textarea"); a.value = t; Object.assign(a.style, { position: 'fixed', top: '-9999px', left: '-9999px' }); document.body.appendChild(a); a.focus(); a.select(); try { if (document.execCommand('copy')) cb(); } catch (e) { console.error(`${SCRIPT_NAME}: execCommand failed`, e); } document.body.removeChild(a); } },
formatTime(s) { if (isNaN(s) || s < 0) return '00:00'; const d = new Date(s * 1000), h = d.getUTCHours(), m = d.getUTCMinutes().toString().padStart(2, '0'), sc = d.getUTCSeconds().toString().padStart(2, '0'); return h > 0 ? `${h}:${m}:${sc}` : `${m}:${sc}`; },
toggleVisibility(e, f) { if (!e) return false; e.classList.toggle('visible', f); return e.classList.contains('visible'); },
createCopyButtonWithFeedback(t, b = '复制') { const n = _ce('button', { className: 'copy-btn', title: '复制', textContent: b }); _el(n, 'click', e => { e.stopPropagation(); this.copyToClipboard(t, () => { n.textContent = '✔ 已复制!'; setTimeout(() => { n.textContent = b; }, 1500); }); }); return n; },
showButtonFeedback(b, { successText, originalContent, duration = 1500, successBgColor = '' }) { const w = b.style.width, bg = b.style.backgroundColor, c = originalContent ?? b.innerHTML, pw = b.getBoundingClientRect().width; b.style.width = `${pw}px`; b.textContent = successText; if (successBgColor) b.style.backgroundColor = successBgColor; setTimeout(() => { b.innerHTML = c; b.style.width = w; b.style.backgroundColor = bg; }, duration); },
getVideoFormat(u) { if (typeof u !== 'string') return { name: '未知', type: '未知' }; if (this.isM3U8(u)) return { name: "M3U8", type: "流式传输" }; const c = u.split('?')[0].toLowerCase(), fs = [{ ext: '.mp4', name: 'MP4' }, { ext: '.mkv', name: 'MKV' }, { ext: '.webm', name: 'WebM' }, { ext: '.flv', name: 'FLV' }], f = fs.find(x => c.endsWith(x.ext)); if (f) return { name: f.name, type: "常规文件" }; try { const o = new URL(u); for (const [k, v] of o.searchParams.entries()) { const lv = v.toLowerCase(), pf = fs.find(x => lv.endsWith(x.ext)); if (pf) return { name: pf.name, type: "常规文件" }; if ((k.includes('type') || k.includes('format')) && fs.some(x => lv.includes(x.ext.substring(1)))) return { name: fs.find(x => lv.includes(x.ext.substring(1))).name, type: "常规文件" }; } } catch (e) { } return { name: "未知", type: "常规文件" }; },
isHighNoisePage() { const p = window.location.pathname.toLowerCase() + window.location.search.toLowerCase(); if (/(play|piay|video(?!s?(\/)?$)|watch|view|episode|embed|\/(anime|drama|movies?|chinese-subtitles|mhkh|zy|hd-uncensored|short|film|dm|tv(?!s?\/+[a-z]))\/(?!$|\?|index|home_list|[^?0-9]+\/$)|archives|\/dy\/|\/dj\/|\/dsj\/|\/p\/|huishou|\/mjbo\/|\/v\/(?!$|.*\.html)|\/[a-z0-9-_]{9,}\/\d{5,})/i.test(p)) return false; if (/^\/-\d+/.test(p)) return false; return /(^|\/|\?|&|-)(s|search|find|query|results?|so|list|cate|categor(y|ies)|tags?|t|topic|genres?|channels?|feed|home|index|default|forum|node|board|uid|user|profile|vodtype|vodshow|type|class|sort|model|star|detail|new|hot|like)($|\/|\.|_|\?|&|=|-)/i.test(p) || /\/(drama|movies?|anime)\/?(\?|$)/i.test(p) || /(^|\/)(pp|pg|page)\d+($|\/)/i.test(p) || /^(\/|\/index\.(html|php|jsp|asp|aspx))(\?|$)/i.test(p) || /\/[a-z0-9-_]+\/\d+($|\/|\.)/i.test(p) || /^\/[a-z0-9-_]+(\/|$|\?)/i.test(p) || /(^|\/)(t|tag|tags)\//i.test(p); },
stopEvent(e) { e.stopPropagation(); if (e.cancelable) e.preventDefault(); }
};
const IframeTerminator = {
observer: null, keywords: ['/acs/phone/', 'googleads', 'googlesyndication', 'doubleclick.net', '/ad/'], processedIframes: new WeakSet(), actionLogger: null,
init(c) { this.actionLogger = c.actionLogger; },
terminate(i) { if (this.processedIframes.has(i)) return false; try { if (i.src && this.keywords.some(k => i.src.includes(k))) { this.actionLogger?.log(`IframeTerminator: 禁用干扰性 iFrame -> ${i.src}`, LOG.TERM); this.processedIframes.add(i); i.src = 'about:blank'; i.style.setProperty('visibility', 'hidden', 'important'); i.style.setProperty('border', 'none', 'important'); return true; } } catch (e) { } return false; },
scanAndTerminate(n) { if (n?.nodeType !== Node.ELEMENT_NODE) return; (n.matches('iframe') ? [n] : n.querySelectorAll('iframe')).forEach(i => this.terminate(i)); },
start() { if (this.observer || !IS_TOP_FRAME) return; this.observer = new MutationObserver(ms => { for (const m of ms) for (const n of m.addedNodes) this.scanAndTerminate(n); }); this.observer.observe(document.documentElement, { childList: true, subtree: true }); document.readyState === 'loading' ? _el(window, 'DOMContentLoaded', () => this.scanAndTerminate(document.documentElement), { once: true }) : this.scanAndTerminate(document.documentElement); },
stop() { this.observer?.disconnect(); this.observer = null; }
};
class ActionLogger {
constructor() { this.logs = []; this.maxEntries = 150; this.diagnosticsTool = null; }
setContext(c) { this.diagnosticsTool = c.diagnosticsTool; this.context = c; }
log(m, t = LOG.INFO) { if (this.logs.length >= this.maxEntries) this.logs.shift(); const e = { type: t, message: m, frame: IS_TOP_FRAME ? 'Top' : 'iFrame' }; this.logs.push(e); this.diagnosticsTool?.logEvent(e); if (!IS_TOP_FRAME) { const fs = [LOG.NEUT, LOG.SCAN, LOG.SCAN_W, LOG.IFRAME, LOG.EXEC, LOG.CORE, LOG.ATT, LOG.FAIL, LOG.WARN, LOG.ERROR]; if (fs.includes(t)) { this.context?.frameCommunicator?.postToTopFrame({ type: MSG.LOG, logEntry: { type: t, message: m } }); } } }
getLogs() { return this.logs; }
}
class DiagnosticsTool {
constructor(c) { this.context = c; this.eventTimeline = []; this.fatalNetworkErrors = []; this.maxLogEntries = 300; this.startTime = new Date(); this.takeoverEvidence = { sources: [], url: null }; this.lastProcessedM3u8 = null; this.playbackHealth = {}; this.slicingReport = {}; this.logSequence = 0; this.resetPlaybackHealth(); }
captureTakeoverEvidence(c) { this.takeoverEvidence = { sources: Array.from(c.sources), url: c.url, iframeSource: c.iframeSource || null, iframeOrigin: c.iframeOrigin || null }; }
captureFatalSnapshot(ec) { const { settingsManager, playerManager } = this.context; this.logEvent({ type: LOG.FATAL, message: `捕获到致命错误快照`, details: JSON.stringify({ error: ec, config: { ...settingsManager.config }, playerState: { isActive: playerManager.isPlayerActiveOrInitializing, videoType: playerManager.currentVideoType, videoUrl: playerManager.currentVideoUrl, videoFormat: playerManager.currentVideoFormat, hlsState: playerManager.hlsInstance?.state }, health: { ...this.playbackHealth } }, null, 2) }); }
logEvent(e) { if (this.eventTimeline.length >= this.maxLogEntries) this.eventTimeline.shift(); const n = new Date(); e.time = n; e.relativeTime = `+${((n - this.startTime) / 1000).toFixed(3)}s`; e.sequence = this.logSequence++; this.eventTimeline.push(e); if (e.type === 'FATAL_NET_ERROR') this.fatalNetworkErrors.push(e); }
init() { this.context.actionLogger.log(`脚本 v${SCRIPT_VERSION} 开始执行 (document-start)`, LOG.LIFE); }
resetPlaybackHealth() { this.playbackHealth = { manifest: { status: 'pending', code: null }, key: { status: 'pending', code: null }, media: { status: 'pending', reason: null }, segments: { status: 'pending', errorCount: 0, consecutiveErrors: 0 } }; }
resetSlicingReport() { this.slicingReport = { totalGroups: 0, slicedGroups: 0, slicedSegments: 0, slicedDuration: 0, slicedTimeRanges: [], activatedEngines: new Set(), foundFeatures: new Map([['URL_PATTERN', new Set()], ['BEHAVIOR_MODEL', new Set()]]) }; }
analyzeUrlPatterns(c, b) { if (!c || typeof c !== 'string' || !b) return null; const ls = c.split('\n'), segs = []; let cd = 0; for (const l of ls) { const tl = l.trim(); if (tl.startsWith('#EXTINF:')) cd = parseFloat(tl.split(':')[1]); else if ((tl.endsWith('.ts') || tl.includes('.jpeg')) && !tl.startsWith('#')) { try { const u = new URL(tl, b), p = u.pathname, ls = p.lastIndexOf('/'); segs.push({ url: u.href, duration: cd, path: (ls > -1) ? p.substring(0, ls + 1) : '/', filename: p.split('/').pop() || '' }); } catch (e) { } } } if (segs.length < 10) return null; const ps = new Map(); segs.forEach(s => { ps.set(s.path, (ps.get(s.path) || 0) + 1); }); if (ps.size <= 1) { this.context.actionLogger.log(`所有分片路径统一,无法通过路径独特性分析。`, LOG.SLICE); return null; } const sp = Array.from(ps.entries()).sort((a, b) => b[1] - a[1]), [mp, mpc] = sp[0], sugs = new Set(); for (let i = 1; i < sp.length; i++) { const [sup, supc] = sp[i]; if (supc < mpc / 2) { const ap = sup.split('/').filter(Boolean), bp = mp.split('/').filter(Boolean); for (let j = 0; j < Math.min(ap.length, bp.length); j++) { if (ap[j] !== bp[j]) { sugs.add(ap[j]); this.context.actionLogger.log(`基于路径独特性发现高嫌疑关键词: ${ap[j]}`, LOG.SLICE); break; } } if (sugs.size === 0 && ap.length !== bp.length) { const lg = ap.length > bp.length ? ap : bp, sh = ap.length > bp.length ? bp : ap; if (lg.join('/').startsWith(sh.join('/'))) { const dp = lg[sh.length]; if (dp) { sugs.add(dp); this.context.actionLogger.log(`基于路径层级差异发现高嫌疑关键词: ${dp}`, LOG.SLICE); } } } } } if (sugs.size > 0) { this.slicingReport.activatedEngines.add('URL_PATTERN'); sugs.forEach(s => this.slicingReport.foundFeatures.get('URL_PATTERN').add(s)); return Array.from(sugs); } this.context.actionLogger.log(`路径分析未得出结论,回退至基于时长的聚类分析。`, LOG.SLICE); const dcs = new Map(); segs.forEach(s => { const k = s.duration.toFixed(2); if (!dcs.has(k)) dcs.set(k, []); dcs.get(k).push(s); }); if (dcs.size <= 1) return null; const sc = Array.from(dcs.values()).sort((a, b) => b.length - a.length), bc = sc[0]; sc.forEach(c => { if (c === bc) return; if (c.length <= 5 && c.every(s => s.duration < 20)) { const ds = c.map(s => s.duration), m = ds.reduce((a, v) => a + v, 0) / ds.length, v = ds.map(d => (d - m) ** 2).reduce((a, v) => a + v, 0) / ds.length; if (v < 0.01) { const as = c[0], bs = bc[0]; if (as.path !== bs.path) { const ap = as.path.split('/').filter(Boolean), bp = bs.path.split('/').filter(Boolean); for (let i = 0; i < Math.min(ap.length, bp.length); i++) { if (ap[i] !== bp[i]) { sugs.add(ap[i]); this.context.actionLogger.log(`[广告过滤-回退] 通过对比发现潜在广告关键词: ${ap[i]}`, LOG.SLICE); return; } } } } } }); if (sugs.size > 0) { this.slicingReport.activatedEngines.add('URL_PATTERN'); sugs.forEach(s => this.slicingReport.foundFeatures.get('URL_PATTERN').add(s)); } return Array.from(sugs); }
generateDeveloperReport() { const nl = '\n'; let rh = `大魔王视频助手 开发者日志 (版本: ${SCRIPT_VERSION})${nl}页面URL: ${window.location.href}${nl}`; if (this.fatalNetworkErrors.length > 0) { rh += `--- 深度网络故障分析 ---${nl}`; this.fatalNetworkErrors.forEach((e, i) => { rh += `[错误 ${i + 1}] @ ${e.relativeTime}: ${e.message}${nl}`; }); } rh += `--- 统一事件时间轴 ---${nl}`; const sl = [...this.eventTimeline].sort((a, b) => b.time - a.time || b.sequence - a.sequence), CP = /(嵌入式扫描器发现广告链接,已跳过|\[路径特征分析\] URL判定为广告)/; let ho = `
${rh.replace(//g, ">").replace(/\n/g, '
')}
`, cl = [], ic = false; const fc = () => { if (cl.length > 0) { ho += `[+] 折叠了 ${cl.length} 条 "广告扫描" 噪音日志... (点击展开)
${cl.join('')}
`; cl = []; } }; sl.forEach(l => { const ft = l.type.replace(' ', ' [') + ']', lm = `[${l.relativeTime}] ${ft} ${l.message}`, em = lm.replace(//g, ">"), clh = `${em}
`; if (l.type === LOG.SCAN_W && CP.test(l.message)) { ic = true; cl.push(clh); } else { if (ic) { fc(); ic = false; } if (l.message.length > 500 && (l.message.includes('base64,') || l.message.includes('M3U8处理完成'))) ho += `[+] 折叠了过长的硬编码数据... (点击展开)
${clh}
`; else ho += clh; } }); fc(); return ho; }
}
class Draggable {
constructor(c, e, h, pm) { this.context = c; this.element = e; this.handles = h; this.playerManager = pm; this.isDragging = false; this.dragStartPos = { x: 0, y: 0 }; this.elementStartPos = { x: 0, y: 0 }; this.boundHandleDragStart = this.handleDragStart.bind(this); this.boundHandleDragMove = this.handleDragMove.bind(this); this.boundHandleDragEnd = this.handleDragEnd.bind(this); this.addListeners(); }
addListeners() { this.handles.forEach(h => _el(h, 'pointerdown', this.boundHandleDragStart, { capture: true })); }
destroy() { this.removeDragListeners(); this.handles.forEach(h => h.removeEventListener('pointerdown', this.boundHandleDragStart, { capture: true })); this.element = null; }
removeDragListeners() { document.removeEventListener('pointermove', this.boundHandleDragMove, { capture: true }); document.removeEventListener('pointerup', this.boundHandleDragEnd, { capture: true }); document.removeEventListener('pointercancel', this.boundHandleDragEnd, { capture: true }); }
handleDragStart(e) { if (!this.element || e.button !== 0) return; Utils.stopEvent(e); this.isDragging = true; this.playerManager?.showIndicatorPermanently(true); this.element.style.transition = 'none'; const r = this.element.getBoundingClientRect(); this.element.style.left = `0px`; this.element.style.top = `0px`; this.element.style.transform = `translate3d(${r.left}px, ${r.top}px, 0)`; this.elementStartPos = { x: r.left, y: r.top }; this.dragStartPos = { x: e.clientX, y: e.clientY }; _el(document, 'pointermove', this.boundHandleDragMove, { capture: true }); _el(document, 'pointerup', this.boundHandleDragEnd, { capture: true }); _el(document, 'pointercancel', this.boundHandleDragEnd, { capture: true }); }
handleDragMove(e) { if (!this.isDragging || !this.element) return; Utils.stopEvent(e); let nx = this.elementStartPos.x + (e.clientX - this.dragStartPos.x), ny = this.elementStartPos.y + (e.clientY - this.dragStartPos.y); nx = Math.max(0, Math.min(nx, window.innerWidth - this.element.offsetWidth)); ny = Math.max(0, Math.min(ny, window.innerHeight - this.element.offsetHeight)); this.element.style.transform = `translate3d(${nx}px, ${ny}px, 0)`; }
handleDragEnd(e) { if (!this.isDragging || !this.element) return; e.stopPropagation(); this.isDragging = false; this.removeDragListeners(); this.playerManager?.resetIndicatorTimeout(); this.element.style.transition = ''; const m = new DOMMatrix(window.getComputedStyle(this.element).transform), fp = { top: `${m.m42}px`, left: `${m.m41}px` }; this.element.style.left = fp.left; this.element.style.top = fp.top; this.element.style.transform = ''; const iv = this.playerManager.videoElement.videoHeight > this.playerManager.videoElement.videoWidth, vok = iv ? 'verticalVideo' : 'horizontalVideo', il = screen.orientation.type.includes('landscape'), pok = il ? 'landscape' : 'portrait'; this.context.settingsManager.savePlayerPosition(window.location.hostname, `${pok}_${vok}`, fp); }
}
class CustomControlsHandler {
constructor(c, ct, v) { this.context = c; this.container = ct; this.video = v; this.eventManager = new EventManager(); this.hideTimeout = null; this.isSeeking = false; this.lastKnownVolumeBeforeMute = 1.0; this.boundHandleVolumeChange = this.handleVolumeChange.bind(this); this.boundHandleTimeUpdate = this.handleTimeUpdate.bind(this); this.boundHandleDurationChange = this.handleDurationChange.bind(this); this.boundHandleProgress = this.handleProgress.bind(this); this.boundOnPlay = this.onPlay.bind(this); this.boundOnPause = this.onPause.bind(this); this.boundHandleTap = this.handleTap.bind(this); this.boundResetHideTimeout = this.resetHideTimeout.bind(this); this.boundHideControls = this.hideControls.bind(this); this.controlActions = { '.dmz-big-play-button': this.togglePlay.bind(this), '.dmz-fullscreen-btn': this.toggleFullscreen.bind(this), '.dmz-mute-btn': this.toggleMute.bind(this), '.dmz-more-btn': this.toggleMoreMenu.bind(this), '.dmz-pip-btn': this.togglePip.bind(this), '.dmz-playback-rate-btn': this.showPlaybackRates.bind(this) }; }
init() { this.controlsBar = this.container.querySelector('.dmz-controls-bar'); this.bigPlayButton = this.container.querySelector('.dmz-big-play-button'); this.currentTimeEl = this.container.querySelector('.dmz-current-time'); this.totalTimeEl = this.container.querySelector('.dmz-total-time'); this.progressBar = this.container.querySelector('.dmz-progress-bar'); this.playedBar = this.container.querySelector('.dmz-progress-played'); this.bufferBar = this.container.querySelector('.dmz-progress-buffer'); this.handle = this.container.querySelector('.dmz-progress-handle'); this.fullscreenEnterIcon = this.container.querySelector('.dmz-fullscreen-btn .icon-fullscreen-enter'); this.fullscreenExitIcon = this.container.querySelector('.dmz-fullscreen-btn .icon-fullscreen-exit'); this.volumeOnIcon = this.container.querySelector('.dmz-mute-btn .icon-volume-on'); this.volumeOffIcon = this.container.querySelector('.dmz-mute-btn .icon-volume-off'); this.moreMenu = this.container.querySelector('.dmz-more-menu'); this.pipBtn = this.container.querySelector('.dmz-pip-btn'); this.playbackRateBtn = this.container.querySelector('.dmz-playback-rate-btn'); this.playbackRateValue = this.container.querySelector('.dmz-playback-rate-btn .dmz-menu-item-value'); this.playbackRatesContainer = this.container.querySelector('.dmz-playback-rates'); this.lastKnownVolumeBeforeMute = this.context.settingsManager.config.lastVolume; this.addEventListeners(); this.updatePlayButton(); this.updateFullscreenButton(); this.updateMuteButton(); if (!document.pictureInPictureEnabled && this.pipBtn) this.pipBtn.style.display = 'none'; }
addEventListeners() { this.eventManager.add(this.video, 'play', this.boundOnPlay); this.eventManager.add(this.video, 'pause', this.boundOnPause); this.eventManager.add(this.video, 'volumechange', this.boundHandleVolumeChange); this.eventManager.add(this.video, 'timeupdate', this.boundHandleTimeUpdate); this.eventManager.add(this.video, 'durationchange', this.boundHandleDurationChange); this.eventManager.add(this.video, 'progress', this.boundHandleProgress); this.eventManager.add(this.container, 'mousemove', this.boundResetHideTimeout); const s = (e) => e.stopPropagation(); if (this.controlsBar) this.eventManager.add(this.controlsBar, 'pointerdown', s, { capture: true }); if (this.controlsBar) this.eventManager.add(this.controlsBar, 'click', this.boundHandleTap, { capture: true }); if (this.bigPlayButton) this.eventManager.add(this.bigPlayButton, 'click', this.boundHandleTap, { capture: true }); }
destroy() { this.eventManager.removeAll(); clearTimeout(this.hideTimeout); }
handleTap(e) { e.stopPropagation(); this.handleTapOnControl(e.target); }
onPlay() { this.updatePlayButton(); const { playerManager, diagnosticsTool, coreLogic } = this.context; if (playerManager.currentVideoType === 'normal') diagnosticsTool.playbackHealth.media.status = 'success'; else if (diagnosticsTool.playbackHealth.media.status === 'pending') diagnosticsTool.playbackHealth.media.status = 'success'; const cu = playerManager.currentVideoUrl; if (!cu) return; const guc = (u) => { try { return new URL(u).pathname; } catch (e) { return u; } }, puc = guc(cu); coreLogic.findAllVideosAndAudioInPage().forEach(m => { if (m.id === C.PLAYER || !m.dataset.dmzNeutralized) return; const os = m.dataset.dmzOriginalSrc; if (os && guc(os) === puc) { coreLogic.neutralizedMedia.add(m); coreLogic.startPersistentEnforcer(); } }); }
onPause() { this.updatePlayButton(); }
handleTapOnControl(t) { if (!t) return false; const ak = Object.keys(this.controlActions).find(s => t.closest(s)); if (ak) { this.controlActions[ak](); return true; } const rb = t.closest('[data-rate]'); if (rb) { this.setPlaybackRate(parseFloat(rb.dataset.rate)); return true; } return false; }
toggleMoreMenu() { const v = Utils.toggleVisibility(this.moreMenu); if (v) { Utils.toggleVisibility(this.playbackRatesContainer, false); if (this.pipBtn) this.pipBtn.style.display = document.pictureInPictureEnabled ? 'flex' : 'none'; if (this.playbackRateBtn) this.playbackRateBtn.style.display = 'flex'; } }
hideMoreMenu() { Utils.toggleVisibility(this.moreMenu, false); }
togglePip() { this.hideMoreMenu(); if (!document.pictureInPictureElement) this.video.requestPictureInPicture().catch(e => this.context.actionLogger.log(`请求画中画失败: ${e.message}`, LOG.ERROR)); else document.exitPictureInPicture(); }
showPlaybackRates() { if (!this.playbackRatesContainer || !this.playbackRateBtn) return; if (this.pipBtn) this.pipBtn.style.display = 'none'; if (this.playbackRateBtn) this.playbackRateBtn.style.display = 'none'; Utils.toggleVisibility(this.playbackRatesContainer, true); }
setPlaybackRate(r) { this.video.playbackRate = r; if (this.playbackRateValue) this.playbackRateValue.textContent = r === 1.0 ? '正常' : `${r}x`; if (this.playbackRatesContainer) { this.playbackRatesContainer.querySelector('.active')?.classList.remove('active'); this.playbackRatesContainer.querySelector(`[data-rate="${r}"]`)?.classList.add('active'); } this.hideMoreMenu(); }
toggleControlsVisibility() { clearTimeout(this.hideTimeout); this.hideMoreMenu(); this.controlsBar?.classList.contains('hidden') ? this.resetHideTimeout() : this.hideControls(); }
updateSeekUI(cx) { if (!this.progressBar || !this.video?.duration) return; const r = this.progressBar.getBoundingClientRect(); let p = (cx - r.left) / r.width; p = Math.max(0, Math.min(1, p)); const nt = p * this.video.duration; if (isNaN(nt)) return; const pc = p * 100; if (this.playedBar) this.playedBar.style.width = `${pc}%`; if (this.handle) this.handle.style.left = `${pc}%`; if (this.currentTimeEl) this.currentTimeEl.textContent = Utils.formatTime(nt); return nt; }
togglePlay() { if (this.video) this.video.paused ? this.video.play() : this.video.pause(); }
toggleFullscreen() { this.context.playerManager.fullscreenHandler?.toggleState(); }
toggleMute() { if (!this.video) return; if (this.video.muted || this.video.volume === 0) { this.video.volume = this.lastKnownVolumeBeforeMute > 0.05 ? this.lastKnownVolumeBeforeMute : 0.5; this.video.muted = false; } else this.video.muted = true; this.context.settingsManager.save({ isMuted: this.video.muted }, false); }
handleVolumeChange() { this.updateMuteButton(); const em = this.video.muted || this.video.volume === 0; if (em) this.context.settingsManager.save({ isMuted: true }, false); else { this.lastKnownVolumeBeforeMute = this.video.volume; this.context.settingsManager.save({ lastVolume: this.video.volume, isMuted: false }, false); } }
updatePlayButton() { const b = this.container.querySelector('.dmz-big-play-button'), c = this.container.querySelector('.dmz-big-play-button-container'); if (!b) return; if (this.video.paused) { b.innerHTML = ICONS.BIG_PLAY; if (c) c.style.opacity = '1'; } else { b.innerHTML = ICONS.BIG_PAUSE.replace(/^