您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在B站视频播放时标记时间点并能快速跳转
当前为
// ==UserScript== // @name Bilibili时间标记跳转 // @namespace http://tampermonkey.net/ // @version 0.5 // @description 在B站视频播放时标记时间点并能快速跳转 // @author 洪小帅 // @match *://www.bilibili.com/video/* // @match *://www.bilibili.com/bangumi/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @run-at document-end // ==/UserScript== (function() { 'use strict'; let markedTimes = []; // 存储标记的时间点 let isInitialized = false; let isDragging = false; let dragOffset = { x: 0, y: 0 }; // 默认设置 const defaultSettings = { markHotkey: 'm', jumpHotkey: 'n', maxTimePoints: 15, panelPosition: { x: 50, y: 120 }, clearOnRefresh: true }; // 获取设置 let settings = GM_getValue('biliTimeMarkerSettings', defaultSettings); // 创建控制面板 const createControlPanel = () => { const panel = document.createElement('div'); panel.className = 'bili-time-marker-panel'; panel.style.cssText = ` position: fixed; top: ${settings.panelPosition.y}px; right: ${settings.panelPosition.x}px; z-index: 999999; background-color: rgba(0, 0, 0, 0.8); border-radius: 8px; padding: 12px; color: white; font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,sans-serif; min-width: 200px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); user-select: none; `; // 添加拖动条 const dragBar = document.createElement('div'); dragBar.style.cssText = ` padding: 4px; margin: -12px -12px 8px -12px; cursor: move; background-color: #00a1d6; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center; `; const title = document.createElement('span'); title.textContent = '403专用时间标记器'; title.style.marginLeft = '8px'; const settingsButton = document.createElement('button'); settingsButton.innerHTML = '⚙️'; settingsButton.style.cssText = ` background: none; border: none; color: white; cursor: pointer; padding: 0 8px; font-size: 16px; `; settingsButton.onclick = () => { document.body.appendChild(createOverlay()); showSettings(); }; dragBar.appendChild(title); dragBar.appendChild(settingsButton); // 添加拖动功能 dragBar.addEventListener('mousedown', (e) => { isDragging = true; const rect = panel.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; panel.style.transition = 'none'; document.addEventListener('mousemove', handleDrag); document.addEventListener('mouseup', () => { isDragging = false; document.removeEventListener('mousemove', handleDrag); // 保存位置 const rect = panel.getBoundingClientRect(); settings.panelPosition = { x: window.innerWidth - rect.right, y: rect.top }; GM_setValue('biliTimeMarkerSettings', settings); }); }); panel.appendChild(dragBar); // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 8px; margin-bottom: 12px; `; const markButton = createButton(`标记 [${settings.markHotkey}]`, '#00a1d6'); const clearButton = createButton('清除标记', '#fb7299'); markButton.onclick = addTimePoint; clearButton.onclick = () => { markedTimes = markedTimes.filter(t => t.pinned); updateTimesList(); // 清除时立即保存到存储 GM_setValue('biliTimeMarkerPoints', markedTimes); }; buttonContainer.appendChild(markButton); buttonContainer.appendChild(clearButton); panel.appendChild(buttonContainer); // 创建时间点列表容器 const timesList = document.createElement('div'); timesList.className = 'bili-time-marker-list'; timesList.style.cssText = ` display: flex; flex-direction: column; gap: 6px; max-height: 300px; overflow-y: auto; padding-right: 4px; `; // 添加滚动条样式 GM_addStyle(` .bili-time-marker-list::-webkit-scrollbar { width: 4px; } .bili-time-marker-list::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.1); border-radius: 2px; } .bili-time-marker-list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 2px; } .bili-time-marker-list::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.5); } `); panel.appendChild(timesList); return panel; }; // 处理拖动 const handleDrag = (e) => { if (!isDragging) return; const panel = document.querySelector('.bili-time-marker-panel'); if (!panel) return; const x = e.clientX - dragOffset.x; const y = e.clientY - dragOffset.y; // 确保面板不会拖出屏幕 const maxX = window.innerWidth - panel.offsetWidth; const maxY = window.innerHeight - panel.offsetHeight; panel.style.left = `${Math.max(0, Math.min(x, maxX))}px`; panel.style.top = `${Math.max(0, Math.min(y, maxY))}px`; panel.style.right = 'auto'; }; // 添加时间点 const addTimePoint = () => { const video = document.querySelector('video'); if (!video) return; const currentTime = video.currentTime; const timeString = formatTime(currentTime); markedTimes.unshift({ time: currentTime, label: timeString, pinned: false }); // 如果超出限制且有未固定的时间点,删除最早的未固定时间点 if (markedTimes.length > settings.maxTimePoints) { const unpinnedIndex = markedTimes.findIndex(t => !t.pinned); if (unpinnedIndex !== -1) { markedTimes.splice(unpinnedIndex, 1); } } updateTimesList(); }; // 创建时间点按钮 const createTimeButton = (timeData, index) => { const container = document.createElement('div'); container.style.cssText = ` display: flex; align-items: center; gap: 4px; background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; padding: 6px; transition: background-color 0.2s; `; const button = document.createElement('button'); button.style.cssText = ` flex: 1; background: none; border: none; color: white; cursor: pointer; font-size: 12px; text-align: left; padding: 0; `; button.textContent = `⏱ ${timeData.label}`; const pinButton = document.createElement('button'); pinButton.innerHTML = timeData.pinned ? '📌' : '📍'; pinButton.style.cssText = ` background: none; border: none; color: white; cursor: pointer; padding: 0 4px; opacity: ${timeData.pinned ? 1 : 0.5}; `; pinButton.onclick = (e) => { e.stopPropagation(); timeData.pinned = !timeData.pinned; updateTimesList(); }; container.onmouseover = () => container.style.backgroundColor = 'rgba(255, 255, 255, 0.2)'; container.onmouseout = () => container.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'; container.onclick = () => { const video = document.querySelector('video'); if (video) { video.currentTime = timeData.time; } }; container.appendChild(button); container.appendChild(pinButton); return container; }; // 显示设置面板 const showSettings = () => { const settingsPanel = document.createElement('div'); settingsPanel.className = 'bili-time-marker-settings'; settingsPanel.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(255, 255, 255, 0.95); padding: 20px; border-radius: 8px; z-index: 1000000; min-width: 300px; color: #333; box-shadow: 0 2px 10px rgba(0,0,0,0.2); `; settingsPanel.innerHTML = ` <h3 style="margin-top: 0; color: #00a1d6;">设置</h3> <div style="margin-bottom: 12px;"> <label>标记快捷键: <input type="text" id="markHotkey" value="${settings.markHotkey}" style="width: 50px; padding: 4px; border: 1px solid #ccc; border-radius: 4px; color: #333;"></label> </div> <div style="margin-bottom: 12px;"> <label>跳转快捷键: <input type="text" id="jumpHotkey" value="${settings.jumpHotkey}" style="width: 50px; padding: 4px; border: 1px solid #ccc; border-radius: 4px; color: #333;"></label> </div> <div style="margin-bottom: 12px;"> <label>最大时间点数量: <input type="number" id="maxTimePoints" value="${settings.maxTimePoints}" min="1" max="50" style="width: 60px; padding: 4px; border: 1px solid #ccc; border-radius: 4px; color: #333;"></label> </div> <div style="margin-bottom: 12px;"> <label style="display: flex; align-items: center; gap: 8px;"> <input type="checkbox" id="clearOnRefresh" ${settings.clearOnRefresh ? 'checked' : ''} style="width: 16px; height: 16px;"> <span>刷新页面时清除标记点</span> </label> </div> <div style="text-align: right;"> <button id="saveSettings" style="padding: 6px 12px; background: #00a1d6; border: none; border-radius: 4px; color: white; cursor: pointer;">保存</button> </div> `; document.body.appendChild(settingsPanel); document.getElementById('saveSettings').onclick = () => { settings.markHotkey = document.getElementById('markHotkey').value; settings.jumpHotkey = document.getElementById('jumpHotkey').value; settings.maxTimePoints = parseInt(document.getElementById('maxTimePoints').value); settings.clearOnRefresh = document.getElementById('clearOnRefresh').checked; GM_setValue('biliTimeMarkerSettings', settings); settingsPanel.remove(); document.querySelector('.bili-time-marker-overlay')?.remove(); init(); }; }; // 添加快捷键支持 document.addEventListener('keydown', (e) => { if (e.target.tagName === 'INPUT') return; if (e.key === settings.markHotkey) { addTimePoint(); } else if (e.key === settings.jumpHotkey && markedTimes.length > 0) { const video = document.querySelector('video'); if (video) { video.currentTime = markedTimes[0].time; } } }); // 格式化时间 const formatTime = (seconds) => { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); const ms = Math.floor((seconds % 1) * 1000); const parts = []; if (h > 0) { parts.push(String(h).padStart(2, '0')); } parts.push(String(m).padStart(2, '0')); parts.push(String(s).padStart(2, '0')); return parts.join(':') + `.${String(ms).padStart(3, '0')}`; }; // 创建按钮的辅助函数 const createButton = (text, color) => { const button = document.createElement('button'); button.textContent = text; button.style.cssText = ` flex: 1; padding: 6px 12px; background-color: ${color}; border: none; border-radius: 4px; color: white; cursor: pointer; font-size: 12px; transition: opacity 0.2s; `; button.onmouseover = () => button.style.opacity = '0.8'; button.onmouseout = () => button.style.opacity = '1'; return button; }; // 更新时间点列表 const updateTimesList = () => { const timesList = document.querySelector('.bili-time-marker-list'); if (!timesList) return; timesList.innerHTML = ''; if (markedTimes.length === 0) { const emptyText = document.createElement('div'); emptyText.style.cssText = ` text-align: center; color: #999; font-size: 12px; padding: 8px; `; emptyText.textContent = '暂无标记时间点'; timesList.appendChild(emptyText); return; } // 保存时间点到本地存储 GM_setValue('biliTimeMarkerPoints', markedTimes); // 创建时间点列表 markedTimes.forEach((timeData, index) => { timesList.appendChild(createTimeButton(timeData, index)); }); }; // 从本地存储加载时间点 const loadTimePoints = () => { if (settings.clearOnRefresh) { markedTimes = []; GM_setValue('biliTimeMarkerPoints', []); // 确保存储也被清除 updateTimesList(); return; } const savedPoints = GM_getValue('biliTimeMarkerPoints', []); if (Array.isArray(savedPoints) && savedPoints.length > 0) { markedTimes = savedPoints; updateTimesList(); } }; // 添加设置面板的遮罩层 const createOverlay = () => { const overlay = document.createElement('div'); overlay.className = 'bili-time-marker-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 999999; `; overlay.onclick = (e) => { if (e.target === overlay) { overlay.remove(); document.querySelector('.bili-time-marker-settings')?.remove(); } }; return overlay; }; // 初始化 const init = () => { if (isInitialized) return; const video = document.querySelector('video'); if (!video) { setTimeout(init, 1000); return; } const existingPanel = document.querySelector('.bili-time-marker-panel'); if (existingPanel) { existingPanel.remove(); } document.body.appendChild(createControlPanel()); loadTimePoints(); updateTimesList(); isInitialized = true; console.log('时间标记面板已添加'); }; // 监听视频加载 const observer = new MutationObserver((mutations, obs) => { if (document.querySelector('video')) { init(); obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); // 立即尝试初始化 init(); // 添加页面卸载时的清理函数 window.addEventListener('beforeunload', () => { if (settings.clearOnRefresh) { GM_setValue('biliTimeMarkerPoints', []); } }); })();