Greasy Fork 还支持 简体中文。

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