diff --git a/plugins/playback-speed/front.js b/plugins/playback-speed/front.js new file mode 100644 index 00000000..a44b72e0 --- /dev/null +++ b/plugins/playback-speed/front.js @@ -0,0 +1,81 @@ +const { watchDOMElement } = require("../../providers/dom-elements"); +const { ElementFromFile, templatePath } = require("../utils"); + +const slider = ElementFromFile(templatePath(__dirname, "slider.html")); + +const MIN_PLAYBACK_SPEED = 0.25; +const MAX_PLAYBACK_SPEED = 2; + +let videoElement; +let playbackSpeedPercentage = 50; // = Playback speed of 1 + +const computePlayBackSpeed = () => { + if (playbackSpeedPercentage <= 50) { + // Slow down video by setting a playback speed between MIN_PLAYBACK_SPEED and 1 + return ( + MIN_PLAYBACK_SPEED + + ((1 - MIN_PLAYBACK_SPEED) / 50) * playbackSpeedPercentage + ); + } + + // Accelerate video by setting a playback speed between 1 and MAX_PLAYBACK_SPEED + return 1 + ((MAX_PLAYBACK_SPEED - 1) / 50) * (playbackSpeedPercentage - 50); +}; + +const updatePlayBackSpeed = () => { + const playbackSpeed = Math.round(computePlayBackSpeed() * 100) / 100; + + if (!videoElement || videoElement.playbackRate === playbackSpeed) { + return; + } + + videoElement.playbackRate = playbackSpeed; + + const playbackSpeedElement = document.querySelector("#playback-speed-value"); + if (playbackSpeedElement) { + playbackSpeedElement.innerHTML = playbackSpeed; + } +}; + +module.exports = () => { + watchDOMElement( + "video", + (document) => document.querySelector("video"), + (element) => { + videoElement = element; + updatePlayBackSpeed(); + } + ); + + watchDOMElement( + "menu", + (document) => + document.querySelector("ytmusic-menu-popup-renderer paper-listbox"), + (menuElement) => { + if (!menuElement.contains(slider)) { + menuElement.prepend(slider); + } + + const playbackSpeedElement = document.querySelector( + "#playback-speed-slider #sliderKnob .slider-knob-inner" + ); + + const playbackSpeedObserver = new MutationObserver((mutations) => { + mutations.forEach(function (mutation) { + if (mutation.type == "attributes") { + const value = playbackSpeedElement.getAttribute("value"); + playbackSpeedPercentage = parseInt(value, 10); + if (isNaN(playbackSpeedPercentage)) { + playbackSpeedPercentage = 50; + } + updatePlayBackSpeed(); + return; + } + }); + }); + playbackSpeedObserver.observe(playbackSpeedElement, { + attributes: true, + }); + } + ); +}; diff --git a/plugins/playback-speed/templates/slider.html b/plugins/playback-speed/templates/slider.html new file mode 100644 index 00000000..c9ad0ac4 --- /dev/null +++ b/plugins/playback-speed/templates/slider.html @@ -0,0 +1,94 @@ + diff --git a/providers/dom-elements.js b/providers/dom-elements.js new file mode 100644 index 00000000..d4ffb6e4 --- /dev/null +++ b/providers/dom-elements.js @@ -0,0 +1,20 @@ +let domElements = {}; + +const watchDOMElement = (name, selectorFn, cb) => { + const observer = new MutationObserver((mutations, observer) => { + if (!domElements[name]) { + domElements[name] = selectorFn(document); + } + + if (domElements[name]) { + cb(domElements[name]); + } + }); + + observer.observe(document, { + childList: true, + subtree: true, + }); +}; + +module.exports = { watchDOMElement };