// ==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.5
// @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,
readyToReload: false,
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', '');
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() {
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');
this.video.addEventListener('timeupdate', ()=> {
if (this.video.duration - this.video.currentTime < 1 && !this.readyToReload) {
this.readyToReload = true;
setTimeout(() => {if (this.loop && document.querySelector('.ytp-spinner[style=""]')) location.reload()}, 2000);
}
});
}).observe(this.playercontainer, {childList: true, subtree: true, attributes: true, attributeFilter: ['class']});
},
findPlayercontainer: function() {
if (window != window.parent && document.getElementById('chat')) return;
const mo = 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();
mo.disconnect();
});
mo.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();