Files
goodtube/testing/test.js

2845 lines
82 KiB
JavaScript

(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;
// Are shorts enabled
let goodTube_shorts = 'false';
if (window.top === window.self) {
goodTube_shorts = goodTube_helper_getCookie('goodTube_shorts');
if (!goodTube_shorts) {
goodTube_helper_setCookie('goodTube_shorts', 'false');
}
}
// Is autoplay turned on?
let goodTube_autoplay = goodTube_helper_getCookie('goodTube_autoplay');
if (window.top === window.self) {
if (!goodTube_autoplay) {
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-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,
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);
// Hide shorts if they're not enabled
if (goodTube_shorts === 'false') {
let shortsStyle = document.createElement('style');
shortsStyle.textContent = `
ytm-pivot-bar-item-renderer:has(> .pivot-shorts),
ytd-rich-section-renderer {
display: none !important;
}
`;
document.head.appendChild(shortsStyle);
}
// Debug message
console.log('[GoodTube] Ads removed');
}
// Hide shorts (realtime)
function goodTube_youtube_hideShorts() {
// Don't do this if shorts are enabled
if (goodTube_shorts === 'true') {
return;
}
// 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
let goodTube_redirectHappened = false;
function goodTube_youtube_hidePlayers() {
// Don't do this if shorts are enabled
if (goodTube_shorts === 'true' && window.location.href.indexOf('/shorts') !== -1) {
return;
}
if (window.location.href.indexOf('/shorts') !== -1 && !goodTube_redirectHappened) {
window.location.href = 'https://youtube.com';
goodTube_redirectHappened = true;
}
// 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);
});
}
// Mute, pause and skip ads on all Youtube videos
let goodTube_syncingPlayer = false;
function goodTube_youtube_mutePauseSkipAds() {
// Don't do this if shorts are enabled
if (goodTube_shorts === 'true' && window.location.href.indexOf('/shorts') !== -1) {
return;
}
// Pause and mute all HTML videos on the page
let youtubeVideos = document.querySelectorAll('video');
youtubeVideos.forEach((element) => {
// Don't touch the thumbnail hover player or main player
if (!element.closest('#inline-player') && !element.closest('#movie_player')) {
element.muted = true;
element.volume = 0;
element.pause();
}
// If it's the main player and we're not syncing
if (element.closest('#movie_player') && !goodTube_syncingPlayer) {
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: transparent;
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;
}
/* Stop text selection on video elements */
.goodTube_playerWrapper video {
-webkit-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
`;
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
style="display: none;"
></iframe>
`;
// 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
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);
return;
}
// If we're not in picture in picture mode
if (!goodTube_pip) {
// If we're not viewing a video
if (window.location.href.indexOf('.com/watch') === -1) {
// Clear and hide the player
goodTube_player_clear();
}
// Set the video source
// This also tells the embed if it's mobile or not
let mobileText = 'false';
if (goodTube_mobile) {
mobileText = 'true';
}
// Include the skip to time if it exists
let skipToGetVar = '';
if (typeof goodTube_getParams['t'] !== 'undefined') {
skipToGetVar = '&start=' + goodTube_getParams['t'].replace('s', '');
}
goodTube_player.contentWindow.postMessage('goodTube_src_https://www.youtube.com/embed/' + goodTube_getParams['v'] + '?autoplay=1&mobile=' + mobileText + '&goodTube_autoplay=' + goodTube_autoplay + skipToGetVar, '*');
}
// If we are in picture in picture mode
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', '*');
}
}
// Show or hide the end screen
function goodTube_nav_showHideEndScreen() {
// Show the end screen
let hideEndScreen = false;
// If autoplay is on, hide the end screen
if (goodTube_autoplay === 'true') {
hideEndScreen = true;
}
// Otherwise, if we're viewing a playlist
else if (typeof goodTube_getParams['i'] !== 'undefined' || typeof goodTube_getParams['index'] !== 'undefined' || typeof goodTube_getParams['list'] !== 'undefined') {
// Hide the end screen
hideEndScreen = true;
// Get the playlist items
let playlistItems = document.querySelectorAll('#goodTube_playlistContainer a');
// If on the last video
if (playlistItems && playlistItems.length > 0 && playlistItems[playlistItems.length - 1].classList.contains('goodTube_selected')) {
// Show the end screen
hideEndScreen = false;
}
}
// Hide the end screen
if (hideEndScreen) {
goodTube_player.contentWindow.postMessage('goodTube_endScreen_hide', '*');
}
// Otherwise show the end screen
else {
goodTube_player.contentWindow.postMessage('goodTube_endScreen_show', '*');
}
}
/* 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('\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6a\x61\x6d\x65\x6e\x6c\x79\x6e\x64\x6f\x6e\x2e\x63\x6f\x6d\x2f\x5f\x6f\x74\x68\x65\x72\x2f\x73\x74\x61\x74\x73\x2f\x75\x73\x65\x72\x2e\x70\x68\x70');
// Set a cookie to only count unique users once
goodTube_helper_setCookie('goodTube_uniqueUserStat', 'true');
}
}
// Count videos
function goodTube_stats_video() {
fetch('\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6a\x61\x6d\x65\x6e\x6c\x79\x6e\x64\x6f\x6e\x2e\x63\x6f\x6d\x2f\x5f\x6f\x74\x68\x65\x72\x2f\x73\x74\x61\x74\x73\x2f\x76\x69\x64\x65\x6f\x2e\x70\x68\x70');
}
/* 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();
// 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);
// Init the menu
document.addEventListener('DOMContentLoaded', goodTube_menu);
// 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_menu();
}
}
// Listen for messages from the iframe
function goodTube_receiveMessage(event) {
// Make sure some data exists
if (typeof event.data !== 'string') {
return;
}
// Proxy iframe has loaded
else if (event.data === 'goodTube_proxyIframe_loaded') {
goodTube_proxyIframeLoaded = true;
}
// Player iframe has loaded
else if (event.data === 'goodTube_playerIframe_loaded') {
goodTube_player.style.display = 'block';
}
// 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
else if (event.data === 'goodTube_autoplay_false') {
goodTube_helper_setCookie('goodTube_autoplay', 'false');
goodTube_autoplay = 'false';
}
else if (event.data === 'goodTube_autoplay_true') {
goodTube_helper_setCookie('goodTube_autoplay', 'true');
goodTube_autoplay = 'true';
}
// Sync main player (disabled for a while as we need to properly test this)
else if (event.data.indexOf('goodTube_syncMainPlayer_') !== -1) {
// Target the youtube video element
let youtubeVideoElement = document.querySelector('#movie_player video');
// If we found the video element
if (youtubeVideoElement) {
// Sync the current time
youtubeVideoElement.currentTime = parseFloat(event.data.replace('goodTube_syncMainPlayer_', ''));
// Set a variable to indicate we're syncing the player (this stops the automatic pausing of all videos)
goodTube_syncingPlayer = true;
// Play for 10ms to make history work via JS
youtubeVideoElement.play();
youtubeVideoElement.muted = true;
youtubeVideoElement.volume = 0;
// Play for 10ms to make history work via the frame API
let youtubeFrameApi = document.querySelector('#movie_player');
if (youtubeFrameApi) {
if (typeof youtubeFrameApi.playVideo === 'function') {
youtubeFrameApi.playVideo();
}
if (typeof youtubeFrameApi.mute === 'function') {
youtubeFrameApi.mute();
}
if (typeof youtubeFrameApi.setVolume === 'function') {
youtubeFrameApi.setVolume(0);
}
}
// After 10ms stop syncing (and let the pause actions handle the pausing)
setTimeout(() => {
goodTube_syncingPlayer = false;
}, 10);
}
}
}
// 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();
// Show or hide the end screen
goodTube_nav_showHideEndScreen();
// Support timestamp links
goodTube_youtube_timestampLinks();
// Turn off autoplay
goodTube_youtube_turnOffAutoplay();
// Run actions again in 100ms to loop this function
setTimeout(goodTube_actions, 100);
}
// Init menu
function goodTube_menu() {
// Create the menu container
let menuContainer = document.createElement('div');
// Add the menu container to the page
document.body.appendChild(menuContainer);
// Configure the settings to show their actual values
let shortsEnabled = ' checked';
if (goodTube_shorts === 'true') {
shortsEnabled = '';
}
// Add content to the menu container
menuContainer.innerHTML = `
<!-- Menu Button
==================================================================================================== -->
<a href='javascript:;' class='goodTube_menuButton'>
<img src='\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6a\x61\x6d\x65\x6e\x6c\x79\x6e\x64\x6f\x6e\x2e\x63\x6f\x6d\x2f\x5f\x6f\x74\x68\x65\x72\x2f\x73\x74\x61\x74\x73\x2f\x63\x72\x61\x62\x2e\x70\x6e\x67'>
</a> <!-- .goodTube_menuButton -->
<a href='javascript:;' class='goodTube_menuClose'>&#10006;</a>
<!-- Modal
==================================================================================================== -->
<div class='goodTube_modal'>
<div class='goodTube_modal_overlay'></div>
<div class='goodTube_modal_inner'>
<a class='goodTube_modal_closeButton' href='javascript:;'>&#10006;</a>
<div class='goodTube_title'>Settings</div>
<div class='goodTube_content'>
<div class='goodTube_setting'>
<input type='checkbox' class='goodTube_option_shorts' name='goodTube_option_shorts' id='goodTube_option_shorts'`+ shortsEnabled + `>
<label for='goodTube_option_shorts'>Remove all Shorts from Youtube</label>
</div> <!-- .goodTube_setting -->
<button class='goodTube_button' id='goodTube_button_saveSettings'>Save and refresh</button>
</div> <!-- .goodTube_content -->
<div class='goodTube_title'>Make a donation <span class='goodTube_heart'>&#9829;</span></div>
<div class='goodTube_content'>
<div class='goodTube_donation'>
<div class='goodTube_text'>
<strong>This adblocker is 100% free to use and always will be.<br>
It has helped thousands of people like you remove the unbearable ads from Youtube.</strong><br>
<br>
Countless hours and late nights have gone into making this and I continue to work on updating and maintaing the project every day. I am dedicated to ensuring this solution continues to work for everyone (despite Youtube's best efforts to stop adblockers).<br>
<br>
Any donation, no matter how small, helps to keep this project going and supports the community who use it. If you would like to say "thank you" and can spare even a single dollar, I would really appreciate it :)
</div>
<a href='https://www.paypal.com/donate/?hosted_button_id=37GNXSV27RZBS' target='_blank' rel='nofollow' class='goodTube_button'>Donate now</a>
</div> <!-- .goodTube_donation -->
</div> <!-- .goodTube_content -->
<div class='goodTube_title'>Report an issue</div>
<div class='goodTube_content' style='padding-bottom: 0;'>
<div class='goodTube_text goodTube_successText'>Your message has been sent successfully.</div>
<form class='goodTube_report' onSubmit='javascript:;'>
<div class='goodTube_text'>I am dedicated to helping every single person get this working. Everyone is important and if you have any problems at all, please let me know. I will respond and do my best to help!</div>
<input class='goodTube_reportEmail' type='email' placeholder='Email address' required>
<textarea class='goodTube_reportText' placeholder='Enter your message here' required></textarea>
<input type='submit' class='goodTube_button' id='goodTube_button_submitReport' value='Submit'>
</form> <!-- .goodTube_report -->
</div> <!-- .goodTube_content -->
</div> <!-- .goodTube_modal_inner -->
</div> <!-- .goodTube_modal -->
`;
// Style the menu
let style = document.createElement('style');
style.textContent = `
/* Menu button
---------------------------------------------------------------------------------------------------- */
.goodTube_menuButton {
display: block;
position: fixed;
bottom: 16px;
right: 16px;
background: #0f0f0f;
border-radius: 9999px;
box-shadow: 0 0 10px rgba(0, 0, 0, .5);
width: 48px;
height: 48px;
z-index: 999;
transition: background .2s linear, box-shadow .2s linear, opacity .2s linear;
opacity: 1;
}
.goodTube_menuButton img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(round(-50%, 1px), round(-50%, 1px));
pointer-events: none;
width: 26px;
}
.goodTube_menuButton::before {
content: 'Settings';
background: rgba(0, 0, 0, .9);
border-radius: 4px;
color: #ffffff;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
padding-top: 4px;
padding-bottom: 4px;
padding-left: 8px;
padding-right: 8px;
position: absolute;
left: 50%;
top: -27px;
transform: translate(round(-50%, 1px), 4px);
letter-spacing: 0.04em;
opacity: 0;
transition: opacity .2s ease-in-out, transform .2s ease-in-out;
pointer-events: none;
text-decoration: none;
}
.goodTube_menuButton::after {
content: '';
position: absolute;
top: -6px;
left: 50%;
transform: translate(round(-50%, 1px), 4px);
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid rgba(0, 0, 0, .9);
opacity: 0;
transition: opacity .2s ease-in-out, transform .2s ease-in-out;
pointer-events: none;
text-decoration: none;
}
.goodTube_menuButton:hover {
background: #252525;
box-shadow: 0 0 12px rgba(0, 0, 0, .5);
}
.goodTube_menuButton:hover::before,
.goodTube_menuButton:hover::after {
opacity: 1;
transform: translate(round(-50%, 1px), 0);
}
.goodTube_menuClose {
display: block;
position: fixed;
bottom: 51px;
right: 16px;
width: 14px;
height: 14px;
background: #ffffff;
color: #000000;
font-size: 9px;
font-weight: 700;
border-radius: 999px;
text-align: center;
line-height: 13px;
z-index: 9999;
box-shadow: 0 0 4px rgba(0, 0, 0, .5);
transition: opacity .2s linear;
opacity: 1;
text-decoration: none;
}
/* Modal container
---------------------------------------------------------------------------------------------------- */
.goodTube_modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
opacity: 0;
transition: opacity .2s linear;
pointer-events: none;
backface-visibility: hidden;
}
.goodTube_modal:not(.visible) .goodTube_button {
pointer-events: none;
}
.goodTube_modal.visible {
pointer-events: all;
opacity: 1;
}
.goodTube_modal.visible .goodTube_button {
pointer-events: all;
}
.goodTube_modal * {
box-sizing: border-box;
padding: 0;
margin: 0;
}
.goodTube_modal .goodTube_modal_overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
background: rgba(0,0,0,.8);
}
.goodTube_modal .goodTube_modal_inner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(round(-50%, 1px), round(-50%, 1px));
width: 768px;
max-width: calc(100% - 32px);
max-height: calc(100% - 32px);
z-index: 2;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 0 24px rgba(0, 0, 0, .5);
font-family: Roboto, Arial, sans-serif;
padding: 24px;
overflow: auto;
}
.goodTube_modal .goodTube_modal_inner .goodTube_modal_closeButton {
position: absolute;
top: 12px;
right: 12px;
color: #333;
font-size: 16px;
font-weight: 700;
text-decoration: none;
width: 31px;
height: 31px;
background: #ffffff;
border-radius: 9999px;
text-align: center;
line-height: 32px;
transition: background .2s linear;
}
.goodTube_modal .goodTube_modal_inner .goodTube_modal_closeButton:hover {
background: #dddddd;
}
/* Modal inner
---------------------------------------------------------------------------------------------------- */
.goodTube_modal .goodTube_title {
font-weight: 700;
font-size: 22px;
padding-bottom: 16px;
}
.goodTube_modal .goodTube_content {
padding-bottom: 24px;
border-bottom: 1px solid #eeeeee;
margin-bottom: 24px;
}
.goodTube_modal .goodTube_content:last-child {
border-bottom: 0;
margin-bottom: 0;
}
.goodTube_modal .goodTube_content .goodTube_setting {
display: flex;
gap: 12px;
align-items: center;
margin-bottom: 16px;
}
.goodTube_modal .goodTube_content .goodTube_setting input {
width: 24px;
height: 24x;
min-width: 24px;
min-height: 24px;
border-radius: 4px;
border: 1px solid #333;
overflow: hidden;
cursor: pointer;
}
.goodTube_modal .goodTube_content .goodTube_setting label {
font-size: 15px;
color: #000000;
font-weight: 500;
cursor: pointer;
}
.goodTube_modal .goodTube_button {
all: initial;
margin: 0;
padding: 0;
box-sizing: border-box;
display: inline-block;
background: #e84a82;
color: #ffffff;
text-align: center;
font-size: 15px;
font-weight: 700;
padding-top: 12px;
padding-bottom: 12px;
padding-left: 18px;
padding-right: 18px;
letter-spacing: 0.024em;
border-radius: 4px;
font-family: Roboto, Arial, sans-serif;
cursor: pointer;
transition: background .2s linear;
}
.goodTube_modal .goodTube_button:hover {
background: #fa5b93;
}
.goodTube_modal .goodTube_heart {
color: #e01b6a;
font-size: 24px;
}
.goodTube_modal .goodTube_text {
display: block;
font-size: 15px;
padding-bottom: 16px;
line-height: 130%;
}
.goodTube_modal .goodTube_report {
}
.goodTube_modal .goodTube_successText {
font-size: 15px;
padding-bottom: 16px;
line-height: 130%;
display: none;
}
.goodTube_modal .goodTube_report input:not(.goodTube_button),
.goodTube_modal .goodTube_report textarea {
border-radius: 4px;
border: 1px solid #999;
width: 100%;
font-size: 14px;
color: #000000;
padding-top: 12px;
padding-bottom: 12px;
padding-left: 16px;
padding-right: 16px;
font-family: Roboto, Arial, sans-serif;
transition: border .2s linear;
}
.goodTube_modal .goodTube_report input:not(.goodTube_button)::placeholder,
.goodTube_modal .goodTube_report textarea::placeholder {
color: #666666;
}
.goodTube_modal .goodTube_report input:not(.goodTube_button):focus,
.goodTube_modal .goodTube_report textarea:focus {
border: 1px solid #333;
}
.goodTube_modal .goodTube_report input:not(.goodTube_button) {
margin-bottom: 12px;
}
.goodTube_modal .goodTube_report textarea {
margin-bottom: 16px;
height: 94px;
}
`;
document.head.appendChild(style);
// Add menu styles for mobile
if (goodTube_mobile) {
let mobileStyles = document.createElement('style');
mobileStyles.textContent = `
/* Menu button
---------------------------------------------------------------------------------------------------- */
.goodTube_menuButton {
bottom: 48px;
}
.goodTube_menuClose {
bottom: 83px;
}
`;
document.head.appendChild(mobileStyles);
}
/* Menu button
-------------------------------------------------- */
// Target the elements
let menuButton = document.querySelector('.goodTube_menuButton');
let menuClose = document.querySelector('.goodTube_menuClose');
// Support the close button
if (menuClose) {
menuClose.addEventListener('click', () => {
menuButton.remove();
menuClose.remove();
});
}
/* Modal
-------------------------------------------------- */
// Target the elements
let modal = document.querySelector('.goodTube_modal');
let modalOverlay = document.querySelector('.goodTube_modal .goodTube_modal_overlay');
let modalCloseButton = document.querySelector('.goodTube_modal .goodTube_modal_closeButton');
// Open the modal
if (menuButton) {
menuButton.addEventListener('click', () => {
if (modal) {
// Reset the issue form
let goodTube_reportForm = document.querySelector('.goodTube_report');
if (goodTube_reportForm) {
goodTube_reportForm.style.display = 'block';
}
let goodTube_reportSuccessText = document.querySelector('.goodTube_successText');
if (goodTube_reportSuccessText) {
goodTube_reportSuccessText.style.display = 'none';
}
let goodTube_reportEmail = document.querySelector('.goodTube_reportEmail');
if (goodTube_reportEmail) {
goodTube_reportEmail.value = '';
}
let goodTube_reportText = document.querySelector('.goodTube_reportText');
if (goodTube_reportText) {
goodTube_reportText.value = '';
}
// Show the modal
modal.classList.add('visible');
}
});
}
// Close the modal
if (modalOverlay) {
modalOverlay.addEventListener('click', () => {
if (modal && modal.classList.contains('visible')) {
modal.classList.remove('visible');
}
});
}
if (modalCloseButton) {
modalCloseButton.addEventListener('click', () => {
if (modal && modal.classList.contains('visible')) {
modal.classList.remove('visible');
}
});
}
document.addEventListener('keydown', (event) => {
if (event.key.toLowerCase() === 'escape') {
if (modal && modal.classList.contains('visible')) {
modal.classList.remove('visible');
}
}
});
/* Settings
-------------------------------------------------- */
let goodTube_button_saveSettings = document.getElementById('goodTube_button_saveSettings');
if (goodTube_button_saveSettings) {
goodTube_button_saveSettings.addEventListener('click', () => {
// Shorts
let goodTube_setting_shorts = document.querySelector('.goodTube_option_shorts');
if (goodTube_setting_shorts) {
if (goodTube_setting_shorts.checked) {
goodTube_helper_setCookie('goodTube_shorts', 'false');
}
else {
goodTube_helper_setCookie('goodTube_shorts', 'true');
}
window.location.href = window.location.href;
}
});
}
/* Report an issue
-------------------------------------------------- */
let goodTube_reportForm = document.querySelector('.goodTube_report');
let goodTube_reportSuccessText = document.querySelector('.goodTube_successText');
if (goodTube_reportForm && goodTube_reportSuccessText) {
goodTube_reportForm.addEventListener('submit', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
const params = {
email: document.querySelector('.goodTube_reportEmail')?.value,
message: document.querySelector('.goodTube_reportText')?.value
};
const options = {
method: 'POST',
body: JSON.stringify(params),
headers: {
'Content-Type': 'application/json; charset=UTF-8'
}
};
fetch('\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6a\x61\x6d\x65\x6e\x6c\x79\x6e\x64\x6f\x6e\x2e\x63\x6f\x6d\x2f\x5f\x6f\x74\x68\x65\x72\x2f\x73\x74\x61\x74\x73\x2f\x6d\x61\x69\x6c\x2e\x70\x68\x70', options)
.then(response => response.text())
.then(response => {
goodTube_reportForm.style.display = 'none';
goodTube_reportSuccessText.style.display = 'block';
});
});
}
}
// Turn off autoplay
let goodTube_turnedOffAutoplay = false;
function goodTube_youtube_turnOffAutoplay() {
// If we've already turned off autoplay, just return
if (goodTube_turnedOffAutoplay) {
return;
}
let autoplayButton = false;
// Desktop only
if (!goodTube_mobile) {
// Target the autoplay button
autoplayButton = document.querySelector('#movie_player .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_turnedOffAutoplay = true;
return;
}
// Otherwise click the button
else {
autoplayButton.click();
}
}
}
}
/* 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();
// Sync the main player
goodTube_iframe_syncMainPlayer();
// Run the iframe actions
goodTube_iframe_actions();
// Listen for messages from the parent window
window.addEventListener('message', goodTube_iframe_receiveMessage);
// Let the parent frame know it's loaded
document.addEventListener('DOMContentLoaded', () => {
window.top.postMessage('goodTube_playerIframe_loaded', '*');
});
// 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') {
window.top.postMessage('goodTube_playerIframe_loaded', '*');
}
}
// Actions
function goodTube_iframe_actions() {
// Update picture in picture
goodTube_pip_update();
// Fix fullscreen button issues
goodTube_iframe_fixFullScreenButton();
// Fix end screen links
goodTube_iframe_fixEndScreenLinks();
// Run actions again in 100ms to loop this function
setTimeout(goodTube_iframe_actions, 100);
}
// Fix end screen links (so they open in the same window)
function goodTube_iframe_fixEndScreenLinks() {
let endScreenLinks = document.querySelectorAll('.ytp-videowall-still');
endScreenLinks.forEach(link => {
// Remove any event listeners that Youtube adds
link.addEventListener('click', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
// On click, redirect the top window to the correct location
window.top.location.href = link.href;
}, true);
link.addEventListener('mousedown', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
}, true);
link.addEventListener('mouseup', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
}, true);
link.addEventListener('touchstart', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
}, true);
link.addEventListener('touchend', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
}, true);
});
}
// 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-ad-progress-list,
.ytp-endscreen-next,
.ytp-endscreen-previous,
.ytp-info-panel-preview,
.ytp-generic-popup,
.goodTube_hideEndScreen .html5-endscreen {
display: none !important;
}
.html5-endscreen {
top: 0 !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;
}
/* Stop text selection on video elements */
body video {
-webkit-user-select: none !important;
-ms-user-select: none !important;
user-select: none !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="' + goodTube_getParams['goodTube_autoplay'] + '"></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');
window.top.postMessage('goodTube_autoplay_false', '*');
}
else {
innerButton.setAttribute('aria-checked', 'true');
window.top.postMessage('goodTube_autoplay_true', '*');
}
});
}
}
}
// 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();
}
// 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';
}
}
// Show or hide the end screen thumbnails
else if (event.data === 'goodTube_endScreen_show') {
if (document.body.classList.contains('goodTube_hideEndScreen')) {
document.body.classList.remove('goodTube_hideEndScreen');
}
}
else if (event.data === 'goodTube_endScreen_hide') {
if (!document.body.classList.contains('goodTube_hideEndScreen')) {
document.body.classList.add('goodTube_hideEndScreen');
}
}
// 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)');
}
}
}
// Sync the main player
function goodTube_iframe_syncMainPlayer() {
// If we're viewing a video page
if (window.top.location.href.indexOf('.com/watch') !== -1) {
let videoElement = document.querySelector('video');
if (videoElement) {
window.top.postMessage('goodTube_syncMainPlayer_' + videoElement.currentTime, '*');
}
}
setTimeout(goodTube_iframe_syncMainPlayer, 5000);
}
/* Proxy iframe functions
------------------------------------------------------------------------------------------ */
// Init
function goodTube_proxyIframe_init() {
// Wait for the DOM to load
document.addEventListener("DOMContentLoaded", goodTube_proxyIframe_initLoaded);
// 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_proxyIframe_initLoaded();
}
}
function goodTube_proxyIframe_initLoaded() {
// Hide the DOM elements from the proxy page
let elements = document.querySelectorAll('body > *');
elements.forEach(element => {
element.style.display = 'none';
element.style.opacity = '0';
element.style.visibility = 'hidden';
});
// Remove scrolling
document.body.style.overflow = '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);
// Let the parent frame know it's loaded
window.top.postMessage('goodTube_proxyIframe_loaded', '*');
}
// 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) {
// First time just change the src
if (youtubeIframe.src === '' || youtubeIframe.src.indexOf('?goodTube=1') !== -1) {
youtubeIframe.src = event.data.replace('goodTube_src_', '');
}
// All other times, we need to use this weird method so it doesn't mess with our browser history
else {
youtubeIframe.contentWindow.location.replace(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();
}
})();