您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为动画疯添加自定义视频播放器,支持弹幕显示和时间微调
当前为
// ==UserScript== // @name 动画疯弹幕播放器 // @namespace http://tampermonkey.net/ // @version 2025-07-11 // @description 为动画疯添加自定义视频播放器,支持弹幕显示和时间微调 // @author You // @match https://ani.gamer.com.tw/animeVideo.php* // @icon https://www.google.com/s2/favicons?sz=64&domain=ani.gamer.com.tw // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; let danmuData = []; let timeOffset = 0; // 时间偏移量(秒) let customPlayer = null; let danmuContainer = null; let isPlaying = false; // 视频播放状态 let activeDanmuElements = new Set(); // 活跃的弹幕元素集合 let isCustomFullscreen = false; // 自定义全屏状态 // 弹幕设置 let danmuSettings = { displayArea: 'full', // 'full' | 'half' | 'quarter' opacity: 0.9, fontSize: 1.0, speed: 1.0, density: 1.0, showScroll: true, showTop: true, showBottom: true }; // 从URL中提取视频sn function getVideoSn() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('sn'); } // 获取弹幕数据 async function fetchDanmu(sn) { try { const response = await fetch(`https://api.gamer.com.tw/anime/v1/danmu.php?videoSn=${sn}&geo=TW%2CHK`); const data = await response.json(); if (data.data && data.data.danmu) { danmuData = data.data.danmu; console.log(`获取到 ${danmuData.length} 条弹幕`); return true; } return false; } catch (error) { console.error('获取弹幕失败:', error); return false; } } // 创建自定义播放器 function createCustomPlayer() { const playerContainer = document.createElement('div'); playerContainer.id = 'custom-player-container'; playerContainer.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80vw; max-width: 1200px; height: 70vh; background: #000; border-radius: 8px; box-shadow: 0 8px 32px rgba(0,0,0,0.8); z-index: 10000; display: none; flex-direction: column; `; // 播放器头部控制栏 const playerHeader = document.createElement('div'); playerHeader.id = 'player-header'; playerHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; background: rgba(0,0,0,0.8); color: white; border-radius: 8px 8px 0 0; transition: opacity 0.3s ease, transform 0.3s ease; position: relative; z-index: 1000; `; const playerTitle = document.createElement('div'); playerTitle.textContent = '自定义弹幕播放器'; playerTitle.style.fontSize = '16px'; const controlButtons = document.createElement('div'); controlButtons.style.display = 'flex'; controlButtons.style.gap = '10px'; // 时间微调控件 const timeAdjustContainer = document.createElement('div'); timeAdjustContainer.style.cssText = ` display: flex; align-items: center; gap: 3px; font-size: 12px; `; const timeAdjustLabel = document.createElement('span'); timeAdjustLabel.textContent = '时间微调:'; // 减少按钮组 const minusButtonsContainer = document.createElement('div'); minusButtonsContainer.style.cssText = ` display: flex; gap: 2px; `; // 创建减少按钮 const adjustValues = [0.1, 0.5, 1, 5]; const minusButtons = []; const plusButtons = []; adjustValues.forEach(value => { const minusBtn = document.createElement('button'); minusBtn.textContent = `-${value}s`; minusBtn.style.cssText = ` padding: 2px 4px; background: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 10px; min-width: 35px; `; minusButtons.push({ button: minusBtn, value: value }); minusButtonsContainer.appendChild(minusBtn); }); // 时间显示 const timeAdjustDisplay = document.createElement('span'); timeAdjustDisplay.textContent = '0.0s'; timeAdjustDisplay.style.cssText = ` min-width: 50px; text-align: center; background: rgba(255,255,255,0.1); padding: 4px 8px; border-radius: 3px; margin: 0 3px; `; // 增加按钮组 const plusButtonsContainer = document.createElement('div'); plusButtonsContainer.style.cssText = ` display: flex; gap: 2px; `; adjustValues.forEach(value => { const plusBtn = document.createElement('button'); plusBtn.textContent = `+${value}s`; plusBtn.style.cssText = ` padding: 2px 4px; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 10px; min-width: 35px; `; plusButtons.push({ button: plusBtn, value: value }); plusButtonsContainer.appendChild(plusBtn); }); // 重置按钮 const resetButton = document.createElement('button'); resetButton.textContent = '重置'; resetButton.style.cssText = ` padding: 2px 6px; background: #6c757d; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 10px; margin-left: 5px; `; timeAdjustContainer.appendChild(timeAdjustLabel); timeAdjustContainer.appendChild(minusButtonsContainer); timeAdjustContainer.appendChild(timeAdjustDisplay); timeAdjustContainer.appendChild(plusButtonsContainer); timeAdjustContainer.appendChild(resetButton); // 弹幕设置按钮 const danmuSettingsButton = document.createElement('button'); danmuSettingsButton.textContent = '⚙️'; danmuSettingsButton.title = '弹幕设置'; danmuSettingsButton.style.cssText = ` background: #6c757d; color: white; border: none; border-radius: 4px; padding: 5px 10px; cursor: pointer; font-size: 14px; margin-right: 5px; `; // 自定义全屏按钮 const fullscreenButton = document.createElement('button'); fullscreenButton.textContent = '⛶'; fullscreenButton.title = '全屏播放'; fullscreenButton.style.cssText = ` background: #007bff; color: white; border: none; border-radius: 4px; padding: 5px 10px; cursor: pointer; font-size: 16px; margin-right: 5px; `; // 关闭按钮 const closeButton = document.createElement('button'); closeButton.textContent = '✕'; closeButton.style.cssText = ` background: #dc3545; color: white; border: none; border-radius: 4px; padding: 5px 10px; cursor: pointer; font-size: 14px; `; controlButtons.appendChild(timeAdjustContainer); controlButtons.appendChild(danmuSettingsButton); controlButtons.appendChild(fullscreenButton); controlButtons.appendChild(closeButton); playerHeader.appendChild(playerTitle); playerHeader.appendChild(controlButtons); // 视频播放区域 const videoContainer = document.createElement('div'); videoContainer.style.cssText = ` position: relative; flex: 1; overflow: hidden; `; const video = document.createElement('video'); video.id = 'custom-video-player'; video.controls = true; video.style.cssText = ` width: 100%; height: 100%; object-fit: contain; `; // 完全禁用video原生全屏 video.disablePictureInPicture = true; video.controlsList = 'nodownload nofullscreen'; // 双击视频全屏 video.addEventListener('dblclick', () => { toggleCustomFullscreen(); }); // 阻止任何可能的原生全屏 video.addEventListener('webkitbeginfullscreen', (e) => { e.preventDefault(); e.stopPropagation(); toggleCustomFullscreen(); }); video.addEventListener('webkitendfullscreen', (e) => { e.preventDefault(); e.stopPropagation(); }); // 弹幕容器 danmuContainer = document.createElement('div'); danmuContainer.id = 'danmu-container'; danmuContainer.style.cssText = ` position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; z-index: 2147483647; `; // 初始化弹幕容器显示区域 updateDanmuContainer(); videoContainer.appendChild(video); videoContainer.appendChild(danmuContainer); playerContainer.appendChild(playerHeader); playerContainer.appendChild(videoContainer); // 事件监听 // 减少按钮事件 minusButtons.forEach(({ button, value }) => { button.addEventListener('click', () => { timeOffset -= value; timeAdjustDisplay.textContent = `${timeOffset.toFixed(1)}s`; }); }); // 增加按钮事件 plusButtons.forEach(({ button, value }) => { button.addEventListener('click', () => { timeOffset += value; timeAdjustDisplay.textContent = `${timeOffset.toFixed(1)}s`; }); }); // 重置按钮事件 resetButton.addEventListener('click', () => { timeOffset = 0; timeAdjustDisplay.textContent = '0.0s'; }); danmuSettingsButton.addEventListener('click', () => { toggleDanmuSettings(); }); fullscreenButton.addEventListener('click', () => { toggleCustomFullscreen(); }); closeButton.addEventListener('click', () => { playerContainer.style.display = 'none'; }); // 视频事件监听 video.addEventListener('timeupdate', () => { if (isPlaying) { displayDanmu(video.currentTime); } }); video.addEventListener('play', () => { isPlaying = true; resumeAllDanmu(); }); video.addEventListener('pause', () => { isPlaying = false; pauseAllDanmu(); }); video.addEventListener('ended', () => { isPlaying = false; clearAllDanmu(); }); // 监听视频元素的全屏事件 video.addEventListener('webkitbeginfullscreen', () => { console.log('视频开始全屏 (webkit)'); handleVideoFullscreen(true); }); video.addEventListener('webkitendfullscreen', () => { console.log('视频结束全屏 (webkit)'); handleVideoFullscreen(false); }); video.addEventListener('seeked', () => { // 拖拽进度条时清除所有弹幕 clearAllDanmu(); }); // 全屏状态变化监听 document.addEventListener('fullscreenchange', () => { handleFullscreenChange(); }); document.addEventListener('webkitfullscreenchange', () => { handleFullscreenChange(); }); document.addEventListener('mozfullscreenchange', () => { handleFullscreenChange(); }); document.addEventListener('MSFullscreenChange', () => { handleFullscreenChange(); }); document.body.appendChild(playerContainer); return { container: playerContainer, video: video, fullscreenButton: fullscreenButton, header: playerHeader, danmuSettingsButton: danmuSettingsButton }; } // 创建弹幕设置面板 function createDanmuSettingsPanel() { const panel = document.createElement('div'); panel.id = 'danmu-settings-panel'; panel.style.cssText = ` position: absolute; top: 60px; right: 10px; width: 300px; background: rgba(0, 0, 0, 0.9); color: white; border-radius: 8px; padding: 20px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); z-index: 10000; display: none; font-size: 14px; `; panel.innerHTML = ` <h3 style="margin: 0 0 15px 0; font-size: 16px; color: #fff;">弹幕设置</h3> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px;">显示区域:</label> <select id="danmu-display-area" style="width: 100%; padding: 5px; border-radius: 4px; border: none; background: #333; color: white;"> <option value="full">全屏显示</option> <option value="half">半屏显示</option> <option value="quarter">1/4屏显示</option> </select> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px;">透明度:<span id="opacity-value">90%</span></label> <input type="range" id="danmu-opacity" min="0.1" max="1" step="0.1" value="0.9" style="width: 100%;"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px;">字体大小:<span id="fontsize-value">100%</span></label> <input type="range" id="danmu-fontsize" min="0.5" max="2" step="0.1" value="1.0" style="width: 100%;"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px;">滚动速度:<span id="speed-value">100%</span></label> <input type="range" id="danmu-speed" min="0.5" max="2" step="0.1" value="1.0" style="width: 100%;"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px;">弹幕密度:<span id="density-value">100%</span></label> <input type="range" id="danmu-density" min="0.1" max="2" step="0.1" value="1.0" style="width: 100%;"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 10px;">弹幕类型:</label> <div style="display: flex; gap: 10px; flex-wrap: wrap;"> <label style="display: flex; align-items: center; cursor: pointer;"> <input type="checkbox" id="danmu-scroll" checked style="margin-right: 5px;"> 滚动弹幕 </label> <label style="display: flex; align-items: center; cursor: pointer;"> <input type="checkbox" id="danmu-top" checked style="margin-right: 5px;"> 顶部弹幕 </label> <label style="display: flex; align-items: center; cursor: pointer;"> <input type="checkbox" id="danmu-bottom" checked style="margin-right: 5px;"> 底部弹幕 </label> </div> </div> <div style="display: flex; gap: 10px; margin-top: 20px;"> <button id="danmu-settings-reset" style="flex: 1; padding: 8px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer;"> 重置 </button> <button id="danmu-settings-close" style="flex: 1; padding: 8px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;"> 关闭 </button> </div> `; return panel; } // 弹幕设置面板控制 let danmuSettingsPanel = null; let isSettingsPanelOpen = false; function toggleDanmuSettings() { if (!danmuSettingsPanel) { danmuSettingsPanel = createDanmuSettingsPanel(); if (customPlayer && customPlayer.container) { customPlayer.container.appendChild(danmuSettingsPanel); setupDanmuSettingsEvents(); } } if (isSettingsPanelOpen) { danmuSettingsPanel.style.display = 'none'; isSettingsPanelOpen = false; } else { danmuSettingsPanel.style.display = 'block'; isSettingsPanelOpen = true; updateSettingsDisplay(); } } // 设置弹幕设置面板的事件监听 function setupDanmuSettingsEvents() { if (!danmuSettingsPanel) return; // 显示区域 const displayAreaSelect = danmuSettingsPanel.querySelector('#danmu-display-area'); displayAreaSelect.addEventListener('change', (e) => { danmuSettings.displayArea = e.target.value; updateDanmuContainer(); }); // 透明度 const opacitySlider = danmuSettingsPanel.querySelector('#danmu-opacity'); const opacityValue = danmuSettingsPanel.querySelector('#opacity-value'); opacitySlider.addEventListener('input', (e) => { danmuSettings.opacity = parseFloat(e.target.value); opacityValue.textContent = Math.round(danmuSettings.opacity * 100) + '%'; updateDanmuStyles(); }); // 字体大小 const fontsizeSlider = danmuSettingsPanel.querySelector('#danmu-fontsize'); const fontsizeValue = danmuSettingsPanel.querySelector('#fontsize-value'); fontsizeSlider.addEventListener('input', (e) => { danmuSettings.fontSize = parseFloat(e.target.value); fontsizeValue.textContent = Math.round(danmuSettings.fontSize * 100) + '%'; updateDanmuStyles(); }); // 滚动速度 const speedSlider = danmuSettingsPanel.querySelector('#danmu-speed'); const speedValue = danmuSettingsPanel.querySelector('#speed-value'); speedSlider.addEventListener('input', (e) => { danmuSettings.speed = parseFloat(e.target.value); speedValue.textContent = Math.round(danmuSettings.speed * 100) + '%'; }); // 弹幕密度 const densitySlider = danmuSettingsPanel.querySelector('#danmu-density'); const densityValue = danmuSettingsPanel.querySelector('#density-value'); densitySlider.addEventListener('input', (e) => { danmuSettings.density = parseFloat(e.target.value); densityValue.textContent = Math.round(danmuSettings.density * 100) + '%'; }); // 弹幕类型 const scrollCheckbox = danmuSettingsPanel.querySelector('#danmu-scroll'); const topCheckbox = danmuSettingsPanel.querySelector('#danmu-top'); const bottomCheckbox = danmuSettingsPanel.querySelector('#danmu-bottom'); scrollCheckbox.addEventListener('change', (e) => { danmuSettings.showScroll = e.target.checked; }); topCheckbox.addEventListener('change', (e) => { danmuSettings.showTop = e.target.checked; }); bottomCheckbox.addEventListener('change', (e) => { danmuSettings.showBottom = e.target.checked; }); // 重置按钮 const resetButton = danmuSettingsPanel.querySelector('#danmu-settings-reset'); resetButton.addEventListener('click', () => { resetDanmuSettings(); }); // 关闭按钮 const closeButton = danmuSettingsPanel.querySelector('#danmu-settings-close'); closeButton.addEventListener('click', () => { toggleDanmuSettings(); }); } // 更新设置显示 function updateSettingsDisplay() { if (!danmuSettingsPanel) return; const displayAreaSelect = danmuSettingsPanel.querySelector('#danmu-display-area'); const opacitySlider = danmuSettingsPanel.querySelector('#danmu-opacity'); const opacityValue = danmuSettingsPanel.querySelector('#opacity-value'); const fontsizeSlider = danmuSettingsPanel.querySelector('#danmu-fontsize'); const fontsizeValue = danmuSettingsPanel.querySelector('#fontsize-value'); const speedSlider = danmuSettingsPanel.querySelector('#danmu-speed'); const speedValue = danmuSettingsPanel.querySelector('#speed-value'); const densitySlider = danmuSettingsPanel.querySelector('#danmu-density'); const densityValue = danmuSettingsPanel.querySelector('#density-value'); const scrollCheckbox = danmuSettingsPanel.querySelector('#danmu-scroll'); const topCheckbox = danmuSettingsPanel.querySelector('#danmu-top'); const bottomCheckbox = danmuSettingsPanel.querySelector('#danmu-bottom'); displayAreaSelect.value = danmuSettings.displayArea; opacitySlider.value = danmuSettings.opacity; opacityValue.textContent = Math.round(danmuSettings.opacity * 100) + '%'; fontsizeSlider.value = danmuSettings.fontSize; fontsizeValue.textContent = Math.round(danmuSettings.fontSize * 100) + '%'; speedSlider.value = danmuSettings.speed; speedValue.textContent = Math.round(danmuSettings.speed * 100) + '%'; densitySlider.value = danmuSettings.density; densityValue.textContent = Math.round(danmuSettings.density * 100) + '%'; scrollCheckbox.checked = danmuSettings.showScroll; topCheckbox.checked = danmuSettings.showTop; bottomCheckbox.checked = danmuSettings.showBottom; } // 重置弹幕设置 function resetDanmuSettings() { danmuSettings = { displayArea: 'full', opacity: 0.9, fontSize: 1.0, speed: 1.0, density: 1.0, showScroll: true, showTop: true, showBottom: true }; updateSettingsDisplay(); updateDanmuContainer(); updateDanmuStyles(); } // 更新弹幕容器显示区域 function updateDanmuContainer() { if (!danmuContainer) return; // 全屏时强制使用全屏显示,不修改容器 if (isCustomFullscreen) { return; } let height = '100%'; let top = '0'; switch (danmuSettings.displayArea) { case 'half': height = '50%'; top = '0'; break; case 'quarter': height = '25%'; top = '0'; break; case 'full': default: height = '100%'; top = '0'; break; } danmuContainer.style.height = height; danmuContainer.style.top = top; } // 更新现有弹幕样式 function updateDanmuStyles() { const existingDanmu = document.querySelectorAll('.danmu-item'); existingDanmu.forEach(element => { element.style.opacity = danmuSettings.opacity; // 更新字体大小 const currentFontSize = parseFloat(element.style.fontSize); if (currentFontSize) { const baseFontSize = currentFontSize / (element.dataset.fontScale || 1); element.style.fontSize = (baseFontSize * danmuSettings.fontSize) + 'px'; element.dataset.fontScale = danmuSettings.fontSize; } }); } // 真正的全屏切换(让播放器容器进入全屏) function toggleCustomFullscreen() { if (!customPlayer) return; if (!isCustomFullscreen) { // 进入真正的全屏 const container = customPlayer.container; // 准备全屏样式 container.style.width = '100vw'; container.style.height = '100vh'; container.style.background = '#000'; container.style.borderRadius = '0'; container.style.boxShadow = 'none'; // 使用浏览器全屏API让容器进入全屏 if (container.requestFullscreen) { container.requestFullscreen(); } else if (container.webkitRequestFullscreen) { container.webkitRequestFullscreen(); } else if (container.mozRequestFullScreen) { container.mozRequestFullScreen(); } else if (container.msRequestFullscreen) { container.msRequestFullscreen(); } console.log('请求进入真正的全屏模式'); } else { // 退出全屏 if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } console.log('请求退出全屏模式'); } } // 处理全屏状态变化 function handleFullscreenChange() { const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; if (fullscreenElement) { // 进入全屏 isCustomFullscreen = true; // 更新全屏按钮 if (customPlayer && customPlayer.fullscreenButton) { customPlayer.fullscreenButton.textContent = '⛷'; customPlayer.fullscreenButton.title = '退出全屏'; } // 隐藏顶部工具栏 if (customPlayer && customPlayer.header) { customPlayer.header.style.opacity = '0'; customPlayer.header.style.transform = 'translateY(-100%)'; customPlayer.header.style.pointerEvents = 'none'; } // 添加鼠标移动显示工具栏的功能 setupFullscreenControls(); // 确保弹幕容器正确显示 if (danmuContainer) { danmuContainer.style.position = 'absolute'; danmuContainer.style.top = '0'; danmuContainer.style.left = '0'; danmuContainer.style.width = '100%'; danmuContainer.style.height = '100%'; danmuContainer.style.zIndex = '2147483647'; danmuContainer.style.pointerEvents = 'none'; danmuContainer.style.display = 'block'; danmuContainer.style.visibility = 'visible'; } console.log('已进入全屏模式'); } else { // 退出全屏 isCustomFullscreen = false; // 更新全屏按钮 if (customPlayer && customPlayer.fullscreenButton) { customPlayer.fullscreenButton.textContent = '⛶'; customPlayer.fullscreenButton.title = '全屏播放'; } // 显示顶部工具栏 if (customPlayer && customPlayer.header) { customPlayer.header.style.opacity = '1'; customPlayer.header.style.transform = 'translateY(0)'; customPlayer.header.style.pointerEvents = 'auto'; } // 清除全屏控制 clearFullscreenControls(); // 恢复播放器容器样式 if (customPlayer && customPlayer.container) { customPlayer.container.style.width = '80vw'; customPlayer.container.style.height = '70vh'; customPlayer.container.style.background = '#000'; customPlayer.container.style.borderRadius = '8px'; customPlayer.container.style.boxShadow = '0 8px 32px rgba(0,0,0,0.8)'; } // 更新弹幕容器显示区域 updateDanmuContainer(); console.log('已退出全屏模式'); } } // 全屏控制相关变量 let fullscreenMouseTimer; let fullscreenMouseListener; // 设置全屏控制 function setupFullscreenControls() { if (!customPlayer || !customPlayer.container) return; // 鼠标移动监听器 fullscreenMouseListener = (e) => { // 显示工具栏 if (customPlayer.header) { customPlayer.header.style.opacity = '1'; customPlayer.header.style.transform = 'translateY(0)'; customPlayer.header.style.pointerEvents = 'auto'; } // 清除之前的定时器 if (fullscreenMouseTimer) { clearTimeout(fullscreenMouseTimer); } // 3秒后隐藏工具栏 fullscreenMouseTimer = setTimeout(() => { if (customPlayer.header && isCustomFullscreen) { customPlayer.header.style.opacity = '0'; customPlayer.header.style.transform = 'translateY(-100%)'; customPlayer.header.style.pointerEvents = 'none'; } }, 3000); }; // 添加鼠标移动监听 customPlayer.container.addEventListener('mousemove', fullscreenMouseListener); // 初始隐藏工具栏 setTimeout(() => { if (customPlayer.header && isCustomFullscreen) { customPlayer.header.style.opacity = '0'; customPlayer.header.style.transform = 'translateY(-100%)'; customPlayer.header.style.pointerEvents = 'none'; } }, 3000); } // 清除全屏控制 function clearFullscreenControls() { if (fullscreenMouseTimer) { clearTimeout(fullscreenMouseTimer); fullscreenMouseTimer = null; } if (fullscreenMouseListener && customPlayer && customPlayer.container) { customPlayer.container.removeEventListener('mousemove', fullscreenMouseListener); fullscreenMouseListener = null; } } // 处理视频全屏(移动端) function handleVideoFullscreen(isFullscreen) { if (isFullscreen && danmuContainer) { console.log('处理视频全屏模式'); // 创建全屏弹幕容器 const fullscreenDanmuContainer = document.createElement('div'); fullscreenDanmuContainer.id = 'fullscreen-danmu-container'; fullscreenDanmuContainer.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; overflow: hidden; z-index: 2147483647; background: transparent; `; // 将现有弹幕移动到全屏容器 while (danmuContainer.firstChild) { fullscreenDanmuContainer.appendChild(danmuContainer.firstChild); } document.body.appendChild(fullscreenDanmuContainer); danmuContainer.dataset.originalContainer = 'true'; danmuContainer = fullscreenDanmuContainer; } else if (!isFullscreen && danmuContainer && danmuContainer.id === 'fullscreen-danmu-container') { console.log('退出视频全屏模式'); // 恢复原始容器 const originalContainer = document.getElementById('danmu-container'); if (originalContainer) { // 将弹幕移回原始容器 while (danmuContainer.firstChild) { originalContainer.appendChild(danmuContainer.firstChild); } // 移除全屏容器 danmuContainer.remove(); danmuContainer = originalContainer; } } } // 显示弹幕 function displayDanmu(currentTime) { if (!danmuData.length || !danmuContainer || !isPlaying) return; // 检查是否处于全屏状态,如果是则确保弹幕容器正确显示 ensureDanmuVisibility(); const adjustedTime = (currentTime + timeOffset) * 10; // 转换为0.1秒单位 // 清除过期弹幕 const existingDanmu = danmuContainer.querySelectorAll('.danmu-item'); existingDanmu.forEach(item => { const itemTime = parseFloat(item.dataset.time); if (adjustedTime - itemTime > 100) { // 10秒后清除 item.remove(); activeDanmuElements.delete(item); } }); // 添加新弹幕(应用密度设置) let danmuCount = 0; const maxDanmuPerFrame = Math.ceil(5 * danmuSettings.density); danmuData.forEach(danmu => { const danmuTime = danmu.time; const timeDiff = Math.abs(adjustedTime - danmuTime); if (timeDiff <= 1 && !danmuContainer.querySelector(`[data-sn="${danmu.sn}"]`)) { // 应用弹幕类型过滤 const position = danmu.position === 2 ? 'bottom' : danmu.position === 1 ? 'top' : 'scroll'; if ((position === 'scroll' && !danmuSettings.showScroll) || (position === 'top' && !danmuSettings.showTop) || (position === 'bottom' && !danmuSettings.showBottom)) { return; } // 应用密度限制 if (danmuCount >= maxDanmuPerFrame) { return; } createDanmuElement(danmu); danmuCount++; } }); } // 确保弹幕容器可见 function ensureDanmuVisibility() { if (!danmuContainer) return; // 确保弹幕容器始终可见 danmuContainer.style.position = 'absolute'; danmuContainer.style.top = '0'; danmuContainer.style.left = '0'; danmuContainer.style.width = '100%'; danmuContainer.style.height = '100%'; danmuContainer.style.zIndex = '2147483647'; danmuContainer.style.pointerEvents = 'none'; danmuContainer.style.display = 'block'; danmuContainer.style.visibility = 'visible'; } // 暂停所有弹幕动画 function pauseAllDanmu() { activeDanmuElements.forEach(element => { // 对于滚动弹幕,记录当前位置并停止动画 if (element.classList.contains('scroll-danmu')) { const computedStyle = window.getComputedStyle(element); const currentRight = computedStyle.right; element.style.transition = 'none'; element.style.right = currentRight; element.dataset.pausedRight = currentRight; // 记录暂停时间,用于计算剩余时间 element.dataset.pausedTime = Date.now(); } // 对于固定弹幕,清除自动移除的定时器 if (element.classList.contains('fixed-danmu') && element.dataset.removeTimer) { clearTimeout(parseInt(element.dataset.removeTimer)); delete element.dataset.removeTimer; } }); } // 恢复所有弹幕动画 function resumeAllDanmu() { activeDanmuElements.forEach(element => { // 对于滚动弹幕,从暂停位置继续滚动 if (element.classList.contains('scroll-danmu') && element.dataset.pausedRight) { const pausedRight = parseFloat(element.dataset.pausedRight); const containerWidth = element.parentElement.offsetWidth; const elementWidth = element.offsetWidth; const totalDistance = containerWidth + elementWidth; // 计算已经移动的距离 const movedDistance = totalDistance * (100 - pausedRight) / 100; const remainingDistance = totalDistance - movedDistance; const remainingTime = (remainingDistance / totalDistance) * 8; // 8秒总时长 element.style.transition = `right ${remainingTime}s linear`; element.style.right = '100%'; // 设置新的移除定时器 const removeTimer = setTimeout(() => { if (element.parentNode) { element.remove(); activeDanmuElements.delete(element); } }, remainingTime * 1000); element.dataset.removeTimer = removeTimer.toString(); delete element.dataset.pausedRight; delete element.dataset.pausedTime; } // 对于固定弹幕,重新设置移除定时器 if (element.classList.contains('fixed-danmu') && !element.dataset.removeTimer) { const removeTimer = setTimeout(() => { if (element.parentNode) { element.remove(); activeDanmuElements.delete(element); } }, 3000); element.dataset.removeTimer = removeTimer.toString(); } }); } // 清除所有弹幕 function clearAllDanmu() { const existingDanmu = danmuContainer.querySelectorAll('.danmu-item'); existingDanmu.forEach(item => { item.remove(); }); activeDanmuElements.clear(); } // 创建弹幕元素 function createDanmuElement(danmu) { const danmuElement = document.createElement('div'); danmuElement.className = 'danmu-item'; danmuElement.dataset.sn = danmu.sn; danmuElement.dataset.time = danmu.time; danmuElement.textContent = danmu.text; // 弹幕样式(应用设置) const baseFontSize = danmu.size === 2 ? 20 : danmu.size === 1 ? 16 : 14; const fontSize = (baseFontSize * danmuSettings.fontSize) + 'px'; const position = danmu.position === 2 ? 'bottom' : danmu.position === 1 ? 'top' : 'scroll'; danmuElement.style.cssText = ` position: absolute; color: ${danmu.color}; font-size: ${fontSize}; font-weight: bold; text-shadow: 1px 1px 2px rgba(0,0,0,0.8); white-space: nowrap; z-index: 2147483647; pointer-events: none; font-family: Arial, sans-serif; opacity: ${danmuSettings.opacity}; `; // 保存字体缩放比例 danmuElement.dataset.fontScale = danmuSettings.fontSize; // 添加到活跃弹幕集合 activeDanmuElements.add(danmuElement); // 根据位置类型设置弹幕位置和动画 if (position === 'scroll') { // 滚动弹幕(应用速度设置) danmuElement.classList.add('scroll-danmu'); // 根据显示区域调整位置(全屏时使用全区域) let maxTop = 70; if (!isCustomFullscreen) { switch (danmuSettings.displayArea) { case 'half': maxTop = 35; break; case 'quarter': maxTop = 15; break; default: maxTop = 70; break; } } const randomTop = Math.random() * maxTop + 10; danmuElement.style.top = `${randomTop}%`; danmuElement.style.right = '-100%'; // 应用速度设置 const duration = 8 / danmuSettings.speed; danmuElement.style.transition = `right ${duration}s linear`; danmuContainer.appendChild(danmuElement); // 触发滚动动画 setTimeout(() => { if (isPlaying) { danmuElement.style.right = '100%'; } }, 50); // 根据速度调整移除时间 const removeTimer = setTimeout(() => { if (danmuElement.parentNode) { danmuElement.remove(); activeDanmuElements.delete(danmuElement); } }, duration * 1000); danmuElement.dataset.removeTimer = removeTimer.toString(); } else if (position === 'top') { // 顶部固定弹幕 danmuElement.classList.add('fixed-danmu'); danmuElement.style.top = '10%'; danmuElement.style.left = '50%'; danmuElement.style.transform = 'translateX(-50%)'; danmuContainer.appendChild(danmuElement); const removeTimer = setTimeout(() => { if (danmuElement.parentNode) { danmuElement.remove(); activeDanmuElements.delete(danmuElement); } }, 3000); danmuElement.dataset.removeTimer = removeTimer.toString(); } else if (position === 'bottom') { // 底部固定弹幕 danmuElement.classList.add('fixed-danmu'); danmuElement.style.bottom = '10%'; danmuElement.style.left = '50%'; danmuElement.style.transform = 'translateX(-50%)'; danmuContainer.appendChild(danmuElement); const removeTimer = setTimeout(() => { if (danmuElement.parentNode) { danmuElement.remove(); activeDanmuElements.delete(danmuElement); } }, 3000); danmuElement.dataset.removeTimer = removeTimer.toString(); } } // 创建上传按钮 function createUploadButton() { const button = document.createElement('button'); button.innerHTML = '🎬 上传视频+弹幕'; button.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 9999; padding: 12px 16px; background: #007bff; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); transition: all 0.3s ease; `; button.addEventListener('mouseenter', () => { button.style.background = '#0056b3'; button.style.transform = 'scale(1.05)'; }); button.addEventListener('mouseleave', () => { button.style.background = '#007bff'; button.style.transform = 'scale(1)'; }); return button; } // 创建文件输入框 function createFileInput() { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'video/*'; fileInput.style.display = 'none'; return fileInput; } // 显示通知 function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 10001; padding: 12px 20px; border-radius: 6px; color: white; font-size: 14px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); transition: all 0.3s ease; ${type === 'success' ? 'background: #28a745;' : type === 'error' ? 'background: #dc3545;' : 'background: #17a2b8;'} `; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 300); }, 3000); } // 处理视频上传 async function handleVideoUpload(file) { const videoSn = getVideoSn(); if (!videoSn) { showNotification('无法获取视频SN,请确保在正确的页面', 'error'); return; } showNotification('正在获取弹幕数据...', 'info'); const success = await fetchDanmu(videoSn); if (!success) { showNotification('获取弹幕失败', 'error'); return; } // 创建播放器(如果还没有) if (!customPlayer) { customPlayer = createCustomPlayer(); } // 加载视频 const fileURL = URL.createObjectURL(file); customPlayer.video.src = fileURL; // 显示播放器 customPlayer.container.style.display = 'flex'; showNotification(`视频加载成功,共 ${danmuData.length} 条弹幕`, 'success'); } // 初始化脚本 function init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); return; } // 创建按钮和文件输入框 const uploadButton = createUploadButton(); const fileInput = createFileInput(); document.body.appendChild(uploadButton); document.body.appendChild(fileInput); // 事件监听 uploadButton.addEventListener('click', () => { fileInput.click(); }); fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; if (!file) return; if (!file.type.startsWith('video/')) { showNotification('请选择视频文件!', 'error'); return; } handleVideoUpload(file); fileInput.value = ''; }); // ESC键退出全屏由浏览器自动处理,不需要手动监听 console.log('动画疯弹幕播放器已加载'); } // 启动脚本 init(); })();