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
2 changes: 2 additions & 0 deletions mods/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const defaultConfig = {
enablePreviousNextButtons: true,
enableSuperThanksButton: false,
enableSpeedControlsButton: true,
enablePipButton: true,
enableTurnOffScreenButton: true,
enablePatchingVideoPlayer: true,
enablePreviews: true,
enableHideWatchedVideos: false,
Expand Down
44 changes: 44 additions & 0 deletions mods/features/turnOffScreen.js
Original file line number Diff line number Diff line change
@@ -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);
}
18 changes: 17 additions & 1 deletion mods/resolveCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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' }, [
Expand All @@ -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');
Expand Down Expand Up @@ -197,5 +210,8 @@ function customAction(action, parameters) {
case 'CHECK_FOR_UPDATES':
checkForUpdates(true);
break;
case 'TURN_OFF_SCREEN':
turnOffScreen();
break;
}
}
4 changes: 3 additions & 1 deletion mods/translations/resources/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
4 changes: 3 additions & 1 deletion mods/translations/resources/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
226 changes: 121 additions & 105 deletions mods/ui/customUI.js
Original file line number Diff line number Diff line change
@@ -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]];

Expand All @@ -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;
Expand All @@ -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' } }
)
}
}

}
Expand Down
Loading