Created
October 3, 2021 10:18
-
-
Save Jip-Hop/f754448f408d37b719d261e3fedb663a to your computer and use it in GitHub Desktop.
Open all videos in a tab as pop-up windows. Basic (non-extension) version of https://github.com/Jip-Hop/pop-up-videos. To be copy-pasted in the browser console.
This file contains hidden or 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
(function enable() { | |
var titleSuffixCounter = 0; | |
var popupWidth = 480; | |
var popupHeight = 270; | |
var xOffset = screen.availLeft, | |
yOffset = screen.availTop; | |
const data = { | |
windows: [], | |
videos: [], | |
}; | |
const cssText = ` | |
* { | |
margin: 0; | |
padding: 0; | |
border: 0; | |
} | |
html, body { | |
background: black; | |
} | |
#wrapper { | |
width: 100vw; | |
height: 100vh; | |
} | |
canvas, video { | |
width: 100%; | |
height: 100%; | |
object-fit: contain; | |
} | |
button { | |
position: absolute; | |
bottom: 0; | |
right: 0; | |
padding: 5px; | |
display: none; | |
} | |
body:hover button { | |
display: block; | |
} | |
`; | |
const closeAllPopups = () => { | |
data.windows.forEach((win) => { | |
try { | |
win.close(); | |
} catch (e) { | |
console.log(e); | |
} | |
}); | |
}; | |
const setTitle = (win, video) => { | |
var titleSuffix; | |
if (video.id) { | |
titleSuffix = video.id + " [id]"; | |
} else if (video.hasAttribute("jiptitlesuffix")) { | |
titleSuffix = video.getAttribute("jiptitlesuffix"); | |
} else { | |
titleSuffixCounter++; | |
titleSuffix = titleSuffixCounter + " [#]"; | |
video.setAttribute("jiptitlesuffix", titleSuffix); | |
} | |
// Window may be temporarily inaccessible when a reload is in process | |
try { | |
win.document.title = window.location.hostname + " | " + titleSuffix; | |
} catch (e) { | |
console.log(e); | |
} | |
}; | |
const unmute = (video) => { | |
if (video.muted) { | |
video.volume = 0; | |
video.muted = false; | |
} | |
}; | |
const syncWin = (win, video) => { | |
// Try if we can access the window | |
try { | |
// Don't setup for closed windows | |
if (win.closed) { | |
return; | |
} | |
} catch (e) { | |
console.log(e); | |
// Don't continue if window isn't accessible temporarily, | |
// retry in 1 millisecond | |
setTimeout(() => syncWin(win, video), 1); | |
return; | |
} | |
// Keep syncing the title | |
setTitle(win, video); | |
if (win.hasBeenSetup || win.willUnload) { | |
return; | |
} | |
win.hasBeenSetup = true; | |
win.onbeforeunload = () => { | |
win.willUnload = true; | |
}; | |
const css = document.createElement("style"); | |
css.type = "text/css"; | |
css.appendChild(document.createTextNode(cssText)); | |
win.document.head.appendChild(css); | |
var wrapper = document.createElement("div"); | |
wrapper.id = "wrapper"; | |
win.document.body.appendChild(wrapper); | |
try { | |
// TODO: maybe check first if there's already a srcObject I can access, so I don't have to capture a new one... | |
// In that case I'd have to keep updating the target srcObject if the source changes | |
const stream = video.captureStream(); | |
const newVid = win.document.createElement("video"); | |
newVid.muted = true; | |
newVid.autoplay = true; | |
newVid.controls = false; | |
video.onplay = () => { | |
// Keep them linked, also when restarting playback | |
newVid.srcObject = video.captureStream(); | |
}; | |
newVid.srcObject = stream; | |
wrapper.appendChild(newVid); | |
} catch (e) { | |
// Use canvas as a fallback when captureStream fails due to CORS | |
const canvas = win.document.createElement("canvas"); | |
wrapper.appendChild(canvas); | |
const ctx = canvas.getContext("2d"); | |
var fpsInterval, now, then, elapsed, lastTime; | |
const startAnimating = (fps) => { | |
fpsInterval = 1000 / fps; | |
then = window.performance.now(); | |
drawVideo(); | |
}; | |
function drawVideo(newtime) { | |
// request another frame | |
requestAnimationFrame(drawVideo); | |
// calc elapsed time since last loop | |
now = newtime; | |
elapsed = now - then; | |
// if enough time has elapsed, draw the next frame | |
if (elapsed > fpsInterval) { | |
// Get ready for next frame by setting then=now, but... | |
// Also, adjust for fpsInterval not being multiple of 16.67 | |
then = now - (elapsed % fpsInterval); | |
// draw stuff here | |
if (video.currentTime !== lastTime) { | |
canvas.width = video.videoWidth; | |
canvas.height = video.videoHeight; | |
ctx.drawImage(video, 0, 0, canvas.width, canvas.height); | |
lastTime = video.currentTime; | |
} | |
} | |
} | |
// Draw at 30fps | |
startAnimating(30); | |
} | |
const button = win.document.createElement("button"); | |
button.textContent = "Full Screen"; | |
button.onclick = () => { | |
wrapper.requestFullscreen(); | |
}; | |
// "Enter" keyboard shortcut for full screen | |
win.document.documentElement.addEventListener("keypress", (e) => { | |
if (e.key === "Enter") { | |
wrapper.requestFullscreen(); | |
} | |
}); | |
win.onresize = () => { | |
if (win.innerHeight === win.screen.height) { | |
// Full screen, also covers case of not using HTML5 fullscreen api | |
button.style.display = "none"; | |
} else { | |
button.style.display = ""; | |
} | |
}; | |
win.document.body.appendChild(button); | |
}; | |
const openVideosInWindow = () => { | |
const sourceVideos = document.querySelectorAll("video"); | |
sourceVideos.forEach((video) => { | |
const videoIndex = data.videos.indexOf(video); | |
var win; | |
// Video may not be muted, else it won't play in the background | |
video.onvolumechange = function () { | |
unmute(video); | |
}; | |
unmute(video); | |
if (videoIndex === -1) { | |
win = window.open( | |
"about:blank# Move the video and go full screen (bottom right button).", | |
performance.now(), | |
`status=no,menubar=no,width=${popupWidth},height=${popupHeight},left=${xOffset},top=${yOffset}` | |
); | |
if (!win) { | |
return; | |
} | |
xOffset += popupWidth; | |
if (xOffset + popupWidth > screen.availWidth) { | |
xOffset = screen.availLeft; | |
yOffset += popupHeight; | |
} | |
if (yOffset + popupHeight > screen.availHeight) { | |
xOffset = screen.availLeft; | |
yOffset = screen.availTop; | |
} | |
if (win.document.readyState === "complete") { | |
syncWin(win, video); | |
} else { | |
win.onload = () => syncWin(win, video); | |
} | |
data.videos.push(video); | |
data.windows.push(win); | |
} else { | |
win = data.windows[videoIndex]; | |
} | |
syncWin(win, video); | |
}); | |
data.videos.slice().forEach((video) => { | |
// Video no longer in source document, close window | |
if ([].indexOf.call(sourceVideos, video) === -1) { | |
const index = data.videos.indexOf(video); | |
if (index > -1) { | |
data.windows[index].close(); | |
data.windows.splice(index, 1); | |
data.videos.splice(index, 1); | |
} | |
} | |
}); | |
data.timer = setTimeout(openVideosInWindow, 1000); | |
}; | |
// Test if pop-ups are allowed | |
var testWin = window.open( | |
"about:blank", | |
performance.now(), | |
`status=no,menubar=no,width=${popupWidth},height=${popupHeight},left=0,top=0` | |
); | |
if (!testWin || testWin.closed || typeof testWin.closed === "undefined") { | |
alert("Please allow pop-ups for this page, reload and try again."); | |
} else { | |
testWin.close(); | |
window.addEventListener("unload", closeAllPopups); | |
openVideosInWindow(); | |
} | |
})(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment