- // ==UserScript==
- // @name Video Control with Reload
- // @namespace http://tampermonkey.net/
- // @version 0.9
- // @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; // 上一次位置
-
- // 创建控制器
- 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>
- `;
- document.body.appendChild(controller);
- return controller;
- }
-
- // 创建显示/隐藏按钮
- function createToggleButton() {
- 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.addEventListener('touchstart', touchStart, false);
-
- function touchStart(e) {
- isDragging = true;
- initialX = e.touches[0].clientX;
- initialY = e.touches[0].clientY;
- lastX = initialX;
- lastY = initialY;
- document.addEventListener('touchmove', touchMove, false);
- document.addEventListener('touchend', touchEnd, false);
- }
-
- function touchMove(e) {
- if (!isDragging) return;
- e.preventDefault();
- let currentX = e.touches[0].clientX;
- let currentY = e.touches[0].clientY;
-
- pos1 = lastX - currentX;
- pos2 = lastY - currentY;
- lastX = currentX;
- lastY = currentY;
-
- button.style.top = (button.offsetTop - pos2) + "px";
- button.style.left = (button.offsetLeft - pos1) + "px";
- }
-
- function touchEnd() {
- isDragging = false;
- document.removeEventListener('touchmove', touchMove, false);
- document.removeEventListener('touchend', touchEnd, false);
- }
-
- button.addEventListener('click', function() {
- if (controller.style.display === 'none') {
- controller.style.display = 'block';
- } else {
- controller.style.display = 'none';
- }
- });
- }
-
- // 使控制器可拖动
- function makeDraggable(element) {
- let pos1 = 0, pos2 = 0;
-
- element.addEventListener('mousedown', dragMouseDown, false);
-
- function dragMouseDown(e) {
- if (e.target.id === 'progress-container' || e.target.id === 'progress-indicator' || e.target.id === 'volume-slider' || e.target.id === 'reload-button') return;
- e.preventDefault();
- isDragging = true;
-
- initialX = e.clientX;
- initialY = e.clientY;
- lastX = initialX;
- lastY = initialY;
- document.addEventListener('mouseup', closeDragElement, false);
- document.addEventListener('mousemove', throttle(elementDrag, 16), false); // 节流控制拖动频率
- }
-
- 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.removeEventListener('mouseup', closeDragElement, false);
- document.removeEventListener('mousemove', throttle(elementDrag, 16), false);
- 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 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');
-
- // 更新进度
- 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);
- });
-
- 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();
- })();
-