网页视频倍速控制器

通过快捷键控制网页视频播放速度,按下时加速,松开时恢复原速

// ==UserScript==
// @name         网页视频倍速控制器
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  通过快捷键控制网页视频播放速度,按下时加速,松开时恢复原速
// @author       模仿煎蛋的太阳
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // 默认设置
    const DEFAULT_KEYS = {
        'Control': 3.0,
        'Alt': 2.0
    };

    // 自定义速度输入框的默认设置
    const DEFAULT_CUSTOM_SPEED = {
        key: 'Shift+V',
        defaultSpeed: 1.5,
        enabled: true
    };

    // 当前设置
    let speedKeys = JSON.parse(GM_getValue('speedKeys', JSON.stringify(DEFAULT_KEYS)));
    let customSpeedSettings = JSON.parse(GM_getValue('customSpeedSettings', JSON.stringify(DEFAULT_CUSTOM_SPEED)));
    let isKeyDown = {}; // 记录每个按键的状态
    let customSpeedValue = parseFloat(GM_getValue('customSpeedValue', customSpeedSettings.defaultSpeed)); // 当前自定义速度值
    let customSpeedActive = false; // 自定义速度是否激活

    // 保存视频原始速度的对象
    const videoOriginalSpeeds = new WeakMap();

    // 添加设置菜单项
    GM_registerMenuCommand('设置视频倍速控制器', openSettings);

    // 初始化
    function init() {
        // 初始化按键状态
        Object.keys(speedKeys).forEach(key => {
            isKeyDown[key] = false;
        });

        // 添加键盘事件监听器
        document.addEventListener('keydown', handleKeyDown);
        document.addEventListener('keyup', handleKeyUp);

        // 监听新视频元素的添加
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1 && node.tagName === 'VIDEO') {
                        setupVideoListener(node);
                    } else if (node.querySelectorAll) {
                        node.querySelectorAll('video').forEach(setupVideoListener);
                    }
                });
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 处理页面上已有的视频元素
        document.querySelectorAll('video').forEach(setupVideoListener);
    }

    // 为视频元素设置事件监听器
    function setupVideoListener(video) {
        // 监听播放速度变化,保存原始速度
        const observer = new MutationObserver(() => {
            if (!isAnyKeyDown() && !customSpeedActive && !videoOriginalSpeeds.has(video)) {
                videoOriginalSpeeds.set(video, video.playbackRate);
            }
        });

        observer.observe(video, {
            attributes: true,
            attributeFilter: ['playbackrate']
        });

        video.addEventListener('ratechange', () => {
            if (!isAnyKeyDown() && !customSpeedActive) {
                videoOriginalSpeeds.set(video, video.playbackRate);
            }
        });

        // 初始设置原始速度
        if (!videoOriginalSpeeds.has(video)) {
            videoOriginalSpeeds.set(video, video.playbackRate);
        }

        video.addEventListener('play', () => {
            // 检查是否有按键被按下,如果有则设置对应速度
            for (const key in speedKeys) {
                if (isKeyDown[key]) {
                    video.playbackRate = speedKeys[key];
                    break;
                }
            }

            // 如果自定义速度激活,设置自定义速度
            if (customSpeedActive) {
                video.playbackRate = customSpeedValue;
            }
        });
    }

    // 检查是否有任何按键被按下
    function isAnyKeyDown() {
        for (const key in isKeyDown) {
            if (isKeyDown[key]) return true;
        }
        return false;
    }

    // 处理按键按下事件
    function handleKeyDown(event) {
        // 处理自定义速度快捷键
        if (customSpeedSettings.enabled && isCustomSpeedKey(event) && !document.getElementById('customSpeedInputContainer')) {
            showCustomSpeedInput();
            event.preventDefault();
            return;
        }

        const key = event.key;
        // 处理预设快捷键
        if (speedKeys.hasOwnProperty(key) && !isKeyDown[key]) {
            isKeyDown[key] = true;
            setAllVideoSpeed(speedKeys[key]);
            event.preventDefault();
        }
    }

    // 处理按键松开事件
    function handleKeyUp(event) {
        const key = event.key;

        // 处理预设快捷键
        if (speedKeys.hasOwnProperty(key) && isKeyDown[key]) {
            isKeyDown[key] = false;
            // 如果还有其他按键按下,设置为对应速度,否则恢复原速
            let restored = false;
            for (const k in speedKeys) {
                if (isKeyDown[k]) {
                    setAllVideoSpeed(speedKeys[k]);
                    restored = true;
                    break;
                }
            }

            // 如果自定义速度激活,保持自定义速度
            if (customSpeedActive) {
                setAllVideoSpeed(customSpeedValue);
                restored = true;
            }

            if (!restored) {
                restoreAllVideoSpeed();
            }
            event.preventDefault();
        }
    }

    // 检查是否按下了自定义速度快捷键
    function isCustomSpeedKey(event) {
        const keys = customSpeedSettings.key.split('+');
        if (keys.length !== 2) return false;

        const modifier = keys[0].trim();
        const mainKey = keys[1].trim();

        return (
            event.key === mainKey &&
            ((modifier === 'Ctrl' && event.ctrlKey) ||
             (modifier === 'Shift' && event.shiftKey) ||
             (modifier === 'Alt' && event.altKey))
        );
    }

    // 显示自定义速度输入框
    // Spotify风格UI设置
    const spotifyStyle = {
      primaryColor: '#1DB954', // Spotify绿
      secondaryColor: 'rgba(29, 185, 84, 0.7)', // 透明度渐变
      bgColor: '#191414', // Spotify深灰背景
      textColor: '#FFFFFF', // 白色文字
      secondaryTextColor: '#B3B3B3', // 浅灰辅助文字
      largeFont: 'bold 24px "Circular", "Helvetica Neue", sans-serif',
      smallFont: '14px "Helvetica Neue", Arial, sans-serif',
      borderRadius: '24px', // Spotify风格圆角
      transition: 'all 0.3s cubic-bezier(0.3, 0, 0, 1)' // Spotify风格动画
    };
    
    // 修改自定义速度输入框样式
    function showCustomSpeedInput() {
      const container = document.createElement('div');
      container.id = 'customSpeedInputContainer';
      container.innerHTML = `
        <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                      background: ${spotifyStyle.bgColor}; padding: 30px; border-radius: ${spotifyStyle.borderRadius}; 
                      box-shadow: 0 8px 24px rgba(0,0,0,0.3); z-index: 10001; 
                      font-family: ${spotifyStyle.smallFont}; width: 400px;
                      border: 1px solid rgba(255,255,255,0.1); color: ${spotifyStyle.textColor};
                      transition: ${spotifyStyle.transition};">
          <h2 style="margin: 0 0 20px 0; font: ${spotifyStyle.largeFont};">
            <span style="color: ${spotifyStyle.primaryColor}">自定义速度</span> CUSTOM SPEED
          </h2>
          <div style="margin-bottom: 25px;">
            <label for="customSpeedInput" style="display: block; margin-bottom: 8px; 
                   color: ${spotifyStyle.secondaryTextColor}; font: ${spotifyStyle.smallFont};">播放速度 (0.1-16):</label>
            <input type="number" id="customSpeedInput" min="0.1" max="16" step="0.1" 
                   value="${customSpeedValue}" style="width: 100%; padding: 12px; 
                   background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); 
                   border-radius: ${spotifyStyle.borderRadius}; font-size: 16px;
                   color: ${spotifyStyle.textColor}; transition: ${spotifyStyle.transition};
                   outline: none;">
          </div>
          <div style="display: flex; justify-content: flex-end; gap: 10px;">
            <button id="closeCustomSpeed" style="background: none; color: ${spotifyStyle.secondaryTextColor}; 
                    border: 1px solid rgba(255,255,255,0.2); padding: 10px 20px; 
                    border-radius: ${spotifyStyle.borderRadius}; cursor: pointer; 
                    transition: ${spotifyStyle.transition};
                    &:hover { background: rgba(255,255,255,0.1); }">取消</button>
            <button id="applyCustomSpeed" style="background: ${spotifyStyle.primaryColor}; 
                    color: white; border: none; padding: 10px 20px; 
                    border-radius: ${spotifyStyle.borderRadius}; cursor: pointer; 
                    transition: ${spotifyStyle.transition};
                    &:hover { background: #1ED760; }">应用</button>
          </div>
        </div>
        <div id="customSpeedOverlay" style="position: fixed; top: 0; left: 0; 
             width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 10000;
             backdrop-filter: blur(4px);"></div>
      `;
    
      // 添加到页面和事件绑定...
      document.body.appendChild(container);

      // 绑定事件
      container.querySelector('#applyCustomSpeed').addEventListener('click', applyCustomSpeed);
      container.querySelector('#closeCustomSpeed').addEventListener('click', closeCustomSpeedInput);
      container.querySelector('#customSpeedInput').addEventListener('keydown', (e) => {
          if (e.key === 'Enter') {
              applyCustomSpeed();
          }
      });
      container.querySelector('#customSpeedOverlay').addEventListener('click', closeCustomSpeedInput);

      // 聚焦到输入框
      container.querySelector('#customSpeedInput').focus();
      container.querySelector('#customSpeedInput').select();
    }

    // 应用自定义速度
    function applyCustomSpeed() {
        const input = document.querySelector('#customSpeedInput');
        const speed = parseFloat(input.value);

        if (isNaN(speed) || speed <= 0) {
            alert('请输入有效的播放速度值');
            return;
        }

        customSpeedValue = speed;
        customSpeedActive = true;

        // 保存自定义速度值
        GM_setValue('customSpeedValue', customSpeedValue);

        // 设置所有视频速度
        setAllVideoSpeed(customSpeedValue);

        // 关闭输入框
        closeCustomSpeedInput();
    }

    // 关闭自定义速度输入框
    function closeCustomSpeedInput() {
        const container = document.querySelector('#customSpeedInputContainer');
        const overlay = document.querySelector('#customSpeedOverlay');
        if (container) container.remove();
        if (overlay) overlay.remove();
    }

    // 设置所有视频元素的播放速度
    function setAllVideoSpeed(speed) {
        const videos = document.querySelectorAll('video');
        videos.forEach(video => {
            // 保存当前速度作为原始速度(如果尚未保存)
            if (!videoOriginalSpeeds.has(video)) {
                videoOriginalSpeeds.set(video, video.playbackRate);
            }
            video.playbackRate = speed;
        });
    }

    // 恢复所有视频元素到原始速度
    function restoreAllVideoSpeed() {
        const videos = document.querySelectorAll('video');
        videos.forEach(video => {
            const originalSpeed = videoOriginalSpeeds.get(video);
            if (originalSpeed !== undefined) {
                video.playbackRate = originalSpeed;
            } else {
                video.playbackRate = 1.0; // 默认速度
            }
        });
    }

    // 打开设置界面
    // 修改设置窗口样式
    function openSettings() {
      const settingsWindow = document.createElement('div');
      settingsWindow.innerHTML = `
        <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                      background: ${spotifyStyle.bgColor}; padding: 30px; border-radius: ${spotifyStyle.borderRadius}; 
                      box-shadow: 0 8px 24px rgba(0,0,0,0.3); z-index: 10000; 
                      font-family: ${spotifyStyle.smallFont}; width: 500px;
                      color: ${spotifyStyle.textColor};">
          <h2 style="margin: 0 0 20px 0; font: ${spotifyStyle.largeFont};">
            视频倍速控制器<span style="font-size: 0.8em; color: ${spotifyStyle.primaryColor}">VIDEO SPEED CONTROL</span>
          </h2>
          <div style="margin-bottom: 20px;">
              <h3 style="margin: 0 0 10px 0; color: #444;">预设快捷键设置</h3>
              <p style="margin: 0 0 10px 0; color: #666;">设置快捷键和对应的播放速度:</p>
              <div id="keySettingsContainer">
                  <!-- 快捷键设置项将通过JavaScript动态添加 -->
              </div>
              <button id="addKeySetting" style="background: ${spotifyStyle.primaryColor}; 
                      color: white; border: none; padding: 12px 24px; 
                      border-radius: ${spotifyStyle.borderRadius}; cursor: pointer; 
                      margin-top: 10px; transition: ${spotifyStyle.transition};
                      &:hover { background: #1ED760; }">添加快捷键</button>
          </div>
      
          <div style="margin-bottom: 20px; padding-top: 10px; border-top: 1px solid #eee;">
              <h3 style="margin: 0 0 10px 0; color: #444;">自定义速度设置</h3>
              <div style="margin-bottom: 10px;">
                  <label style="display: block; margin-bottom: 5px; color: #555;">
                      <input type="checkbox" id="customSpeedEnabled" style="margin-right: 5px;">
                      启用自定义速度功能
                  </label>
              </div>
              <div style="margin-bottom: 10px;">
                  <label for="customSpeedKey" style="display: block; margin-bottom: 5px; color: #555;">触发快捷键:</label>
                  <input type="text" id="customSpeedKey" placeholder="例如: Shift+V"
                         style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                  <p style="margin: 5px 0 0 0; font-size: 12px; color: #888;">格式: Ctrl+V 或 Shift+V 或 Alt+V</p>
              </div>
              <div style="margin-bottom: 10px;">
                  <label for="customSpeedDefault" style="display: block; margin-bottom: 5px; color: #555;">默认播放速度:</label>
                  <input type="number" id="customSpeedDefault" min="0.1" max="16" step="0.1"
                         style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
              </div>
          </div>
      
          <div style="display: flex; justify-content: space-between;">
              <button id="saveSettings" style="background: ${spotifyStyle.primaryColor}; 
                      color: white; border: none; padding: 12px 24px; 
                      border-radius: ${spotifyStyle.borderRadius}; cursor: pointer; 
                      transition: ${spotifyStyle.transition};
                      &:hover { background: #1ED760; }">保存设置</button>
              <button id="resetSettings" style="background: ${spotifyStyle.bgColor}; 
                      color: ${spotifyStyle.textColor}; border: 1px solid rgba(255,255,255,0.2); 
                      padding: 12px 24px; border-radius: ${spotifyStyle.borderRadius}; 
                      cursor: pointer; transition: ${spotifyStyle.transition};
                      &:hover { background: rgba(255,255,255,0.1); }">恢复默认</button>
              <button id="closeSettings" style="background: ${spotifyStyle.bgColor}; 
                      color: ${spotifyStyle.textColor}; border: 1px solid rgba(255,255,255,0.2); 
                      padding: 12px 24px; border-radius: ${spotifyStyle.borderRadius}; 
                      cursor: pointer; transition: ${spotifyStyle.transition};
                      &:hover { background: rgba(255,255,255,0.1); }">关闭</button>
          </div>
      </div>
      <div id="settingsOverlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%;
           background: rgba(0,0,0,0.7); z-index: 9999; backdrop-filter: blur(4px);"></div>
      `;
    
      // 添加到页面
      document.body.appendChild(settingsWindow);
    
      // 填充现有设置
      const container = settingsWindow.querySelector('#keySettingsContainer');
      Object.keys(speedKeys).forEach((key, index) => {
          addKeySettingRow(container, key, speedKeys[key], index);
      });
    
      // 填充自定义速度设置
      settingsWindow.querySelector('#customSpeedEnabled').checked = customSpeedSettings.enabled;
      settingsWindow.querySelector('#customSpeedKey').value = customSpeedSettings.key;
      settingsWindow.querySelector('#customSpeedDefault').value = customSpeedSettings.defaultSpeed;
    
      // 绑定事件
      settingsWindow.querySelector('#addKeySetting').addEventListener('click', () => {
          const index = Object.keys(speedKeys).length;
          addKeySettingRow(container, '', 2.0, index);
      });
    
      settingsWindow.querySelector('#saveSettings').addEventListener('click', saveSettings);
      settingsWindow.querySelector('#resetSettings').addEventListener('click', resetSettings);
      settingsWindow.querySelector('#closeSettings').addEventListener('click', closeSettings);
      settingsWindow.querySelector('#settingsOverlay').addEventListener('click', closeSettings);
      }

      // 添加快捷键设置行
      function addKeySettingRow(container, key, speed, index) {
          const row = document.createElement('div');
          row.style.display = 'flex';
          row.style.marginBottom = '10px';
          row.style.alignItems = 'center';
          row.innerHTML = `
              <select class="keySelect" style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-right: 10px;">
                  <option value="">请选择</option>
                  <option value="Control">Ctrl</option>
                  <option value="Shift">Shift</option>
                  <option value="Alt">Alt</option>
                  <option value=" ">空格</option>
              </select>
              <input type="number" class="speedInput" min="0.1" max="16" step="0.1" value="${speed}"
                     style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-right: 10px;">
              <button class="removeKeySetting" style="background: #1ED760; color: white; border: none; width: 24px; height: 24px;
                  border-radius: 50%; cursor: pointer;">×</button>
          `;
    
          row.querySelector('.keySelect').value = key;
          container.appendChild(row);
    
          // 绑定删除事件
          row.querySelector('.removeKeySetting').addEventListener('click', () => {
              container.removeChild(row);
          });
      }

      // 保存设置
      function saveSettings() {
          // 保存预设快捷键设置
          const newSpeedKeys = {};
          const rows = document.querySelectorAll('#keySettingsContainer > div');
    
          let valid = true;
          rows.forEach(row => {
              const key = row.querySelector('.keySelect').value;
              const speed = parseFloat(row.querySelector('.speedInput').value);
    
              if (key && !isNaN(speed) && speed > 0) {
                  newSpeedKeys[key] = speed;
              } else {
                  valid = false;
              }
          });
    
          if (!valid) {
              alert('请确保所有预设快捷键设置项都已正确填写');
              return;
          }
    
          if (Object.keys(newSpeedKeys).length === 0) {
              alert('请至少设置一个快捷键');
              return;
          }
    
          // 保存自定义速度设置
          const customEnabled = document.querySelector('#customSpeedEnabled').checked;
          const customKey = document.querySelector('#customSpeedKey').value.trim();
          const customDefault = parseFloat(document.querySelector('#customSpeedDefault').value);
    
          if (customEnabled && (!customKey || isNaN(customDefault) || customDefault <= 0)) {
              alert('请正确填写自定义速度设置');
              return;
          }
    
          const newCustomSpeedSettings = {
              key: customKey,
              defaultSpeed: customDefault,
              enabled: customEnabled
          };
    
          // 保存到存储
          GM_setValue('speedKeys', JSON.stringify(newSpeedKeys));
          GM_setValue('customSpeedSettings', JSON.stringify(newCustomSpeedSettings));
    
          // 更新当前设置
          speedKeys = newSpeedKeys;
          customSpeedSettings = newCustomSpeedSettings;
    
          // 更新按键状态对象
          isKeyDown = {};
          Object.keys(speedKeys).forEach(key => {
              isKeyDown[key] = false;
          });
    
          alert('设置已保存!');
          closeSettings();
      }

      // 恢复默认设置
      function resetSettings() {
          if (confirm('确定要恢复默认设置吗?')) {
              speedKeys = {...DEFAULT_KEYS};
              customSpeedSettings = {...DEFAULT_CUSTOM_SPEED};
    
              GM_setValue('speedKeys', JSON.stringify(speedKeys));
              GM_setValue('customSpeedSettings', JSON.stringify(customSpeedSettings));
    
              // 更新按键状态对象
              isKeyDown = {};
              Object.keys(speedKeys).forEach(key => {
                  isKeyDown[key] = false;
              });
    
              alert('已恢复默认设置!');
              closeSettings();
          }
      }

      // 关闭设置界面
      function closeSettings() {
          const settingsWindow = document.querySelector('div[style*="position: fixed; top: 50%; left: 50%"]');
          const overlay = document.querySelector('#settingsOverlay');
          if (settingsWindow) settingsWindow.remove();
          if (overlay) overlay.remove();
      }

      // 页面加载完成后初始化
      if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', init);
      } else {
          init();
      }
  })();

// 添加Apple风格动画效果
function animateElement(element, scale = 1.05) {
  element.style.transition = 'all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)';
  element.style.transform = `scale(${scale})`;
  setTimeout(() => {
    element.style.transform = 'scale(1)';
  }, 300);
}