// ==UserScript==
// @name 视频倍速快捷键
// @version 1.1
// @description speed up/down video
// @author BlueSky
// @match *://*.bilibili.com/*
// @match *://*.netflix.com/*
// @match *://*.youtube.com/*
// @grant none
// @namespace https://greasyfork.org/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()
}
}
})();