Loop Fix

Fixes youtube video repeating when watching in playlist + refreshing page if video stopped downloading

当前为 2024-09-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Loop Fix
  3. // @description Fixes youtube video repeating when watching in playlist + refreshing page if video stopped downloading
  4. // @version 0.1.10
  5. // @author 0vC4
  6. // @namespace https://greasyfork.org/users/670183
  7. // @match *://*.youtube.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  9. // @run-at document-start
  10. // @license MIT
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (() => {
  15. function blockEvents(condition, ...events) {
  16. events = events.flat();
  17. const tag = window.EventTarget.prototype;
  18. tag._add = tag.addEventListener;
  19. tag.addEventListener = function (name, callback, options) {
  20. if (!events.includes(name)) return tag._add.call(this, name, callback, options);
  21.  
  22. function cb(e) {
  23. if (!condition.call(this)) return;
  24. callback.call(this, e);
  25. };
  26.  
  27. tag._add.call(this, name, cb, options);
  28. };
  29. }
  30.  
  31. blockEvents(function() {
  32. return !this.loop; // have loop = block events
  33. }, 'pause', 'timeupdate', 'waiting');
  34. })();
  35.  
  36. const log = window.console.log;
  37. window.loopState = localStorage.getItem('loopState') ?? false;
  38. function clicking(e) {
  39. window.loopState = JSON.parse(this.ariaChecked);
  40. };
  41.  
  42. let created = false;
  43. let refreshing = false;
  44. let loaded = false;
  45. let oldVid = null;
  46. let seeking = false;
  47.  
  48. const maxPing = 500;
  49. const blob = new window.Blob([`setTimeout(() => postMessage(0), ${maxPing});`]);
  50. const workerScript = window.URL.createObjectURL(blob);
  51. const script = window.trustedTypes && window.trustedTypes.createPolicy ? window.trustedTypes.createPolicy('timeout', {createScriptURL: str => str}).createScriptURL(workerScript) : workerScript;
  52.  
  53. window.setInterval(()=>{
  54. const vid = window.document.querySelector('video.html5-main-video');
  55. if (vid) {
  56. // to prevent false detecting when changing video
  57. if (vid != oldVid) {
  58. loaded = false;
  59. oldVid = vid;
  60. }
  61.  
  62. if (!created) {
  63. created = true;
  64. localStorage.removeItem('loopState');
  65.  
  66. vid.addEventListener('seeked', function() {
  67. seeking = true;
  68. const callback = () => {
  69. seeking = false;
  70. };
  71. new window.Worker(script).onmessage = callback;
  72. });
  73.  
  74. // refresh if no data for half sec
  75. const callback = () => {
  76. if (!loaded && vid.readyState === window.HTMLMediaElement.HAVE_NOTHING) {
  77. localStorage.setItem('loopState', window.loopState);
  78. window.location.href = window.location.href;
  79. }
  80. };
  81. new window.Worker(script).onmessage = callback;
  82. }
  83.  
  84. // apply loopState after .src or page refresh
  85. if (vid.loop != window.loopState) vid.loop = window.loopState;
  86.  
  87. // to prevent early page refresh
  88. if (+vid.buffered.end(0).toFixed(0) !== 0) loaded = true;
  89.  
  90. const noData = vid.readyState === window.HTMLMediaElement.HAVE_CURRENT_DATA || vid.readyState === window.HTMLMediaElement.HAVE_METADATA;
  91. let noNewData = +vid.currentTime.toFixed(0) >= +vid.buffered.end(0).toFixed(0) - 2;
  92.  
  93. if (loaded && !refreshing && noData && !seeking && !vid.paused && +vid.currentTime.toFixed(0) >= +vid.buffered.end(0).toFixed(0) - 2) {
  94. localStorage.setItem('loopState', window.loopState);
  95. window.location.href = window.location.href.split('?')[0] + '?t=' + (+vid.currentTime.toFixed(0) + 1) + '&' + window.location.href.split('?')[1];
  96. refreshing = true;
  97. }
  98. }
  99.  
  100. // attach loop click detector
  101. const repeat = window.document.querySelectorAll('.ytp-menuitem[tabindex="-1"]')[0];
  102. if (repeat && repeat.onclick != clicking) {
  103. repeat.onclick = clicking
  104. }
  105. });