// ==UserScript== // @name Instagram视频进度条Reels 2.1.0 // @namespace http://tampermonkey.net/ // @version 2.1.0 // @description 全面优化版Instagram视频控制器,集成连播,音量滑块从右弹出,进度拖动修复,白色倍速文字 // @author Greasy Fork:蛋定的文弱书生 // @match *://*.instagram.com/* // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/550217/Instagram%E8%A7%86%E9%A2%91%E8%BF%9B%E5%BA%A6%E6%9D%A1Reels%20210.user.js // @updateURL https://update.greasyfork.icu/scripts/550217/Instagram%E8%A7%86%E9%A2%91%E8%BF%9B%E5%BA%A6%E6%9D%A1Reels%20210.meta.js // ==/UserScript== (function() { 'use strict'; // 全局变量 let progressWindow = null; let currentVideo = null; let isDragging = false; let lastVideoCheck = 0; let volumeSliderTimer = null; let volumeRestoring = false; let currentVolumeChangeHandler = null; let currentPlayGuardHandler = null; let currentEndedHandler = null; // 连播结束监听 // 用户设置 const userSettings = { progressBarColor: '#ff4757', windowPosition: loadWindowPosition(), defaultVolume: localStorage.getItem('ig-video-volume') ? parseFloat(localStorage.getItem('ig-video-volume')) : 1.0, defaultPlaybackRate: localStorage.getItem('ig-playback-rate') ? parseFloat(localStorage.getItem('ig-playback-rate')) : 1.0, autoPlayNext: localStorage.getItem('ig-autoplay-next') ? localStorage.getItem('ig-autoplay-next') === 'true' : false }; function loadWindowPosition() { try { const saved = localStorage.getItem('ig-window-position'); if (saved) { const pos = JSON.parse(saved); if (pos.x >= 0 && pos.y >= 0 && pos.x < window.innerWidth && pos.y < window.innerHeight) { return pos; } } } catch (e) {} return { x: window.innerWidth - 300, y: 100 }; } function saveWindowPosition() { try { const rect = progressWindow.getBoundingClientRect(); localStorage.setItem('ig-window-position', JSON.stringify({ x: rect.left, y: rect.top })); } catch (e) {} } function springScale(element, fromScale = 1, toScale = 1.15, duration = 200) { element.style.transition = `transform ${duration}ms cubic-bezier(0.25, 0.8, 0.25, 1)`; element.style.transform = `scale(${toScale})`; setTimeout(() => { element.style.transform = `scale(${fromScale})`; }, duration); } // ==================== 连播按钮 ==================== function createAutoPlaySVGIcon(isActive) { const svgNS = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(svgNS, 'svg'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('width', '17'); svg.setAttribute('height', '17'); svg.setAttribute('fill', 'none'); svg.setAttribute('stroke', isActive ? userSettings.progressBarColor : 'rgba(255,255,255,0.45)'); svg.setAttribute('stroke-width', '2.2'); svg.setAttribute('stroke-linecap', 'round'); svg.setAttribute('stroke-linejoin', 'round'); svg.style.cssText = 'transition: all 0.3s ease; display: block;'; const path1 = document.createElementNS(svgNS, 'path'); path1.setAttribute('d', 'M6 5C4.5 6.5 3.5 8.5 3.5 10.5C3.5 14 5.8 17 9 18.5'); const poly1 = document.createElementNS(svgNS, 'polyline'); poly1.setAttribute('points', '5,18 9,18.5 8.5,14.5'); const path2 = document.createElementNS(svgNS, 'path'); path2.setAttribute('d', 'M18 19C19.5 17.5 20.5 15.5 20.5 13.5C20.5 10 18.2 7 15 5.5'); const poly2 = document.createElementNS(svgNS, 'polyline'); poly2.setAttribute('points', '19,6 15,5.5 15.5,9.5'); const circle = document.createElementNS(svgNS, 'circle'); circle.setAttribute('cx', '12'); circle.setAttribute('cy', '12'); circle.setAttribute('r', '2'); circle.setAttribute('fill', isActive ? userSettings.progressBarColor : 'rgba(255,255,255,0.45)'); circle.setAttribute('stroke', 'none'); circle.style.cssText = 'transition: all 0.3s ease;'; svg.appendChild(path1); svg.appendChild(poly1); svg.appendChild(path2); svg.appendChild(poly2); svg.appendChild(circle); return svg; } function createAutoPlayControl() { const container = document.createElement('div'); container.style.cssText = `position: relative; display: flex; align-items: center;`; const button = document.createElement('div'); button.id = 'ig-autoplay-button'; button.title = userSettings.autoPlayNext ? '连播已开启 - 点击关闭' : '连播已关闭 - 点击开启'; button.style.cssText = ` cursor: pointer; width: 28px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 5px; background: ${userSettings.autoPlayNext ? 'rgba(255,255,255,0.12)' : 'rgba(255,255,255,0.04)'}; transition: all 0.25s ease; position: relative; `; button.addEventListener('mouseenter', () => { button.style.background = userSettings.autoPlayNext ? 'rgba(255,255,255,0.2)' : 'rgba(255,255,255,0.1)'; button.style.transform = 'scale(1.08)'; }); button.addEventListener('mouseleave', () => { button.style.background = userSettings.autoPlayNext ? 'rgba(255,255,255,0.12)' : 'rgba(255,255,255,0.04)'; button.style.transform = 'scale(1)'; }); const iconWrapper = document.createElement('div'); iconWrapper.style.cssText = `display: flex; align-items: center; justify-content: center; transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);`; iconWrapper.appendChild(createAutoPlaySVGIcon(userSettings.autoPlayNext)); button.appendChild(iconWrapper); const indicator = document.createElement('div'); indicator.id = 'ig-autoplay-indicator'; indicator.style.cssText = ` position: absolute; top: 2px; right: 3px; width: 5px; height: 5px; border-radius: 50%; background: ${userSettings.progressBarColor}; opacity: ${userSettings.autoPlayNext ? '1' : '0'}; transform: scale(${userSettings.autoPlayNext ? '1' : '0.3'}); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 0 4px ${userSettings.progressBarColor}; `; button.appendChild(indicator); button.addEventListener('click', (e) => { e.stopPropagation(); toggleAutoPlay(); }); container.appendChild(button); return { container, button, indicator, iconWrapper }; } function toggleAutoPlay() { userSettings.autoPlayNext = !userSettings.autoPlayNext; localStorage.setItem('ig-autoplay-next', userSettings.autoPlayNext.toString()); const button = document.getElementById('ig-autoplay-button'); const indicator = document.getElementById('ig-autoplay-indicator'); if (button) { button.title = userSettings.autoPlayNext ? '连播已开启 - 点击关闭' : '连播已关闭 - 点击开启'; button.style.background = userSettings.autoPlayNext ? 'rgba(255,255,255,0.12)' : 'rgba(255,255,255,0.04)'; } if (indicator) { indicator.style.opacity = userSettings.autoPlayNext ? '1' : '0'; indicator.style.transform = userSettings.autoPlayNext ? 'scale(1)' : 'scale(0.3)'; } const wrapper = button?.querySelector('div'); if (wrapper) { wrapper.style.transform = 'rotate(180deg)'; setTimeout(() => { wrapper.style.transform = 'rotate(360deg)'; }, 50); setTimeout(() => { wrapper.style.transform = 'rotate(0deg)'; }, 400); } updateAutoPlayIcon(); } function updateAutoPlayIcon() { const button = document.getElementById('ig-autoplay-button'); if (!button) return; const iconWrapper = button.querySelector('div'); if (!iconWrapper) return; const oldSvg = iconWrapper.querySelector('svg'); if (oldSvg) oldSvg.remove(); iconWrapper.appendChild(createAutoPlaySVGIcon(userSettings.autoPlayNext)); } // ==================== 音量控制(还原2.0.2样式 + 从右向左弹出动画) ==================== function createVolumeControl() { const volumeContainer = document.createElement('div'); volumeContainer.style.cssText = `position: relative; display: flex; align-items: center;`; // 垂直滑块容器(从右弹出) const slider = document.createElement('div'); slider.id = 'ig-volume-slider-container'; slider.style.cssText = ` position: absolute; left: -40px; bottom: -10px; width: 30px; height: 100px; border-radius: 12px; background: rgba(255,255,255,0.3); display: flex; justify-content: center; align-items: flex-end; transform: translateX(60px); opacity: 0; pointer-events: none; transition: transform 0.25s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.2s ease; z-index: 1001; overflow: hidden; `; // 填充条 const fill = document.createElement('div'); fill.id = 'ig-volume-fill'; fill.style.cssText = ` width: 100%; height: ${userSettings.defaultVolume * 100}%; background: ${userSettings.progressBarColor}; border-radius: 12px; transition: height 0.15s ease; box-shadow: 0 0 6px ${userSettings.progressBarColor}; `; slider.appendChild(fill); // 轨道点击/拖动事件 slider.addEventListener('mousedown', (e) => { e.preventDefault(); const rect = slider.getBoundingClientRect(); const handleDrag = (e) => { const dragY = Math.max(0, Math.min(e.clientY - rect.top, rect.height)); const vol = Math.max(0, Math.min(1, 1 - (dragY / rect.height))); setVolume(vol); }; const stopDrag = () => { document.removeEventListener('mousemove', handleDrag); document.removeEventListener('mouseup', stopDrag); }; document.addEventListener('mousemove', handleDrag); document.addEventListener('mouseup', stopDrag); const initY = e.clientY - rect.top; setVolume(Math.max(0, Math.min(1, 1 - (initY / rect.height)))); }); slider.addEventListener('click', (e) => { const rect = slider.getBoundingClientRect(); const clickY = e.clientY - rect.top; setVolume(Math.max(0, Math.min(1, 1 - (clickY / rect.height)))); }); // SVG音量图标 const volumeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); volumeIcon.setAttribute('id', 'ig-volume-icon-svg'); volumeIcon.setAttribute('viewBox', '0 0 24 24'); volumeIcon.setAttribute('width', '18'); volumeIcon.setAttribute('height', '18'); volumeIcon.setAttribute('fill', 'none'); volumeIcon.setAttribute('stroke', '#ffffff'); volumeIcon.setAttribute('stroke-width', '2'); volumeIcon.setAttribute('stroke-linecap', 'round'); volumeIcon.setAttribute('stroke-linejoin', 'round'); volumeIcon.style.cssText = ` cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; z-index: 1002; transition: transform 0.15s ease; `; const speakerPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); speakerPath.setAttribute('d', 'M11 5L6 9H2v6h4l5 4V5z'); speakerPath.setAttribute('fill', '#ffffff'); speakerPath.setAttribute('stroke', 'none'); volumeIcon.appendChild(speakerPath); const wave1 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); wave1.setAttribute('id', 'ig-volume-wave1'); wave1.setAttribute('d', 'M15.54 8.46a5 5 0 010 7.07'); wave1.setAttribute('stroke', '#ffffff'); wave1.setAttribute('stroke-width', '2'); wave1.style.cssText = 'transition: opacity 0.2s, stroke-width 0.2s;'; volumeIcon.appendChild(wave1); const wave2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); wave2.setAttribute('id', 'ig-volume-wave2'); wave2.setAttribute('d', 'M19.07 4.93a10 10 0 010 14.14'); wave2.setAttribute('stroke', '#ffffff'); wave2.setAttribute('stroke-width', '2'); wave2.style.cssText = 'transition: opacity 0.2s, stroke-width 0.2s;'; volumeIcon.appendChild(wave2); const muteX = document.createElementNS('http://www.w3.org/2000/svg', 'path'); muteX.setAttribute('id', 'ig-volume-mute-x'); muteX.setAttribute('d', 'M23 9L17 15M17 9l6 6'); muteX.setAttribute('stroke', '#ff4757'); muteX.setAttribute('stroke-width', '2'); muteX.setAttribute('opacity', '0'); muteX.style.cssText = 'transition: opacity 0.2s;'; volumeIcon.appendChild(muteX); volumeIcon.addEventListener('click', toggleMute); volumeIcon.addEventListener('mouseenter', () => { volumeIcon.style.transform = 'scale(1.12)'; showVolumeSlider(); }); volumeIcon.addEventListener('mouseleave', () => { volumeIcon.style.transform = 'scale(1)'; scheduleHideSlider(); }); slider.addEventListener('mouseenter', () => clearTimeout(volumeSliderTimer)); slider.addEventListener('mouseleave', () => scheduleHideSlider()); volumeContainer.appendChild(slider); volumeContainer.appendChild(volumeIcon); return volumeContainer; } function showVolumeSlider() { clearTimeout(volumeSliderTimer); const slider = document.getElementById('ig-volume-slider-container'); if (slider) { slider.style.transform = 'translateX(0)'; slider.style.opacity = '1'; slider.style.pointerEvents = 'auto'; } } function scheduleHideSlider() { volumeSliderTimer = setTimeout(() => { const slider = document.getElementById('ig-volume-slider-container'); if (slider) { slider.style.transform = 'translateX(60px)'; slider.style.opacity = '0'; slider.style.pointerEvents = 'none'; } }, 300); } function updateVolumeFill() { const fill = document.getElementById('ig-volume-fill'); if (fill) fill.style.height = (userSettings.defaultVolume * 100) + '%'; } function updateVolumeIconSVG() { const wave1 = document.getElementById('ig-volume-wave1'); const wave2 = document.getElementById('ig-volume-wave2'); const muteX = document.getElementById('ig-volume-mute-x'); if (!wave1 || !wave2 || !muteX) return; const isMuted = currentVideo ? currentVideo.muted : (userSettings.defaultVolume === 0); const volume = currentVideo ? currentVideo.volume : userSettings.defaultVolume; if (isMuted) { wave1.style.opacity = '0'; wave2.style.opacity = '0'; muteX.style.opacity = '1'; } else { muteX.style.opacity = '0'; if (volume > 0.5) { wave1.style.opacity = '1'; wave2.style.opacity = '1'; wave1.setAttribute('stroke-width', '2'); wave2.setAttribute('stroke-width', '2'); } else if (volume > 0.1) { wave1.style.opacity = '1'; wave2.style.opacity = '0'; wave1.setAttribute('stroke-width', '2'); } else { wave1.style.opacity = '0.4'; wave2.style.opacity = '0'; wave1.setAttribute('stroke-width', '1.5'); } } } function syncVolumeToUserSetting() { if (!currentVideo || volumeRestoring) return; const shouldMuted = (userSettings.defaultVolume === 0); if (Math.abs(currentVideo.volume - userSettings.defaultVolume) > 0.001 || currentVideo.muted !== shouldMuted) { volumeRestoring = true; currentVideo.volume = userSettings.defaultVolume; currentVideo.muted = shouldMuted; updateVolumeIconSVG(); updateVolumeFill(); setTimeout(() => { volumeRestoring = false; }, 0); } } function setVolume(volume) { userSettings.defaultVolume = volume; localStorage.setItem('ig-video-volume', volume); updateVolumeFill(); if (currentVideo) { volumeRestoring = true; currentVideo.volume = volume; currentVideo.muted = (volume === 0); updateVolumeIconSVG(); setTimeout(() => { volumeRestoring = false; }, 0); } const icon = document.getElementById('ig-volume-icon-svg'); if (icon) springScale(icon, 1, 1.2, 150); } function toggleMute() { if (currentVideo) { if (currentVideo.muted) { setVolume(userSettings.defaultVolume > 0 ? userSettings.defaultVolume : 0.5); } else { setVolume(0); } } else { setVolume(userSettings.defaultVolume === 0 ? 0.5 : 0); } } function setupVolumeChangeGuard(video) { if (currentVolumeChangeHandler) video.removeEventListener('volumechange', currentVolumeChangeHandler); currentVolumeChangeHandler = () => { syncVolumeToUserSetting(); updateVolumeIconSVG(); }; video.addEventListener('volumechange', currentVolumeChangeHandler); } function setupPlayGuard(video) { if (currentPlayGuardHandler) video.removeEventListener('play', currentPlayGuardHandler); currentPlayGuardHandler = () => syncVolumeToUserSetting(); video.addEventListener('play', currentPlayGuardHandler); } // ==================== 倍速控制(白色文字) ==================== function createSpeedControl() { const container = document.createElement('div'); container.style.cssText = `position: relative; display: flex; align-items: center;`; const speedIcon = document.createElement('div'); speedIcon.id = 'ig-speed-icon'; speedIcon.textContent = userSettings.defaultPlaybackRate + 'x'; speedIcon.style.cssText = ` cursor: pointer; font-size: 13px; width: 28px; height: 24px; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.1); border-radius: 6px; font-weight: bold; color: #ffffff; user-select: none; transition: background 0.2s, transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1); `; speedIcon.addEventListener('mouseenter', () => { speedIcon.style.background = 'rgba(255,255,255,0.2)'; speedIcon.style.transform = 'scale(1.08)'; }); speedIcon.addEventListener('mouseleave', () => { speedIcon.style.background = 'rgba(255,255,255,0.1)'; speedIcon.style.transform = 'scale(1)'; }); const menu = document.createElement('div'); menu.id = 'ig-speed-menu'; menu.style.cssText = ` position: absolute; bottom: 36px; left: -40px; background: rgba(20,20,20,0.95); border-radius: 10px; display: none; flex-direction: column; padding: 6px; z-index: 1001; min-width: 80px; box-shadow: 0 8px 24px rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.08); backdrop-filter: blur(12px); transform: translateY(10px) scale(0.95); opacity: 0; transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s ease; transform-origin: bottom center; `; [0.5, 0.75, 1, 1.25, 1.5, 2].forEach(speed => { const item = document.createElement('div'); item.textContent = speed + 'x'; item.style.cssText = ` padding: 6px 12px; color: white; cursor: pointer; text-align: center; border-radius: 6px; font-size: 13px; position: relative; transition: background 0.2s, transform 0.15s; margin: 1px 0; ${speed === userSettings.defaultPlaybackRate ? ` background: rgba(255,255,255,0.2); font-weight: bold; box-shadow: 0 0 8px ${userSettings.progressBarColor}40; ` : ''} `; item.addEventListener('mouseenter', () => { if (speed !== userSettings.defaultPlaybackRate) { item.style.background = 'rgba(255,255,255,0.15)'; item.style.transform = 'scale(1.05)'; } }); item.addEventListener('mouseleave', () => { if (speed !== userSettings.defaultPlaybackRate) { item.style.background = 'transparent'; item.style.transform = 'scale(1)'; } }); item.addEventListener('click', () => { setPlaybackRate(speed); closeSpeedMenu(); }); menu.appendChild(item); }); speedIcon.addEventListener('click', (e) => { e.stopPropagation(); if (menu.style.display === 'flex') closeSpeedMenu(); else openSpeedMenu(); }); document.addEventListener('click', (e) => { if (!container.contains(e.target)) closeSpeedMenu(); }); container.appendChild(speedIcon); container.appendChild(menu); return container; } function openSpeedMenu() { const menu = document.getElementById('ig-speed-menu'); if (!menu) return; menu.style.display = 'flex'; requestAnimationFrame(() => { menu.style.transform = 'translateY(0) scale(1)'; menu.style.opacity = '1'; }); } function closeSpeedMenu() { const menu = document.getElementById('ig-speed-menu'); if (!menu) return; menu.style.transform = 'translateY(10px) scale(0.95)'; menu.style.opacity = '0'; setTimeout(() => { menu.style.display = 'none'; }, 250); } function setPlaybackRate(rate) { userSettings.defaultPlaybackRate = rate; localStorage.setItem('ig-playback-rate', rate); if (currentVideo) currentVideo.playbackRate = rate; const icon = document.getElementById('ig-speed-icon'); if (icon) { icon.textContent = rate + 'x'; springScale(icon, 1, 1.15, 200); } const menu = document.getElementById('ig-speed-menu'); if (menu) { Array.from(menu.children).forEach(item => { const isActive = parseFloat(item.textContent) === rate; item.style.background = isActive ? 'rgba(255,255,255,0.2)' : 'transparent'; item.style.fontWeight = isActive ? 'bold' : 'normal'; item.style.boxShadow = isActive ? `0 0 8px ${userSettings.progressBarColor}40` : 'none'; }); } } // ==================== 进度条 ==================== function handleProgressClick(e) { if (!currentVideo || isDragging) return; const track = document.getElementById('ig-progress-track'); const rect = track.getBoundingClientRect(); const percentage = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); if (currentVideo.duration) { currentVideo.currentTime = percentage * currentVideo.duration; updateProgressDisplay(); } } function handleProgressDragStart(e) { if (!currentVideo) return; isDragging = true; e.preventDefault(); const track = document.getElementById('ig-progress-track'); window._originalMuted = currentVideo.muted; window._originalPaused = currentVideo.paused; currentVideo.muted = true; currentVideo.pause(); function handleDrag(e) { const rect = track.getBoundingClientRect(); const percentage = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); if (currentVideo.duration) { currentVideo.currentTime = percentage * currentVideo.duration; document.getElementById('ig-progress-fill').style.width = (percentage * 100) + '%'; updateTimeDisplay(percentage * currentVideo.duration, currentVideo.duration); } } function stopDrag() { isDragging = false; if (currentVideo) { currentVideo.muted = window._originalMuted; if (!window._originalPaused) currentVideo.play().catch(() => {}); } delete window._originalMuted; delete window._originalPaused; document.removeEventListener('mousemove', handleDrag); document.removeEventListener('mouseup', stopDrag); } document.addEventListener('mousemove', handleDrag); document.addEventListener('mouseup', stopDrag); const rect = track.getBoundingClientRect(); const initPercent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); if (currentVideo.duration) { currentVideo.currentTime = initPercent * currentVideo.duration; updateProgressDisplay(); } } function updateTimeDisplay(currentTime, duration) { const display = document.getElementById('ig-time-display'); if (!display) return; const fmt = (s) => { const m = Math.floor(s/60); const sec = Math.floor(s%60); return `${m}:${sec.toString().padStart(2,'0')}`; }; display.textContent = `${fmt(currentTime)} / ${fmt(duration)}`; } function updateProgressDisplay() { if (!currentVideo || !currentVideo.duration) return; const fill = document.getElementById('ig-progress-fill'); if (fill) fill.style.width = (currentVideo.currentTime / currentVideo.duration * 100) + '%'; updateTimeDisplay(currentVideo.currentTime, currentVideo.duration); } // ==================== 视频查找与连播 ==================== function findCurrentVideo() { const videos = Array.from(document.querySelectorAll('video')); const playing = videos.find(v => !v.paused && v.currentTime > 0); if (playing) return playing; const visible = videos.filter(v => { const rect = v.getBoundingClientRect(); return rect.top < window.innerHeight && rect.bottom > 0 && rect.width > 0; }); return visible[0] || videos[0]; } function playNextVideo() { if (!currentVideo) return; const videos = Array.from(document.querySelectorAll('video')); const index = videos.indexOf(currentVideo); if (index >= 0 && index < videos.length - 1) { const next = videos[index + 1]; next.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => next.play().catch(()=>{}), 500); } else { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); } } function handleVideoEnded() { if (userSettings.autoPlayNext) { playNextVideo(); } } function checkCurrentVideo() { const now = Date.now(); if (now - lastVideoCheck < 400) return; lastVideoCheck = now; const video = findCurrentVideo(); if (video && video !== currentVideo) { if (currentVideo) { currentVideo.removeEventListener('timeupdate', updateProgressDisplay); if (currentVolumeChangeHandler) currentVideo.removeEventListener('volumechange', currentVolumeChangeHandler); if (currentPlayGuardHandler) currentVideo.removeEventListener('play', currentPlayGuardHandler); if (currentEndedHandler) currentVideo.removeEventListener('ended', currentEndedHandler); } currentVideo = video; currentVideo.volume = userSettings.defaultVolume; currentVideo.muted = (userSettings.defaultVolume === 0); currentVideo.playbackRate = userSettings.defaultPlaybackRate; updateVolumeIconSVG(); updateVolumeFill(); currentVideo.addEventListener('timeupdate', updateProgressDisplay); setupVolumeChangeGuard(currentVideo); setupPlayGuard(currentVideo); // 连播监听 currentEndedHandler = handleVideoEnded; currentVideo.addEventListener('ended', currentEndedHandler); progressWindow.style.display = 'flex'; progressWindow.style.opacity = '1'; updateProgressDisplay(); } else if (!video && currentVideo) { progressWindow.style.display = 'none'; if (currentVideo) { currentVideo.removeEventListener('timeupdate', updateProgressDisplay); if (currentVolumeChangeHandler) currentVideo.removeEventListener('volumechange', currentVolumeChangeHandler); if (currentPlayGuardHandler) currentVideo.removeEventListener('play', currentPlayGuardHandler); if (currentEndedHandler) currentVideo.removeEventListener('ended', currentEndedHandler); } currentVideo = null; } else if (video === currentVideo) { syncVolumeToUserSetting(); } } function setupKeyboardControl() { document.addEventListener('keydown', (e) => { const active = document.activeElement; if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.contentEditable === 'true')) return; if (e.ctrlKey && e.shiftKey && e.key === 'P') { e.preventDefault(); progressWindow.style.display = progressWindow.style.display === 'none' ? 'flex' : 'none'; return; } if (e.ctrlKey && e.shiftKey && e.key === 'H') { e.preventDefault(); progressWindow.style.display = 'none'; return; } if (e.ctrlKey && e.shiftKey && e.key === 'A') { e.preventDefault(); toggleAutoPlay(); return; } if (currentVideo) { switch(e.key) { case ' ': e.preventDefault(); e.stopPropagation(); currentVideo.paused ? currentVideo.play() : currentVideo.pause(); break; case 'ArrowRight': e.preventDefault(); currentVideo.currentTime = Math.min(currentVideo.currentTime + 3, currentVideo.duration); break; case 'ArrowLeft': e.preventDefault(); currentVideo.currentTime = Math.max(currentVideo.currentTime - 3, 0); break; case 'ArrowUp': e.preventDefault(); setVolume(Math.min(1, currentVideo.volume + 0.1)); break; case 'ArrowDown': e.preventDefault(); setVolume(Math.max(0, currentVideo.volume - 0.1)); break; } } }); } window.addEventListener('resize', () => { if (progressWindow && progressWindow.style.display !== 'none') { const rect = progressWindow.getBoundingClientRect(); const maxX = window.innerWidth - rect.width; const maxY = window.innerHeight - rect.height; if (rect.left > maxX) progressWindow.style.left = maxX + 'px'; if (rect.top > maxY) progressWindow.style.top = maxY + 'px'; saveWindowPosition(); } }); function initFloatingWindow() { progressWindow = document.createElement('div'); progressWindow.id = 'ig-progress-floating-window'; progressWindow.style.cssText = ` position: fixed; top: ${userSettings.windowPosition.y}px; left: ${userSettings.windowPosition.x}px; width: 300px; height: 85px; background: rgba(0,0,0,0.85); border-radius: 12px; z-index: 100000; backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 4px 20px rgba(0,0,0,0.3); display: none; flex-direction: column; padding: 10px; font-family: Arial, sans-serif; overflow: visible; `; const titleBar = document.createElement('div'); titleBar.style.cssText = ` color: white; font-size: 12px; margin-bottom: 12px; font-weight: bold; user-select: none; cursor: move; display: flex; justify-content: space-between; align-items: center; `; const titleText = document.createElement('div'); titleText.textContent = 'IG 视频控制器'; const closeButton = document.createElement('div'); closeButton.innerHTML = '×'; closeButton.style.cssText = ` cursor: pointer; font-size: 16px; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 4px; background: rgba(255,255,255,0.1); transition: background 0.2s; `; closeButton.addEventListener('mouseenter', () => closeButton.style.background = 'rgba(255,255,255,0.25)'); closeButton.addEventListener('mouseleave', () => closeButton.style.background = 'rgba(255,255,255,0.1)'); closeButton.addEventListener('click', () => { progressWindow.style.display = 'none'; }); const ctrlBtns = document.createElement('div'); ctrlBtns.style.cssText = `display: flex; gap: 6px;`; ctrlBtns.appendChild(closeButton); titleBar.appendChild(titleText); titleBar.appendChild(ctrlBtns); const contentArea = document.createElement('div'); contentArea.style.cssText = `display: flex; flex-direction: column; flex: 1; gap: 10px;`; const timeDisplay = document.createElement('div'); timeDisplay.id = 'ig-time-display'; timeDisplay.textContent = '0:00 / 0:00'; timeDisplay.style.cssText = `color: white; font-size: 12px; font-family: monospace; text-align: center; padding: 2px 0;`; const progressArea = document.createElement('div'); progressArea.style.cssText = `display: flex; align-items: center; gap: 8px;`; const leftControls = document.createElement('div'); leftControls.style.cssText = `display: flex; gap: 5px; align-items: center; flex-shrink: 0;`; // 音量控件(包含弹出滑块) leftControls.appendChild(createVolumeControl()); leftControls.appendChild(createSpeedControl()); const autoPlayCtrl = createAutoPlayControl(); leftControls.appendChild(autoPlayCtrl.container); const progressTrack = document.createElement('div'); progressTrack.id = 'ig-progress-track'; progressTrack.style.cssText = ` flex: 1; height: 8px; background: rgba(255,255,255,0.2); border-radius: 3px; position: relative; cursor: pointer; min-width: 60px; `; const progressFill = document.createElement('div'); progressFill.id = 'ig-progress-fill'; progressFill.style.cssText = ` height: 100%; background: ${userSettings.progressBarColor}; border-radius: 3px; width: 0%; transition: width 0.1s ease; `; progressTrack.appendChild(progressFill); progressArea.appendChild(leftControls); progressArea.appendChild(progressTrack); contentArea.appendChild(timeDisplay); contentArea.appendChild(progressArea); progressWindow.appendChild(titleBar); progressWindow.appendChild(contentArea); document.body.appendChild(progressWindow); // 拖动浮窗 let isDraggingWin = false, startX, startY, initX, initY; titleBar.addEventListener('mousedown', (e) => { if (e.target.closest('#ig-speed-menu') || e.target.closest('#ig-volume-slider-container') || e.target.closest('#ig-autoplay-button') || e.target.closest('div[style*="gap: 6px"]')) return; isDraggingWin = true; startX = e.clientX; startY = e.clientY; initX = progressWindow.offsetLeft; initY = progressWindow.offsetTop; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDraggingWin) return; const dx = e.clientX - startX, dy = e.clientY - startY; const maxX = window.innerWidth - progressWindow.offsetWidth; const maxY = window.innerHeight - progressWindow.offsetHeight; progressWindow.style.left = Math.max(0, Math.min(initX + dx, maxX)) + 'px'; progressWindow.style.top = Math.max(0, Math.min(initY + dy, maxY)) + 'px'; }); document.addEventListener('mouseup', () => { if (isDraggingWin) { isDraggingWin = false; saveWindowPosition(); } }); progressTrack.addEventListener('click', handleProgressClick); progressTrack.addEventListener('mousedown', handleProgressDragStart); } function initialize() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialize); return; } initFloatingWindow(); setupKeyboardControl(); setInterval(checkCurrentVideo, 1000); const observer = new MutationObserver(() => { setTimeout(checkCurrentVideo, 100); }); observer.observe(document.body, { childList: true, subtree: true }); console.log('✅ Instagram 控制器 v2.0.6 已启动 - 回归优美音量条 + 连播功能'); } if (document.readyState === 'complete' || document.readyState === 'interactive') { initialize(); } else { document.addEventListener('DOMContentLoaded', initialize); } })();