Greasy Fork

来自缓存

Greasy Fork is available in English.

视频倍速快捷键

speed up/down video

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         视频倍速快捷键
// @version      1.1
// @description  speed up/down video
// @author       BlueSky
// @match        *://*.bilibili.com/*
// @match        *://*.netflix.com/*
// @match        *://*.youtube.com/*
// @grant        none
// @namespace http://greasyfork.icu/users/447360
// ==/UserScript==

/**
 * configMap: {
 *   [siteName: string]: {
 *     titleSelectors: string[]     // 视频标题元素选择器
 *     videoSelector: string        // 视频元素选择器
 *     fullscreenSelector: string   // 全屏按钮元素选择器
 *     keyMap: {                    // 按键功能映射
 *       forward0_25?: string       // 速度增加 0.25x
 *       back0_25?: string          // 速度减少 0.25x
 *       forward0_5?: string        // 速度增加 0.5x
 *       back0_5?: string           // 速度减少 0.5x
 *       rate0_5?: string           // 设为 0.5x
 *       rate1?: string             // 设为 1x
 *       rate2?: string             // 设为 2x
 *       rate3?: string             // 设为 3x
 *       rate4?: string             // 设为 4x
 *       fullscreen?: string        // 切换全屏
 *     }
 *   }
 * }
 */

(function () {
  'use strict';
  const configMap = {
    bilibili: {
      titleSelectors: [
        'div.bilibili-player-video-top-title',
        'span.tit',
      ],
      videoSelector: 'bwp-video',
      fullscreenSelector: '.bilibili-player-video-btn.bilibili-player-video-btn-fullscreen',
      keyMap: {
        forward0_25: ']',
        back0_25: '[',
        forward0_5: '=',
        back0_5: '-',
        rate0_5: '`',
        rate1: '1',
        rate2: '2',
        rate3: '3',
        rate4: '4',
        fullscreen: 'Enter',
      },
    },
    youtube: {
      titleSelectors: [
        '.ytp-title-link.yt-uix-sessionlink.ytp-title-fullerscreen-link',
        'h1 > yt-formatted-string.style-scope.ytd-video-primary-info-renderer'
      ],
      videoSelector: 'video',
      fullscreenSelector: '.ytp-fullscreen-button.ytp-button',
      keyMap: {
        forward0_25: ']',
        back0_25: '[',
        forward0_5: '=',
        back0_5: '-',
        rate0_5: '`',
        fullscreen: 'Enter',
      },
    },
    netflix: {
      titleSelectors: [
        '.watch-video h4',
      ],
      videoSelector: 'video',
      fullscreenSelector: 'button[data-uia^=control-fullscreen]',
      keyMap: {
        forward0_25: ']',
        back0_25: '[',
        forward0_5: '=',
        back0_5: '-',
        rate0_5: '`',
        rate1: '1',
        rate2: '2',
        rate3: '3',
        rate4: '4',
      },
    },
  }
  let rate = 1
  let titleSelectors = []
  let videoSelector = 'video'
  let fullscreenSelector = ''
  let keyMap = {}
  const siteName = location.hostname.split('.').slice(-2)[0]
  const config = configMap[siteName]
  if (config) {
    console.debug(`found config for ${siteName}:`, config)
    titleSelectors = config.titleSelectors
    videoSelector = config.videoSelector
    fullscreenSelector = config.fullscreenSelector
    keyMap = config.keyMap
  }


  window.addEventListener('keydown', (e) => {
    if (e.key === keyMap.rate0_5) {
      rate = 0.5
    } else if (e.key === keyMap.rate1) {
      rate = 1
    } else if (e.key === keyMap.rate2) {
      rate = 2
    } else if (e.key === keyMap.rate3) {
      rate = 3
    } else if (e.key === keyMap.rate4) {
      rate = 4
    } else if (e.key === keyMap.forward0_25 && rate < 16) {
      rate += 0.25
    } else if (e.key === keyMap.back0_25 && rate > 0.25) {
      rate -= 0.25
    } else if (e.key === keyMap.forward0_5 && rate < 16) {
      rate += 0.5
    } else if (e.key === keyMap.back0_5 && rate > 0.5) {
      rate -= 0.5
    } else if (e.key === keyMap.fullscreen) {
      toggleFullScreen()
    } else {
      return
    }
    setVideoRate()
    setVideoTitle()
  })

  function getTitle() {
    return titleSelectors
      .map(selector => document.querySelector(selector))
      .filter(el => el && el.innerText)
      .map(el => el.innerText)[0]
  }

  function setVideoRate() {
    let el = document.querySelector(videoSelector) || document.querySelector('video')
    if (el) {
      console.debug(`rate: ${rate}x`)
      el.playbackRate = rate
    }
  }

  function setVideoTitle() {
    const title = (getTitle() || '').replace(/^\[.*\] /, '')
    for (const selector of titleSelectors) {
      const el = document.querySelector(selector)
      if (el) {
        el.innerHTML = `[${rate}x] ${title}`
      }
    }
  }

  // 浏览器原生全屏事件,无控制条&弹幕
  function nativeToggleFullScreen() {
    const el = document.querySelector(videoSelector)
    if (el) {
      if (document.fullscreenElement) {
        console.debug(`enter fullscreen`)
        document.exitFullscreen()
      } else {
        console.debug(`exit fullscreen`)
        el.webkitRequestFullScreen()
      }
    }
  }

  function toggleFullScreen() {
    const el = document.querySelector(fullscreenSelector)
    if (el) {
      console.debug(`toggleFullScreen`)
      el.click()
    }
  }
})();