youtube HTML5 Auto Loop

youtube再生時に自動ループする

  1. // ==UserScript==
  2. // @name youtube HTML5 Auto Loop
  3. // @namespace youtube HTML5 Auto Loop
  4. // @grant none
  5. // @description youtube再生時に自動ループする
  6. // @author TNB
  7. // @match https://www.youtube.com/*
  8. // @version 1.5.5
  9. // @run-at document-start
  10. // ==/UserScript==
  11.  
  12. /******************** SETTING **************************/
  13. const loop_off = {
  14. when_enable_next_video_autoplay: false,
  15. when_playlist: false,
  16. with_embedded_video: false
  17. };
  18. /********************************************************/
  19.  
  20.  
  21. 'use strict';
  22.  
  23. const YoutubeHTML5AutoLoop = {
  24. loop: true,
  25. readyToReload: false,
  26. playercontainer: null,
  27. video: null,
  28. prevSrc: null,
  29. button: null,
  30. eventcache: {},
  31. cancelInit: false,
  32. ele: {
  33. when_enable_next_video_autoplay: '.ytp-right-controls > button[style]:not([style *= "display: none"]) .ytp-autonav-toggle-button[aria-checked="true"]',
  34. when_playlist: '#secondary-inner #playlist:not([hidden])',
  35. with_embedded_video: 'html[data-cast-api-enabled="true"]'
  36. },
  37. init: function() {
  38. this.addListener();
  39. },
  40. isLoop: function() {
  41. return !Object.keys(loop_off).some(a => loop_off[a] && document.querySelector(this.ele[a]));
  42. },
  43. goLoop: function() {
  44. this.video.currentTime = 0;
  45. this.video.play;
  46. },
  47. enableLoop: function() {
  48. if (this.loop) this.video.setAttribute('loop', '');
  49. else this.video.removeAttribute('loop');
  50. },
  51. initLoop: function() {
  52. this.loop = this.isLoop();
  53. this.enableLoop();
  54. },
  55. loopToggle: function() {
  56. this.loop = !this.loop;
  57. this.enableLoop();
  58. },
  59. displayLoopStatus: function() {
  60. const video = document.querySelector('video:hover');
  61. if (!video) return;
  62. const checkBox = document.querySelector('.ytp-contextmenu [aria-checked]');
  63. checkBox.setAttribute('aria-checked', this.loop);
  64. if (!this.eventcache.checkBox) {
  65. checkBox.addEventListener('click', this, true);
  66. this.eventcache.checkBox = true;
  67. }
  68. },
  69. isEnded: function(m) {
  70. return m.classList.contains('ended-mode') || m.querySelector('.ytp-autonav-endscreen-button-container:not([style*="display"]),.html5-endscreen:not([style*="display"])');
  71. },
  72. toggleNextVideoAutoplay: function() {
  73. if ((!loop_off.when_enable_next_video_autoplay && this.loop) || loop_off.when_enable_next_video_autoplay) {
  74. setTimeout(() => {
  75. if (document.querySelector(`.ytp-right-controls > button[style]:not([style *= "display: none"]) .ytp-autonav-toggle-button[aria-checked="${this.loop}"]`)) this.button.click();
  76. }, 1000);
  77. }
  78. },
  79. observeVideo: function() {
  80. new MutationObserver(() => {
  81. if (this.video.src != this.prevSrc) this.cancelInit = false;
  82. if (!this.cancelInit) this.initLoop();
  83. // if (this.loop && this.isEnded(this.playercontainer)) this.goLoop();
  84. if (!this.eventcache.toggleAutoPlay) this.addToggleEvent();
  85. this.toggleNextVideoAutoplay();
  86. this.prevSrc = this.video.src;
  87. this.video = this.playercontainer.querySelector('video');
  88. this.video.addEventListener('timeupdate', ()=> {
  89. if (this.video.duration - this.video.currentTime < 1 && !this.readyToReload) {
  90. this.readyToReload = true;
  91. setTimeout(() => {if (this.loop && document.querySelector('.ytp-spinner[style=""]')) location.reload()}, 2000);
  92. }
  93. });
  94. }).observe(this.playercontainer, {childList: true, subtree: true, attributes: true, attributeFilter: ['class']});
  95. },
  96. findPlayercontainer: function() {
  97. if (window != window.parent && document.getElementById('chat')) return;
  98. const mo = new MutationObserver(() => {
  99. this.playercontainer = document.getElementById('movie_player');
  100. if (!this.playercontainer) return;
  101. this.video = this.playercontainer.querySelector('video');
  102. this.observeVideo();
  103. this.initLoop();
  104. this.addToggleEvent();
  105. this.toggleNextVideoAutoplay();
  106. mo.disconnect();
  107. });
  108. mo.observe(document.body, {childList: true, subtree: true});
  109. },
  110. addToggleEvent: function() {
  111. this.button = document.querySelector('.ytp-right-controls > button[style]:not([style *= "display: none"]) .ytp-autonav-toggle-button');
  112. if (!loop_off.when_enable_next_video_autoplay || !this.button) return;
  113. this.button.addEventListener('click', () => {
  114. this.loop = JSON.parse(this.button.getAttribute('aria-checked'));
  115. this.enableLoop();
  116. this.cancelInit = true;
  117. this.eventcache.toggleAutoPlay = true;
  118. });
  119. },
  120. addListener: function() {
  121. window.addEventListener('DOMContentLoaded', this);
  122. window.addEventListener('contextmenu', this);
  123. },
  124. handleEvent: function(e) {
  125. switch (e.type) {
  126. case 'DOMContentLoaded':
  127. this.findPlayercontainer();
  128. break;
  129. case 'contextmenu':
  130. this.displayLoopStatus();
  131. break;
  132. case 'click':
  133. this.loopToggle();
  134. document.body.click();
  135. this.toggleNextVideoAutoplay();
  136. this.cancelInit = true;
  137. e.stopPropagation();
  138. break;
  139. }
  140. }
  141. };
  142.  
  143. YoutubeHTML5AutoLoop.init();