Skip to content

Instantly share code, notes, and snippets.

@lbmaian
Last active December 15, 2022 15:38
Show Gist options
  • Save lbmaian/c757a022bc8c33420b0b295ad4f971c0 to your computer and use it in GitHub Desktop.
Save lbmaian/c757a022bc8c33420b0b295ad4f971c0 to your computer and use it in GitHub Desktop.
YouTube - Ensure Video Plays On Load
// ==UserScript==
// @name YouTube - Ensure Video Plays On Load
// @namespace https://gist.github.com/lbmaian/c757a022bc8c33420b0b295ad4f971c0
// @downloadURL https://gist.github.com/lbmaian/c757a022bc8c33420b0b295ad4f971c0/raw/youtube-ensure-video-plays-on-load.user.js
// @updateURL https://gist.github.com/lbmaian/c757a022bc8c33420b0b295ad4f971c0/raw/youtube-ensure-video-plays-on-load.user.js
// @version 0.3.3
// @description Workaround for when YouTube somehow no longer autoplays videos on load
// @author lbmaian
// @match https://www.youtube.com/*
// @exclude https://www.youtube.com/embed/*
// @run-at document-start
// @grant none
// @icon https://www.youtube.com/favicon.ico
// ==/UserScript==
(function() {
'use strict';
const logContext = '[YouTube - Ensure Video Plays On Load]';
function debug(...args) {
console.debug(logContext, ...args); // uncomment to disable debugging
}
function log(...args) {
console.log(logContext, ...args);
}
function warn(...args) {
console.warn(logContext, ...args);
}
function error(...args) {
console.error(logContext, ...args);
}
function getVideoElement() {
const player = document.getElementById('movie_player');
return player?.getElementsByTagName('VIDEO')?.[0] ?? null;
}
function waitForVideo() {
const video = getVideoElement();
if (video) {
debug('found video', video, '; paused?', video.paused);
tryPlayVideoInit(video);
} else {
debug('waiting for video');
new MutationObserver((records, observer) => {
const video = getVideoElement();
if (video) {
debug('found video', video, '; paused?', video.paused);
observer.disconnect();
tryPlayVideoInit(video);
}
}).observe(document, {
childList: true,
subtree: true,
});
}
}
const symInit = Symbol(logContext + ' initialized');
const symTryPlay = Symbol(logContext + ' currently trying to play video');
function tryPlayVideoInit(video) {
// Custom attribute is to ensure event listeners are only added once per element
if (!video[symInit]) {
// If ads are playing, there will be multiple videos without yt-navigate-finish events triggering,
// so need to monitor video element events (canplay works here) to ensure we try playing new videos
// that may also be initially paused
video.addEventListener('canplay', evt => {
const video = evt.target;
debug('canplay event', evt);
debugVideoStatus(video);
tryPlayVideo(video);
});
// When window is out of focus, video sometimes pauses in the middle inexpicably,
// so try resuming when this happens
video.addEventListener('pause', evt => {
const video = evt.target;
debug('pause event', evt);
debugVideoStatus(video);
if (!video.ended && !document.hasFocus()) {
log('video inexpicably paused while window out of focus');
tryPlayVideo(video);
}
});
video[symInit] = true;
}
tryPlayVideo(video);
}
function tryPlayVideo(video) {
const paused = video.paused;
if (paused && !video[symTryPlay]) {
video[symTryPlay] = true; // for idempotency
log('video paused - attempting to play');
debugVideoStatus(video);
// video.autoplay only works in Chrome when video.muted
// YT will flip video.muted back to false later anyway
video.muted = true;
video.autoplay = true;
video.play();
setTimeout(tryPlayVideoLoop, 1000);
} else {
if (!paused) {
debug('video already unpaused');
}
if (video[symTryPlay]) {
debug('already attempting to play');
}
}
}
// This often fails to do anything the first couple tries,
// but somehow works eventually as long as the window is in focus
function tryPlayVideoLoop() {
const video = getVideoElement();
if (!video) {
warn('video element not found');
} else if (video.paused) {
log('video still paused - attempting to play');
debugVideoStatus(video);
video.play();
setTimeout(tryPlayVideoLoop, 1000);
} else {
log('video no longer paused');
debugVideoStatus(video);
// YT should have already toggled video.muted to false
video.autoplay = false; // not sure if this helps
video[symTryPlay] = false;
}
};
function debugVideoStatus(video) {
debug('paused?', video.paused, 'muted?', video.muted, 'autoplay?', video.autoplay, 'ended?', video.ended, 'hasFocus?', document.hasFocus());
}
// Navigating to YouTube watch page can happen via AJAX rather than new page load
// We can monitor this with YT's custom yt-navigate-finish event,
// which conveniently also fires even for new/refreshed pages
document.addEventListener('yt-navigate-finish', evt => {
if (evt.detail.pageType === 'watch') {
debug('Navigated to', evt.detail.response.url);
waitForVideo();
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment