PKGA YouTube Theater Mode

A more fullscreen experience for theater mode

当前为 2023-06-29 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name	 	PKGA YouTube Theater Mode
// @description A more fullscreen experience for theater mode
// @license GNU GPLv3
// @namespace /lig/
// @version		6
// @grant GM.info
// @include		https://*.youtube.com/*
// ==/UserScript==
const appendStyle = (css, id = '', doc = document) => {
	const style = document.createElement('style');
	style.id = id;
	style.innerHTML = css;
	doc.head.appendChild(style);
	return style;
}

// main style
const mainCSS = `
#ytc-filter .vc-toolbar button[title="Show ytcFilter"] {
	right: 0 !important;
	position: absolute;
}

button.html5-video-info-panel-close {
	left: 5px;
	right: unset !important;
}

.seventv-yt-theater-mode-button-container {
	display: none;
}

.seventv-overlay > div > div {
	transform: none !important;
	width: 100%;
	height: 100%;
}

.seventv-emote-menu {
	box-sizing: border-box;
	left: unset !important;
	max-width: 100% !important;
	max-height: calc(100% - 105px);
}

.seventv-emote-menu .seventv-emote-menu-tab {
	max-height: 30px !important;
	min-height: 20px;
}

.seventv-emote-menu-header > button:after {
	color: white !important;
	font-weight: bold;
	margin: auto;
	margin-left: 5px;
}

.seventv-emote-menu-header > button:nth-child(1):after {
	content: "7TV";
}

.seventv-emote-menu-header > button:nth-child(2):after {
	content: "BTTV";
}

.seventv-emote-menu-header > button:nth-child(3):after {
	content: "FFZ";
}

.seventv-emote-menu-header > button {
	border-color: #aaa;
	border-width: 1px;
	border-radius: .4rem;
}

.seventv-emote-menu-header > button.selected {
	border-color: #fff;
}

.seventv-emote-menu .seventv-emote-menu-scrollable {
	padding-top: 0;
}

.seventv-emote-menu .seventv-emote-menu-tab img {
	width: auto;
	height: 25px;
	margin-left: auto;
}

.seventv-emote-menu-scrollable {
	max-height: calc();
}

.iaextractor-webx-iframe {
	z-index: 10000;
}

* {
	scrollbar-color: #727273 #171719;
}

::-webkit-scrollbar-button {
	background: #727273 !important;
}

::-webkit-scrollbar-thumb {
	background: #727273 !important;
}

::-webkit-scrollbar-track {
	background: #171719 !important;
}

app-drawer[opened] {
	z-index: 10000
}

#clarify-box {
	display:none !important;
}

/*  Chat container  */
body.theater ytd-live-chat-frame#chat {
	/*min-height: 0 !important;
	height: 40px !important;
	min-width: 0 !important;
	width: 50px;
	overflow: hidden;
	border: hidden;*/
	margin: 0 !important;
	position: absolute;
	top: 0;
	right: 0;
	width: calc(12.79vw) !important;
	min-width: 285px;
	max-width: 440px;
	border-width: 0px !important;
	pointer-events: none;
	overflow: visible !important;
}

/*  Chat panel  */
body.theater ytd-live-chat-frame#chat iframe.ytd-live-chat-frame {
	position: absolute;
	top: 0px;
	right: 0;
	width: calc(12.79vw) !important;
	min-width: 285px;
	max-width: 440px;
	height: 100vh;
	z-index: 0;
	pointer-events: auto;
}

ytd-watch-flexy[fullscreen] #player-wide-container.live-chat ~ #columns #chat iframe {
	height: calc(100vh - 75px);
}

body.theater.live-chat-top ytd-live-chat-frame#chat iframe.ytd-live-chat-frame {
	top: var(--video-height);
	max-width: 100vw;
	width: 100vw !important;
	height: var(--chat-height);
}

/*  Toggle Button  */
#show-hide-button tp-yt-paper-button#button.ytd-toggle-button-renderer {
	z-index: 1001;
	position: absolute;
	top: 0px;
	right: 0;
	/*width: 30px !important;*/
	height: 30px;
	padding: 3px !important;
	pointer-events: auto;
	/*transform: translate(300px, 0);*/
}

#show-hide-button yt-formatted-string#text.ytd-toggle-button-renderer {
	width: 40px;
	filter: drop-shadow(0 0 5px #000);
}

#show-hide-button paper-ripple {
	width: 40px;
}

.live-chat-top #show-hide-button tp-yt-paper-button#button.ytd-toggle-button-renderer[aria-pressed="false"] {
	/*top: calc(var(--video-height));*/
}

@media only screen and (max-width: 750px) {}

/*  super chat ticker  */
.live-chat-top yt-live-chat-renderer #ticker.yt-live-chat-renderer {
	left: 127px;
	right: 107px;
	transform: translateY(-40px);
	position: absolute;
	z-index: 10000;
}

.live-chat-top yt-live-chat-renderer #ticker.yt-live-chat-renderer #items {
	padding-bottom: 0;
}

.live-chat-top yt-live-chat-renderer #contents {
	overflow: visible !important;
}

/* Resize the player in theater mode only */
ytd-watch-flexy[theater]:not([fullscreen]) #player-wide-container.ytd-watch-flexy {
	max-height: 100vh;
	height: 100vh;
}

/*  Live Chat Variant  */
ytd-watch-flexy[theater]:not([fullscreen]) #player-wide-container.ytd-watch-flexy.live-chat,
ytd-watch-flexy[fullscreen] #player-wide-container.ytd-watch-flexy.live-chat {
	left: 0;
	right: min(440px, max(285px, calc(12.79vw)));
	width: calc(100vw - min(440px, max(285px, calc(12.79vw)))) !important;
}

.live-chat-top ytd-watch-flexy[theater]:not([fullscreen]) #player-wide-container.ytd-watch-flexy.live-chat {
	width: 100vw !important;
	height: var(--video-height);
	margin-bottom: var(--chat-height);
	right: 0;
	min-height: 0 !important;
}

ytd-watch-flexy[fullscreen] #player-wide-container.ytd-watch-flexy.live-chat .html5-video-container {
	width: 100%;
	height: 100%;
}

ytd-watch-flexy[fullscreen] #player-wide-container.ytd-watch-flexy.live-chat .html5-video-container video {
	width: 100% !important;
}

body.theater #show-hide-button {
	pointer-events: all;
	width: 102px;
	left: 114px;
	position: absolute;
}

#show-hide-button button {
	height: 30px;
}

body.theater.live-chat-top #show-hide-button {
	top: var(--video-height);
}

.yt-spec-button-shape-next--button-text-content {
	text-overflow: clip;
	width: 64px;
}

#ytc-filter,
#ytc-filter > div,
#ytc-filter .vc-toolbar {
	pointer-events: none;
}
	
div.vc-toolbar > * {
	pointer-events: all;
}

body.no-ytc-filter yt-live-chat-header-renderer {
	margin-top: 30px;
}

body.no-ytc-filter #show-hide-button {
	left: unset;
	right: 0;
}

body.theater #show-hide-button.chat-hidden {
	top: 0;
	right: 0;
	left: unset;
	width: unset;
}

body.theater #show-hide-button.chat-hidden .yt-spec-button-shape-next--button-text-content {
	width: unset;
}

`;
appendStyle(mainCSS, 'pkga-css');

