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.15
  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. // refresh if no reply during these actions
  15. const maxLoadingTime = 500;
  16. const maxSeekingTime = 1500;
  17.  
  18. (() => {
  19. function blockEvents(condition, ...events) {
  20. events = events.flat();
  21. const tag = window.EventTarget.prototype;
  22. tag._add = tag.addEventListener;
  23. tag.addEventListener = function (name, callback, options) {
  24. if (!events.includes(name)) return tag._add.call(this, name, callback, options);
  25.  
  26. function cb(e) {
  27. if (!condition.call(this)) return;
  28. callback.call(this, e);
  29. };
  30.  
  31. tag._add.call(this, name, cb, options);
  32. };
  33. }
  34.  
  35. blockEvents(function() {
  36. return !this.loop; // have loop = block events
  37. }, 'pause', 'timeupdate', 'waiting');
  38. })();
  39.  
  40. const log = window.console.log;
  41. window.loopState = localStorage.getItem('loopState') ?? false;
  42. function clicking(e) {
  43. window.loopState = JSON.parse(this.ariaChecked);
  44. };
  45.  
  46. let created = false;
  47. let refreshing = false;
  48.  
  49. let loaded = false;
  50. let navigating = true;
  51.  
  52. let seeking = false;
  53. let seekingId = 0;
  54.  
  55. const policy = window.trustedTypes && window.trustedTypes.createPolicy ? window.trustedTypes.createPolicy('timeout', {createScriptURL: str => str}) : {createScriptURL: str => str};
  56. const timeout = delay => URL.createObjectURL(new Blob([`setTimeout(() => postMessage(0), ${delay});`]));
  57. const refreshOnLoad = policy.createScriptURL(timeout(maxLoadingTime));
  58. const seekingDebounce = policy.createScriptURL(timeout(500));
  59.  
  60. document.addEventListener('yt-navigate-start', () => {
  61. navigating = true;
  62. loaded = false;
  63. });
  64. document.addEventListener('yt-navigate-finish', () => {
  65. navigating = false;
  66. });
  67.  
  68. window.setInterval(()=>{
  69. const vid = window.document.querySelector('video.html5-main-video');
  70. if (vid) {
  71. if (!created) {
  72. created = true;
  73. localStorage.removeItem('loopState');
  74. vid.addEventListener('seeking', () => {
  75. clearTimeout(seekingId);
  76. seeking = true;
  77. seekingId = setTimeout(() => {
  78. seeking = false;
  79. }, maxSeekingTime);
  80. });
  81.  
  82. // refresh if no data for half sec
  83. const callback = () => {
  84. if (!loaded && vid.readyState === window.HTMLMediaElement.HAVE_NOTHING) {
  85. localStorage.setItem('loopState', window.loopState);
  86. window.location.href = window.location.href;
  87. }
  88. };
  89. new Worker(refreshOnLoad).onmessage = callback;
  90. }
  91.  
  92. // apply loopState after .src or page refresh
  93. if (vid.loop != window.loopState) vid.loop = window.loopState;
  94.  
  95. // to prevent early page refresh
  96. if (vid.readyState === window.HTMLMediaElement.HAVE_CURRENT_DATA || vid.readyState === window.HTMLMediaElement.HAVE_ENOUGH_DATA) loaded = !navigating;
  97.  
  98. const noData = vid.readyState === window.HTMLMediaElement.HAVE_CURRENT_DATA || vid.readyState === window.HTMLMediaElement.HAVE_METADATA;
  99.  
  100. if (loaded && !refreshing && noData && !seeking && !vid.paused && +vid.currentTime.toFixed(0) >= +vid.buffered.end(0).toFixed(0) - 2) {
  101. refreshing = true;
  102. const callback = () => {
  103. if (seeking) {
  104. refreshing = false;
  105. return;
  106. }
  107. localStorage.setItem('loopState', window.loopState);
  108. window.location.href = window.location.href.split('?')[0] + '?t=' + (+vid.currentTime.toFixed(0) + 1) + '&' + window.location.href.split('?')[1];
  109. };
  110. new Worker(seekingDebounce).onmessage = callback;
  111. }
  112.  
  113. // apply loopState after .src or page refresh
  114. if (vid.loop != window.loopState) vid.loop = window.loopState;
  115.  
  116. // attach loop click detector
  117. const repeat = window.document.querySelectorAll('.ytp-menuitem[tabindex="-1"]')[0];
  118. if (repeat && repeat.onclick != clicking) {
  119. repeat.onclick = clicking;
  120. }
  121. }
  122. });