Greasy Fork is available in English.

youtube HTML5 Auto Loop

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

// ==UserScript==
// @name           youtube HTML5 Auto Loop
// @namespace      youtube HTML5 Auto Loop
// @grant          none
// @description    youtube再生時に自動ループする
// @author         TNB
// @match          https://www.youtube.com/*
// @version        1.5.3
// @run-at         document-start
// ==/UserScript==


/********************  SETTING **************************/
const loop_off = {
  when_enable_next_video_autoplay: false,
  when_playlist: false,
  with_embedded_video: false
};
/********************************************************/

'use strict';

const YoutubeHTML5AutoLoop = {
  loop: true,
  playercontainer: null,
  video: null,
  prevSrc: null,
  button: null,
  eventcache: {},
  cancelInit: false,
  ele: {
    when_enable_next_video_autoplay: '.ytp-right-controls > button[style]:not([style *= "display: none"]) .ytp-autonav-toggle-button[aria-checked="true"]',
    when_playlist: '#secondary-inner > #playlist:not([hidden])',
    with_embedded_video: 'html[data-cast-api-enabled="true"]'
  },
  init: function() {
    this.addListener();
  },
  isLoop: function() {
    return !Object.keys(loop_off).some(a => loop_off[a] && document.querySelector(this.ele[a]));
  },
  goLoop: function() {
    this.video.currentTime = 0;
    this.video.play;
  },
  enableLoop: function() {
    if (this.loop) this.video.setAttribute('loop', this.loop);
    else this.video.removeAttribute('loop');
  },
  initLoop: function() {
    this.loop = this.isLoop();
    this.enableLoop();
  },
  loopToggle: function() {
    this.loop = !this.loop;
    this.enableLoop();
  },
  displayLoopStatus: function() {
    const video = document.querySelector('video:hover');
    if (!video) return;
    const checkBox = document.querySelector('.ytp-contextmenu [aria-checked]');
    checkBox.setAttribute('aria-checked', this.loop);
    if (!this.eventcache.checkBox) {
      checkBox.addEventListener('click', this, true);
      this.eventcache.checkBox = true;
    }
  },
  isEnded: function(m) {
    return m.classList.contains('ended-mode') || m.querySelector('.ytp-autonav-endscreen-button-container:not([style*="display"]),.html5-endscreen:not([style*="display"])');
  },
  toggleNextVideoAutoplay: function() {
    if ((!loop_off.when_enable_next_video_autoplay && this.loop) || loop_off.when_enable_next_video_autoplay) {
      setTimeout(() => {
          if (document.querySelector(`.ytp-right-controls > button[style]:not([style *= "display: none"]) .ytp-autonav-toggle-button[aria-checked="${this.loop}"]`)) this.button.click();
      }, 1000);
    }
  },
  observeVideo: function() {
    const mo = new MutationObserver(() => {
      if (this.video.src != this.prevSrc) this.cancelInit = false;
      if (!this.cancelInit) this.initLoop();
      if (this.loop && this.isEnded(this.playercontainer)) this.goLoop();
      if (!this.eventcache.toggleAutoPlay) this.addToggleEvent();
      this.toggleNextVideoAutoplay();
      this.prevSrc = this.video.src;
      this.video = this.playercontainer.querySelector('video');
    });
    mo.observe(this.playercontainer, {attributes: true, attributeFilter: ['class']});
  },
  findPlayercontainer: function() {
    if (window != window.parent && document.getElementById('chat')) return;
    const mm = new MutationObserver(() => {
      this.playercontainer = document.getElementById('movie_player');
      if (!this.playercontainer) return;
      this.video = this.playercontainer.querySelector('video');
      this.observeVideo();
      this.initLoop();
      this.addToggleEvent();
      this.toggleNextVideoAutoplay();
      mm.disconnect();
    });
    mm.observe(document.body, {childList: true, subtree: true});
  },
  addToggleEvent: function() {
    this.button = document.querySelector('.ytp-right-controls > button[style]:not([style *= "display: none"]) .ytp-autonav-toggle-button');
    if (!loop_off.when_enable_next_video_autoplay || !this.button) return;
    this.button.addEventListener('click', () => {
      this.loop = JSON.parse(this.button.getAttribute('aria-checked'));
      this.enableLoop();
      this.cancelInit = true;
      this.eventcache.toggleAutoPlay = true;
    });
  },
  addListener: function() {
    window.addEventListener('DOMContentLoaded', this);
    window.addEventListener('contextmenu', this);
  },
  handleEvent: function(e) {
    switch (e.type) {
      case 'DOMContentLoaded':
        this.findPlayercontainer();
        break;
      case 'contextmenu':
        this.displayLoopStatus();
        break;
      case 'click':
        this.loopToggle();
        document.body.click();
        this.toggleNextVideoAutoplay();
        this.cancelInit = true;
        e.stopPropagation();
        break;
    }
  }
};

YoutubeHTML5AutoLoop.init();