Skip to content

Commit f7f3185

Browse files
committed
Add plugin to control playback speed like in YouTube (from 0.25 to 2)
1 parent 3415ce3 commit f7f3185

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-0
lines changed

plugins/playback-speed/front.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const { watchDOMElement } = require("../../providers/dom-elements");
2+
const { ElementFromFile, templatePath } = require("../utils");
3+
4+
const slider = ElementFromFile(templatePath(__dirname, "slider.html"));
5+
6+
const MIN_PLAYBACK_SPEED = 0.25;
7+
const MAX_PLAYBACK_SPEED = 2;
8+
9+
let videoElement;
10+
let playbackSpeedPercentage = 50; // = Playback speed of 1
11+
12+
const computePlayBackSpeed = () => {
13+
if (playbackSpeedPercentage <= 50) {
14+
// Slow down video by setting a playback speed between MIN_PLAYBACK_SPEED and 1
15+
return (
16+
MIN_PLAYBACK_SPEED +
17+
((1 - MIN_PLAYBACK_SPEED) / 50) * playbackSpeedPercentage
18+
);
19+
}
20+
21+
// Accelerate video by setting a playback speed between 1 and MAX_PLAYBACK_SPEED
22+
return 1 + ((MAX_PLAYBACK_SPEED - 1) / 50) * (playbackSpeedPercentage - 50);
23+
};
24+
25+
const updatePlayBackSpeed = () => {
26+
const playbackSpeed = Math.round(computePlayBackSpeed() * 100) / 100;
27+
28+
if (!videoElement || videoElement.playbackRate === playbackSpeed) {
29+
return;
30+
}
31+
32+
videoElement.playbackRate = playbackSpeed;
33+
34+
const playbackSpeedElement = document.querySelector("#playback-speed-value");
35+
if (playbackSpeedElement) {
36+
playbackSpeedElement.innerHTML = playbackSpeed;
37+
}
38+
};
39+
40+
module.exports = () => {
41+
watchDOMElement(
42+
"video",
43+
(document) => document.querySelector("video"),
44+
(element) => {
45+
videoElement = element;
46+
updatePlayBackSpeed();
47+
}
48+
);
49+
50+
watchDOMElement(
51+
"menu",
52+
(document) =>
53+
document.querySelector("ytmusic-menu-popup-renderer paper-listbox"),
54+
(menuElement) => {
55+
if (!menuElement.contains(slider)) {
56+
menuElement.prepend(slider);
57+
}
58+
59+
const playbackSpeedElement = document.querySelector(
60+
"#playback-speed-slider #sliderKnob .slider-knob-inner"
61+
);
62+
63+
const playbackSpeedObserver = new MutationObserver((mutations) => {
64+
mutations.forEach(function (mutation) {
65+
if (mutation.type == "attributes") {
66+
const value = playbackSpeedElement.getAttribute("value");
67+
playbackSpeedPercentage = parseInt(value, 10);
68+
if (isNaN(playbackSpeedPercentage)) {
69+
playbackSpeedPercentage = 50;
70+
}
71+
updatePlayBackSpeed();
72+
return;
73+
}
74+
});
75+
});
76+
playbackSpeedObserver.observe(playbackSpeedElement, {
77+
attributes: true,
78+
});
79+
}
80+
);
81+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<div
2+
class="menu-item ytmusic-menu-popup-renderer"
3+
role="option"
4+
tabindex="-1"
5+
aria-disabled="false"
6+
aria-selected="false"
7+
>
8+
<paper-slider
9+
id="playback-speed-slider"
10+
class="volume-slider style-scope ytmusic-player-bar on-hover"
11+
max="100"
12+
min="0"
13+
step="5"
14+
dir="ltr"
15+
title="Playback speed"
16+
aria-label="Playback speed"
17+
role="slider"
18+
tabindex="0"
19+
aria-valuemin="0"
20+
aria-valuemax="100"
21+
aria-valuenow="50"
22+
aria-disabled="false"
23+
value="50"
24+
><!--css-build:shady-->
25+
<div id="sliderContainer" class="style-scope paper-slider">
26+
<div class="bar-container style-scope paper-slider">
27+
<paper-progress
28+
id="sliderBar"
29+
aria-hidden="true"
30+
class="style-scope paper-slider"
31+
role="progressbar"
32+
value="50"
33+
aria-valuenow="50"
34+
aria-valuemin="0"
35+
aria-valuemax="100"
36+
aria-disabled="false"
37+
style="touch-action: none;"
38+
><!--css-build:shady-->
39+
40+
<div id="progressContainer" class="style-scope paper-progress">
41+
<div
42+
id="secondaryProgress"
43+
class="style-scope paper-progress"
44+
hidden="true"
45+
style="transform: scaleX(0);"
46+
></div>
47+
<div
48+
id="primaryProgress"
49+
class="style-scope paper-progress"
50+
style="transform: scaleX(0.5);"
51+
></div>
52+
</div>
53+
</paper-progress>
54+
</div>
55+
<dom-if class="style-scope paper-slider"
56+
><template is="dom-if"></template
57+
></dom-if>
58+
<div
59+
id="sliderKnob"
60+
class="slider-knob style-scope paper-slider"
61+
style="left: 50%; touch-action: none;"
62+
>
63+
<div
64+
class="slider-knob-inner style-scope paper-slider"
65+
value="50"
66+
></div>
67+
<paper-ripple
68+
id="ink"
69+
center=""
70+
class="circle style-scope paper-slider"
71+
style="display: none;"
72+
><!--css-build:shady-->
73+
74+
<div
75+
id="background"
76+
class="style-scope paper-ripple"
77+
style="opacity: 0.006008;"
78+
></div>
79+
<div id="waves" class="style-scope paper-ripple"></div>
80+
</paper-ripple>
81+
</div>
82+
</div>
83+
<dom-if class="style-scope paper-slider"
84+
><template is="dom-if"></template
85+
></dom-if>
86+
</paper-slider>
87+
88+
<div
89+
class="text style-scope ytmusic-toggle-menu-service-item-renderer"
90+
id="ytmcustom-playback-speed"
91+
>
92+
Speed (<span id="playback-speed-value">1</span>)
93+
</div>
94+
</div>

providers/dom-elements.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
let domElements = {};
2+
3+
const watchDOMElement = (name, selectorFn, cb) => {
4+
const observer = new MutationObserver((mutations, observer) => {
5+
if (!domElements[name]) {
6+
domElements[name] = selectorFn(document);
7+
}
8+
9+
if (domElements[name]) {
10+
cb(domElements[name]);
11+
}
12+
});
13+
14+
observer.observe(document, {
15+
childList: true,
16+
subtree: true,
17+
});
18+
};
19+
20+
module.exports = { watchDOMElement };

0 commit comments

Comments
 (0)