您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Overlay with 10s skip + single speed menu. Ghost taps blocked. Speed selection updates and closes menu on tap.
当前为
// ==UserScript== // @name Mobile Video Controller (Movable + Single Speed Menu) - Final Fix // @namespace https://your.namespace // @version 4.9.7 // @description Overlay with 10s skip + single speed menu. Ghost taps blocked. Speed selection updates and closes menu on tap. // @match *://*/* // @grant none // ==/UserScript== (function () { 'use strict'; const MIN_FRAC_PAUSED = 0.20; const MIN_FRAC_PLAYING = 0.08; const EDGE = 6; const DRAG_OFFSET_Y = 40; const SPEEDS = [3, 2.75, 2.5, 2, 1.75, 1.5, 1.25, 1, 0.75, 0.5, 0.25]; let activeVideo = null; let uiWrap = null; let manualDrag = false; let menu, speedBtn, hideSpeedMenu; const clamp = (v, a, b) => Math.max(a, Math.min(b, v)); const clampTime = (v, t) => { const d = Number.isFinite(v.duration) ? v.duration : Infinity; return clamp(t, 0, d); }; // ---------- UI ---------- function createUI() { uiWrap = document.createElement('div'); uiWrap.style.cssText = ` position: fixed; left: 12px; top: 12px; z-index: 2147483647; font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; display: none; pointer-events: auto; `; const panel = document.createElement('div'); panel.style.cssText = ` display: flex; flex-direction: column; align-items: center; gap: 2px; background: transparent; color: #fff; border-radius: 2px; touch-action: none; user-select: none; pointer-events: auto; `; const dragHandle = document.createElement('div'); dragHandle.style.cssText = ` width: 36px; height: 6px; border-radius: 3px; background: rgba(200,200,200,0.5); margin-bottom: 4px; cursor: grab; `; const row = document.createElement('div'); row.style.cssText = ` display: flex; align-items: center; gap: 8px; padding: 1px 4px; background: transparent; pointer-events: auto; `; const btnStyle = ` appearance: none; border: 0; border-radius: 10px; padding: 6px 10px; font-size: 18px; font-weight: 600; color: #fff; background: rgba(43,43,43,0.35); min-width: 56px; text-align: center; line-height: 1; pointer-events: auto; `; const mkBtn = (txt, title, minW) => { const b = document.createElement('button'); b.textContent = txt; b.title = title || ''; b.style.cssText = btnStyle; if (minW) b.style.minWidth = minW; ['pointerdown','pointerup','click','touchstart','touchend'].forEach(type => { b.addEventListener(type, e => { e.stopPropagation(); }); }); return b; }; const rewind = mkBtn('⟲ 10', 'Rewind 10s'); speedBtn = mkBtn('1x', 'Playback speed', '72px'); const forward = mkBtn('10 ⟳', 'Forward 10s'); // Create single speed menu container menu = document.createElement('div'); menu.className = 'mvc-speed-menu'; menu.style.cssText = ` display: none; position: fixed; background: rgba(0,0,0,0.92); border-radius: 8px; z-index: 2147483647; min-width: 86px; max-height: 64vh; overflow-y: auto; pointer-events: auto; touch-action: manipulation; -webkit-tap-highlight-color: transparent; `; document.body.append(menu); // Block clicks on empty menu background menu.addEventListener('click', e => { if (e.target === menu) { e.stopPropagation(); e.preventDefault(); } }, true); // --- Options --- function makeOpt(sp, isFirst) { const opt = document.createElement('div'); opt.textContent = `${sp}x`; opt.dataset.sp = String(sp); opt.style.cssText = ` color: white; padding: 0.6em 1.1em; font-size: 16px; text-align: center; border-top: ${isFirst ? 'none' : '1px solid rgba(255,255,255,0.12)'}; pointer-events: auto; touch-action: manipulation; -webkit-tap-highlight-color: transparent; user-select: none; `; opt.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); const spv = Number(opt.dataset.sp); if (activeVideo && Number.isFinite(spv)) { activeVideo.playbackRate = spv; speedBtn.firstChild.nodeValue = `${spv}x`; } highlightSelected(spv); hideSpeedMenu(); }); opt.addEventListener('mouseenter', () => { opt.style.background = 'rgba(255,255,255,0.06)'; }); opt.addEventListener('mouseleave', () => { opt.style.background = 'none'; }); return opt; } let firstOption = true; for (const sp of SPEEDS) { menu.appendChild(makeOpt(sp, firstOption)); firstOption = false; } function highlightSelected(sp) { Array.from(menu.children).forEach(el => { el.style.background = 'none'; el.style.fontWeight = 'normal'; }); const sel = Array.from(menu.children).find(el => el.textContent === `${sp}x`); if (sel) { sel.style.background = 'rgba(255,255,255,0.14)'; sel.style.fontWeight = '700'; } } function showAndMeasure(el) { el.style.display = 'block'; el.style.visibility = 'hidden'; el.style.left = '-9999px'; el.style.top = '-9999px'; const rect = el.getBoundingClientRect(); return { w: rect.width, h: rect.height }; } function placeMenu() { const { w: menuW, h: menuH } = showAndMeasure(menu); const rect = speedBtn.getBoundingClientRect(); let left = Math.round(rect.right - menuW); if (left < EDGE) left = EDGE; if (left + menuW > window.innerWidth - EDGE) left = window.innerWidth - menuW - EDGE; let top; if (rect.bottom + menuH + 6 <= window.innerHeight - EDGE) top = rect.bottom + 6; else top = Math.max(EDGE, rect.top - menuH - 6); Object.assign(menu.style, { left: left + 'px', top: top + 'px', visibility: 'visible' }); } hideSpeedMenu = function() { menu.style.display = 'none'; menu.style.visibility = 'visible'; }; function toggleSpeedMenu() { const open = menu.style.display === 'block'; hideSpeedMenu(); if (!open) { placeMenu(); menu.style.display = 'block'; if (activeVideo) highlightSelected(Number(activeVideo.playbackRate) || 1); } } document.addEventListener('pointerdown', (e) => { if (!uiWrap || uiWrap.contains(e.target) || menu.contains(e.target)) return; hideSpeedMenu(); }, true); // Button actions rewind.onclick = e => { e.stopPropagation(); e.preventDefault(); if (activeVideo) activeVideo.currentTime = clampTime(activeVideo, activeVideo.currentTime - 10); }; forward.onclick = e => { e.stopPropagation(); e.preventDefault(); if (activeVideo) activeVideo.currentTime = clampTime(activeVideo, activeVideo.currentTime + 10); }; speedBtn.onclick = e => { e.stopPropagation(); e.preventDefault(); if (activeVideo) toggleSpeedMenu(); }; // Dragging let dragging = false; dragHandle.onpointerdown = e => { dragging = true; try { dragHandle.setPointerCapture(e.pointerId); } catch (err) {} moveUnderFinger(e); manualDrag = true; e.preventDefault(); e.stopPropagation(); }; dragHandle.onpointermove = e => { if (!dragging) return; moveUnderFinger(e); e.preventDefault(); e.stopPropagation(); }; dragHandle.onpointerup = e => { dragging = false; }; dragHandle.onpointercancel = () => { dragging = false; }; function moveUnderFinger(e) { const w = uiWrap.offsetWidth, h = uiWrap.offsetHeight; const x = clamp(e.clientX - w / 2, EDGE, window.innerWidth - w - EDGE); const y = clamp(e.clientY - h / 2 - DRAG_OFFSET_Y, EDGE, window.innerHeight - h - EDGE); uiWrap.style.left = x + 'px'; uiWrap.style.top = y + 'px'; uiWrap.style.right = 'auto'; uiWrap.style.bottom = 'auto'; } row.append(rewind, speedBtn, forward); panel.append(dragHandle, row); uiWrap.append(panel); document.body.appendChild(uiWrap); } // ---------- Video handling ---------- function visibleArea(v) { const r = v.getBoundingClientRect(); const iw = window.innerWidth, ih = window.innerHeight; const w = Math.max(0, Math.min(r.right, iw) - Math.max(r.left, 0)); const h = Math.max(0, Math.min(r.bottom, ih) - Math.max(r.top, 0)); return w * h; } const isPlaying = v => !v.paused && !v.ended && v.readyState > 2; function pickCandidate() { const vids = Array.from(document.querySelectorAll('video')); if (!vids.length) return null; let best = null, bestScore = -1, bestFrac = 0; const viewArea = window.innerWidth * window.innerHeight; for (const v of vids) { const area = visibleArea(v); if (area <= 0) continue; const frac = area / viewArea; const playing = isPlaying(v); const score = area + (playing ? viewArea : 0); if (score > bestScore) { best = v; bestScore = score; bestFrac = frac; } } if (!best) return null; const minFrac = isPlaying(best) ? MIN_FRAC_PLAYING : MIN_FRAC_PAUSED; if (bestFrac >= minFrac) return best; return null; } function setActiveVideo(v) { if (activeVideo === v) return; activeVideo = v; if (!activeVideo) { uiWrap.style.display = 'none'; return; } if (!document.body.contains(uiWrap)) document.body.appendChild(uiWrap); uiWrap.style.display = 'block'; manualDrag = false; hideSpeedMenu(); positionOnVideo(); } function positionOnVideo() { if (!activeVideo || manualDrag) return; const r = activeVideo.getBoundingClientRect(); const w = uiWrap.offsetWidth, h = uiWrap.offsetHeight; if (!(r && r.width > 0 && r.height > 0)) return; let x = r.right - w - 40; let y = r.bottom - h - 10; x = clamp(x, EDGE, window.innerWidth - w - EDGE); y = clamp(y, EDGE, window.innerHeight - h - EDGE); uiWrap.style.left = x + 'px'; uiWrap.style.top = y + 'px'; } function evaluateActive() { const cand = pickCandidate(); setActiveVideo(cand); } function tick() { try { positionOnVideo(); evaluateActive(); } catch (e) {} requestAnimationFrame(tick); } function observeVideos() { const io = new IntersectionObserver(() => evaluateActive(), { threshold: [0,0.25,0.5,0.75,1] }); const observed = new WeakSet(); const attachIO = () => { document.querySelectorAll('video').forEach(v => { if (!observed.has(v)) { io.observe(v); observed.add(v); } }); }; attachIO(); new MutationObserver(attachIO).observe(document.body, { childList: true, subtree: true }); setInterval(evaluateActive, 1500); addEventListener('resize', positionOnVideo); addEventListener('scroll', () => { evaluateActive(); positionOnVideo(); }, { passive: true }); } function init() { createUI(); observeVideos(); evaluateActive(); positionOnVideo(); requestAnimationFrame(tick); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, { once: true }); else init(); })();