// ==UserScript==
// @name video, faster
// @namespace Violentmonkey Scripts
// @include *://*/*
// @grant none
// @version 2.2
// @author KraXen72
// @description speed up video on any site
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
// TODO
// auto look for video elements on location change
// remember speed across videos (reapply)?
// NOTE: to get compact video speed buttons, add this css in violentMonkey custom css in settings
/*
[data-message="video, faster"] + .submenu-buttons + .submenu-commands {
display: flex;
justify-content: center;
}
[data-message="video, faster"] + .submenu-buttons + .submenu-commands .menu-item {
padding: 0.5rem;
margin: 0px;
width: auto;
}
[data-message="video, faster"] + .submenu-buttons + .submenu-commands .menu-item .icon {
display: none;
}
*/
GM_addStyle(`
.userscript-video-top-bar {
box-sizing: border-box;
background-color: black;
color: white;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 22px;
z-index: 100;
display: flex;
padding: 2px 16px;
transition: opacity 0.2s ease-in-out;
}
.userscript-video-top-bar:hover { opacity: 1 }
.userscript-video-top-bar button {
margin: 0 3px;
height: 100%;
padding: 2px;
font-size: 14px;
line-height: 14px;
width: min-content;
}
.userscript-simple-btn {
background: #262626 !important;
color: white;
border: 1px solid #191919 !important;
border-radius: 2px;
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
font-weight: normal;
}
.userscript-simple-btn:hover {
background: #303030 !important;
border-color: #383838 !important;
}
.userscript-simple-btn:active {
background: #383838 !important;
}
.userscript-simple-btn,
.userscript-simple-btn:hover,
.userscript-simple-btn:active {
background-image: none !important;
box-sizing: border-box !important;
box-shadow: none !important;
text-shadow: none !important;
}
.userscript-hoverinv {
opacity: 0;
}
`);
const jumpVal = 5
let videoElem = null
const commands = {
"1x": () => playbackRate(1),
"1.5x": () => playbackRate(1.5),
"2x": () => playbackRate(2),
"2.5x": () => playbackRate(2.5),
"2.75x": () => playbackRate(2.75),
"3x": () => playbackRate(3),
"3.5x": () => playbackRate(3.5),
"4x": () => playbackRate(4)
}
function cancelEvent(e) {
e?.preventDefault()
e?.stopPropagation()
}
function ff(vid = null, e) {
cancelEvent(e);
if (!vid) vid = videoElem;
vid.currentTime += jumpVal;
}
function rw(vid = null, e) {
cancelEvent(e);
if (!vid) vid = videoElem;
vid.currentTime -= jumpVal;
}
function findVideoElement(debug = false) {
if (!document.body) return;
if (document.body.contains(videoElem)) return;
let qs = "video";
videoElem = document.querySelector(qs);
if (videoElem !== null) {
if (debug) console.log("found video elem", videoElem, qs);
} else {
// Look for video elements within iframes
const iframes = document.querySelectorAll("iframe");
iframes.forEach(iframe => {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const iframeVideoElem = iframeDoc.querySelector("video");
if (iframeVideoElem) {
videoElem = iframeVideoElem;
if (debug) console.log("found video elem in iframe", videoElem, iframe.src);
}
});
}
}
const btnDefaults = { classList: "userscript-simple-btn" }
function addTopBarToVideos() {
document.querySelectorAll('video').forEach((video) => {
const topBar = document.createElement('div');
topBar.classList.add('userscript-video-top-bar', "userscript-hoverinv");
topBar.appendChild(Object.assign(document.createElement("button"), {...btnDefaults, textContent: "<<", onclick: (e) => rw(video, e), title:`rewind ${jumpVal}s` }))
topBar.appendChild(Object.assign(document.createElement("button"), {...btnDefaults, textContent: ">>", onclick: (e) => ff(video, e), title:`forward ${jumpVal}s` }))
for (const cmd of Object.keys(commands)) {
const btn = document.createElement("button")
btn.textContent = cmd
btn.classList = "userscript-simple-btn"
btn.onclick = (e) => playbackRate(Number(cmd.replace("x", "")), e, video)
topBar.appendChild(btn)
}
topBar.appendChild(Object.assign(document.createElement("button"), {
...btnDefaults,
onclick: function(e) {
cancelEvent(e);
topBar.classList.toggle("userscript-hoverinv");
this.textContent = topBar.classList.contains("userscript-hoverinv") ? "📌" : "📍"
this.title = topBar.classList.contains("userscript-hoverinv") ? "pin" : "unpin"
},
textContent: "📌",
title: "pin",
style: "postition: relative;right:0;"
}))
video.parentElement.insertBefore(topBar, video);
});
}
function playbackRate(rate, e = null, video = null) {
if (video == null) {
findVideoElement()
video = videoElem
}
if (e != null) cancelEvent(e);
let wasplaying = !video.paused
if (wasplaying) video.pause()
video.playbackRate = rate
if (wasplaying && video.paused) video.play()
}
function registerCommands() {
Object.keys(commands).forEach(command => {
try {
GM_unregisterMenuCommand(command)
} catch (e) { console.error(e) }
})
Object.entries(commands).forEach(command => {
GM_registerMenuCommand(command[0], command[1])
})
}
registerCommands()
findVideoElement()
GM_registerMenuCommand("addvideobars", addTopBarToVideos)
addTopBarToVideos()