Video Control with Reload and Floating Window

为指定视频添加可移动的进度条、音量控制器、重新加载按钮和悬浮窗功能和去黑边

Per 26-07-2024. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Video Control with Reload and Floating Window
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  为指定视频添加可移动的进度条、音量控制器、重新加载按钮和悬浮窗功能和去黑边
// @match        https://app.kosmi.io/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
	'use strict';

	let videoElement = null;
	let controller = null;
	let isDragging = false;
	let initialX = 0,
		initialY = 0;
	let lastX = 0,
		lastY = 0;
	let buttonCreated = false;
	let floatingWindow = null;

	function createController() {
		controller = document.createElement('div');
		controller.id = 'video-controller';
		controller.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 10px;
            border-radius: 5px;
            z-index: 9999;
            cursor: move;
            user-select: none;
            width: 300px;
            height: 233px;
            transition: width 0.3s, height 0.3s;
            display: none;
        `;
		controller.innerHTML = `
             <progress id="progress-bar" value="0" max="100" style="width: 100%; height: 10px; background-color: #444; cursor: pointer;"></progress>
            <div id="time-display" style="text-align: center; margin-top: 5px;">0:00 / 0:00</div>
            <div id="volume-container" style="display: flex; align-items: center; margin-top: 10px; cursor: pointer;">
                <span id="volume-icon" style="margin-right: 10px;">🔊</span>
                <input type="range" id="volume-slider" min="0" max="1" step="0.1" value="1" style="flex-grow: 1; cursor: pointer;">
            </div>
            <button id="reload-button" style="width: 100%; margin-top: 10px; padding: 5px; background-color: #4CAF50; border: none; color: white; cursor: pointer;">重新加载视频控制</button>
            <button id="float-video-button" style="width: 100%; margin-top: 10px; padding: 5px; background-color: #2196F3; border: none; color: white; cursor: pointer;">创建悬浮视频窗口</button>
            <button id="video-remove-black" style="width: 100%; margin-top: 10px; padding: 5px; background-color: #FFA500; border: none; color: white; cursor: pointer;">视频去黑边</button>
            <div style="display: flex; justify-content: space-between; margin-top: 10px;">
                <button class="size-button" data-action="increase-width" style="cursor: pointer;">宽度+</button>
                <button class="size-button" data-action="decrease-width"  style="cursor: pointer;">宽度-</button>
                <button class="size-button" data-action="increase-height"  style="cursor: pointer;">高度+</button>
                <button class="size-button" data-action="decrease-height"  style="cursor: pointer;">高度-</button>
            </div>
        `;
		document.body.appendChild(controller);
		return controller;
	}

	function createToggleButton() {
		if (!buttonCreated) {
			const button = document.createElement('button');
			button.textContent = '🎥';
			button.style.cssText = `
                position: fixed;
                left: 10px;
                top: 50%;
                transform: translateY(-50%);
                padding: 5px;
                font-size: 20px;
                background-color: rgba(0, 0, 0, 0.5);
                color: white;
                border: none;
                border-radius: 50%;
                cursor: move;
                z-index: 9999;
            `;
			document.body.appendChild(button);

			let pos1 = 0,
				pos2 = 0;

			button.onmousedown = function(e) {
				e.preventDefault();
				isDragging = true;
				initialX = e.clientX;
				initialY = e.clientY;
				lastX = initialX;
				lastY = initialY;
				document.onmouseup = closeDragElement;
				document.onmousemove = throttle(buttonDrag, 16);
			};

			function buttonDrag(e) {
				if (!isDragging) return;
				e.preventDefault();
				pos1 = lastX - e.clientX;
				pos2 = lastY - e.clientY;
				lastX = e.clientX;
				lastY = e.clientY;

				button.style.top = (button.offsetTop - pos2) + "px";
				button.style.left = (button.offsetLeft - pos1) + "px";
			}

			function closeDragElement() {
				document.onmouseup = null;
				document.onmousemove = null;
				isDragging = false;
			}

			button.addEventListener('click', function() {
				if (controller.style.display === 'none') {
					controller.style.display = 'block';
				} else {
					controller.style.display = 'none';
				}
			});

			let isClicking = false;
			button.addEventListener('touchstart', function(e) {
				e.preventDefault();
				if (e.touches.length === 1) {
					isClicking = true;
					setTimeout(() => {
						if (isClicking) {
							if (controller.style.display === 'none' || controller.style.display === '') {
								controller.style.display = 'block';
							} else {
								controller.style.display = 'none';
							}
						}
					}, 200);
				} else {
					isClicking = false;
				}

				const touch = e.touches[0];
				initialX = touch.clientX - button.offsetLeft;
				initialY = touch.clientY - button.offsetTop;
				isDragging = true;
			});

			button.addEventListener('touchmove', function(e) {
				e.preventDefault();
				if (!isDragging) return;
				isClicking = false;
				const touch = e.touches[0];
				const newX = touch.clientX - initialX;
				const newY = touch.clientY - initialY;
				button.style.left = newX + 'px';
				button.style.top = newY + 'px';
			});

			button.addEventListener('touchend', function() {
				isDragging = false;
			});
		}
		buttonCreated = true;
	}

	// 让元素能在手机拖动
	function makeDraggable(element) {
		console.log('progressContainer1');
		let pos1 = 0,
			pos2 = 0,
			pos3 = 0,
			pos4 = 0;
		let isDragging = false;

		element.onmousedown = dragMouseDown;
		element.ontouchstart = dragTouchStart;

		function dragMouseDown(e) {
			console.log('progressContainer2');
			if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return;
			e.preventDefault();
			pos3 = e.clientX;
			pos4 = e.clientY;
			document.onmouseup = closeDragElement;
			document.onmousemove = elementDrag;
			isDragging = true;
		}

		function dragTouchStart(e) {
			console.log(e.target.tagName);
			console.log(e);
			if (e.target.tagName === 'BUTTON' || e.target.tagName === 'PROGRESS' || e.target.tagName === 'INPUT') return;
			e.preventDefault();
			const touch = e.touches[0];
			pos3 = touch.clientX;
			pos4 = touch.clientY;
			document.ontouchend = closeDragElement;
			document.ontouchmove = elementTouchDrag;
			isDragging = true;
		}

		function elementDrag(e) {
			console.log('progressContainer');
			if (!isDragging) return;
			e.preventDefault();
			pos1 = pos3 - e.clientX;
			pos2 = pos4 - e.clientY;
			pos3 = e.clientX;
			pos4 = e.clientY;
			element.style.top = (element.offsetTop - pos2) + "px";
			element.style.left = (element.offsetLeft - pos1) + "px";
		}

		function elementTouchDrag(e) {
			console.log('progressContainer4');
			if (!isDragging) return;
			e.preventDefault();
			const touch = e.touches[0];
			pos1 = pos3 - touch.clientX;
			pos2 = pos4 - touch.clientY;
			pos3 = touch.clientX;
			pos4 = touch.clientY;
			element.style.top = (element.offsetTop - pos2) + "px";
			element.style.left = (element.offsetLeft - pos1) + "px";
		}

		function closeDragElement() {
			console.log('progressContainer5');
			isDragging = false;
			document.onmouseup = null;
			document.onmousemove = null;
			document.ontouchend = null;
			document.ontouchmove = null;
		}
	}

	function throttle(func, limit) {
		let lastFunc;
		let lastRan;
		return function() {
			const context = this;
			const args = arguments;
			if (!lastRan) {
				func.apply(context, args);
				lastRan = Date.now();
			} else {
				clearTimeout(lastFunc);
				lastFunc = setTimeout(function() {
					if ((Date.now() - lastRan) >= limit) {
						func.apply(context, args);
						lastRan = Date.now();
					}
				}, limit - (Date.now() - lastRan));
			}
		};
	}

	function formatTime(seconds) {
		const minutes = Math.floor(seconds / 60);
		seconds = Math.floor(seconds % 60);
		return `${minutes}:${seconds.toString().padStart(2, '0')}`;
	}

	function createFloatingWindow() {
		videoElement = document.querySelector('video[src^="blob:https://app.kosmi.io/"]');
		if (videoElement) {
			if (floatingWindow) {
				floatingWindow.remove();
			}

			floatingWindow = document.createElement('div');
			floatingWindow.style.cssText = `
            position: fixed;
            top: 50px;
            left: 01px;
            width: 320px;
            height: 240px;
            background-color: #000;
            border: 0px solid #000;
            z-index: 10000;
            resize: both;
            cursor: move;
            overflow: hidden;
        `;

			const closeButton = document.createElement('button');
			closeButton.textContent = 'X';
			closeButton.style.cssText = `
            position: absolute;
            top: 5px;
            right: 5px;
            background-color: red;
            color: white;
            border: none;
            cursor: pointer;
            z-index: 10001;
        `;
			closeButton.onclick = () => floatingWindow.remove();

			floatingWindow.appendChild(closeButton);
			document.body.appendChild(floatingWindow);

			// 保存视频的原始尺寸
			const originalWidth = videoElement.offsetWidth;
			const originalHeight = videoElement.offsetHeight;
			// 调整视频大小以适应悬浮窗
			videoElement.style.width = '100%';
			videoElement.style.height = '100%';

			// 将视频添加到悬浮窗中
			floatingWindow.appendChild(videoElement);

			// 添加悬浮窗到body
			document.body.appendChild(floatingWindow);

			makeDraggable(floatingWindow);
		}
	}

	function removeVideoBlack() {
		videoElement = document.querySelector('video[src^="blob:https://app.kosmi.io/"]');
		if (videoElement) {
			videoElement.style.width = '100%';
			videoElement.style.height = '100%';
			videoElement.style.objectFit = 'cover';
		}
	}

	function main() {
		const existingController = document.getElementById('video-controller');
		const existingButton = document.getElementById('toggle-button');
		if (existingController) {
			existingController.remove();
		}
		if (existingButton) {
			existingButton.remove();
		}

		videoElement = document.querySelector('video[src^="blob:https://app.kosmi.io/"]');
		if (!videoElement) {
			console.log('未找到指定视频');
			return;
		}

		controller = createController();
		makeDraggable(controller);
		createToggleButton();

		const progressBar = document.getElementById('progress-bar');
		const timeDisplay = document.getElementById('time-display');
		const volumeSlider = document.getElementById('volume-slider');
		const volumeIcon = document.getElementById('volume-icon');
		const reloadButton = document.getElementById('reload-button');
		const floatVideoButton = document.getElementById('float-video-button');
		const videoRemoveBlack = document.getElementById('video-remove-black');
		const sizeButtons = document.querySelectorAll('.size-button');

		function updateProgress() {
			const progress = (videoElement.currentTime / videoElement.duration) * 100;
			progressBar.value = progress; // 设置进度条的value属性
			const current = formatTime(videoElement.currentTime);
			const total = formatTime(videoElement.duration);
			timeDisplay.textContent = `${current} / ${total}`;
		}

		// 添加点击事件监听器以改变视频播放位置
		progressBar.addEventListener('click', function(e) {
			if (isDragging) return;

			// 获取进度条的位置
			const rect = progressBar.getBoundingClientRect();
			const pos = (e.clientX - rect.left) / rect.width;

			// 设置视频播放位置
			videoElement.currentTime = pos * videoElement.duration;
		});

		volumeSlider.addEventListener('input', function() {
			videoElement.volume = this.value;
			updateVolumeIcon(this.value);
		});

		function updateVolumeIcon(volume) {
			if (volume > 0.5) {
				volumeIcon.textContent = '🔊';
			} else if (volume > 0) {
				volumeIcon.textContent = '🔉';
			} else {
				volumeIcon.textContent = '🔇';
			}
		}

		volumeSlider.value = videoElement.volume;
		updateVolumeIcon(videoElement.volume);

		reloadButton.addEventListener('click', function() {
			videoElement.removeEventListener('timeupdate', updateProgress);
			videoElement.removeEventListener('loadedmetadata', updateProgress);
			main();
			videoElement.addEventListener('timeupdate', updateProgress);
			videoElement.addEventListener('loadedmetadata', updateProgress);
		});

		floatVideoButton.addEventListener('click', createFloatingWindow);

		videoRemoveBlack.addEventListener('click', removeVideoBlack);

		sizeButtons.forEach(button => {
			button.addEventListener('click', function() {
				if (!floatingWindow) return;

				const action = this.dataset.action;
				const step = 20;

				switch (action) {
					case 'increase-width':
						floatingWindow.style.width = (floatingWindow.offsetWidth + step) + 'px';
						break;
					case 'decrease-width':
						floatingWindow.style.width = Math.max(160, floatingWindow.offsetWidth - step) + 'px';
						break;
					case 'increase-height':
						floatingWindow.style.height = (floatingWindow.offsetHeight + step) + 'px';
						break;
					case 'decrease-height':
						floatingWindow.style.height = Math.max(120, floatingWindow.offsetHeight - step) + 'px';
						break;
				}
			});
		});

		videoElement.addEventListener('timeupdate', updateProgress);
		videoElement.addEventListener('loadedmetadata', updateProgress);
	}

	function waitForVideo() {
		const video = document.querySelector('video[src^="blob:https://app.kosmi.io/"]');
		if (video) {
			main();
		} else {
			setTimeout(waitForVideo, 1000);
		}
	}

	waitForVideo();
})();