Youtube - Resumer

Store video.currentTime locally

目前为 2023-02-02 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube - Resumer
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.7.1
  5. // @description Store video.currentTime locally
  6. // @author You
  7. // @match https://www.youtube.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  9. // @grant GM.setValue
  10. // @grant GM.getValue
  11. // @grant GM_addStyle
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15.  
  16. function l(...args){
  17. // console.log('[Resumer]', ...args)
  18. }
  19.  
  20. function videoId(url=document.URL){
  21. return new URL(url).searchParams.get('v')
  22. }
  23.  
  24. let lastTimeInSeconds
  25. function save(video, id){
  26. const seconds = Math.floor(video.currentTime)
  27. if(lastTimeInSeconds != seconds){ //save less often
  28. let completion = video.currentTime / video.duration
  29. GM.setValue(id, video.currentTime)
  30. GM.setValue(id + '-completion', completion)
  31. }
  32. lastTimeInSeconds = seconds
  33. }
  34.  
  35. function listen(){
  36. let video = document.querySelector('video')
  37. let lastSrc
  38.  
  39. video.addEventListener('timeupdate', () => {
  40. //Video source is '' and duration is NaN when going back to the home page
  41. //When loading a new video, the event is fired with currentTime 0 and duration NaN
  42. if(video.src && !isNaN(video.duration)){
  43. let id = videoId() //if you use the miniplayer the url no longer includes the video id
  44. l(id, lastId, video.src, lastSrc)
  45. if(id){
  46. save(video, id)
  47. lastSrc = video.src
  48. }else if(video.src === lastSrc){ //in case you click another video while using the miniplayer
  49. save(video, lastId) //save even if in miniplayer
  50. }
  51. }
  52. })
  53. }
  54.  
  55. async function resume(){
  56. let video = document.querySelector('video')
  57. let lastTime = await GM.getValue(videoId())
  58. if(lastTime){
  59. l('resuming', video.currentTime, lastTime)
  60. video.currentTime = lastTime
  61. }
  62. }
  63.  
  64. function cleanUrl(){
  65. //Remove t paramater when opening a video that had a progress bar
  66. let url = new URL(document.URL)
  67. url.searchParams.delete('t')
  68. window.history.replaceState(null, null, url)
  69. }
  70.  
  71. function addProgressBar(overlays, width){
  72. let parent = document.createElement('div')
  73. parent.innerHTML = '<ytd-thumbnail-overlay-resume-playback-renderer class="style-scope ytd-thumbnail"><!--css-build:shady--><div id="progress" class="style-scope ytd-thumbnail-overlay-resume-playback-renderer"></div></ytd-thumbnail-overlay-resume-playback-renderer>'
  74. overlays.appendChild(parent.firstChild)
  75. let progress = overlays.querySelector('#progress')
  76. styleProgressBar(progress, width)
  77. }
  78.  
  79. function styleProgressBar(progress, width){
  80. progress.style.width = `${width}%`
  81. progress.style.backgroundColor = 'blue'
  82. }
  83.  
  84. function progressBars(){
  85. let related = document.querySelector('#related')
  86. //Add progress bars in the related section
  87. const observer = new MutationObserver(async (mutationsList, observer) => {
  88. for(let mutation of mutationsList){
  89. if(mutation.target.id === 'overlays'){
  90. let href = mutation.target.parentElement.parentElement.parentElement.parentElement.querySelector('a').href
  91. let id = videoId(href)
  92. let completion = await GM.getValue(id + '-completion')
  93. if(completion){
  94. let width = parseInt(completion * 100)
  95. let progress = mutation.target.querySelector('#progress')
  96. if(progress){
  97. styleProgressBar(progress, width)
  98. }else{
  99. addProgressBar(mutation.target, width)
  100. }
  101. }
  102. }
  103. }
  104. })
  105. observer.observe(related, {childList:true, subtree:true})
  106. }
  107.  
  108. let listening = false //the video element exists even if you go back to the home page, so no need to readd event listeners
  109. let lastId //don't resume if going back to same page from miniplayer
  110.  
  111. //Event for each page change
  112. document.addEventListener("yt-navigate-finish", async () => {
  113. l('navigate-finish', lastId, videoId())
  114. //video page
  115. if(videoId() && lastId !== videoId()) {
  116. lastId = videoId()
  117. cleanUrl()
  118. resume()
  119. //Add listeners once
  120. if(!listening){
  121. l('listening')
  122. listen()
  123. progressBars()
  124. listening = true
  125. }
  126. }
  127. })