Video Control with Reload and Floating Window

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

当前为 2024-07-26 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Video Control with Reload and Floating Window
// @namespace    http://tampermonkey.net/
// @version      1.3
// @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;
            transition: width 0.3s, height 0.3s;
            display: none;
        `;
		controller.innerHTML = `
            <div id="progress-container" style="width: 100%; height: 10px; background-color: #444; position: relative; cursor: pointer;">
                <div id="progress-indicator" style="width: 0%; height: 100%; background-color: #fff; position: absolute;"></div>
            </div>
            <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;">
                <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;">
            </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>
            <div style="display: flex; justify-content: space-between; margin-top: 10px;">
                <button class="size-button" data-action="increase-width">宽度+</button>
                <button class="size-button" data-action="decrease-width">宽度-</button>
                <button class="size-button" data-action="increase-height">高度+</button>
                <button class="size-button" data-action="decrease-height">高度-</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) {
		let pos1 = 0,
			pos2 = 0;

		element.onmousedown = dragMouseDown;

		function dragMouseDown(e) {
			if (e.target.id === 'progress-container' || e.target.id === 'progress-indicator' || e.target.id === 'volume-slider' || e.target.tagName === 'BUTTON') return;
			e.preventDefault();
			isDragging = true;

			initialX = e.clientX;
			initialY = e.clientY;
			lastX = initialX;
			lastY = initialY;
			document.onmouseup = closeDragElement;
			document.onmousemove = throttle(elementDrag, 16);
		}

		function elementDrag(e) {
			if (!isDragging) return;
			e.preventDefault();
			pos1 = initialX - e.clientX;
			pos2 = initialY - e.clientY;
			initialX = e.clientX;
			initialY = e.clientY;

			requestAnimationFrame(() => {
				element.style.top = (element.offsetTop - pos2) + "px";
				element.style.left = (element.offsetLeft - pos1) + "px";
				element.style.bottom = 'auto';
			});
		}

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

	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() {
		if (floatingWindow) {
			floatingWindow.remove();
		}

		floatingWindow = document.createElement('div');
		floatingWindow.style.cssText = `
            position: fixed;
            top: 50px;
            left: 50px;
            width: 320px;
            height: 240px;
            background-color: #000;
            border: 2px solid #fff;
            z-index: 10000;
            resize: both;
            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);
		videoElement = document.querySelector('video[src^="blob:"]');
		if (videoElement) {
			// 保存视频的原始尺寸
			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 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 progressContainer = document.getElementById('progress-container');
		const progressIndicator = document.getElementById('progress-indicator');
		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 sizeButtons = document.querySelectorAll('.size-button');

		function updateProgress() {
			const progress = (videoElement.currentTime / videoElement.duration) * 100;
			progressIndicator.style.width = `${progress}%`;
			const current = formatTime(videoElement.currentTime);
			const total = formatTime(videoElement.duration);
			timeDisplay.textContent = `${current} / ${total}`;
		}

		progressContainer.addEventListener('click', function(e) {
			if (isDragging) return;
			const rect = progressContainer.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);

		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();
})();