Files
goodtube/beta/test.user.js
goodtube4u d73e06f8d7 Test
2025-06-11 16:15:54 +10:00

2162 lines
62 KiB
JavaScript

// ==UserScript==
// @name GoodTube Testing
// @namespace http://tampermonkey.net/
// @version 1.003
// @description A testing ground for GoodTube.
// @author GoodTube - Embed
// @match *://m.youtube.com/*
// @match *://www.youtube.com/*
// @match *://youtube.com/*
// @match *://*.wikipedia.org/*
// @icon https://cdn-icons-png.flaticon.com/256/1384/1384060.png
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
/* Bypass CSP restrictions (introduced by the latest Chrome updates)
------------------------------------------------------------------------------------------ */
if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) {
window.trustedTypes.createPolicy('default', {
createHTML: string => string,
createScriptURL: string => string,
createScript: string => string
});
}
/* Helper functions
------------------------------------------------------------------------------------------ */
// Setup GET parameters
function goodTube_helper_setupGetParams() {
let getParams = {};
document.location.search.replace(/\??(?:([^=]+)=([^&]*)&?)/g, function () {
function decode(s) {
return decodeURIComponent(s.split("+").join(" "));
}
getParams[decode(arguments[1])] = decode(arguments[2]);
});
// If we're on a playlist, but we don't have a video id in the URL - then get it from the page api
if (typeof getParams['list'] !== 'undefined' && typeof getParams['v'] === 'undefined') {
if (goodTube_page_api && typeof goodTube_page_api.getVideoData === 'function') {
let videoData = goodTube_page_api.getVideoData();
if (typeof videoData['video_id'] !== 'undefined' && videoData['video_id']) {
getParams['v'] = videoData['video_id'];
}
}
}
return getParams;
}
// Set a cookie
function goodTube_helper_setCookie(name, value) {
// 399 days
document.cookie = name + "=" + encodeURIComponent(value) + ";max-age=" + (399 * 24 * 60 * 60);
}
// Get a cookie
function goodTube_helper_getCookie(name) {
// Split the cookie string and get all individual name=value pairs in an array
let cookies = document.cookie.split(";");
// Loop through the array elements
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].split("=");
// Removing whitespace at the beginning of the cookie name and compare it with the given string
if (name == cookie[0].trim()) {
// Decode the cookie value and return
return decodeURIComponent(cookie[1]);
}
}
// Return null if not found
return null;
}
// Add CSS classes to show or hide elements / the Youtube player
function goodTube_helper_showHide_init() {
let style = document.createElement('style');
style.textContent = `
.goodTube_hidden {
position: fixed !important;
top: -9999px !important;
left: -9999px !important;
transform: scale(0) !important;
pointer-events: none !important;
}
.goodTube_hiddenPlayer {
position: relative;
overflow: hidden;
z-index: 1;
}
.goodTube_hiddenPlayer::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #ffffff;
z-index: 998;
}
html[dark] .goodTube_hiddenPlayer::before {
background: #0f0f0f;
}
`;
document.head.appendChild(style);
}
// Hide an element
function goodTube_helper_hideElement(element) {
if (element && !element.classList.contains('goodTube_hidden')) {
element.classList.add('goodTube_hidden');
}
}
// Show an element
function goodTube_helper_showElement(element) {
if (element && element.classList.contains('goodTube_hidden')) {
element.classList.remove('goodTube_hidden');
}
}
// Hide the Youtube player
function goodTube_helper_hideYoutubePlayer(element) {
// Add a wrapping div to help avoid detection
if (!element.closest('.goodTube_hiddenPlayer')) {
let parent = element.parentNode;
let wrapper = document.createElement('div');
wrapper.classList.add('goodTube_hiddenPlayer');
parent.replaceChild(wrapper, element);
wrapper.appendChild(element);
}
}
/* Global variables
------------------------------------------------------------------------------------------ */
// Stores the GET params
let goodTube_getParams = goodTube_helper_setupGetParams();
// Are we on mobile?
let goodTube_mobile = false;
if (window.location.href.indexOf('m.youtube') !== -1 || (typeof goodTube_getParams['mobile'] !== 'undefined' && goodTube_getParams['mobile'] === 'true')) {
goodTube_mobile = true;
}
// A reference to our player's wrapper
let goodTube_playerWrapper = false;
// A reference to our player's iframe
let goodTube_player = false;
// The page api
let goodTube_page_api = false;
// The iframe api
let goodTube_iframe_api = false;
// Are we in picture in picture?
let goodTube_pip = false;
// Is autoplay turned on? (on by default if no cookie exists or you're on mobile)
let goodTube_autoplay = goodTube_helper_getCookie('goodTube_autoplay');
if (!goodTube_autoplay || goodTube_mobile) {
goodTube_helper_setCookie('goodTube_autoplay', 'true');
goodTube_autoplay = 'true';
}
/* Youtube functions
------------------------------------------------------------------------------------------ */
// Hide ads, shorts, etc using CSS
function goodTube_youtube_hideAdsShortsEtc() {
let style = document.createElement('style');
style.textContent = `
.ytd-search ytd-shelf-renderer,
ytd-reel-shelf-renderer,
ytd-merch-shelf-renderer,
ytd-action-companion-ad-renderer,
ytd-display-ad-renderer,
ytd-rich-section-renderer,
ytd-video-masthead-ad-advertiser-info-renderer,
ytd-video-masthead-ad-primary-video-renderer,
ytd-in-feed-ad-layout-renderer,
ytd-ad-slot-renderer,
ytd-statement-banner-renderer,
ytd-banner-promo-renderer-background
ytd-ad-slot-renderer,
ytd-in-feed-ad-layout-renderer,
ytd-engagement-panel-section-list-renderer:not(.ytd-popup-container):not([target-id='engagement-panel-clip-create']),
ytd-compact-video-renderer:has(.goodTube_hidden),
ytd-rich-item-renderer:has(> #content > ytd-ad-slot-renderer)
.ytd-video-masthead-ad-v3-renderer,
div#root.style-scope.ytd-display-ad-renderer.yt-simple-endpoint,
div#sparkles-container.style-scope.ytd-promoted-sparkles-web-renderer,
div#main-container.style-scope.ytd-promoted-video-renderer,
div#player-ads.style-scope.ytd-watch-flexy,
#clarify-box,
ytm-rich-shelf-renderer,
ytm-search ytm-shelf-renderer,
ytm-button-renderer.icon-avatar_logged_out,
ytm-companion-slot,
ytm-reel-shelf-renderer,
ytm-merch-shelf-renderer,
ytm-action-companion-ad-renderer,
ytm-display-ad-renderer,
ytm-rich-section-renderer,
ytm-video-masthead-ad-advertiser-info-renderer,
ytm-video-masthead-ad-primary-video-renderer,
ytm-in-feed-ad-layout-renderer,
ytm-ad-slot-renderer,
ytm-statement-banner-renderer,
ytm-banner-promo-renderer-background
ytm-ad-slot-renderer,
ytm-in-feed-ad-layout-renderer,
ytm-compact-video-renderer:has(.goodTube_hidden),
ytm-rich-item-renderer:has(> #content > ytm-ad-slot-renderer)
.ytm-video-masthead-ad-v3-renderer,
div#root.style-scope.ytm-display-ad-renderer.yt-simple-endpoint,
div#sparkles-container.style-scope.ytm-promoted-sparkles-web-renderer,
div#main-container.style-scope.ytm-promoted-video-renderer,
div#player-ads.style-scope.ytm-watch-flexy,
ytm-pivot-bar-item-renderer:has(> .pivot-shorts),
ytd-compact-movie-renderer,
yt-about-this-ad-renderer,
masthead-ad,
ad-slot-renderer,
yt-mealbar-promo-renderer,
statement-banner-style-type-compact,
ytm-promoted-sparkles-web-renderer,
tp-yt-iron-overlay-backdrop,
#masthead-ad
{
display: none !important;
}
.style-scope[page-subtype='channels'] ytd-shelf-renderer,
.style-scope[page-subtype='channels'] ytm-shelf-renderer {
display: block !important;
}
`;
document.head.appendChild(style);
// Debug message
console.log('[GoodTube] Ads removed');
console.log('[GoodTube] Shorts removed');
}
// Hide shorts (realtime)
function goodTube_youtube_hideShorts() {
// If we're on a channel page, don't hide shorts
if (window.location.href.indexOf('@') !== -1) {
return;
}
// Hide shorts links
let shortsLinks = document.querySelectorAll('a:not(.goodTube_hidden)');
shortsLinks.forEach((element) => {
if (element.href.indexOf('shorts/') !== -1) {
goodTube_helper_hideElement(element);
goodTube_helper_hideElement(element.closest('ytd-video-renderer'));
goodTube_helper_hideElement(element.closest('ytd-compact-video-renderer'));
goodTube_helper_hideElement(element.closest('ytd-rich-grid-media'));
}
});
}
// Support timestamp links in comments
function goodTube_youtube_timestampLinks() {
// Links in video description and comments
let timestampLinks = document.querySelectorAll('#description a, ytd-comments .yt-core-attributed-string a, ytm-expandable-video-description-body-renderer a, .comment-content a');
// For each link
timestampLinks.forEach((element) => {
// Make sure we've not touched it yet, this stops doubling up on event listeners
if (!element.classList.contains('goodTube_timestampLink') && element.getAttribute('href') && element.getAttribute('href').indexOf(goodTube_getParams['v']) !== -1 && element.getAttribute('href').indexOf('t=') !== -1) {
element.classList.add('goodTube_timestampLink');
// Add the event listener to send our player to the correct time
element.addEventListener('click', function () {
let bits = element.getAttribute('href').split('t=');
if (typeof bits[1] !== 'undefined') {
let time = bits[1].replace('s', '');
goodTube_player_skipTo(time);
}
});
}
});
}
// Hide all Youtube players
function goodTube_youtube_hidePlayers() {
// Hide the normal Youtube player
let regularPlayers = document.querySelectorAll('#player');
regularPlayers.forEach((element) => {
goodTube_helper_hideYoutubePlayer(element);
});
// Remove the full screen and theater Youtube player
let fullscreenPlayers = document.querySelectorAll('#full-bleed-container');
fullscreenPlayers.forEach((element) => {
goodTube_helper_hideYoutubePlayer(element);
});
// Hide the mobile controls
let mobileControls = document.querySelectorAll('#player-control-container');
mobileControls.forEach((element) => {
goodTube_helper_hideElement(element);
});
// Hide the Youtube miniplayer
let miniPlayers = document.querySelectorAll('ytd-miniplayer');
miniPlayers.forEach((element) => {
goodTube_helper_hideElement(element);
});
}
// Turn off autoplay
let goodTube_youtube_turnedOffAutoplay = false;
function goodTube_youtube_turnOffAutoplay() {
// If we've already turned off autoplay, just return
if (goodTube_youtube_turnedOffAutoplay) {
return;
}
let autoplayButton = false;
// Desktop
if (!goodTube_mobile) {
// Target the autoplay button
autoplayButton = document.querySelector('.ytp-autonav-toggle-button');
// If we found it
if (autoplayButton) {
// Set a variable if autoplay has been turned off
if (autoplayButton.getAttribute('aria-checked') === 'false') {
goodTube_youtube_turnedOffAutoplay = true;
return;
}
// Otherwise click the button
else {
autoplayButton.click();
}
}
}
// Mobile
else {
// Target the autoplay button
autoplayButton = document.querySelector('.ytm-autonav-toggle-button-container');
// If we found it
if (autoplayButton) {
// Set a variable if autoplay has been turned off
if (autoplayButton.getAttribute('aria-pressed') === 'false') {
goodTube_youtube_turnedOffAutoplay = true;
return;
}
// Otherwise click the button
else {
autoplayButton.click();
}
}
// If we didn't find it - click the player a bit, this helps to actually make the autoplay button show (after ads)
else {
document.querySelector('#player .html5-video-player')?.click();
document.querySelector('#player').click();
document.querySelector('.ytp-unmute')?.click();
}
}
}
// Mute, pause and skip ads on all Youtube videos
function goodTube_youtube_mutePauseSkipAds() {
// Pause and mute all HTML videos on the page
let youtubeVideos = document.querySelectorAll('video');
youtubeVideos.forEach((element) => {
// Don't touch the thumbnail hover player
if (!element.closest('#inline-player')) {
element.muted = true;
element.volume = 0;
element.pause();
}
});
}
/* Player functions
------------------------------------------------------------------------------------------ */
// Init player
let goodTube_proxyIframeLoaded = false;
function goodTube_player_init() {
// Get the page API
goodTube_page_api = document.getElementById('movie_player');
// Get the video data to check loading state
let videoData = false;
if (goodTube_page_api && typeof goodTube_page_api.getVideoData === 'function') {
videoData = goodTube_page_api.getVideoData();
}
// Keep trying to get the frame API until it exists
if (!videoData) {
setTimeout(goodTube_player_init, 100);
return;
}
// Add CSS styles for the player
let style = document.createElement('style');
style.textContent = `
/* Desktop */
#goodTube_playerWrapper {
border-radius: 12px;
background: #ffffff;
position: absolute;
top: 0;
left: 0;
z-index: 999;
overflow: hidden;
}
html[dark] #goodTube_playerWrapper {
background: #0f0f0f;
}
/* Mobile */
#goodTube_playerWrapper.goodTube_mobile {
position: fixed;
background: #000000;
border-radius: 0;
z-index: 3;
}
/* Theater mode */
#goodTube_playerWrapper.goodTube_theater {
background: #000000;
border-radius: 0;
}
`;
document.head.appendChild(style);
// Setup player layout
let playerWrapper = document.createElement('div');
playerWrapper.id = 'goodTube_playerWrapper';
// Add a mobile class
if (goodTube_mobile) {
playerWrapper.classList.add('goodTube_mobile');
}
// Add player to the page
document.body.appendChild(playerWrapper);
// Add video iframe embed (via proxy iframe)
playerWrapper.innerHTML = `
<iframe
src="\x68\x74\x74\x70\x73\x3a\x2f\x2f\x65\x6e\x2e\x77\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\x67\x2f\x77\x69\x6b\x69\x2f\x46\x75\x63\x6b\x3f\x67\x6f\x6f\x64\x54\x75\x62\x65\x3d\x31"
width="100%"
height="100%"
src=""
frameborder="0"
scrolling="yes"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
></iframe>
`;
// Expose the player and wrapper globally
goodTube_playerWrapper = document.querySelector('#goodTube_playerWrapper');
goodTube_player = goodTube_playerWrapper.querySelector('iframe');
// Expose when the proxy iframe has loaded
goodTube_player.addEventListener('load', function () {
goodTube_proxyIframeLoaded = true;
});
// Setup player dynamic positioning and sizing
goodTube_player_positionAndSize();
// Run the actions
goodTube_actions();
}
// Position and size the player
let goodTube_loadTimeout = setTimeout(() => {}, 0);
function goodTube_player_positionAndSize() {
// If we're viewing a video
if (window.location.href.indexOf('.com/watch') !== -1) {
// Show the GoodTube player
goodTube_helper_showElement(goodTube_playerWrapper);
// This is used to position and size the player
let positionElement = false;
// Desktop
if (!goodTube_mobile) {
// Theater mode
if (document.querySelector('ytd-watch-flexy[theater]')) {
positionElement = document.getElementById('full-bleed-container');
if (!goodTube_playerWrapper.classList.contains('goodTube_theater')) {
goodTube_playerWrapper.classList.add('goodTube_theater');
}
}
// Regular mode
else {
positionElement = document.getElementById('player');
if (goodTube_playerWrapper.classList.contains('goodTube_theater')) {
goodTube_playerWrapper.classList.remove('goodTube_theater');
}
}
// Position the player
if (positionElement && positionElement.offsetHeight > 0) {
// Our wrapper has "position: absolute" so take into account the window scroll
let rect = positionElement.getBoundingClientRect();
goodTube_playerWrapper.style.top = (rect.top + window.scrollY) + 'px';
goodTube_playerWrapper.style.left = (rect.left + window.scrollX) + 'px';
// Match the size of the position element
goodTube_playerWrapper.style.width = positionElement.offsetWidth + 'px';
goodTube_playerWrapper.style.height = positionElement.offsetHeight + 'px';
}
}
// Mobile
else {
positionElement = document.getElementById('player');
// Position the player
if (positionElement && positionElement.offsetHeight > 0) {
// Our wrapper has "position: absolute" so don't take into account the window scroll
let rect = positionElement.getBoundingClientRect();
goodTube_playerWrapper.style.top = rect.top + 'px';
goodTube_playerWrapper.style.left = rect.left + 'px';
// Match the size of the position element
goodTube_playerWrapper.style.width = positionElement.offsetWidth + 'px';
goodTube_playerWrapper.style.height = positionElement.offsetHeight + 'px';
}
}
}
// Call this function again on next draw frame
window.requestAnimationFrame(function () {
goodTube_player_positionAndSize();
});
}
// Load a video
function goodTube_player_load() {
// Pause the video first (this helps to prevent audio flashes)
goodTube_player_pause();
// Make sure the proxy iframe has loaded
if (!goodTube_proxyIframeLoaded) {
clearTimeout(goodTube_loadTimeout);
goodTube_loadTimeout = setTimeout(goodTube_player_load, 100);
}
// Ensure we're still viewing a video (sometimes you can browse to another page before the iframe loads)
if (window.location.href.indexOf('.com/watch') !== -1) {
// If a restore time exists, skip to it
if (typeof goodTube_getParams['t'] !== 'undefined') {
goodTube_player_skipTo(goodTube_getParams['t'].replace('s', ''));
}
// Set autoplay initial state
goodTube_player.contentWindow.postMessage('goodTube_autoplay_' + goodTube_autoplay, '*');
}
// If we're not still viewing a video
else {
// Clear and hide the player
goodTube_player_clear();
}
// Set the video source (we need to use this weird method so it doesn't mess with browser history)
// This also tells the embed if it's mobile or not
let mobileText = 'false';
if (goodTube_mobile) {
mobileText = 'true';
}
goodTube_player.contentWindow.postMessage('goodTube_src_https://www.youtube.com/embed/' + goodTube_getParams['v'] + '?autoplay=1&mobile=' + mobileText, '*');
// Show the player
goodTube_helper_showElement(goodTube_playerWrapper);
}
// // Load a video
// let goodTube_player_firstLoad = true;
// function goodTube_player_load() {
// // Debug message
// console.log('[GoodTube] Loading video ' + goodTube_getParams['v'] + '...');
// // Pause the video first (this helps to prevent audio flashes)
// goodTube_player_pause();
// // On first load, or we're not in picture in picture (we fully refresh the iframe normally as this helps to stop audio flashes, temp fix as this is kinda sucky)
// // if (goodTube_player_firstLoad || !goodTube_pip) {
// if (goodTube_player_firstLoad) {
// // On iframe load
// goodTube_player.addEventListener('load', function () {
// // Set the video source (we need to use this weird method so it doesn't mess with browser history)
// // This also tells the embed if it's mobile or not
// let mobileText = 'false';
// if (goodTube_mobile) {
// mobileText = 'true';
// }
// goodTube_player.contentWindow.postMessage('goodTube_src_https://www.youtube.com/embed/' + goodTube_getParams['v'] + '?autoplay=1&mobile=' + mobileText, '*');
// // Ensure we're still viewing a video (sometimes you can browse to another page before the iframe loads)
// if (window.location.href.indexOf('.com/watch') !== -1) {
// // If a restore time exists, skip to it
// if (typeof goodTube_getParams['t'] !== 'undefined') {
// goodTube_player_skipTo(goodTube_getParams['t'].replace('s', ''));
// }
// // Set autoplay initial state
// goodTube_player.contentWindow.postMessage('goodTube_autoplay_' + goodTube_autoplay, '*');
// }
// // If we're not still viewing a video
// else {
// // Clear and hide the player
// goodTube_player_clear();
// }
// });
// // Turn first load off
// goodTube_player_firstLoad = false;
// }
// // On other loads (picture in picture only currently)
// else {
// // Load the video via the iframe api
// goodTube_player.contentWindow.postMessage('goodTube_load_' + goodTube_getParams['v'], '*');
// }
// // Show the player
// goodTube_helper_showElement(goodTube_playerWrapper);
// }
// Clear and hide the player
function goodTube_player_clear() {
// Stop the video via the iframe api (but not if we're in picture in picture)
if (!goodTube_pip) {
goodTube_player.contentWindow.postMessage('goodTube_stopVideo', '*');
}
// Hide the player
goodTube_helper_hideElement(goodTube_playerWrapper);
}
// Skip to time
function goodTube_player_skipTo(time) {
goodTube_player.contentWindow.postMessage('goodTube_skipTo_' + time, '*');
}
// Pause
function goodTube_player_pause() {
goodTube_player.contentWindow.postMessage('goodTube_pause', '*');
}
// Play
function goodTube_player_play() {
goodTube_player.contentWindow.postMessage('goodTube_play', '*');
}
/* Keyboard shortcuts
------------------------------------------------------------------------------------------ */
// Add keyboard shortcuts
function goodTube_shortcuts_init() {
document.addEventListener('keydown', function (event) {
// Don't do anything if we're holding control
if (event.ctrlKey) {
return;
}
// Get the key pressed in lower case
let keyPressed = event.key.toLowerCase();
// If we're not focused on a HTML form element
let focusedElement = event.srcElement;
let focusedElement_tag = false;
let focusedElement_id = false;
if (focusedElement) {
if (typeof focusedElement.nodeName !== 'undefined') {
focusedElement_tag = focusedElement.nodeName.toLowerCase();
}
if (typeof focusedElement.getAttribute !== 'undefined') {
focusedElement_id = focusedElement.getAttribute('id');
}
}
if (
!focusedElement ||
(
focusedElement_tag.indexOf('input') === -1 &&
focusedElement_tag.indexOf('label') === -1 &&
focusedElement_tag.indexOf('select') === -1 &&
focusedElement_tag.indexOf('textarea') === -1 &&
focusedElement_tag.indexOf('fieldset') === -1 &&
focusedElement_tag.indexOf('legend') === -1 &&
focusedElement_tag.indexOf('datalist') === -1 &&
focusedElement_tag.indexOf('output') === -1 &&
focusedElement_tag.indexOf('option') === -1 &&
focusedElement_tag.indexOf('optgroup') === -1 &&
focusedElement_id !== 'contenteditable-root'
)
) {
if (
// Fullscreen
keyPressed === 'f' ||
// Speed up playback
keyPressed === '>' ||
// Slow down playback
keyPressed === '<'
) {
event.preventDefault();
event.stopImmediatePropagation();
// Pass the keyboard shortcut to the iframe
goodTube_player.contentWindow.postMessage('goodTube_shortcut_' + keyPressed, '*');
}
// If we're not holding down the shift key
if (!event.shiftKey) {
// If we're focused on the video element
if (focusedElement && typeof focusedElement.closest !== 'undefined' && focusedElement.closest('#goodTube_player')) {
// Theater mode (focus the body, this makes the default youtube shortcut work)
if (keyPressed === 't') {
document.querySelector('body').focus();
}
}
if (
// Prev frame (24fps calculation)
keyPressed === ',' ||
// Next frame (24fps calculation)
keyPressed === '.' ||
// Prev 5 seconds
keyPressed === 'arrowleft' ||
// Next 5 seconds
keyPressed === 'arrowright' ||
// Toggle play/pause
keyPressed === ' ' || keyPressed === 'k' ||
// Toggle mute
keyPressed === 'm' ||
// Toggle fullscreen
keyPressed === 'f' ||
// Prev 10 seconds
keyPressed === 'j' ||
// Next 10 seconds
keyPressed === 'l' ||
// Start of video
keyPressed === 'home' ||
// End of video
keyPressed === 'end' ||
// Skip to percentage
keyPressed === '0' ||
keyPressed === '1' ||
keyPressed === '2' ||
keyPressed === '3' ||
keyPressed === '4' ||
keyPressed === '5' ||
keyPressed === '6' ||
keyPressed === '7' ||
keyPressed === '8' ||
keyPressed === '9'
) {
event.preventDefault();
event.stopImmediatePropagation();
// Pass the keyboard shortcut to the iframe
goodTube_player.contentWindow.postMessage('goodTube_shortcut_' + keyPressed, '*');
}
// Toggle picture in picture
if (keyPressed === 'i') {
event.preventDefault();
event.stopImmediatePropagation();
// Tell the iframe to toggle pip
goodTube_player.contentWindow.postMessage('goodTube_pip', '*');
}
}
}
}, true);
}
// Trigger a keyboard shortcut
function goodTube_shortcuts_trigger(shortcut) {
// Focus the body first
document.querySelector('body').focus();
// Setup the keyboard shortcut
let theKey = false;
let keyCode = false;
let shiftKey = false;
if (shortcut === 'theater') {
theKey = 't';
keyCode = 84;
shiftKey = false;
}
else {
return;
}
// Trigger the keyboard shortcut
let e = false;
e = new window.KeyboardEvent('focus', {
bubbles: true,
key: theKey,
keyCode: keyCode,
shiftKey: shiftKey,
charCode: 0,
});
document.dispatchEvent(e);
e = new window.KeyboardEvent('keydown', {
bubbles: true,
key: theKey,
keyCode: keyCode,
shiftKey: shiftKey,
charCode: 0,
});
document.dispatchEvent(e);
e = new window.KeyboardEvent('beforeinput', {
bubbles: true,
key: theKey,
keyCode: keyCode,
shiftKey: shiftKey,
charCode: 0,
});
document.dispatchEvent(e);
e = new window.KeyboardEvent('keypress', {
bubbles: true,
key: theKey,
keyCode: keyCode,
shiftKey: shiftKey,
charCode: 0,
});
document.dispatchEvent(e);
e = new window.KeyboardEvent('input', {
bubbles: true,
key: theKey,
keyCode: keyCode,
shiftKey: shiftKey,
charCode: 0,
});
document.dispatchEvent(e);
e = new window.KeyboardEvent('change', {
bubbles: true,
key: theKey,
keyCode: keyCode,
shiftKey: shiftKey,
charCode: 0,
});
document.dispatchEvent(e);
e = new window.KeyboardEvent('keyup', {
bubbles: true,
key: theKey,
keyCode: keyCode,
shiftKey: shiftKey,
charCode: 0,
});
document.dispatchEvent(e);
}
/* Navigation (playlists and autoplay)
------------------------------------------------------------------------------------------ */
// Have we opened the playlist (mobile)
let goodTube_nav_clickedPlaylistOpen = false;
// A reference to the previous video
let goodTube_nav_prevVideo = [];
// Are the next and previous buttons enabled?
let goodTube_nav_nextButton = true;
let goodTube_nav_prevButton = false;
// Generate playlist links (these are internally used to help us navigate through playlists and use autoplay)
function goodTube_nav_generatePlaylistLinks() {
// If we're not viewing a playlist, just return.
if (typeof goodTube_getParams['i'] === 'undefined' && typeof goodTube_getParams['index'] === 'undefined' && typeof goodTube_getParams['list'] === 'undefined') {
return;
}
// Get the playlist items
let playlistLinks = false;
let playlistTitles = false;
// Desktop
if (!goodTube_mobile) {
playlistLinks = document.querySelectorAll('#playlist-items > a');
playlistTitles = document.querySelectorAll('#playlist-items #video-title');
}
// Mobile
else {
playlistLinks = document.querySelectorAll('ytm-playlist-panel-renderer a.compact-media-item-image');
playlistTitles = document.querySelectorAll('ytm-playlist-panel-renderer .compact-media-item-headline span');
}
// If the playlist links exist
if (playlistLinks.length > 0) {
// Target the playlist container
let playlistContainer = document.getElementById('goodTube_playlistContainer');
// Add the playlist container if we don't have it
if (!playlistContainer) {
playlistContainer = document.createElement('div');
playlistContainer.setAttribute('id', 'goodTube_playlistContainer');
playlistContainer.style.display = 'none';
document.body.appendChild(playlistContainer);
}
// Empty the playlist container
playlistContainer.innerHTML = '';
// For each playlist item
let i = 0;
playlistLinks.forEach((playlistItem) => {
// Create a link element
let playlistItemElement = document.createElement('a');
// Set the href
playlistItemElement.href = playlistItem.href;
// Set the title
playlistItemElement.innerHTML = playlistTitles[i].innerHTML.trim();
// If we're currently on this item, set the selected class
if (playlistItem.href.indexOf('v=' + goodTube_getParams['v']) !== -1) {
playlistItemElement.classList.add('goodTube_selected');
}
// Add the item to the playlist container
playlistContainer.appendChild(playlistItemElement);
i++;
});
}
}
// Play the previous video
function goodTube_nav_prev() {
// Check if we clicked a playlist item
let clickedPlaylistItem = false;
// If we are viewing a playlist
if (typeof goodTube_getParams['i'] !== 'undefined' || typeof goodTube_getParams['index'] !== 'undefined' || typeof goodTube_getParams['list'] !== 'undefined') {
// Get the playlist items
let playlistItems = document.querySelectorAll('#goodTube_playlistContainer a');
// For each playlist item
let clickNext = false;
// Loop in reverse
for (let i = (playlistItems.length - 1); i >= 0; i--) {
let playlistItem = playlistItems[i];
if (clickNext) {
// Find the matching playlist item on the page and click it
let bits = playlistItem.href.split('/watch');
let findUrl = '/watch' + bits[1];
// Desktop
if (!goodTube_mobile) {
clickedPlaylistItem = true;
document.querySelector('#playlist-items > a[href="' + findUrl + '"]')?.click();
}
// Mobile
else {
clickedPlaylistItem = true;
document.querySelector('ytm-playlist-panel-renderer a.compact-media-item-image[href="' + findUrl + '"]')?.click();
}
if (clickedPlaylistItem) {
clickedPlaylistItem = true;
// Double check that the playlist is open, if not - open it.
let playlistContainer = document.querySelector('ytm-playlist-panel-renderer');
if (!playlistContainer) {
let openButton = document.querySelector('ytm-playlist-panel-entry-point');
if (openButton && !goodTube_nav_clickedPlaylistOpen) {
goodTube_nav_clickedPlaylistOpen = true;
openButton.click();
setTimeout(goodTube_nav_prev, 500);
}
return;
}
goodTube_nav_clickedPlaylistOpen = false;
// Click the matching playlist item
document.querySelector('ytm-playlist-panel-renderer a.compact-media-item-image[href="' + findUrl + '"]')?.click();
}
}
if (playlistItem.classList.contains('goodTube_selected')) {
clickNext = true;
}
else {
clickNext = false;
}
}
}
// If we didn't click a playlist item, play previous video (if it exists in our history)
if (!clickedPlaylistItem && goodTube_nav_prevVideo[goodTube_nav_prevVideo.length - 2] && goodTube_nav_prevVideo[goodTube_nav_prevVideo.length - 2] !== window.location.href) {
// Debug message
console.log('[GoodTube] Playing previous video...');
// Go back to the previous video
goodTube_helper_setCookie('goodTube_previous', 'true');
window.history.go(-1);
}
}
// Play the next video
function goodTube_nav_next(pressedButton = false) {
// Check if we clicked a playlist item
let clickedPlaylistItem = false;
// If we are viewing a playlist
if (typeof goodTube_getParams['i'] !== 'undefined' || typeof goodTube_getParams['index'] !== 'undefined' || typeof goodTube_getParams['list'] !== 'undefined') {
// Get the playlist items
let playlistItems = document.querySelectorAll('#goodTube_playlistContainer a');
// For each playlist item
let clickNext = false;
playlistItems.forEach((playlistItem) => {
if (clickNext) {
// Find the matching playlist item on the page and click it
let bits = playlistItem.href.split('/watch');
let findUrl = '/watch' + bits[1];
// Desktop
if (!goodTube_mobile) {
clickedPlaylistItem = true;
document.querySelector('#playlist-items > a[href="' + findUrl + '"]')?.click();
}
// Mobile
else {
clickedPlaylistItem = true;
// Double check that the playlist is open, if not - open it.
let playlistContainer = document.querySelector('ytm-playlist-panel-renderer');
if (!playlistContainer) {
let openButton = document.querySelector('ytm-playlist-panel-entry-point');
if (openButton && !goodTube_nav_clickedPlaylistOpen) {
goodTube_nav_clickedPlaylistOpen = true;
openButton.click();
setTimeout(goodTube_nav_next, 500);
}
return;
}
goodTube_nav_clickedPlaylistOpen = false;
// Click the matching playlist item
document.querySelector('ytm-playlist-panel-renderer a.compact-media-item-image[href="' + findUrl + '"]')?.click();
}
if (clickedPlaylistItem) {
// Debug message
console.log('[GoodTube] Playing next video in playlist...');
}
}
if (playlistItem.classList.contains('goodTube_selected')) {
clickNext = true;
}
else {
clickNext = false;
}
});
}
// If we didn't click a playlist item, autoplay next video (only if they pressed the next button or autoplay is on)
if (!clickedPlaylistItem && (goodTube_autoplay === 'true' || pressedButton)) {
// Re fetch the page API (this fixes issues on mobile)
goodTube_page_api = document.getElementById('movie_player');
// Make sure it exists
if (goodTube_page_api && typeof goodTube_page_api.nextVideo === 'function') {
// Play the next video
goodTube_page_api.nextVideo();
}
// Debug message
console.log('[GoodTube] Autoplaying next video...');
}
}
// Setup the previous button history
function goodTube_nav_setupPrevHistory() {
// If we've hit the previous button
if (goodTube_helper_getCookie('goodTube_previous') === 'true') {
// Remove the last item from the previous video array
goodTube_nav_prevVideo.pop();
goodTube_helper_setCookie('goodTube_previous', 'false');
}
// Otherwise it's a normal video load
else {
// Add this page to the previous video array
goodTube_nav_prevVideo.push(window.location.href);
}
}
// Show or hide the next and previous button
function goodTube_nav_showHideNextPrevButtons() {
let prevButton = false;
let nextButton = true;
// Don't show next / prev unless we're viewing a video
if (typeof goodTube_getParams['v'] === 'undefined') {
prevButton = false;
nextButton = false;
}
// For the regular player
else {
// If we're viewing a playlist
if (typeof goodTube_getParams['i'] !== 'undefined' || typeof goodTube_getParams['index'] !== 'undefined' || typeof goodTube_getParams['list'] !== 'undefined') {
let playlist = document.querySelectorAll('#goodTube_playlistContainer a');
if (!playlist || !playlist.length) {
return;
}
// If the first video is NOT selected
if (!playlist[0].classList.contains('goodTube_selected')) {
// Enable the previous button
prevButton = true;
}
}
// Otherwise we're not in a playlist, so if a previous video exists
else if (goodTube_nav_prevVideo[goodTube_nav_prevVideo.length - 2] && goodTube_nav_prevVideo[goodTube_nav_prevVideo.length - 2] !== window.location.href) {
// Enable the previous button
prevButton = true;
}
}
// Tell the iframe to show or hide the previous button
if (prevButton) {
goodTube_nav_prevButton = true;
goodTube_player.contentWindow.postMessage('goodTube_prevButton_show', '*');
}
else {
goodTube_nav_prevButton = false;
goodTube_player.contentWindow.postMessage('goodTube_prevButton_hide', '*');
}
// Tell the iframe to show or hide the next button
if (nextButton) {
goodTube_nav_nextButton = true;
goodTube_player.contentWindow.postMessage('goodTube_nextButton_show', '*');
}
else {
goodTube_nav_nextButton = false;
goodTube_player.contentWindow.postMessage('goodTube_nextButton_hide', '*');
}
}
/* Usage stats
------------------------------------------------------------------------------------------ */
// Don't worry everyone - this is just a counter that totals unique users / how many videos were played with GoodTube.
// It's only in here so I can have some fun and see how many people use this thing I made - no private info is tracked.
// Count unique users
function goodTube_stats_user() {
// If there's no cookie
if (!goodTube_helper_getCookie('goodTube_uniqueUserStat')) {
// Count a unique user
fetch('https://jamenlyndon.com/_other/stats/user.php');
// Set a cookie to only count unique users once
goodTube_helper_setCookie('goodTube_uniqueUserStat', 'true');
}
}
// Count videos
function goodTube_stats_video() {
fetch('https://jamenlyndon.com/_other/stats/video.php');
}
/* Core functions
------------------------------------------------------------------------------------------ */
// Init
function goodTube_init() {
/* Disable Youtube
-------------------------------------------------- */
// Mute, pause and skip ads
goodTube_youtube_mutePauseSkipAds();
setInterval(goodTube_youtube_mutePauseSkipAds, 1);
// Add CSS classes to hide elements (without Youtube knowing)
goodTube_helper_showHide_init();
// Hide the youtube players
goodTube_youtube_hidePlayers();
setInterval(goodTube_youtube_hidePlayers, 100);
// Add CSS to hide ads, shorts, etc
goodTube_youtube_hideAdsShortsEtc();
// Turn off autoplay
setInterval(goodTube_youtube_turnOffAutoplay, 1000);
// Hide shorts that popup as you use the site (like video results)
setInterval(goodTube_youtube_hideShorts, 100);
/* Load GoodTube
-------------------------------------------------- */
// Init our player (after DOM is loaded)
document.addEventListener("DOMContentLoaded", goodTube_player_init);
// Also check if the DOM is already loaded, as if it is, the above event listener will not trigger.
if (document.readyState === "interactive" || document.readyState === "complete") {
goodTube_player_init();
}
// Usage stats
goodTube_stats_user();
// Keyboard shortcuts (desktop only)
if (!goodTube_mobile) {
goodTube_shortcuts_init();
}
// Listen for messages from the iframe
window.addEventListener('message', goodTube_receiveMessage);
}
// Listen for messages from the iframe
function goodTube_receiveMessage(event) {
// Make sure some data exists
if (typeof event.data !== 'string') {
return;
}
// Picture in picture
if (event.data.indexOf('goodTube_pip_') !== -1) {
let pipEnabled = event.data.replace('goodTube_pip_', '');
if (pipEnabled === 'true') {
goodTube_pip = true;
}
else {
goodTube_pip = false;
// If we're not viewing a video
if (typeof goodTube_getParams['v'] === 'undefined') {
// Clear the player
goodTube_player_clear();
}
}
}
// Previous video
else if (event.data === 'goodTube_prevVideo') {
goodTube_nav_prev();
}
// Next video
else if (event.data === 'goodTube_nextVideo') {
goodTube_nav_next();
}
// Theater mode (toggle)
else if (event.data === 'goodTube_theater') {
goodTube_shortcuts_trigger('theater');
}
// Autoplay (toggle)
else if (event.data === 'goodTube_autoplayToggle') {
if (goodTube_autoplay === 'true') {
goodTube_helper_setCookie('goodTube_autoplay', 'false');
goodTube_autoplay = 'false';
}
else {
goodTube_helper_setCookie('goodTube_autoplay', 'true');
goodTube_autoplay = 'true';
}
}
}
// Actions
let goodTube_previousUrl = false;
function goodTube_actions() {
// Get the previous and current URL
// Remove hashes, these mess with things sometimes
// Also remove "index="
let previousUrl = goodTube_previousUrl;
if (previousUrl) {
previousUrl = previousUrl.split('#')[0];
previousUrl = previousUrl.split('index=')[0];
}
let currentUrl = window.location.href;
if (currentUrl) {
currentUrl = currentUrl.split('#')[0];
currentUrl = currentUrl.split('index=')[0];
}
// If the URL has changed (this will always fire on first page load)
if (previousUrl !== currentUrl) {
// The URL has changed, so setup our player
// ----------------------------------------------------------------------------------------------------
// Setup GET parameters
goodTube_getParams = goodTube_helper_setupGetParams();
// If we're viewing a video
if (window.location.href.indexOf('.com/watch') !== -1) {
// Setup the previous button history
goodTube_nav_setupPrevHistory();
// Load the video
goodTube_player_load();
// Usage stats
goodTube_stats_video();
}
// Otherwise if we're not viewing a video
else {
// Clear the player
goodTube_player_clear();
}
// Set the previous URL (which pauses this function until the URL changes again)
goodTube_previousUrl = window.location.href;
}
// Generate the playlist links (used to navigate playlists correctly)
goodTube_nav_generatePlaylistLinks();
// Show or hide the next / prev buttons
goodTube_nav_showHideNextPrevButtons();
// Support timestamp links
goodTube_youtube_timestampLinks();
// Run actions again in 100ms to loop this function
setTimeout(goodTube_actions, 100);
}
/* Iframe functions
------------------------------------------------------------------------------------------ */
// Init
function goodTube_iframe_init() {
// Get the iframe API
goodTube_iframe_api = document.getElementById('movie_player');
// Add the styles
goodTube_iframe_style();
// Get the video data to check loading state
let videoData = false;
if (goodTube_iframe_api && typeof goodTube_iframe_api.getVideoData === 'function') {
videoData = goodTube_iframe_api.getVideoData();
}
// Keep trying to get the frame API until it exists
if (!videoData) {
setTimeout(goodTube_iframe_init, 100);
return;
}
// Add custom buttons
goodTube_iframe_addCustomButtons();
// Add custom events
goodTube_iframe_addCustomEvents();
// Add keyboard shortcuts
goodTube_iframe_addKeyboardShortcuts();
// Support picture in picture
goodTube_pip_init();
// Run the iframe actions
goodTube_iframe_actions();
// Listen for messages from the parent window
window.addEventListener('message', goodTube_iframe_receiveMessage);
}
// Actions
function goodTube_iframe_actions() {
// Update picture in picture
goodTube_pip_update();
// Fix fullscreen button issues
goodTube_iframe_fixFullScreenButton();
// Run actions again in 100ms to loop this function
setTimeout(goodTube_iframe_actions, 100);
}
// Style the iframe
function goodTube_iframe_style() {
let style = document.createElement('style');
let cssOutput = `
/* Hide unwanted stuff */
.ytp-gradient-top,
.ytp-show-cards-title,
.ytp-pause-overlay,
.ytp-youtube-button,
.ytp-cued-thumbnail-overlay,
.ytp-paid-content-overlay,
.ytp-impression-link,
.ytp-endscreen-content,
.ytp-ad-progress-list,
.ytp-endscreen-next,
.ytp-endscreen-previous,
.ytp-info-panel-preview,
.ytp-popup {
display: none !important;
}
/* Make next and prev buttons not disabled */
.ytp-prev-button,
.ytp-next-button {
opacity: 1 !important;
cursor: pointer !important;
}
/* Show video title in fullscreen */
body .ytp-fullscreen .ytp-gradient-top,
body .ytp-fullscreen .ytp-show-cards-title {
display: block !important;
}
body .ytp-fullscreen .ytp-show-cards-title .ytp-button,
body .ytp-fullscreen .ytp-show-cards-title .ytp-title-channel {
display: none !important;
}
body .ytp-fullscreen .ytp-show-cards-title .ytp-title-text {
padding-left: 36px !important;
}
`;
// Add theater mode button (desktop only)
if (!goodTube_mobile) {
cssOutput += `
.ytp-size-button {
display: inline-block !important;
}
`;
}
// Enable the picture in picture button (unless you're on firefox)
if (navigator.userAgent.toLowerCase().indexOf('firefox') === -1) {
cssOutput += `
.ytp-pip-button {
display: inline-block !important;
}
`;
}
// Position the autoplay button for mobile
if (goodTube_mobile) {
cssOutput += `
#goodTube_autoplayButton {
position: fixed;
top: 0;
right: 0;
}
`;
}
style.textContent = cssOutput;
document.head.appendChild(style);
}
// Add custom buttons
function goodTube_iframe_addCustomButtons() {
// Target the play button
let playButton = document.querySelector('.ytp-play-button');
// Make sure it exists before continuing
if (!playButton) {
setTimeout(goodTube_iframe_addCustomButtons, 100);
return;
}
// Previous button
let prevButton = document.querySelector('.ytp-prev-button');
if (prevButton) {
// Add actions
prevButton.addEventListener('click', function () {
// Tell the top frame to go to the previous video
window.top.postMessage('goodTube_prevVideo', '*');
});
}
// Next button
let nextButton = document.querySelector('.ytp-next-button');
if (nextButton) {
// Add actions
nextButton.addEventListener('click', function () {
// Tell the top frame to go to the next video
window.top.postMessage('goodTube_nextVideo', '*');
});
}
// Theater mode button
let theaterButton = document.querySelector('.ytp-size-button');
if (theaterButton) {
// Style button
theaterButton.setAttribute('data-tooltip-target-id', 'ytp-size-button');
theaterButton.setAttribute('data-title-no-tooltip', 'Theater mode (t)');
theaterButton.setAttribute('aria-label', 'Theater mode (t)');
theaterButton.setAttribute('title', 'Theater mode (t)');
theaterButton.innerHTML = '<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%"><use class="ytp-svg-shadow" xlink:href="#ytp-id-30"></use><path d="m 28,11 0,14 -20,0 0,-14 z m -18,2 16,0 0,10 -16,0 0,-10 z" fill="#fff" fill-rule="evenodd" id="ytp-id-30"></path></svg>';
// Add actions
theaterButton.addEventListener('click', function () {
// Tell the top window to toggle theater mode
window.top.postMessage('goodTube_theater', '*');
});
}
// Add autoplay button (before subtitles button)
let subtitlesButton = document.querySelector('.ytp-subtitles-button');
if (subtitlesButton) {
// Add button
subtitlesButton.insertAdjacentHTML('beforebegin', '<button class="ytp-button" id="goodTube_autoplayButton" data-priority="2" data-tooltip-target-id="ytp-autonav-toggle-button"><div class="ytp-autonav-toggle-button-container"><div class="ytp-autonav-toggle-button" aria-checked="true"></div></div></button>');
// Add actions
let autoplayButton = document.querySelector('#goodTube_autoplayButton');
if (autoplayButton) {
autoplayButton.addEventListener('click', function () {
// Toggle the style of the autoplay button
let innerButton = autoplayButton.querySelector('.ytp-autonav-toggle-button');
let innerButtonState = innerButton.getAttribute('aria-checked');
if (innerButtonState === 'true') {
innerButton.setAttribute('aria-checked', 'false');
}
else {
innerButton.setAttribute('aria-checked', 'true');
}
// Tell the top window to toggle autoplay
window.top.postMessage('goodTube_autoplayToggle', '*');
});
}
}
}
// Add custom events
function goodTube_iframe_addCustomEvents() {
// Target the video element
let videoElement = document.querySelector('#player video');
// Make sure it exists before continuing
if (!videoElement) {
setTimeout(goodTube_iframe_addCustomEvents, 100);
return;
}
// When the video ends
videoElement.addEventListener('ended', function () {
// Tell the top frame to go to the next video
window.top.postMessage('goodTube_nextVideo', '*');
});
}
// Add keyboard shortcuts
function goodTube_iframe_addKeyboardShortcuts() {
document.addEventListener('keydown', function (event) {
// Don't do anything if we're holding control
if (event.ctrlKey) {
return;
}
// Theater mode (t)
if (event.key === 't') {
// Tell the top window to toggle theater mode
window.top.postMessage('goodTube_theater', '*');
}
// Picture in picture (i)
if (event.key === 'i') {
let pipButton = document.querySelector('.ytp-pip-button');
if (pipButton) {
pipButton.click();
}
}
// Prev video (shift+p)
else if (event.key.toLowerCase() === 'p' && event.shiftKey) {
// Tell the top window to go to the previous video
window.top.postMessage('goodTube_prevVideo', '*');
}
// Next video (shift+n)
else if (event.key.toLowerCase() === 'n' && event.shiftKey) {
// Tell the top window to go to the next video
window.top.postMessage('goodTube_nextVideo', '*');
}
});
}
// Receive a message from the parent window
function goodTube_iframe_receiveMessage(event) {
// Make sure some data exists
if (typeof event.data !== 'string') {
return;
}
// Load video
if (event.data.indexOf('goodTube_load_') !== -1) {
let videoId = event.data.replace('goodTube_load_', '');
// Pause and mute the video first (this helps to prevent audio flashes)
goodTube_iframe_mute();
goodTube_iframe_pause();
// Then load the new video
goodTube_iframe_api.loadVideoById(videoId);
}
// Stop video
else if (event.data === 'goodTube_stopVideo') {
goodTube_iframe_api.stopVideo();
}
// Set autoplay state
else if (event.data.indexOf('goodTube_autoplay_') !== -1) {
// Get the data and expose it globally
goodTube_autoplay = event.data.replace('goodTube_autoplay_', '');
// Toggle the style of the autoplay button
let autoplayToggleButton = document.querySelector('#goodTube_autoplayButton .ytp-autonav-toggle-button');
if (autoplayToggleButton) {
autoplayToggleButton.setAttribute('aria-checked', goodTube_autoplay);
}
}
// Skip to time
else if (event.data.indexOf('goodTube_skipTo_') !== -1) {
let time = event.data.replace('goodTube_skipTo_', '');
goodTube_iframe_skipTo(time);
}
// Pause
else if (event.data === 'goodTube_pause') {
goodTube_iframe_pause();
}
// Play
else if (event.data === 'goodTube_play') {
goodTube_iframe_play();
}
// Toggle picture in picture
else if (event.data === 'goodTube_pip') {
let pipButton = document.querySelector('.ytp-pip-button');
if (pipButton) {
pipButton.click();
}
}
// Show the previous button
else if (event.data === 'goodTube_prevButton_hide') {
goodTube_nav_prevButton = false;
let prevButton = document.querySelector('.ytp-prev-button');
if (prevButton) {
prevButton.style.display = 'none';
}
}
// Hide the previous button
else if (event.data === 'goodTube_prevButton_show') {
goodTube_nav_prevButton = true;
let prevButton = document.querySelector('.ytp-prev-button');
if (prevButton) {
prevButton.style.display = 'block';
}
}
// Show the next button
else if (event.data === 'goodTube_nextButton_hide') {
goodTube_nav_nextButton = false;
let nextButton = document.querySelector('.ytp-next-button');
if (nextButton) {
nextButton.style.display = 'none';
}
}
// Hide the next button
else if (event.data === 'goodTube_nextButton_show') {
goodTube_nav_nextButton = true;
let nextButton = document.querySelector('.ytp-next-button');
if (nextButton) {
nextButton.style.display = 'block';
}
}
// Keyboard shortcut
else if (event.data.indexOf('goodTube_shortcut_') !== -1) {
// Get the key pressed
let keyPressed = event.data.replace('goodTube_shortcut_', '');
// Target the player
let player = document.querySelector('video');
if (!player) {
return;
}
// Fullscreen
if (keyPressed === 'f') {
document.querySelector('.ytp-fullscreen-button')?.click();
}
// Speed up playback
else if (keyPressed === '>') {
if (parseFloat(player.playbackRate) == .25) {
player.playbackRate = .5;
}
else if (parseFloat(player.playbackRate) == .5) {
player.playbackRate = .75;
}
else if (parseFloat(player.playbackRate) == .75) {
player.playbackRate = 1;
}
else if (parseFloat(player.playbackRate) == 1) {
player.playbackRate = 1.25;
}
else if (parseFloat(player.playbackRate) == 1.25) {
player.playbackRate = 1.5;
}
else if (parseFloat(player.playbackRate) == 1.5) {
player.playbackRate = 1.75;
}
else if (parseFloat(player.playbackRate) == 1.75) {
player.playbackRate = 2;
}
}
// Slow down playback
else if (keyPressed === '<') {
if (parseFloat(player.playbackRate) == .5) {
player.playbackRate = .25;
}
else if (parseFloat(player.playbackRate) == .75) {
player.playbackRate = .5;
}
else if (parseFloat(player.playbackRate) == 1) {
player.playbackRate = .75;
}
else if (parseFloat(player.playbackRate) == 1.25) {
player.playbackRate = 1;
}
else if (parseFloat(player.playbackRate) == 1.5) {
player.playbackRate = 1.25;
}
else if (parseFloat(player.playbackRate) == 1.75) {
player.playbackRate = 1.5;
}
else if (parseFloat(player.playbackRate) == 2) {
player.playbackRate = 1.75;
}
}
// If we're not holding down the shift key
if (!event.shiftKey) {
// Prev frame (24fps calculation)
if (keyPressed === ',') {
if (player.paused || player.ended) {
player.currentTime -= 0.04166666666666667;
}
}
// Next frame (24fps calculation)
if (keyPressed === '.') {
if (player.paused || player.ended) {
player.currentTime += 0.04166666666666667;
}
}
// Prev 5 seconds
if (keyPressed === 'arrowleft') {
player.currentTime -= 5;
}
// Next 5 seconds
if (keyPressed === 'arrowright') {
player.currentTime += 5;
}
// Toggle play/pause
if (keyPressed === ' ' || keyPressed === 'k') {
if (player.paused || player.ended) {
player.play();
}
else {
player.pause();
}
}
// Toggle mute
if (keyPressed === 'm') {
document.querySelector('.ytp-mute-button').click();
}
// Toggle fullscreen
if (keyPressed === 'f') {
let fullScreenButton = document.querySelector('.ytp-fullscreen-button');
if (fullScreenButton) {
fullScreenButton.click();
}
}
// Prev 10 seconds
else if (keyPressed === 'j') {
player.currentTime -= 10;
}
// Next 10 seconds
else if (keyPressed === 'l') {
player.currentTime += 10;
}
// Start of video
else if (keyPressed === 'home') {
player.currentTime = 0;
}
// End of video
else if (keyPressed === 'end') {
player.currentTime += player.duration;
}
// Skip to percentage
if (keyPressed === '0') {
player.currentTime = 0;
}
else if (keyPressed === '1') {
player.currentTime = ((player.duration / 100) * 10);
}
else if (keyPressed === '2') {
player.currentTime = ((player.duration / 100) * 20);
}
else if (keyPressed === '3') {
player.currentTime = ((player.duration / 100) * 30);
}
else if (keyPressed === '4') {
player.currentTime = ((player.duration / 100) * 40);
}
else if (keyPressed === '5') {
player.currentTime = ((player.duration / 100) * 50);
}
else if (keyPressed === '6') {
player.currentTime = ((player.duration / 100) * 60);
}
else if (keyPressed === '7') {
player.currentTime = ((player.duration / 100) * 70);
}
else if (keyPressed === '8') {
player.currentTime = ((player.duration / 100) * 80);
}
else if (keyPressed === '9') {
player.currentTime = ((player.duration / 100) * 90);
}
}
}
}
// Skip to time
function goodTube_iframe_skipTo(time) {
// Target the video
let videoElement = document.querySelector('video');
// If the video exists, restore the time
if (videoElement) {
videoElement.currentTime = parseFloat(time);
}
// Otherwise retry until the video exists
else {
setTimeout(goodTube_iframe_skipTo, 100);
}
}
// Pause
function goodTube_iframe_pause() {
// Target the video
let videoElement = document.querySelector('video');
// If the video exists, pause it
if (videoElement) {
videoElement.pause();
}
// Otherwise retry until the video exists
else {
setTimeout(goodTube_iframe_pause, 100);
}
}
// Mute
function goodTube_iframe_mute() {
// Target the video
let videoElement = document.querySelector('video');
// If the video exists, mute it
if (videoElement) {
videoElement.muted = true;
}
// Otherwise retry until the video exists
else {
setTimeout(goodTube_iframe_mute, 100);
}
}
// Unmute
function goodTube_iframe_unmute() {
// Target the video
let videoElement = document.querySelector('video');
// If the video exists, unmute it
if (videoElement) {
videoElement.muted = false;
}
// Otherwise retry until the video exists
else {
setTimeout(goodTube_iframe_unmute, 100);
}
}
// Play
function goodTube_iframe_play() {
// Target the video
let videoElement = document.querySelector('video');
// If the video exists, restore the time
if (videoElement) {
videoElement.play();
}
// Otherwise retry until the video exists
else {
setTimeout(goodTube_iframe_pause, 100);
}
}
// Fix fullscreen button issues
function goodTube_iframe_fixFullScreenButton() {
let fullScreenButton = document.querySelector('.ytp-fullscreen-button');
if (fullScreenButton) {
fullScreenButton.setAttribute('aria-disabled', 'false');
if (document.querySelector('.ytp-fullscreen')) {
fullScreenButton.setAttribute('title', 'Exit full screen (f)');
}
else {
fullScreenButton.setAttribute('title', 'Full screen (f)');
}
}
}
/* Proxy iframe functions
------------------------------------------------------------------------------------------ */
// Init
function goodTube_proxyIframe_init() {
// Hide the body content with an overlay
let overlay = document.createElement('div');
document.body.appendChild(overlay);
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.bottom = '0';
overlay.style.right = '0';
overlay.style.left = '0';
overlay.style.background = '#000000';
overlay.style.zIndex = '99998';
// Remove scrolling
document.body.style.overflow = 'hidden';
// Wait for the DOM to load
document.addEventListener("DOMContentLoaded", () => {
// Hide the DOM elements from the proxy page
let elements = document.querySelectorAll('body > *:not(.goodTube_overlay)');
elements.forEach(element => {
element.style.display = 'none';
element.style.opacity = '0';
element.style.visibility = 'hidden';
});
// Change the background colour
document.body.style.background = '#000000';
// Create a youtube iframe
let youtubeIframe = document.createElement('div');
// Add the youtube iframe to the page
document.body.appendChild(youtubeIframe);
// Update the content of the youtube iframe
youtubeIframe.innerHTML = `
<iframe
width="100%"
height="100%"
src=""
frameborder="0"
scrolling="yes"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
id="goodTube_youtube_iframe"
></iframe>
`;
// Style the youtube iframe
youtubeIframe.style.position = 'fixed';
youtubeIframe.style.top = '0';
youtubeIframe.style.bottom = '0';
youtubeIframe.style.right = '0';
youtubeIframe.style.left = '0';
youtubeIframe.style.zIndex = '99999';
// Listen for messages from the parent window
window.addEventListener('message', goodTube_proxyIframe_receiveMessage);
});
}
// Receive a message from the parent window
function goodTube_proxyIframe_receiveMessage(event) {
// Make sure some data exists
if (typeof event.data !== 'string') {
return;
}
// Target the youtube iframe
let youtubeIframe = document.getElementById('goodTube_youtube_iframe');
// Make sure we found the youtube iframe
if (youtubeIframe) {
// Change the source of the youtube iframe
if (event.data.indexOf('goodTube_src_') !== -1) {
youtubeIframe.src = event.data.replace('goodTube_src_', '');
}
// Pass all other messages down to the youtube iframe
else {
youtubeIframe.contentWindow.postMessage(event.data, '*');
}
}
}
/* Picture in picture
------------------------------------------------------------------------------------------ */
// Init
function goodTube_pip_init() {
// If we leave the picture in picture
addEventListener('leavepictureinpicture', (event) => {
goodTube_pip = false;
// Set the picture in picture state in the top window
window.top.postMessage('goodTube_pip_false', '*');
});
// If we enter the picture in picture
addEventListener('enterpictureinpicture', (event) => {
goodTube_pip = true;
// Set the picture in picture state in the top window
window.top.postMessage('goodTube_pip_true', '*');
});
}
// Update
function goodTube_pip_update() {
if (!goodTube_pip) {
return;
}
// Support play and pause (but only attach these events once!)
if ("mediaSession" in navigator) {
// Next track
if (goodTube_nav_nextButton) {
navigator.mediaSession.setActionHandler("nexttrack", () => {
// Tell the top frame to go to the next video
window.top.postMessage('goodTube_nextVideo', '*');
});
}
else {
navigator.mediaSession.setActionHandler('nexttrack', null);
}
// Prev track
if (goodTube_nav_prevButton) {
navigator.mediaSession.setActionHandler("previoustrack", () => {
// Tell the top frame to go to the previous video
window.top.postMessage('goodTube_prevVideo', '*');
});
}
else {
navigator.mediaSession.setActionHandler('previoustrack', null);
}
}
}
/* Start GoodTube
------------------------------------------------------------------------------------------ */
// Youtube page
if (window.top === window.self) {
goodTube_init();
}
// Proxy iframe embed
else if (window.location.href.indexOf('?goodTube=1') !== -1) {
goodTube_proxyIframe_init();
}
// Iframe embed
else if (window.location.href.indexOf('youtube.com') !== -1) {
goodTube_iframe_init();
}
})();