Greasy Fork is available in English.
Youtube 小屏播放。当你向下滚动时,小屏播放器将会出现。
// ==UserScript==
// @name YouTube Mini Player
// @name:zh-CN Youtube 小屏播放
// @namespace http://tampermonkey.net/
// @version 2.4.1
// @license MIT
// @description Youtube Mini Player. When you scroll down the mini player will appear.
// @description:zh-CN Youtube 小屏播放。当你向下滚动时,小屏播放器将会出现。
// @author https://github.com/AkiyaKiko
// @homepage https://github.com/AkiyaKiko/YouTubeMiniPlayer
// @match https://www.youtube.com/*
// @icon https://www.youtube.com/favicon.ico
// @run-at document-end
// @grant GM_addStyle
// @grant GM_log
// ==/UserScript==
(function() {
'use strict';
GM_log('🛠️ 脚本 "YouTube Mini Player (No Placeholder)" 开始执行');
const miniPlayerClass = 'youtube-mini-player-active';
let playerElement = null;
let outerContainer = null;
let innerContainer = null;
let videoElement = null;
let ivVideoContent = null;
let bottomChrome = null;
let originalOuterContainerStyle = null;
let originalInnerContainerStyle = null;
let originalVideoStyle = null;
let originalIvContentStyle = null;
let intersectionObserver = null;
let observer = null;
let isMiniPlayerActive = false;
let lastUrl = location.href;
let initializedUrl = null;
let isDragging = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
let storedPlayerHeight = 0;
function isFullscreen() {
return !!document.fullscreenElement;
}
function minimizeOuterContainer() {
if (!outerContainer || isMiniPlayerActive || isFullscreen()) {
GM_log('🔽 不满足小窗条件,跳过。');
return;
}
originalOuterContainerStyle = outerContainer.getAttribute('style');
originalInnerContainerStyle = innerContainer?.getAttribute('style');
originalVideoStyle = videoElement?.getAttribute('style');
originalIvContentStyle = ivVideoContent?.getAttribute('style');
storedPlayerHeight = playerElement.offsetHeight;
GM_log(`📏 设置 #player 高度: ${storedPlayerHeight}px`);
playerElement.style.height = `${storedPlayerHeight}px`;
const floatingWidth = window.innerWidth / 5;
const aspectRatio = outerContainer.offsetWidth / outerContainer.offsetHeight;
const floatingHeight = floatingWidth / aspectRatio;
const rightOffset = window.innerWidth * 0.03;
const bottomOffset = window.innerHeight * 0.02;
outerContainer.style.position = 'fixed';
outerContainer.style.bottom = `${bottomOffset}px`;
outerContainer.style.right = `${rightOffset}px`;
outerContainer.style.left = 'auto';
outerContainer.style.top = 'auto';
outerContainer.style.width = `${floatingWidth}px`;
outerContainer.style.height = `${floatingHeight}px`;
outerContainer.style.zIndex = '3000';
outerContainer.style.boxShadow = '2px 2px 5px rgba(0, 0, 0, 0.3)';
outerContainer.style.minWidth = '0px';
outerContainer.classList.add(miniPlayerClass);
isMiniPlayerActive = true;
if (innerContainer) {
innerContainer.style.width = `${floatingWidth}px`;
innerContainer.style.height = `${floatingHeight}px`;
innerContainer.style.paddingTop = '0px';
}
if (bottomChrome) bottomChrome.style.display = 'none';
if (videoElement) {
videoElement.style.width = `${floatingWidth}px`;
videoElement.style.height = `${floatingHeight}px`;
}
if (ivVideoContent) {
ivVideoContent.style.width = `${floatingWidth}px`;
ivVideoContent.style.height = `${floatingHeight}px`;
}
enableDragging();
}
function restoreOuterContainer() {
if (!outerContainer || !isMiniPlayerActive || isFullscreen()) {
GM_log('🔼 不满足恢复条件,跳过。');
return;
}
outerContainer.setAttribute('style', originalOuterContainerStyle || '');
outerContainer.classList.remove(miniPlayerClass);
isMiniPlayerActive = false;
if (playerElement) {
playerElement.style.height = '';
GM_log('🔁 恢复 #player 高度样式为空');
}
if (innerContainer) innerContainer.removeAttribute('style');
if (bottomChrome) bottomChrome.style.display = '';
if (videoElement) videoElement.setAttribute('style', originalVideoStyle || '');
if (ivVideoContent) ivVideoContent.setAttribute('style', originalIvContentStyle || '');
disableDragging();
}
function enableDragging() {
if (!outerContainer) return;
outerContainer.addEventListener('mousedown', onMouseDown);
}
function disableDragging() {
if (!outerContainer) return;
outerContainer.removeEventListener('mousedown', onMouseDown);
}
function onMouseDown(e) {
if (!isMiniPlayerActive) return;
isDragging = true;
dragOffsetX = e.clientX - outerContainer.getBoundingClientRect().left;
dragOffsetY = e.clientY - outerContainer.getBoundingClientRect().top;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
e.preventDefault();
}
function onMouseMove(e) {
if (!isDragging) return;
outerContainer.style.left = `${e.clientX - dragOffsetX}px`;
outerContainer.style.top = `${e.clientY - dragOffsetY}px`;
outerContainer.style.right = 'auto';
outerContainer.style.bottom = 'auto';
}
function onMouseUp() {
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
function observePlayerVisibility() {
if (!playerElement) return;
if (intersectionObserver) intersectionObserver.disconnect();
intersectionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
const playerRect = entry.boundingClientRect;
if (playerRect.bottom < 0) {
if (!isFullscreen()) minimizeOuterContainer();
} else {
restoreOuterContainer();
}
});
}, { threshold: 0 });
intersectionObserver.observe(playerElement);
}
function waitForElements() {
if (!observer) {
observer = new MutationObserver((mutations, obs) => {
if (document.getElementById('player') &&
document.getElementById('player-container-outer') &&
document.getElementById('player-container-inner') &&
document.querySelector('video.video-stream.html5-main-video') &&
document.getElementById('contents')) {
obs.disconnect();
observer = null;
initialize();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
}
function handleResize() {
if (outerContainer && outerContainer.classList.contains(miniPlayerClass)) {
const floatingWidth = window.innerWidth / 5;
const aspectRatio = outerContainer.offsetWidth / outerContainer.offsetHeight;
const floatingHeight = floatingWidth / aspectRatio;
const rightOffset = window.innerWidth * 0.03;
const bottomOffset = window.innerHeight * 0.02;
outerContainer.style.width = `${floatingWidth}px`;
outerContainer.style.height = `${floatingHeight}px`;
outerContainer.style.right = `${rightOffset}px`;
outerContainer.style.bottom = `${bottomOffset}px`;
outerContainer.style.left = 'auto';
outerContainer.style.top = 'auto';
if (innerContainer) {
innerContainer.style.width = `${floatingWidth}px`;
innerContainer.style.height = `${floatingHeight}px`;
}
if (videoElement) {
videoElement.style.width = `${floatingWidth}px`;
videoElement.style.height = `${floatingHeight}px`;
}
if (ivVideoContent) {
ivVideoContent.style.width = `${floatingWidth}px`;
ivVideoContent.style.height = `${floatingHeight}px`;
}
}
}
function cleanup() {
if (intersectionObserver) {
intersectionObserver.disconnect();
intersectionObserver = null;
}
if (observer) {
observer.disconnect();
observer = null;
}
restoreOuterContainer();
window.removeEventListener('resize', handleResize);
playerElement = null;
outerContainer = null;
innerContainer = null;
videoElement = null;
ivVideoContent = null;
bottomChrome = null;
initializedUrl = null;
isMiniPlayerActive = false;
isDragging = false;
storedPlayerHeight = 0;
}
function initialize() {
playerElement = document.getElementById('player');
outerContainer = document.getElementById('player-container-outer');
innerContainer = document.getElementById('player-container-inner');
videoElement = document.querySelector('video.video-stream.html5-main-video');
ivVideoContent = document.querySelector('.ytp-iv-video-content');
bottomChrome = document.querySelector('.ytp-chrome-bottom');
if (playerElement && outerContainer && innerContainer && videoElement) {
observePlayerVisibility();
window.addEventListener('resize', handleResize);
if (observer) {
observer.disconnect();
observer = null;
}
isMiniPlayerActive = false;
const rect = playerElement.getBoundingClientRect();
if (rect.bottom < 0) minimizeOuterContainer();
else restoreOuterContainer();
initializedUrl = location.href;
} else {
waitForElements();
}
}
function checkUrlAndInitialize() {
if (location.pathname.startsWith('/watch')) {
if (location.href !== initializedUrl) {
cleanup();
setTimeout(() => initialize(), 500);
}
} else {
cleanup();
}
}
function startUrlWatcher() {
setInterval(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
checkUrlAndInitialize();
}
}, 300);
}
function start() {
checkUrlAndInitialize();
startUrlWatcher();
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
start();
} else {
document.addEventListener('DOMContentLoaded', start);
}
GM_addStyle(`
.${miniPlayerClass} {
transition: width 0.3s ease, height 0.3s ease, right 0.3s ease, bottom 0.3s ease, top 0.3s ease, left 0.3s ease;
cursor: move;
}
`);
})();