Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mods/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const defaultConfig = {
dimmingOpacity: 0.5,
enablePaidPromotionOverlay: true,
speedSettingsIncrement: 0.25,
holdToSpeed: 2,
videoPreferredCodec: 'any',
launchToOnStartup: null,
reloadHomeOnStartup: true,
Expand Down
138 changes: 138 additions & 0 deletions mods/features/holdToSpeed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { configRead } from '../config.js';

const SELECT_KEY_CODE = 13;
const HOLD_DELAY_MS = 500;
const DEFAULT_HOLD_PLAYBACK_RATE = 2;
const SPEED_TOAST_ID = 'tt-hold-speed-toast';

let holdTimer = null;
let heldVideo = null;
let previousPlaybackRate = null;
let selectPressTarget = null;
let isReplayingSelectPress = false;

function getPlayingVideo() {
const video = document.querySelector('video');
if (!video || video.paused || video.ended) return null;
return video;
}

function getHoldPlaybackRate() {
const configuredSpeed = Number(configRead('holdToSpeed'));
return configuredSpeed > 0 ? configuredSpeed : DEFAULT_HOLD_PLAYBACK_RATE;
}

function showSpeedToast(playbackRate) {
let toast = document.getElementById(SPEED_TOAST_ID);
if (toast) return;

toast = document.createElement('div');
toast.id = SPEED_TOAST_ID;
toast.textContent = `${playbackRate}x`;
toast.style.cssText = [
'position:fixed',
'top:48px',
'left:50%',
'transform:translateX(-50%)',
'z-index:2147483647',
'padding:12px 22px',
'border-radius:999px',
'background:rgba(0, 0, 0, 0.82)',
'color:#fff',
'font:600 28px Roboto, Arial, sans-serif',
'line-height:1',
'pointer-events:none'
].join(';');
document.body.appendChild(toast);
}

function hideSpeedToast() {
document.getElementById(SPEED_TOAST_ID)?.remove();
}

function restorePlaybackRate() {
if (holdTimer !== null) {
clearTimeout(holdTimer);
holdTimer = null;
}

if (heldVideo && previousPlaybackRate !== null) {
heldVideo.playbackRate = previousPlaybackRate;
}

heldVideo = null;
previousPlaybackRate = null;
hideSpeedToast();
}

function consumeEvent(evt) {
evt.preventDefault();
evt.stopImmediatePropagation();
}

function replaySelectPress() {
if (!selectPressTarget) return;

isReplayingSelectPress = true;
try {
for (const type of ['keydown', 'keyup']) {
const event = document.createEvent('Event');
event.initEvent(type, true, true);
event.keyCode = SELECT_KEY_CODE;
event.which = SELECT_KEY_CODE;
selectPressTarget.dispatchEvent(event);
}
} finally {
isReplayingSelectPress = false;
}
}

function handleKeyDown(evt) {
if (evt.keyCode !== SELECT_KEY_CODE || isReplayingSelectPress) return;

if (holdTimer !== null || heldVideo) {
consumeEvent(evt);
return;
}

const video = getPlayingVideo();
if (!video) return;

consumeEvent(evt);
selectPressTarget = evt.target || document;
holdTimer = setTimeout(() => {
holdTimer = null;

if (video !== getPlayingVideo()) return;

heldVideo = video;
previousPlaybackRate = video.playbackRate;
const playbackRate = getHoldPlaybackRate();
video.playbackRate = playbackRate;
showSpeedToast(playbackRate);
}, HOLD_DELAY_MS);
}

function handleKeyUp(evt) {
if (evt.keyCode !== SELECT_KEY_CODE || isReplayingSelectPress || !selectPressTarget) return;

consumeEvent(evt);

const wasHeldForSpeed = Boolean(heldVideo);
restorePlaybackRate();
if (!wasHeldForSpeed) replaySelectPress();
selectPressTarget = null;
}

window.addEventListener('keydown', handleKeyDown, true);
window.addEventListener('keyup', handleKeyUp, true);
window.addEventListener('blur', () => {
restorePlaybackRate();
selectPressTarget = null;
});
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
restorePlaybackRate();
selectPressTarget = null;
}
});
4 changes: 4 additions & 0 deletions mods/translations/resources/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
"title": "Speed Settings Increments",
"subtitle": "Set the speed increments for video playback speed adjustments"
},
"holdToSpeed": {
"title": "Hold OK Playback Speed",
"subtitle": "Choose the temporary speed while holding the OK button during playback"
},
"preferredVideoCodec": {
"title": "Preferred Video Codec",
"subtitle": "Choose the preferred video codec for playback"
Expand Down
28 changes: 28 additions & 0 deletions mods/ui/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ import { getComprehensiveLanguageList } from '../features/moreSubtitles.js';

const qrcodes = {};

function playbackSpeedOptions(key) {
const maxSpeed = 5;
const increment = configRead('speedSettingsIncrement') || 0.25;
const options = [];

for (let speed = increment; speed <= maxSpeed; speed += increment) {
const fixedSpeed = Math.round(speed * 100) / 100;
options.push({
name: `${fixedSpeed}x`,
key,
value: fixedSpeed
});
}

return options;
}

export default function modernUI(update, parameters) {
const settings = [
{
Expand Down Expand Up @@ -411,6 +428,17 @@ export default function modernUI(update, parameters) {
}
})
},
{
name: t('settings.options.videoPlayer.options.holdToSpeed.title'),
icon: 'SLOW_MOTION_VIDEO',
value: null,
menuId: 'tt-hold-to-speed',
menuHeader: {
title: t('settings.options.videoPlayer.options.holdToSpeed.title'),
subtitle: t('settings.options.videoPlayer.options.holdToSpeed.subtitle')
},
options: playbackSpeedOptions('holdToSpeed')
},
{
name: t('settings.options.videoPlayer.options.preferredVideoCodec.title'),
icon: 'VIDEO_QUALITY',
Expand Down
3 changes: 2 additions & 1 deletion mods/userScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import "./ui/theme.js";
import "./ui/settings.js";
import "./ui/disableWhosWatching.js";
import "./features/moreSubtitles.js";
import "./features/holdToSpeed.js";
import "./features/updater.js";
import "./features/pictureInPicture.js";
import "./features/preferredVideoQuality.js";
Expand All @@ -24,4 +25,4 @@ import "./features/enableFeatures.js";
import "./ui/customUI.js";
import "./ui/customGuideAction.js";
import "./features/autoFrameRate.js";
import "./ui/clock.js";
import "./ui/clock.js";