Youtube - Resumer

Store video.currentTime locally

目前為 2022-12-01 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Youtube - Resumer
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.7
  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. function save(video, id){
  25. let completion = video.currentTime / video.duration
  26. GM.setValue(id, video.currentTime)
  27. GM.setValue(id + '-completion', completion)
  28. }
  29.  
  30. function listen(){
  31. let video = document.querySelector('video')
  32. let lastSrc
  33.  
  34. video.addEventListener('timeupdate', () => {
  35. //Video source is '' and duration is NaN when going back to the home page
  36. //When loading a new video, the event is fired with currentTime 0 and duration NaN
  37. if(video.src && !isNaN(video.duration)){
  38. let id = videoId() //if you use the miniplayer the url no longer includes the video id
  39. l(id, lastId, video.src, lastSrc)
  40. if(id){
  41. save(video, id)
  42. lastSrc = video.src
  43. }else if(video.src === lastSrc){ //in case you click another video while using the miniplayer
  44. save(video, lastId) //save even if in miniplayer
  45. }
  46. }
  47. })
  48. }
  49.  
  50. async function resume(){
  51. let video = document.querySelector('video')
  52. let lastTime = await GM.getValue(videoId())
  53. if(lastTime){
  54. l('resuming', video.currentTime, lastTime)
  55. video.currentTime = lastTime
  56. }
  57. }
  58.  
  59. function cleanUrl(){
  60. //Remove t paramater when opening a video that had a progress bar
  61. let url = new URL(document.URL)
  62. url.searchParams.delete('t')
  63. window.history.replaceState(null, null, url)
  64. }
  65.  
  66. function addProgressBar(overlays, width){
  67. let parent = document.createElement('div')
  68. 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>'
  69. overlays.appendChild(parent.firstChild)
  70. let progress = overlays.querySelector('#progress')
  71. styleProgressBar(progress, width)
  72. }
  73.  
  74. function styleProgressBar(progress, width){
  75. progress.style.width = `${width}%`
  76. progress.style.backgroundColor = 'blue'
  77. }
  78.  
  79. function progressBars(){
  80. let related = document.querySelector('#related')
  81. //Add progress bars in the related section
  82. const observer = new MutationObserver(async (mutationsList, observer) => {
  83. for(let mutation of mutationsList){
  84. if(mutation.target.id === 'overlays'){
  85. let href = mutation.target.parentElement.parentElement.parentElement.parentElement.querySelector('a').href
  86. let id = videoId(href)
  87. let completion = await GM.getValue(id + '-completion')
  88. if(completion){
  89. let width = parseInt(completion * 100)
  90. let progress = mutation.target.querySelector('#progress')
  91. if(progress){
  92. styleProgressBar(progress, width)
  93. }else{
  94. addProgressBar(mutation.target, width)
  95. }
  96. }
  97. }
  98. }
  99. })
  100. observer.observe(related, {childList:true, subtree:true})
  101. }
  102.  
  103. let listening = false //the video element exists even if you go back to the home page, so no need to readd event listeners
  104. let lastId //don't resume if going back to same page from miniplayer
  105.  
  106. //Event for each page change
  107. document.addEventListener("yt-navigate-finish", async () => {
  108. l('navigate-finish', lastId, videoId())
  109. //video page
  110. if(videoId() && lastId !== videoId()) {
  111. lastId = videoId()
  112. cleanUrl()
  113. resume()
  114. //Add listeners once
  115. if(!listening){
  116. l('listening')
  117. listen()
  118. progressBars()
  119. listening = true
  120. }
  121. }
  122. })