// ==UserScript== // @name GoodTube Testing // @namespace http://tampermonkey.net/ // @version 1.002 // @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 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 = ` `; // Expose the player and wrapper globally goodTube_playerWrapper = document.querySelector('#goodTube_playerWrapper'); goodTube_player = goodTube_playerWrapper.querySelector('iframe'); // Setup player dynamic positioning and sizing goodTube_player_positionAndSize(); // Run the actions goodTube_actions(); } // Position and size the player 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 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 = ''; // 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', ''); // 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 = ` `; // 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(); } })();