Video Control with Reload and Floating Window

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

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Video Control with Reload and Floating Window
// @namespace    http://tampermonkey.net/
// @version      1.5
// @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: grey;
            color: white;
            border: none;
            cursor: pointer;
            z-index: 10001;
            opacity: 0.1;
        `;
			
closeButton.onclick = () => {
    // 停止播放视频
    videoElement.pause();
    videoElement.load();
    // 移除浮窗
    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();
})();