YouTube Cinema Mode

Maximizes YouTube's video player to fill the entire browser viewport and fixes a few minor annoyances

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        YouTube Cinema Mode
// @description Maximizes YouTube's video player to fill the entire browser viewport and fixes a few minor annoyances
// @license     MIT
// @author      Rotem Dan <[email protected]>
// @match       https://www.youtube.com/*
// @version     0.2.4
// @run-at      document-start
// @grant       none
// @require     https://code.jquery.com/jquery-2.1.4.min.js
// @namespace   https://github.com/rotemdan
// @homepageURL https://github.com/rotemdan/YouTubeCinemaMode
// ==/UserScript==

////////////////////////////////////////////////////////////////
// Main Operations
////////////////////////////////////////////////////////////////

// Detect the type of YouTube page loaded
var isWatchPage = location.href.indexOf("https://www.youtube.com/watch?") === 0;

// Start or install operation handlers
onDocumentStart();
$(document).on("ready", onDocumentEnd);
$(window).on("load", onWindowLoad);

// Operations that are run as early as possible during page load
function onDocumentStart() {
	installUnsafewindowPolyfill();
	disableSPF();

	if (isWatchPage) {
		disableMatchMedia();
		installFullSizePlayerStylesheet();
	}
}

// Operations that run when DOMContentLoaded event is fired
function onDocumentEnd() {
	if (isWatchPage) {
		installTopBarAutohide();
		expandVideoDescription();
		installPlayerAutoFocus();
		installPlayerKeyboardShortcutExtensions();
		installPlaylistRepositioner();
	}

	installPlayerAutoPause();
}

// Operations that run when window is loaded (after YouTube scripts are run)
function onWindowLoad() {
	if (isWatchPage) {
		setTimeout(switchPlayerToTheaterMode, 2000);
	}
}

////////////////////////////////////////////////////////////////
// Methods
////////////////////////////////////////////////////////////////

// Ensure unsafeWindow object is available both in Firefox and Chrome
function installUnsafewindowPolyfill() {
	if (typeof unsafeWindow === 'undefined') {
		if (typeof XPCNativeWrapper === 'function' && typeof XPCNativeWrapper.unwrap === 'function')
			unsafeWindow = XPCNativeWrapper.unwrap(window);
		else if (window.wrappedJSObject)
			unsafeWindow = window.wrappedJSObject;
	}
}

// Disable SPF (Structured Page Fragments), which prevents properly attaching to page load events when navigation occurs
// Will also disable the red loading bar.
function disableSPF() {
	if (unsafeWindow._spf_state && unsafeWindow._spf_state.config) {
		unsafeWindow._spf_state.config['navigate-limit'] = 0;
		unsafeWindow._spf_state.config['navigate-part-received-callback'] = function (targetUrl) { location.href = targetUrl; }
	}

	setTimeout(disableSPF, 50);
}

// Disable matchMedia - allow proper resizing of the video player and its contents.
function disableMatchMedia() {
	window.matchMedia = undefined;
}

// Add full-size player page stylesheet (works correctly only when the player is in "theater" mode).
function installFullSizePlayerStylesheet() {
	var runningInChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);

	var styleSheet =
		"<style type='text/css'>\n" +
		"body { overflow-x: hidden !important; }\n" +
		"#masthead-positioner-height-offset { display: none }\n" +
		"#masthead-positioner { visibility: hidden; opacity: 0; transition: opacity 0.2s ease-in-out; }\n" +
		".player-width { width: 100% !important; margin: 0px !important; left: 0px !important; right: 0px !important; }\n" +
		".player-height { height: 100vh !important; }\n" +
		":focus { outline: 0; }\n";

	//if (!runningInChrome)
	styleSheet += "body { overflow-y: scroll !important; }\n";

	styleSheet += "</style>\n";

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

