Last active
December 15, 2022 15:38
-
-
Save lbmaian/c757a022bc8c33420b0b295ad4f971c0 to your computer and use it in GitHub Desktop.
YouTube - Ensure Video Plays On Load
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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