From fba5c77dd9b481794063bec51ce781718adea8ca Mon Sep 17 00:00:00 2001 From: JackieDo Date: Thu, 11 Dec 2025 05:55:22 +0700 Subject: [PATCH 1/2] Set `selectedIndex` for options. Automatically select the previous value for the `single choice` options in the settings. --- mods/ui/settings.js | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/mods/ui/settings.js b/mods/ui/settings.js index 6ef97850..e3391da6 100644 --- a/mods/ui/settings.js +++ b/mods/ui/settings.js @@ -269,7 +269,12 @@ export default function modernUI(update, parameters) { } ] ) - ) + ), + (() => { + const savedVal = configRead('preferredVideoQuality'); + const idx = ['auto', '2160p', '1440p', '1080p', '720p', '480p', '360p', '240p', '144p'].indexOf(savedVal); + return idx !== -1 ? idx : null; + })() ) } }, @@ -318,7 +323,12 @@ export default function modernUI(update, parameters) { ] ) - ) + ), + (() => { + const savedVal = parseFloat(configRead('hideWatchedVideosThreshold')); + const idx = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100].indexOf(savedVal); + return idx !== -1 ? idx : null; + })() ) } }, @@ -404,7 +414,12 @@ export default function modernUI(update, parameters) { } ] ); - }) + }), + (() => { + const savedVal = parseFloat(configRead('dimmingTimeout')); + const idx = [10, 20, 30, 60, 120, 180, 240, 300].indexOf(savedVal); + return idx !== -1 ? idx : null; + })() ) } }, @@ -441,7 +456,12 @@ export default function modernUI(update, parameters) { } ] ) - ) + ), + (() => { + const savedVal = parseFloat(configRead('dimmingOpacity')); + const idx = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].indexOf(savedVal); + return idx !== -1 ? idx : null; + })() ) } } @@ -480,7 +500,12 @@ export default function modernUI(update, parameters) { } ] ) - ) + ), + (() => { + const savedVal = parseFloat(configRead('speedSettingsIncrement')); + const idx = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5].indexOf(savedVal); + return idx !== -1 ? idx : null; + })() ) } }, @@ -517,7 +542,12 @@ export default function modernUI(update, parameters) { } ] ) - ) + ), + (() => { + const savedVal = configRead('videoPreferredCodec'); + const idx = ['any', 'vp9', 'av01', 'avc1'].indexOf(savedVal); + return idx !== -1 ? idx : null; + })() ) } } From e8e0163d5363f4285b518a74de995eab6b9d28b8 Mon Sep 17 00:00:00 2001 From: JackieDo Date: Fri, 27 Mar 2026 06:40:46 +0700 Subject: [PATCH 2/2] Feat: Turn off screen while playing video - Add Turn Off Screen feature. - Add option to showing Turn Off Screen button. - Add option for showing PIP button. --- mods/config.js | 2 + mods/features/turnOffScreen.js | 44 ++++++ mods/resolveCommand.js | 18 ++- mods/translations/resources/en.json | 4 +- mods/translations/resources/vi.json | 4 +- mods/ui/customUI.js | 226 +++++++++++++++------------- mods/ui/settings.js | 10 ++ 7 files changed, 200 insertions(+), 108 deletions(-) create mode 100644 mods/features/turnOffScreen.js diff --git a/mods/config.js b/mods/config.js index acf67848..d5ea8316 100644 --- a/mods/config.js +++ b/mods/config.js @@ -34,6 +34,8 @@ const defaultConfig = { enablePreviousNextButtons: true, enableSuperThanksButton: false, enableSpeedControlsButton: true, + enablePipButton: true, + enableTurnOffScreenButton: true, enablePatchingVideoPlayer: true, enablePreviews: true, enableHideWatchedVideos: false, diff --git a/mods/features/turnOffScreen.js b/mods/features/turnOffScreen.js new file mode 100644 index 00000000..20f9cf58 --- /dev/null +++ b/mods/features/turnOffScreen.js @@ -0,0 +1,44 @@ +export function turnOffScreen() { + // Turn off screen + const container = document.getElementById('container'); + const captionBlock = document.getElementById('ytp-caption-window-container'); + + container.style.setProperty('opacity', '0', 'important'); + container.style.setProperty('pointer-events', 'none', 'important'); + + if (captionBlock) { + captionBlock.style.setProperty('opacity', '0', 'important'); + captionBlock.style.setProperty('pointer-events', 'none', 'important'); + } + + console.log('Screen is turned off.'); + + const wakeUpScreen = (event) => { + // Prevent events + // Prevent default key behavior (e.g., scrolling pages with Space/Arrows, submitting forms with Enter). + event.preventDefault(); + // Prevent the event from continuing down to the elements below (other modules will not receive this key). + event.stopPropagation(); + + // Turn on screen + const container = document.getElementById('container'); + const captionBlock = document.getElementById('ytp-caption-window-container'); + + container.style.removeProperty('opacity'); + container.style.removeProperty('pointer-events'); + + if (captionBlock) { + captionBlock.style.removeProperty('opacity'); + captionBlock.style.removeProperty('pointer-events'); + } + + console.log('Screen is turned on.'); + + // Important: Remove event listening after the screen has been reopened + // to prevent this function from being called redundantly on subsequent keystrokes + document.removeEventListener('keydown', wakeUpScreen, true); + }; + + // 3. Listen for any key press event (keydown). + document.addEventListener('keydown', wakeUpScreen, true); +} \ No newline at end of file diff --git a/mods/resolveCommand.js b/mods/resolveCommand.js index 1d580700..defaef80 100644 --- a/mods/resolveCommand.js +++ b/mods/resolveCommand.js @@ -4,6 +4,7 @@ import modernUI, { optionShow } from './ui/settings.js'; import { speedSettings } from './ui/speedUI.js'; import { showToast, buttonItem } from './ui/ytUI.js'; import checkForUpdates from './features/updater.js'; +import { turnOffScreen } from './features/turnOffScreen.js'; export default function resolveCommand(cmd, _) { // resolveCommand function is pretty OP, it can do from opening modals, changing client settings and way more. @@ -92,7 +93,7 @@ export function patchResolveCommand() { } } - cmd.openPopupAction.popup.overlaySectionRenderer.overlay.overlayTwoPanelRenderer.actionPanel.overlayPanelRenderer.content.overlayPanelItemListRenderer.items.splice(2, 0, + items.splice(2, 0, buttonItem( { title: 'Mini Player' }, { icon: 'CLEAR_COOKIES' }, [ @@ -103,6 +104,18 @@ export function patchResolveCommand() { } ]) ); + + items.splice(2, 0, + buttonItem( + { title: 'Turn off screen' }, + { icon: 'VISIBILITY_OFF' }, [ + { + customAction: { + action: 'TURN_OFF_SCREEN' + } + } + ]) + ); } else if (cmd?.watchEndpoint?.videoId) { window.isPipPlaying = false; const ytlrPlayerContainer = document.querySelector('ytlr-player-container'); @@ -197,5 +210,8 @@ function customAction(action, parameters) { case 'CHECK_FOR_UPDATES': checkForUpdates(true); break; + case 'TURN_OFF_SCREEN': + turnOffScreen(); + break; } } \ No newline at end of file diff --git a/mods/translations/resources/en.json b/mods/translations/resources/en.json index 106f8f38..38a7067c 100644 --- a/mods/translations/resources/en.json +++ b/mods/translations/resources/en.json @@ -77,7 +77,9 @@ "enableVPUIPatching": "Enable Video Player UI Patching", "previousNextBtns": "Enable Previous and Next Buttons", "showSuperThxBtn": "Show Super Thanks Button", - "showSpeedCtrlBtn": "Show Speed Controls Button" + "showSpeedCtrlBtn": "Show Speed Controls Button", + "showPipBtn": "Show Mini Player Button", + "showTurnOffScreenBtn": "Show Turn Off Screen Button" } }, "preferredVideoQuality": { diff --git a/mods/translations/resources/vi.json b/mods/translations/resources/vi.json index d4cc1d78..9865e4c4 100644 --- a/mods/translations/resources/vi.json +++ b/mods/translations/resources/vi.json @@ -77,7 +77,9 @@ "enableVPUIPatching": "Bật vá giao diện trình phát video", "previousNextBtns": "Bật nút Trước và Tiếp theo", "showSuperThxBtn": "Hiển thị nút Super Thanks", - "showSpeedCtrlBtn": "Hiển thị nút Điều khiển tốc độ" + "showSpeedCtrlBtn": "Hiển thị nút Điều khiển tốc độ", + "showPipBtn": "Hiển thị nút Trình phát nhỏ", + "showTurnOffScreenBtn": "Hiển thị nút Tắt màn hình" } }, "preferredVideoQuality": { diff --git a/mods/ui/customUI.js b/mods/ui/customUI.js index ce2534fb..aad78c5e 100644 --- a/mods/ui/customUI.js +++ b/mods/ui/customUI.js @@ -1,20 +1,32 @@ // Custom UI for video player import { extractAssignedFunctions } from "../utils/ASTParser.js"; -import { configRead } from "../config.js"; +import { configRead, configChangeEmitter } from "../config.js"; import { ButtonRenderer } from "./ytUI.js"; +configChangeEmitter.addEventListener('configChange', (event) => { + const { key, value } = event.detail; + const listenedKeys = [ + 'enablePatchingVideoPlayer', + 'enablePreviousNextButtons', + 'enableSuperThanksButton', + 'enableSpeedControlsButton', + 'enablePipButton', + 'enableTurnOffScreenButton', + ] + if (listenedKeys.includes(key)) { + applyPatches(); + } +}); + function applyPatches() { - if (!window._yttv) return setTimeout(applyPatches, 250); - if (!document.querySelector('video')) return setTimeout(applyPatches, 250); + if (!window._yttv || !document.querySelector('video')) return setTimeout(applyPatches, 250); + const methods = Object.keys(window._yttv).filter(key => { return typeof window._yttv[key] === 'function' && window._yttv[key].toString().includes('TRANSPORT_CONTROLS_BUTTON_TYPE_FEATURED_ACTION'); }); - if (methods.length === 0) { - setTimeout(applyPatches, 250); - return; - } + if (methods.length === 0) return setTimeout(applyPatches, 250); const origMethod = window._yttv[methods[0]]; @@ -30,8 +42,7 @@ function applyPatches() { } if (!(this instanceof YtlrPlayerActionsContainer)) { - if (isClass) return constructAsNew(origMethod, args); - return origMethod.apply(this, args); + return isClass ? constructAsNew(origMethod, args) : origMethod.apply(this, args); } let inst; @@ -43,118 +54,123 @@ function applyPatches() { } const functions = extractAssignedFunctions(origMethod.toString()); - - const pipCommand = { - "type": "TRANSPORT_CONTROLS_BUTTON_TYPE_PIP", - "button": { - "buttonRenderer": ButtonRenderer( - false, - 'Mini Player', - 'CLEAR_COOKIES', - { - customAction: { - action: 'ENTER_PIP' - } - } - ) - } - } - - const settingActionGroup = functions.find(func => { - return func.rhs.includes('TRANSPORT_CONTROLS_BUTTON_TYPE_PLAYBACK_SETTINGS'); - }).left.split('.')[1]; - - if (!settingActionGroup) return inst; - - const origSettingActionGroup = inst[settingActionGroup]; - inst[settingActionGroup] = function () { - const res = origSettingActionGroup.apply(this, arguments); - res.find(item => item.type === 'TRANSPORT_CONTROLS_BUTTON_TYPE_PIP') || res.splice(1, 0, pipCommand); - return res; + const findKey = (targetStr) => { + const found = functions.find(f => f.rhs.includes(targetStr)); + return found ? found.left.split('.')[1] : null; }; - const previousButtonName = functions.find(func => { - if (func.rhs.includes('skipNextButton')) { - const skipNextButtonIndex = func.rhs.indexOf('skipNextButton'); - const skipPreviousButtonIndex = func.rhs.indexOf('skipPreviousButton'); - if (skipPreviousButtonIndex > skipNextButtonIndex) { - return true; - } - } - }).left.split('.')[1]; - - const nextButtonName = functions.find(func => { - if (func.rhs.includes('skipPreviousButton')) { - const skipNextButtonIndex = func.rhs.indexOf('skipNextButton'); - const skipPreviousButtonIndex = func.rhs.indexOf('skipPreviousButton'); - if (skipNextButtonIndex > skipPreviousButtonIndex) { - return true; + // Patch buttons for playback settings group + const settingActionGroup = findKey('TRANSPORT_CONTROLS_BUTTON_TYPE_PLAYBACK_SETTINGS'); + if (settingActionGroup) { + const origSettingActionGroup = inst[settingActionGroup]; + inst[settingActionGroup] = function () { + const res = origSettingActionGroup.apply(this, arguments); + + // PIP button + if (configRead('enablePipButton') && !res.some(i => i.type === 'TRANSPORT_CONTROLS_BUTTON_TYPE_PIP')) { + res.splice(1, 0, { + type: 'TRANSPORT_CONTROLS_BUTTON_TYPE_PIP', + button: { + buttonRenderer: ButtonRenderer( + false, + 'Mini player', + 'CLEAR_COOKIES', + { customAction: { action: 'ENTER_PIP' } } + ) + } + }); } - } - }).left.split('.')[1]; - const engagementActionButton = functions.find(func => func.rhs.includes('props.data.engagementActions')).left.split('.')[1]; + // Turn Off Screen button + if (configRead('enableTurnOffScreenButton') && !res.some(i => i.type === 'TRANSPORT_CONTROLS_BUTTON_TYPE_TURN_OFF_SCREEN')) { + res.unshift({ + type: 'TRANSPORT_CONTROLS_BUTTON_TYPE_TURN_OFF_SCREEN', + button: { + buttonRenderer: ButtonRenderer( + false, + 'Turn off screen', + 'VISIBILITY_OFF', + { customAction: { action: 'TURN_OFF_SCREEN' } } + ) + } + }); + } - if (engagementActionButton && configRead('enableSpeedControlsButton')) { - const origEngagementActionButton = inst[engagementActionButton]; - inst[engagementActionButton] = function () { - const res = origEngagementActionButton.apply(this, arguments); - res.find(item => item.type === 'TRANSPORT_CONTROLS_BUTTON_TYPE_SPEED') || res.push({ - type: 'TRANSPORT_CONTROLS_BUTTON_TYPE_SPEED', - button: { - buttonRenderer: ButtonRenderer( - false, - "Speed Controls", - 'SLOW_MOTION_VIDEO', - { - customAction: - { - action: 'TT_SPEED_SETTINGS_SHOW', - } - } - ) - } - }); return res; - } + }; } - if (!configRead('enableSuperThanksButton')) { + // Patch buttons for engagement actions group + const engagementActionButton = findKey('props.data.engagementActions'); + if (engagementActionButton) { const origEngagementActionButton = inst[engagementActionButton]; inst[engagementActionButton] = function () { - const res = origEngagementActionButton.apply(this, arguments); - const superThanksFiltered = res.filter(item => item.type !== 'TRANSPORT_CONTROLS_BUTTON_TYPE_SUPER_THANKS'); - const shoppingFiltered = superThanksFiltered.filter(item => item.type !== 'TRANSPORT_CONTROLS_BUTTON_TYPE_SHOPPING'); - return shoppingFiltered; + let res = origEngagementActionButton.apply(this, arguments); + + // Speed Control button + if (configRead('enableSpeedControlsButton') && !res.some(i => i.type === 'TRANSPORT_CONTROLS_BUTTON_TYPE_SPEED')) { + res.push({ + type: 'TRANSPORT_CONTROLS_BUTTON_TYPE_SPEED', + button: { + buttonRenderer: ButtonRenderer( + false, + "Speed Controls", + 'SLOW_MOTION_VIDEO', + { customAction: { action: 'TT_SPEED_SETTINGS_SHOW', } } + ) + } + }); + } + + // Super Thank button + if (!configRead('enableSuperThanksButton')) { + res = res.filter(i => i.type !== 'TRANSPORT_CONTROLS_BUTTON_TYPE_SUPER_THANKS' && i.type !== 'TRANSPORT_CONTROLS_BUTTON_TYPE_SHOPPING'); + } + + return res; } } + // Patch Previous and Next button if (configRead('enablePreviousNextButtons')) { - if (!previousButtonName || !nextButtonName) return inst; - inst[previousButtonName] = function () { - return ButtonRenderer( - false, - 'Previous', - 'SKIP_PREVIOUS', - { - signalAction: { - signal: 'PLAYER_PLAY_PREVIOUS' - } + const previousButtonName = functions.find(func => { + if (func.rhs.includes('skipNextButton')) { + const skipNextButtonIndex = func.rhs.indexOf('skipNextButton'); + const skipPreviousButtonIndex = func.rhs.indexOf('skipPreviousButton'); + if (skipPreviousButtonIndex > skipNextButtonIndex) { + return true; } - ) - } - - inst[nextButtonName] = function () { - return ButtonRenderer( - false, - 'Next', - 'SKIP_NEXT', - { - signalAction: { - signal: 'PLAYER_PLAY_NEXT' - } + } + }).left.split('.')[1]; + + const nextButtonName = functions.find(func => { + if (func.rhs.includes('skipPreviousButton')) { + const skipNextButtonIndex = func.rhs.indexOf('skipNextButton'); + const skipPreviousButtonIndex = func.rhs.indexOf('skipPreviousButton'); + if (skipNextButtonIndex > skipPreviousButtonIndex) { + return true; } - ) + } + }).left.split('.')[1]; + + if (previousButtonName && nextButtonName) { + inst[previousButtonName] = function () { + return ButtonRenderer( + false, + 'Previous', + 'SKIP_PREVIOUS', + { signalAction: { signal: 'PLAYER_PLAY_PREVIOUS' } } + ) + } + + inst[nextButtonName] = function () { + return ButtonRenderer( + false, + 'Next', + 'SKIP_NEXT', + { signalAction: { signal: 'PLAYER_PLAY_NEXT' } } + ) + } } } diff --git a/mods/ui/settings.js b/mods/ui/settings.js index dfab4504..eaf0a565 100644 --- a/mods/ui/settings.js +++ b/mods/ui/settings.js @@ -357,6 +357,16 @@ export default function modernUI(update, parameters) { name: t('settings.options.videoPlayer.options.patching.options.showSpeedCtrlBtn'), icon: 'SLOW_MOTION_VIDEO', value: 'enableSpeedControlsButton' + }, + { + name: t('settings.options.videoPlayer.options.patching.options.showPipBtn'), + icon: 'CLEAR_COOKIES', + value: 'enablePipButton' + }, + { + name: t('settings.options.videoPlayer.options.patching.options.showTurnOffScreenBtn'), + icon: 'VISIBILITY_OFF', + value: 'enableTurnOffScreenButton' } ] },