Skip to content

Instantly share code, notes, and snippets.

@Jip-Hop
Created October 3, 2021 10:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jip-Hop/f754448f408d37b719d261e3fedb663a to your computer and use it in GitHub Desktop.
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.
(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