您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Store video.currentTime locally
// ==UserScript== // @name Youtube - Resumer // @namespace http://tampermonkey.net/ // @version 1.2 // @description Store video.currentTime locally // @author You // @match https://www.youtube.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @grant GM.setValue // @grant GM.getValue // @grant GM_addStyle // @license MIT // ==/UserScript== function l(...args){ console.log('[Resumer]', ...args) } function videoId(url=document.URL){ return new URL(url).searchParams.get('v') } let lastTimeInSeconds function save(video, id){ const seconds = Math.floor(video.currentTime) if(lastTimeInSeconds != seconds){ // save less often let completion = video.currentTime / video.duration GM.setValue(id, video.currentTime) GM.setValue(id + '-completion', completion) } lastTimeInSeconds = seconds } function findVideo(onVideoFound){ const observer = new MutationObserver((mutations, observer) => { // Keep trying to find video let video = document.querySelector('video.video-stream') if(video){ onVideoFound(video) observer.disconnect() } }) observer.observe(document, {childList:true, subtree:true}) } let id = videoId() //if you use the miniplayer the url no longer includes the video id function listen(video){ let lastSrc function handleTimeUpdate(){ //Video source is '' and duration is NaN when going back to the home page //When loading a new video, the event is fired with currentTime 0 and duration NaN if(video.src && !isNaN(video.duration)){ l('timeupdate', id, lastId, video.src, lastSrc) if(id){ save(video, id) lastSrc = video.src }else if(video.src === lastSrc){ //in case you click another video while using the miniplayer save(video, lastId) //save even if in miniplayer } } } video.addEventListener('timeupdate', handleTimeUpdate) return () => { video.removeEventListener('timeupdate', handleTimeUpdate) } } async function resume(video){ id = videoId() // set id here because in firefox the url changes before navigate-finish completes let lastTime = await GM.getValue(id) if(lastTime){ if(lastTime < video.duration - 1){ l('resuming', id, video.currentTime, lastTime) video.currentTime = lastTime }else{ l('nearly complete, skipping resume for', id); } }else{ l('new video', video.currentTime) } } function cleanUrl(){ //Remove t paramater when opening a video that had a progress bar let url = new URL(document.URL) url.searchParams.delete('t') window.history.replaceState(null, null, url) } let lastId // don't resume if going back to same page from miniplayer // Event for each page change document.addEventListener("yt-navigate-finish", () => { l('navigate-finish', lastId, videoId()) // video page if(videoId() && lastId !== videoId()) { lastId = videoId() cleanUrl() let removeListeners findVideo(video => { resume(video) // clean previous listeners if(removeListeners) removeListeners() removeListeners = listen(video) }) } }) ///////////////////// function addProgressBar(thumbnail, completion){ let overlays = thumbnail.querySelector('#overlays') let existingProgressBar = thumbnail.querySelector('ytd-thumbnail-overlay-resume-playback-renderer') if(!existingProgressBar) { let parent = document.createElement('div') parent.innerHTML = ` <ytd-thumbnail-overlay-resume-playback-renderer class="style-scope ytd-thumbnail"> <div id="progress" class="style-scope ytd-thumbnail-overlay-resume-playback-renderer" style="width: 100%"></div> </ytd-thumbnail-overlay-resume-playback-renderer> ` overlays.appendChild(parent.children[0]) } // style let progress = overlays.querySelector('#progress') let width = parseInt(completion * 100) progress.style.width = `${width}%` progress.style.backgroundColor = 'blue' } function progressBars(){ // Add progress bars in the related section const observer = new MutationObserver(async (mutations, observer) => { for(let mutation of mutations){ if(mutation.addedNodes.length > 0) { let thumbnails = mutation.target.querySelectorAll('a.ytd-thumbnail') for(let thumbnail of thumbnails){ let href = thumbnail.href if(href) { let id = videoId(href) let completion = await GM.getValue(id + '-completion') if(completion) { addProgressBar(thumbnail, completion) } } } } } }) observer.observe(document, {childList:true, subtree:true}) } progressBars() // TODO doesn't always work