Greasy Fork

来自缓存

Greasy Fork is available in English.

自定义视频倍速播放

自定义视频播放速度

当前为 2026-01-03 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         自定义视频倍速播放
// @version      2.4
// @description  自定义视频播放速度
// @author       DeepSeek
// @match        http://*/*
// @match        https://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// @namespace    http://greasyfork.icu/users/452911
// ==/UserScript==

(function() {
  'use strict';
  
  // 全局变量
  let currentSpeed = parseFloat(GM_getValue('videoSpeed')) || 1;
  let controlBtn = null;      // 控制按钮
  let inputPanel = null;      // 输入框面板
  let speedInput = null;      // 输入框
  let initialized = false;
  
  // 获取所有视频元素
  function getAllVideoElements() {
    return document.querySelectorAll('video,[class*="player"] *');
  }
  
  // 主初始化函数
  function init() {
    if (initialized || document.fullscreenElement) return;
    
    let videos = getAllVideoElements();
    if (videos.length === 0) return;
    
    initialized = true;
    
    // 应用速度到当前视频
    applySpeedToVideos(videos, currentSpeed);
    
    // 创建控制按钮(如果不存在)
    if (!controlBtn || !controlBtn.parentNode) {
      createControlButton();
    }
    
    // 尝试处理iframe中的视频
    tryProcessIframeVideos();
  }
  
  // 创建控制按钮
  function createControlButton() {
    controlBtn = document.createElement('div');
    controlBtn.textContent = `倍速: ${currentSpeed}x`;
    controlBtn.title = '点击修改播放倍速';
    controlBtn.style.cssText = `
      position: fixed;
      right: 5px;
      top: 30px;
      padding: 8px 12px;
      background: rgba(0, 0, 0, 0.8);
      color: white;
      border: 1px solid #555;
      border-radius: 4px;
      cursor: pointer;
      z-index: 9999;
      font-size: 14px;
      font-family: Arial, sans-serif;
      user-select: none;
      min-width: 70px;
      text-align: center;
    `;
    
    controlBtn.addEventListener('click', showSpeedInput);
    document.body.appendChild(controlBtn);
  }
  
  // 显示速度输入框
  function showSpeedInput() {
    // 如果已经有输入面板,先移除
    if (inputPanel && inputPanel.parentNode) {
      inputPanel.parentNode.removeChild(inputPanel);
    }
    
    // 创建输入框面板
    inputPanel = document.createElement('div');
    inputPanel.style.cssText = `
      position: fixed;
      right: 5px;
      top: 70px;
      background: rgba(0, 0, 0, 0.85);
      border: 1px solid #555;
      border-radius: 6px;
      z-index: 10000;
      font-size: 14px;
      font-family: Arial, sans-serif;
      user-select: none;
      min-width: 150px;
      padding: 12px;
      box-shadow: 0 2px 10px rgba(0,0,0,0.3);
      display: flex;
      flex-direction: column;
      gap: 10px;
    `;
    
    // 创建标题
    const title = document.createElement('div');
    title.textContent = '设置播放倍速';
    title.style.cssText = `
      color: white;
      font-weight: bold;
      font-size: 14px;
      margin-bottom: 5px;
      text-align: center;
    `;
    
    // 创建输入框
    speedInput = document.createElement('input');
    speedInput.type = 'number';
    speedInput.value = currentSpeed;
    speedInput.min = '0.1';
    speedInput.max = '16';
    speedInput.step = '0.1';
    speedInput.style.cssText = `
      width: 100%;
      padding: 8px 10px;
      border: 1px solid #666;
      border-radius: 4px;
      background: #333;
      color: white;
      font-size: 14px;
      outline: none;
      box-sizing: border-box;
    `;
    
    // 创建按钮容器
    const buttonContainer = document.createElement('div');
    buttonContainer.style.cssText = `
      display: flex;
      gap: 8px;
      justify-content: space-between;
      margin-top: 5px;
    `;
    
    // 创建取消按钮
    const cancelBtn = document.createElement('button');
    cancelBtn.textContent = '取消';
    cancelBtn.style.cssText = `
      flex: 1;
      padding: 8px 0;
      background: #666;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 13px;
      font-weight: bold;
      transition: background 0.2s;
    `;
    
    // 创建确定按钮
    const confirmBtn = document.createElement('button');
    confirmBtn.textContent = '确定';
    confirmBtn.style.cssText = `
      flex: 1;
      padding: 8px 0;
      background: #4CAF50;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 13px;
      font-weight: bold;
      transition: background 0.2s;
    `;
    
    // 按钮悬停效果
    cancelBtn.addEventListener('mouseover', () => {
      cancelBtn.style.background = '#777';
    });
    
    cancelBtn.addEventListener('mouseout', () => {
      cancelBtn.style.background = '#666';
    });
    
    confirmBtn.addEventListener('mouseover', () => {
      confirmBtn.style.background = '#45a049';
    });
    
    confirmBtn.addEventListener('mouseout', () => {
      confirmBtn.style.background = '#4CAF50';
    });
    
    // 按钮事件处理
    cancelBtn.addEventListener('click', hideSpeedInput);
    confirmBtn.addEventListener('click', applyNewSpeed);
    
    // 输入框回车和ESC事件
    speedInput.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') {
        applyNewSpeed();
      } else if (e.key === 'Escape') {
        hideSpeedInput();
      }
    });
    
    // 创建常用倍速按钮容器
    const quickButtons = document.createElement('div');
    quickButtons.style.cssText = `
      display: flex;
      flex-wrap: wrap;
      gap: 5px;
      margin-top: 5px;
    `;
    
    // 常用倍速按钮
    const quickSpeeds = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0];
    
    quickSpeeds.forEach(speed => {
      const btn = document.createElement('button');
      btn.textContent = `${speed}x`;
      btn.style.cssText = `
        flex: 1;
        min-width: calc(33% - 4px);
        padding: 6px 0;
        background: ${Math.abs(speed - currentSpeed) < 0.01 ? '#2196F3' : '#555'};
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 12px;
        transition: background 0.2s;
      `;
      
      btn.addEventListener('click', () => {
        speedInput.value = speed;
        // 立即应用快速倍速,不需要确认
        setSpeed(speed);
        hideSpeedInput();
      });
      
      btn.addEventListener('mouseover', () => {
        if (Math.abs(speed - currentSpeed) >= 0.01) {
          btn.style.background = '#666';
        }
      });
      
      btn.addEventListener('mouseout', () => {
        if (Math.abs(speed - currentSpeed) >= 0.01) {
          btn.style.background = '#555';
        }
      });
      
      quickButtons.appendChild(btn);
    });
    
    // 组装所有元素
    buttonContainer.appendChild(cancelBtn);
    buttonContainer.appendChild(confirmBtn);
    
    inputPanel.appendChild(title);
    inputPanel.appendChild(speedInput);
    inputPanel.appendChild(buttonContainer);
    inputPanel.appendChild(quickButtons);
    
    document.body.appendChild(inputPanel);
    
    // 自动选中输入框内容并聚焦
    setTimeout(() => {
      speedInput.focus();
      speedInput.select();
    }, 10);
    
    // 点击页面其他区域关闭输入框
    document.addEventListener('click', handleOutsideClick, true);
  }
  
  // 处理外部点击
  function handleOutsideClick(e) {
    if (inputPanel && inputPanel.parentNode) {
      // 如果点击的是输入框或按钮,不关闭
      if (inputPanel.contains(e.target) || controlBtn.contains(e.target)) {
        return;
      }
      hideSpeedInput();
    }
  }
  
  // 隐藏输入框
  function hideSpeedInput() {
    if (inputPanel && inputPanel.parentNode) {
      inputPanel.parentNode.removeChild(inputPanel);
      inputPanel = null;
      // 移除外部点击监听
      document.removeEventListener('click', handleOutsideClick, true);
    }
  }
  
  // 应用新速度
  function applyNewSpeed() {
    let newSpeed = parseFloat(speedInput.value);
    if (!isNaN(newSpeed) && newSpeed >= 0.1 && newSpeed <= 16) {
      setSpeed(newSpeed);
      hideSpeedInput();
    } else {
      // 无效输入,恢复原值
      speedInput.value = currentSpeed;
      alert('请输入有效的倍速值 (0.1 - 16)');
      speedInput.focus();
      speedInput.select();
    }
  }
  
  // 设置速度(主函数)
  function setSpeed(newSpeed) {
    // 更新当前速度
    currentSpeed = parseFloat(newSpeed.toFixed(2));
    GM_setValue('videoSpeed', currentSpeed);
    
    // 更新控制按钮显示
    if (controlBtn) {
      controlBtn.textContent = `倍速: ${currentSpeed}x`;
    }
    
    // 应用新速度到当前文档的所有视频
    let videos = getAllVideoElements();
    applySpeedToVideos(videos, currentSpeed);
    
    // 触发自定义事件(用于页面内通信)
    window.dispatchEvent(new CustomEvent('speedChanged', {
      detail: { speed: currentSpeed }
    }));
    
    // 尝试处理iframe中的视频
    tryProcessIframeVideos(currentSpeed);
  }
  
  // 应用速度到视频元素
  function applySpeedToVideos(videoElements, speed) {
    videoElements.forEach(element => {
      try {
        // 如果是video元素
        if (element.tagName === 'VIDEO') {
          element.playbackRate = speed;
        } 
        // 如果是其他元素,查找其中的video元素
        else {
          let video = element.querySelector('video');
          if (video) {
            video.playbackRate = speed;
          } else {
            // 如果元素本身有playbackRate属性,也尝试设置
            if ('playbackRate' in element) {
              element.playbackRate = speed;
            }
          }
        }
      } catch (e) {
        // 忽略错误
      }
    });
  }
  
  // 尝试处理iframe中的视频
  function tryProcessIframeVideos(speed = currentSpeed) {
    document.querySelectorAll('iframe').forEach(iframe => {
      try {
        // 尝试访问iframe内容(仅同源iframe)
        if (iframe.contentDocument) {
          // 使用相同的选择器
          let iframeVideos = iframe.contentDocument.querySelectorAll('video,[class*="player"] *');
          applySpeedToVideos(iframeVideos, speed);
          
          // 递归处理嵌套iframe
          let nestedIframes = iframe.contentDocument.querySelectorAll('iframe');
          nestedIframes.forEach(nestedIframe => {
            tryProcessIframeElement(nestedIframe, speed);
          });
        }
      } catch (e) {
        // 跨域iframe会抛出安全错误,尝试使用postMessage
        tryPostMessageToIframe(iframe, speed);
      }
    });
  }
  
  // 递归处理iframe元素
  function tryProcessIframeElement(iframe, speed) {
    try {
      if (iframe.contentDocument) {
        let iframeVideos = iframe.contentDocument.querySelectorAll('video,[class*="player"] *');
        applySpeedToVideos(iframeVideos, speed);
      }
    } catch (e) {
      // 忽略错误
    }
  }
  
  // 尝试使用postMessage与iframe通信
  function tryPostMessageToIframe(iframe, speed) {
    try {
      iframe.contentWindow.postMessage({
        type: 'SET_VIDEO_SPEED',
        speed: speed
      }, '*');
    } catch (e) {
      // 忽略错误
    }
  }
  
  // 监听来自iframe的消息(用于跨域通信)
  window.addEventListener('message', (e) => {
    if (e.data && e.data.type === 'SPEED_CHANGED' && e.data.speed) {
      setSpeed(e.data.speed);
    }
  });
  
  // 监听自定义事件(用于页面内通信)
  window.addEventListener('speedChanged', (e) => {
    if (e.detail && e.detail.speed) {
      setSpeed(e.detail.speed);
    }
  });
  
  // 轮询检查新视频(处理动态加载的视频)
  function checkForNewVideos() {
    if (document.fullscreenElement) return;
    
    let videos = getAllVideoElements();
    if (videos.length > 0) {
      // 如果有视频但控制按钮不存在,初始化
      if (!controlBtn || !controlBtn.parentNode) {
        init();
      } else {
        // 否则只应用速度
        applySpeedToVideos(videos, currentSpeed);
      }
    }
  }
  
  // 节流函数
  let throttleTimer;
  function throttleCheck() {
    if (throttleTimer) return;
    throttleTimer = setTimeout(() => {
      checkForNewVideos();
      throttleTimer = null;
    }, 800);
  }
  
  // 事件监听器
  window.addEventListener('scroll', throttleCheck);
  window.addEventListener('load', throttleCheck);
  document.addEventListener('DOMContentLoaded', throttleCheck);
  
  // MutationObserver 监听DOM变化
  const observer = new MutationObserver(throttleCheck);
  observer.observe(document.body, {
    childList: true,
    subtree: true
  });
  
  // 初始运行
  setTimeout(throttleCheck, 1000);
  
  // 添加全局函数以便其他脚本调用
  window.setVideoSpeed = setSpeed;
  window.getCurrentVideoSpeed = () => currentSpeed;
  
})();