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 Youtube Save/Resume Progress
// @namespace http://tampermonkey.net/
// @version 1.4.5
// @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()
})();