您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Have you ever closed a YouTube video by accident, or have you gone to another one and when you come back the video starts from 0? With this extension it won't happen anymore
当前为
// ==UserScript== // @license MIT // @name YoutubePlayBack // @namespace http://tampermonkey.net/ // @version 1.4.4 // @description Have you ever closed a YouTube video by accident, or have you gone to another one and when you come back the video starts from 0? With this extension it won't happen anymore // @author Costin Alexandru Sandu // @match https://www.youtube.com/watch* // @icon https://tse4.mm.bing.net/th/id/OIG3.UOFNuEtdysdoeX0tMsVU?pid=ImgGn // @grant none // ==/UserScript== (function () { 'strict' var configData = { savedProgressAlreadySet: false, savingInterval: 1500, currentVideoId: null, lastSaveTime: 0 } // ref: https://stackoverflow.com/questions/3733227/javascript-seconds-to-minutes-and-seconds function fancyTimeFormat(duration) { // Hours, minutes and seconds const hrs = ~~(duration / 3600); const mins = ~~((duration % 3600) / 60); const secs = ~~duration % 60; // Output like "1:01" or "4:03:59" or "123:03:59" let ret = ""; if (hrs > 0) { ret += "" + hrs + ":" + (mins < 10 ? "0" : ""); } ret += "" + mins + ":" + (secs < 10 ? "0" : ""); ret += "" + secs; return ret; } function executeFnInPageContext(fn) { const fnStringified = fn.toString() return window.eval('(' + fnStringified + ')' + '()') } function getVideoCurrentTime() { const currentTime = executeFnInPageContext(() => { const player = document.querySelector('#movie_player') return player.getCurrentTime() }) return currentTime } function getVideoId() { if (configData.currentVideoId) { return configData.currentVideoId } const id = executeFnInPageContext(() => { const player = document.querySelector('#movie_player') return player.getVideoData().video_id }) return id } function playerExists() { const exists = executeFnInPageContext(() => { const player = document.querySelector('#movie_player') return Boolean(player) }) return exists } function setVideoProgress(progress) { window.eval('var progress =' + progress) executeFnInPageContext(() => { const player = document.querySelector('#movie_player') player.seekTo(window.progress) }) window.eval('delete progress') } function updateLastSaved(videoProgress) { const lastSaveEl = document.querySelector('.last-save-info') if (lastSaveEl) { lastSaveEl.innerHTML = "Last saved " + fancyTimeFormat(videoProgress) } } function saveVideoProgress() { const videoProgress = getVideoCurrentTime() const videoId = getVideoId() configData.currentVideoId = videoId configData.lastSaveTime = Date.now() updateLastSaved(videoProgress) window.localStorage.setItem(videoId, videoProgress) } function getSavedVideoProgress() { const videoId = getVideoId() const savedVideoProgress = window.localStorage.getItem(videoId) return savedVideoProgress } function videoHasChapters() { const chaptersSection = document.querySelector('.ytp-chapter-container[style=""]') const chaptersSectionDisplay = getComputedStyle(chaptersSection).display return chaptersSectionDisplay !== 'none' } function setSavedProgress() { const savedProgress = getSavedVideoProgress(); setVideoProgress(savedProgress) configData.savedProgressAlreadySet = true } // code ref: https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 observer.observe(document.body, { childList: true, subtree: true }); }); } async function onPlayerElementExist(callback) { await waitForElm('#movie_player') callback() } function isReadyToSetSavedProgress() { return !configData.savedProgressAlreadySet && playerExists() && getSavedVideoProgress() } function insertInfoElement(element) { const leftControls = document.querySelector('.ytp-left-controls') leftControls.appendChild(element) } function insertInfoElementInChaptersContainer(element) { const chaptersContainer = document.querySelector('.ytp-chapter-container[style=""]') chaptersContainer.style.display = 'flex' chaptersContainer.appendChild(element) } function createInfoUI() { const infoElContainer = document.createElement('div') infoElContainer.classList.add('last-save-info-container') const infoEl = document.createElement('div') infoEl.classList.add('last-save-info') infoElContainer.style.all = 'initial' infoElContainer.style.fontFamily = 'inherit' infoElContainer.style.fontSize = '1.3rem' infoElContainer.style.marginLeft = '0.5rem' infoElContainer.style.display = 'flex' infoElContainer.style.alignItems = 'center' infoEl.style.textShadow = 'none' infoEl.style.background = 'white' infoEl.style.color = 'black' infoEl.style.padding = '.5rem' infoEl.style.borderRadius = '.5rem' infoElContainer.appendChild(infoEl) return infoElContainer } async function onChaptersReadyToMount(callback) { const chaptersContainer = await waitForElm('.ytp-chapter-container[style=""]') callback() } function initializeUI() { const infoEl = createInfoUI() insertInfoElement(infoEl) onChaptersReadyToMount(() => insertInfoElementInChaptersContainer(infoEl)) } function initialize() { onPlayerElementExist(() => { initializeUI() if (isReadyToSetSavedProgress()) { setSavedProgress() } }) setInterval(saveVideoProgress, configData.savingInterval) } initialize() })();