const watchCSS = `
	html::-webkit-scrollbar,
	body::-webkit-scrollbar {
		display: none;
	}

	body, html {
		scrollbar-width: none;
	}

	body.theater #masthead-container {
		position: fixed;
		opacity: 0;
		transition: opacity 0.25s, transform 0.3s ease !important;
		pointer-events: none;
	}

	body.theater #masthead-container:hover,
	body.theater #masthead-container:focus,
	body.theater #masthead-container.show-header {
		opacity: 1;
	}

	body.theater ytd-searchbox.ytd-masthead {
		margin-left: 9px;
	}

	#search-icon-legacy.ytd-searchbox {
		width: 35px;
	}

	body.theater #masthead {
		border-bottom-left-radius: 15px;
		border-bottom-right-radius: 15px;
		box-shadow: 0 0 20px #000a;
		margin: 0 auto;
		pointer-events: auto;
	}

	body.theater #masthead,
	body.theater #masthead #background {
		width: calc(100vw - 700px);
		max-width: min(1750px, calc(100vw - 2*min(440px, max(285px, calc(12.79vw)))));;
		min-width: 750px;
	}

	body.theater.live-chat-top #masthead {
		width: calc(100vw - 200px);
		max-width: unset;
		min-width: 450px;
	}

	body.theater #masthead-bg {
		position: fixed;
		z-index: -1;
		background: #0006;
		pointer-events: none;
		width: 100vw;
		height: 100vh;
		top: 0;
		left: 0;
		transition: opacity 0.25s;
		opacity: 0;
	}

	body.theater .show-header #masthead-bg {
		opacity: 0;
	}

	body.theater #page-manager {
		margin-top: 0 !important;
	}

	ytd-watch-flexy[theater] #secondary {
		position: unset !important;
	}

	ytd-watch-flexy[flexy][js-panel-height_] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy, ytd-watch-flexy[flexy][js-panel-height_] #chat-container.ytd-watch-flexy:not([chat-collapsed]).ytd-watch-flexy {
		height: 100vh !important;
		border-radius: 0 !important;
	}

	ytd-live-chat-frame[rounded-container] iframe.ytd-live-chat-frame {
		border-radius: 0 !important;
	}

	body.theater.live-chat-top #chat {
		width: 100vw !important;
		left: 0 !important;
		right: 0 !important;
		max-width: 100vw !important;
	}

	@media screen and (max-width: 1015px) {
		.live-chat-top #chat {
			top: calc(-14px - 100vh) !important;
			left: 0 !important;
			right: 0 !important;
			z-index: 100000;
		}

		.live-chat-top #chatframe {
			left:-24px !important;
			right: 0 !important;
		}
	}

	#ytd-player .html5-video-container {
		height: 100%;
	}

	#ytd-player video {
		object-fit: contain;
		object-position: center;
		width: 100% !important;
		height: 100% !important;
		left: unset !important;
		right: unset !important;
		top: unset !important;
		bottom: unset !important;
		transform: none !important;
	}
`;

