Greasy Fork

Greasy Fork is available in English.

自定义视频倍速播放

自定义视频播放速度

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         自定义视频倍速播放
// @version      2.7
// @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;
  let isProcessingClick = false; // 防止重复点击处理
  let speedCheckInterval = null; // 速度检查间隔
  let isFullscreen = false; // 全屏状态
  
  // 防抖函数
  function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }
  
  // 获取所有视频元素
  function getAllVideoElements() {
    return document.querySelectorAll('video,[class*="player"] *');
  }
  
  // 获取所有有效的视频元素(包含video标签或具有playbackRate属性的元素)
  function getAllValidVideoElements() {
    const elements = getAllVideoElements();
    const validVideos = [];
    
    elements.forEach(element => {
      try {
        if (element.tagName === 'VIDEO' && element.readyState > 0) {
          validVideos.push(element);
        } else if ('playbackRate' in element && element.playbackRate !== undefined) {
          validVideos.push(element);
        } else {
          // 检查子元素中的video
          const videoChild = element.querySelector('video');
          if (videoChild && videoChild.readyState > 0) {
            validVideos.push(videoChild);
          }
        }
      } catch (e) {
        // 忽略错误
      }
    });
    
    return validVideos;
  }
  
  // 检测并纠正视频速度
  function checkAndCorrectVideoSpeed() {
    // 如果处于全屏模式,不进行检测和纠正
    if (document.fullscreenElement || document.webkitFullscreenElement || 
        document.mozFullScreenElement || document.msFullscreenElement) {
      isFullscreen = true;
      return;
    }
    
    isFullscreen = false;
    
    const videos = getAllValidVideoElements();
    if (videos.length === 0) return;
    
    let needCorrection = false;
    
    videos.forEach(video => {
      try {
        // 检查速度是否与设置的不一致(容差0.01)
        if (Math.abs(video.playbackRate - currentSpeed) > 0.01) {
          console.log(`检测到速度不一致: ${video.playbackRate}x -> ${currentSpeed}x`);
          video.playbackRate = currentSpeed;
          needCorrection = true;
        }
      } catch (e) {
        // 忽略权限错误
      }
    });
    
    // 如果有纠正,更新按钮显示
    if (needCorrection && controlBtn) {
      controlBtn.textContent = `倍速: ${currentSpeed}x`;
    }
  }
  
  // 启动速度检查
  function startSpeedCheck() {
    if (speedCheckInterval) {
      clearInterval(speedCheckInterval);
    }
    
    // 每2秒检查一次速度
    speedCheckInterval = setInterval(checkAndCorrectVideoSpeed, 2000);
  }
  
  // 停止速度检查
  function stopSpeedCheck() {
    if (speedCheckInterval) {
      clearInterval(speedCheckInterval);
      speedCheckInterval = null;
    }
  }
  
  // 主初始化函数
  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();
    }
    
    // 启动速度检查
    startSpeedCheck();
    
    // 监听全屏变化
    document.addEventListener('fullscreenchange', handleFullscreenChange);
    document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
    document.addEventListener('mozfullscreenchange', handleFullscreenChange);
    document.addEventListener('MSFullscreenChange', handleFullscreenChange);
  }
  
  // 处理全屏变化
  function handleFullscreenChange() {
    const isNowFullscreen = !!(document.fullscreenElement || 
                               document.webkitFullscreenElement ||
                               document.mozFullScreenElement ||
                               document.msFullscreenElement);
    
    if (isNowFullscreen !== isFullscreen) {
      isFullscreen = isNowFullscreen;
      
      if (isFullscreen) {
        // 进入全屏,停止检查
        stopSpeedCheck();
        if (inputPanel && inputPanel.parentNode) {
          hideSpeedInput();
        }
      } else {
        // 退出全屏,重新开始检查并纠正
        setTimeout(() => {
          startSpeedCheck();
          checkAndCorrectVideoSpeed();
        }, 500);
      }
    }
  }
  
  // 创建控制按钮
  function createControlButton() {
    // 如果已存在,先移除
    if (controlBtn && controlBtn.parentNode) {
      controlBtn.parentNode.removeChild(controlBtn);
    }
    
    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;
      pointer-events: auto;
    `;
    
    // 使用防抖防止快速多次点击
    controlBtn.addEventListener('click', debounce(handleControlBtnClick, 300));
    document.body.appendChild(controlBtn);
  }
  
  // 处理控制按钮点击
  function handleControlBtnClick() {
    if (isProcessingClick) return;
    
    isProcessingClick = true;
    try {
      showSpeedInput();
    } finally {
      setTimeout(() => {
        isProcessingClick = false;
      }, 100);
    }
  }
  
  // 显示速度输入框
  function showSpeedInput() {
    // 如果已经有输入面板,先移除
    if (inputPanel && inputPanel.parentNode) {
      hideSpeedInput();
      return;
    }
    
    // 创建输入框面板
    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;
      pointer-events: auto;
    `;
    
    // 创建标题
    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, 4.0, 5.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', () => {
        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.contains(e.target) && 
        !controlBtn.contains(e.target)) {
      hideSpeedInput();
    }
  }
  
  // 隐藏输入框
  function hideSpeedInput() {
    if (inputPanel && inputPanel.parentNode) {
      document.body.removeChild(inputPanel);
      inputPanel = null;
      speedInput = 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);
    
    // 立即检查并纠正
    checkAndCorrectVideoSpeed();
    
    // 触发自定义事件(用于页面内通信)
    window.dispatchEvent(new CustomEvent('speedChanged', {
      detail: { speed: currentSpeed }
    }));
  }
  
  // 应用速度到视频元素
  function applySpeedToVideos(videoElements, speed) {
    videoElements.forEach(element => {
      try {
        if (element.tagName === 'VIDEO') {
          element.playbackRate = speed;
        } else {
          let video = element.querySelector('video');
          if (video) {
            video.playbackRate = speed;
          } else if ('playbackRate' in element) {
            element.playbackRate = speed;
          }
        }
      } catch (e) {
        // 忽略错误
      }
    });
  }
  
  // 优化检查新视频函数
  function checkForNewVideos() {
    if (document.fullscreenElement || !document.body) return;
    
    let videos = getAllVideoElements();
    if (videos.length > 0) {
      // 应用速度但不重复创建控制按钮
      if (!controlBtn || !controlBtn.parentNode) {
        createControlButton();
      }
      
      // 只对新视频或速度不同的视频应用速度
      videos.forEach(video => {
        try {
          if (video.tagName === 'VIDEO' && Math.abs(video.playbackRate - currentSpeed) > 0.01) {
            video.playbackRate = currentSpeed;
          }
        } catch (e) {
          // 忽略错误
        }
      });
      
      // 检查并纠正速度
      checkAndCorrectVideoSpeed();
    }
  }
  
  // 节流函数
  let throttleTimer;
  function throttleCheck() {
    if (throttleTimer) return;
    throttleTimer = setTimeout(() => {
      checkForNewVideos();
      throttleTimer = null;
    }, 1500);
  }
  
  // 优化 MutationObserver 配置
  let observer = null;
  
  function initObserver() {
    if (observer) {
      observer.disconnect();
    }
    
    observer = new MutationObserver(debounce(throttleCheck, 500));
    
    // 只监听子节点变化,减少监听范围
    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  }
  
  // 延迟初始化
  function delayedInit() {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', () => {
        setTimeout(init, 1000);
        setTimeout(initObserver, 1500);
      });
    } else {
      setTimeout(init, 1000);
      setTimeout(initObserver, 1500);
    }
  }
  
  // 初始运行
  delayedInit();
  
  // 清理函数
  window.addEventListener('beforeunload', () => {
    stopSpeedCheck();
    if (observer) {
      observer.disconnect();
    }
  });
  
  // 添加全局函数以便其他脚本调用
  window.setVideoSpeed = setSpeed;
  window.getCurrentVideoSpeed = () => currentSpeed;
  window.enableAutoSpeedCorrection = startSpeedCheck;
  window.disableAutoSpeedCorrection = stopSpeedCheck;
  
})();