// Switch player to theater mode if in default mode.
function switchPlayerToTheaterMode() {
	if ($("div#page").hasClass("watch-non-stage-mode"))
		$("button.ytp-size-button, div.ytp-size-toggle-large").click();
}

// Automatically shows/hides the top bar based on different properties of the view.
function installTopBarAutohide() {
	var topBar = getTopBar();
	var videoPlayer = getVideoPlayer();
	var videoPlayerElement = videoPlayer[0];

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

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

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

	function toggleTopBar() {
		if (topBarIsVisible())
			hideTopBar();
		else
			showTopBar();
	}

	function getScrollTop() {
		return $(document).scrollTop();
	}

	function onPageScroll() {
		if (getScrollTop() > 0)
			showTopBar();
		else
			hideTopBar();
	}

	var searchInput = $("input#masthead-search-term");
	function onKeyDown(e) {
		if (e.which === 27) { // Handle escape key
			if (getScrollTop() === 0) {
				toggleTopBar();

				if (topBarIsVisible())
					setTimeout(function () { searchInput.focus() }, 200);

				e.stopPropagation();
			}
		}
	}

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

// Continuously auto-focus the player when some conditions are met.
function installPlayerAutoFocus() {
	function autoFocusIfNeeded() {
		var topbarVisibility = getTopBar().css("visibility");
		if (topbarVisibility !== undefined) {
			if (topbarVisibility === "hidden") {
				$(".html5-video-player").focus();
			}
			//else if ($(".html5-video-player").is(":focus") && $(document).scrollTop() > 0)
			//{
			//	$(".html5-video-player").blur();
			//}
		}

		setTimeout(autoFocusIfNeeded, 20);
	}

	autoFocusIfNeeded();
}

function installPlayerKeyboardShortcutExtensions() {
	// Install keyboard shortcut extensions
	function onPlayerKeyDown(e) {
		if ($(".html5-video-player").is(":focus")) {
			if (e.ctrlKey) {
				if (e.which === 37) { // Handle ctl + left key
					var previousButton = $("a.ytp-prev-button, div.ytp-button-prev")[0];
					if (previousButton)
						previousButton.click();
				}
				else if (e.which === 39) { // Handle ctl + right key
					var nextButton = $("a.ytp-next-button, div.ytp-button-next")[0]
					if (nextButton)
						nextButton.click();
				}
			}
		}
	}

	$(document).on("keydown", onPlayerKeyDown);

	$(".html5-video-player").on("keydown", function (e) {
		if (e.which === 38 || e.which === 40) {
			console.log("up or down");
			e.stopPropagation();
		}
	});
}

// Expands video description
function expandVideoDescription() {
	$("#action-panel-details").removeClass("yt-uix-expander-collapsed");
}

// Correct the positioning of the playlist on window resize.
function installPlaylistRepositioner() {
	$("#watch-appbar-playlist").removeClass("player-height");

	function onResize() {
		var playlistContainer = $("#watch-appbar-playlist");

		if (playlistContainer.length === 0)
			return;

		var playlistOffset = playlistContainer.offset();
		playlistOffset.top = $("#watch-header").offset().top;
		playlistContainer.offset(playlistOffset);
	}

	onResize();
	$(window).resize(onResize);
}

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

	if (videoPlayer.length === 0) {
		//console.log("Player not found, retrying in 100ms..");
		setTimeout(installPlayerAutoPause, 100);
		return;
	}

	var videoPlayerElement = videoPlayer.get(0);

	// Generate a random script instance ID
	var instanceID = Math.random().toString();

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

		function pauseWhenAnotherPlayerStartsPlaying() {
			if (localStorage["YouTubeCinemaMode_PlayingInstanceID"] !== instanceID)
				videoPlayerElement.pause();
			else
				setTimeout(pauseWhenAnotherPlayerStartsPlaying, 10);
		}

		pauseWhenAnotherPlayerStartsPlaying();
	}

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

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

// 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 $('video.html5-main-video').filter(function (index) { return $(this).attr("src") !== undefined });
}

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