let delay = ms => new Promise(res => setTimeout(res, ms));

let checkForHTML = async (selector, element = document) => {
	let selected;
	while(true) {
		if(selected = element.querySelector(selector))
			return selected;
		await delay(100);
	}
}

let getAnimationFrame = () => new Promise(res => requestAnimationFrame(res));

function getCookies() {
	let cookies = {};
	for(let [k,v] of document.cookie.split('; ').map(e => e.split('=')))
		cookies[k] = v;
	return cookies;
}

if(parseInt(getCookies().wide || 0) == 1) {
	document.body.classList.add('theater');
	window.dispatchEvent(new Event('resize'));
}

document.addEventListener('click', async e => {
	let target;
	if(target = e.target.closest('button.ytp-size-button')) {
		document.body.classList.toggle('theater');
	} else if(target = e.target.closest('#show-hide-button')) {
		await getAnimationFrame();
		let player = document.querySelector('#player-wide-container.ytd-watch-flexy');
		if(target.innerText.toUpperCase().startsWith('SHOW CHAT')) {
			player.classList.remove('live-chat');
			target.classList.add('chat-hidden');
		} else {
			player.classList.add('live-chat');
			target.classList.remove('chat-hidden');
		}
		window.dispatchEvent(new Event('resize'));
	}
});

let root = document.body.parentElement,
	header = document.querySelector('#masthead-container'),
	lastScroll = 0;
document.addEventListener('scroll', e => {
	/*let currentScroll = Date.now();
	if(currentScroll - lastScroll < 100) return;
	lastScroll = currentScroll;*/
	if(!header) header = document.querySelector('#masthead-container');
	if(root.scrollTop != 0)
		header.classList.add('show-header');
	else header.classList.remove('show-header');
});

function checkIfLiveChat() {
	return document.querySelector('#chat')
		&& document.querySelector('#show-hide-button').innerText.toUpperCase().startsWith('HIDE CHAT');
}

