Maximizer For YouTube™

Maximizes the YouTube player to fill the entire browser viewport when in theater mode, plus a few other enhancements.

目前為 2018-08-26 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Maximizer For YouTube™
// @description Maximizes the YouTube player to fill the entire browser viewport when in theater mode, plus a few other enhancements.
// @license     MIT
// @author      Rotem Dan <[email protected]>
// @match       https://www.youtube.com/*
// @version     0.2.1
// @run-at      document-start
// @grant       none
// @namespace   https://github.com/rotemdan
// @homepageURL https://github.com/rotemdan/MaximizerForYouTube
// @require     https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js
// @require     https://code.jquery.com/jquery-3.3.1.slim.min.js
// ==/UserScript==

////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utility definitions
////////////////////////////////////////////////////////////////////////////////////////////////////////

const debugModeEnabled = true;
function log(...args) {
	if (debugModeEnabled) {
		console.log("[MaximizerForYoutube]", ...args)
	}
}

// Try to emulate setImmediate() like execution:
function setImmediate(func) {
	const channel = new MessageChannel();
	channel.port1.onmessage = () => func();
	channel.port2.postMessage("");
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
// Core script modification functions
////////////////////////////////////////////////////////////////////////////////////////////////////////

// Install or uninstall full-size player page stylesheet if needed
function installOrUninstallPlayerModIfNeeded() {
	if (inWatchPage() && theaterModeEnabled()) {
		if ($("#MaximizerForYouTube_PlayerMod").length == 0) {
			const styleSheet = `
					<style id='MaximizerForYouTube_PlayerMod' type='text/css'>
						ytd-page-manager { margin-top: 0px !important; }

						#masthead-container { visibility: hidden; opacity: 0; transition: opacity 0.2s ease-in-out; }

						#player-theater-container { height: 100vh !important; min-height: 0vh !important; max-height: 100vh !important; }

						:focus { outline: 0; }

						#movie_player { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }

						body::-webkit-scrollbar {
							width: 10px;
							background-color: #000000;
						}

						body::-webkit-scrollbar-track {
							border-radius: 10px;
							background: rgba(0,0,0,0.1);
							border: 1px solid #383838;
						}

						body::-webkit-scrollbar-thumb {
							border-radius: 10px;
							background: linear-gradient(left, #fff, #e4e4e4);
							border: 1px solid #5b5b5b;
						}

						body::-webkit-scrollbar-thumb:hover {
							background: #fff;
						}
					</style>`;

			$("head").append(styleSheet);

			log("Player mod installed");
		}

		if (pageScrolledToTop())
			hideTopBar();
	} else {
		if ($("#MaximizerForYouTube_PlayerMod").length > 0) {
			$("#MaximizerForYouTube_PlayerMod").remove();
			showTopBar();

			log("Player mod uninstalled");
		}
	}
}

// Automatically shows/hides the top bar based on different properties of the view.
function installTopBarAutohide() {
	function onPageScroll() {
		if (!inWatchPage())
			return;

		if (pageScrolledToTop() && theaterModeEnabled())
			hideTopBar();
		else
			showTopBar();
	}

	function onKeyDown(e) {
		if (!inWatchPage() || !theaterModeEnabled())
			return;

		if (e.which === 27) { // Handle escape key
			log("esc");

			if (pageScrolledToTop()) {
				if (topBarIsVisible()) {
					hideTopBar();
					e.stopPropagation();
				} else {
					showTopBar();

					setTimeout(() => $("input#search").focus(), 50);
				}
			}
		}
	}

	function installEscHandlerToSearchInput() {
		let inputElement = $("input#search");

		if (inputElement.length > 0) {
			inputElement.on("keydown", onKeyDown);
			//inputElement[0].addEventListener("keydown", onKeyDown, true);
			log("Esc handler installed on search input");
		} else {
			setTimeout(() => installEscHandlerToSearchInput(), 50);
		}
	}

	installEscHandlerToSearchInput();
	$(document).on("keydown", onKeyDown);
	$(document).on("scroll", onPageScroll);
}

// Continuously auto-focus the player keyboard input when some conditions are met.
function installPlayerInputAutoFocus() {
	function autoFocusIfNeeded() {
		if (inWatchPage() && !topBarIsVisible()) {
			getVideoContainer().focus();
		}

		setTimeout(autoFocusIfNeeded, 20);
	}

	autoFocusIfNeeded();
}

function installPlayerKeyboardShortcutExtensions() {
	// Install keyboard shortcut extensions
	function onKeyDown(e) {
		if (!inWatchPage())
			return;

		if (getVideoContainer().is(":focus")) {
			if (e.ctrlKey) {
				if (e.which === 37) { // Handle ctl + left key
					var previousButton = $("a.ytp-prev-button")[0];
					if (previousButton)
						previousButton.click();
				}
				else if (e.which === 39) { // Handle ctl + right key
					var nextButton = $("a.ytp-next-button")[0]
					if (nextButton)
						nextButton.click();
				}
			}
		}
	}

	$(document).on("keydown", onKeyDown);
}

// Expands video description
function ensureExpandedVideoDescription() {
	setInterval(() => {
		$("ytd-expander.description, ytd-expander.ytd-video-secondary-info-renderer").removeAttr("collapsed")
	}, 50);
}

// Expands video description
function ensureModdedTheaterModeButton() {
	setInterval(() => {
		const playerModeButton = $("button.ytp-size-button");

		if (playerModeButton.length === 0 || playerModeButton.hasClass("MaximizerForYouTube_PlayerMod_Modded"))
			return;

		playerModeButton.on("click", () => {
			setTimeout(() => {
				installOrUninstallPlayerModIfNeeded();

				const resizeEvent = new Event('resize');
				window.dispatchEvent(resizeEvent);
			}, 0)
		})

		playerModeButton.addClass("MaximizerForYouTube_PlayerMod_Modded");
	}, 50);
}

function ensurePlayerIsAlwaysPaused() {
	setInterval(() => {
		const player = getVideoPlayer().get(0);
		if (player) {
			player.pause();
		}
	}, 1000);
}

function hideSPFLoadingBar() {
	$("head").append("<style>#progress, yt-page-navigation-progress {display: none !important}</style>");
}

// Pauses playing videos in other tabs when a video play event is detected (works in both watch and channel page videos)
function ensurePlayerAutoPause() {
	const videoPlayer = getVideoPlayer();

	if (videoPlayer.length > 0 && !videoPlayer.hasClass("MaximizerForYouTube_Modded_Autopause")) {
		// Generate a random script instance ID
		const instanceID = Math.random().toString();

		function onVideoPlay() {
			log("onVideoPlay")
			localStorage["MaximizerForYouTube_PlayingInstanceID"] = instanceID;

			function pauseWhenAnotherPlayerStartsPlaying() {
				if (localStorage["MaximizerForYouTube_PlayingInstanceID"] !== instanceID)
					videoPlayer[0].pause();
				else
					setTimeout(pauseWhenAnotherPlayerStartsPlaying, 20);
			}

			pauseWhenAnotherPlayerStartsPlaying();
		}

		// If video isn't paused on startup, fire the handler immediately
		if (!videoPlayer[0].paused)
			onVideoPlay();

		// Add event handler for the "play" event.
		videoPlayer.on("play", onVideoPlay);

		// Mark the player as modded to ensure the autopause mod isn't installed again
		videoPlayer.addClass("MaximizerForYouTube_Modded_Autopause");
	}

	setTimeout(ensurePlayerAutoPause, 50);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utility functions
////////////////////////////////////////////////////////////////////////////////////////////////////////

// Get the video player element
function getVideoContainer() {
	// Note: the channel page has another hidden video except the main one (if it exists). The hidden video doesn't have an "src" attribute.
	return $("div.html5-video-player");
}

// Get the video player element
function getVideoPlayer() {
	// Note: the channel page has another hidden video except the main one (if it exists). The hidden video doesn't have an "src" attribute.
	return $('.html5-main-video').filter(function (index) { return $(this).attr("src") !== undefined });
}

// Get the top bar element
function getTopBar() {
	return $("#masthead-container");
}

function showTopBar() {
	getTopBar().css("visibility", "visible");
	getTopBar().css("opacity", "1");
}

function hideTopBar() {
	getTopBar().css("opacity", "0");
	getTopBar().css("visibility", "hidden");
}

function pageScrolledToTop() {
	return $(document).scrollTop() === 0;
}

function scrollPageToTopIfNeeded() {
	setTimeout(() => {
		if (inWatchPage() && $(document).scrollTop() > 0) {
			log("Scrolling page to top");
			$(document).scrollTop(0);
		}
	}, 20);
}

function topBarIsVisible() {
	return getTopBar().css("visibility") === "visible";
}

function inWatchPage() {
	return location.href.indexOf("https://www.youtube.com/watch?") === 0;
}

function theaterModeEnabled() {
	return Cookies.get("wide") === "1";
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
// Event handlers
////////////////////////////////////////////////////////////////////////////////////////////////////////

let installInterval;

function onDocumentStart() {
	log("onDocumentStart")
	log("Script loaded, theater mode enabled:", theaterModeEnabled());

	//installOrUninstallPlayerModIfNeeded();

	installInterval = setInterval(() => {
		installOrUninstallPlayerModIfNeeded();
		//log("Trying to install mod")
	}, 1);
}

function onDocumentEnd() {
	log("onDocumentEnd");

	clearInterval(installInterval)
	installOrUninstallPlayerModIfNeeded();

	//ensurePlayerIsAlwaysPaused();

	hideSPFLoadingBar();
	installTopBarAutohide();
	installPlayerInputAutoFocus();
	installPlayerKeyboardShortcutExtensions();
	ensureExpandedVideoDescription();
}

function onWindowLoad() {
	log("onWindowLoad");

	ensureModdedTheaterModeButton();
	ensurePlayerAutoPause();
}

function onNavigation() {
	log("onNavigation, new location:", location.href);

	//scrollPageToTopIfNeeded();
	installOrUninstallPlayerModIfNeeded();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
// Install event handlers and start script
////////////////////////////////////////////////////////////////////////////////////////////////////////

function startScript() {
	document.addEventListener('DOMContentLoaded', onDocumentEnd, false);
	$(window).on("load", onWindowLoad);
	$(window).on("yt-navigate-start", () => { log("yt-navigate-start"), onNavigation() });
	$(window).on("popstate", () => { log("popstate"); onNavigation() });

	onDocumentStart();
}

if (window.self === window.top) {
	startScript();
}