(async() => {
	let watchStyle;
	while(true) {
		(() => {
			let player = document.querySelector('#player-wide-container'),
				video = player ? player.querySelector('video'):null,
				offline = document.querySelector('.ytp-offline-slate');
			if(player && (video && video.src || offline)) {
				if(!watchStyle) {
					appendStyle(watchCSS, 'pkga-watch-css');
					watchStyle = document.querySelector('#pkga-watch-css');
				}
				let isLiveChat = checkIfLiveChat();
				if(isLiveChat) {
					player.classList.add('live-chat');
					document.querySelector('#show-hide-button').classList.remove('chat-hidden');
					let chatDocument = document.querySelector('#chatframe').contentDocument;
					if(!chatDocument || !chatDocument.body || !chatDocument.head)
						return;
					if(!chatDocument.querySelector('#ytc-filter')) {
						document.body.classList.add('no-ytc-filter');
						chatDocument.body.classList.add('no-ytc-filter');
					} else {
						document.body.classList.remove('no-ytc-filter');
						chatDocument.body.classList.remove('no-ytc-filter');
					}
					if(!chatDocument.querySelector('#pkga-css'))
						appendStyle(mainCSS, 'pkga-css', chatDocument);
					//await getAnimationFrame();
					//window.dispatchEvent(new Event('resize'));
				} else if(player.classList.contains('live-chat')) {
					player.classList.remove('live-chat');
					//await getAnimationFrame();
					//window.dispatchEvent(new Event('resize'));
				}
				/*document.querySelector('#movie_player').classList.add('ytp-big-mode');*/
			} else if(watchStyle) {
				watchStyle.remove();
				watchStyle = null;
			}
		})();
		await delay(100);
	}
})();

let oldVideo;
(async() => {
	while(true) {
		let video = document.querySelector('video');
		if(!video) {
			if(video = document.querySelector('.ytp-offline-slate-background')) {
				video.src = video.getAttribute('style');
				video.offline = true;
			}
		}
		if(video && (!oldVideo || oldVideo.src != video.src)) {
			if(video.offline) {
				console.log('[PKGA Theater Mode] Offline Stream Found');
				setTopChat();
			} else if(!video.videoWidth) {
				await new Promise(res => {
					video.addEventListener('loadedmetadata', res);
				});
				setTopChat();
			}
		}
		oldVideo = video;
		await delay(100);
	}
})();

(async() => {
	let header = await checkForHTML('#masthead-container');
	header.addEventListener('focusin', e => {
		header.classList.add('show-header');
	});
	header.addEventListener('focusout', e => {
		header.classList.remove('show-header');
	});
})();

let lastResize = 0;

async function setTopChat(currentResize = Date.now()) {
	let video = document.querySelector('video'),
		offline = document.querySelector('.ytp-offline-slate'),
		player = document.querySelector('#player-wide-container');
	if(!(player && (video || offline))) return;
	if(!checkIfLiveChat()) {
		document.body.classList.remove('live-chat-top');
	}
	if(video && (!oldVideo || oldVideo.src != video.src)) {
		if(!video.videoWidth) {
			await new Promise(res => {
				video.addEventListener('loadedmetadata', res);
			});
			if(currentResize < lastResize) return;
		}
	}
	let videoHeight = video ? Math.round(video.videoHeight*window.innerWidth/video.videoWidth):720*window.innerWidth/1280,
		chatHeight = window.innerHeight - videoHeight,
		chatDocument = document.querySelector('#chatframe');
	chatDocument = chatDocument ? chatDocument.contentDocument:null;
	if(chatHeight >= 350) {
		for(let e of [document, chatDocument]) {
			if(!e) continue;
			e.body.classList.add('live-chat-top');
			e.documentElement.style.setProperty('--chat-height', chatHeight+'px');
			e.documentElement.style.setProperty('--video-height', videoHeight+'px');
		}
	} else {
		document.body.classList.remove('live-chat-top');
		chatDocument && chatDocument.body.classList.remove('live-chat-top');
	}
	console.log('[PKGA Theater Mode] videoHeight', videoHeight, 'chatHeight', chatHeight);
}

window.addEventListener('resize', async e => {
	let currentResize = Date.now();
	do {
		if(currentResize <= lastResize) break;
		else if(currentResize - lastResize > 100) {
			setTopChat(currentResize);
			lastResize = currentResize;
			break;
		} else await delay(currentResize - lastResize + 10);
	} while(true);
});