mirror of
https://codeberg.org/vlw/still-alive.git
synced 2025-09-14 00:13:41 +02:00
Add Proxy and window handler
This commit is contained in:
parent
a1981bc73a
commit
8b659066cc
5 changed files with 107 additions and 166 deletions
|
@ -1,116 +1,35 @@
|
|||
import { default as PlayerWindow } from "./PlayerWindow.mjs";
|
||||
import { default as Monkeydo } from "./monkeydo/Monkeydo.mjs";
|
||||
import { default as Player } from "./PlayerWindow.mjs";
|
||||
|
||||
export default class WindowManager {
|
||||
constructor(mediaElement) {
|
||||
const self = this;
|
||||
this.mediaElement = mediaElement;
|
||||
|
||||
// Bi-directional communcation to player windows
|
||||
this.channels = {
|
||||
"#lyrics": new BroadcastChannel("#lyrics"),
|
||||
"#credits": new BroadcastChannel("#credits"),
|
||||
"#art": new BroadcastChannel("#art")
|
||||
export default class PlayerManager {
|
||||
constructor() {
|
||||
this.players = {
|
||||
"lyrics": new Player("lyrics","monkeydo_lyrics.json")//,
|
||||
//"credits": new Player("credits","monkeydo_credits.json"),
|
||||
//"art": new Player("art")
|
||||
};
|
||||
|
||||
for(const channel of Object.values(this.channels)) {
|
||||
this.channels = new WeakMap();
|
||||
for(const player of Object.values(this.players)) {
|
||||
// Create BroadcastChannels for each player
|
||||
const channel = new BroadcastChannel(player.name);
|
||||
this.channels.set(player,channel);
|
||||
channel.addEventListener("message",event => this.message(event));
|
||||
}
|
||||
|
||||
// Monkeydo methods
|
||||
const methods = {
|
||||
blank: (target) => {
|
||||
self.channels[target].postMessage(["BLANK",target]);
|
||||
},
|
||||
lineFeed: (target) => {
|
||||
self.channels[target].postMessage(["LINE_FEED"]);
|
||||
},
|
||||
textFeed: (text,target = "#lyrics") => {
|
||||
self.channels[target].postMessage(["TEXT_FEED",text]);
|
||||
},
|
||||
drawArt: (index,target = "#art") => {
|
||||
self.channels[target].postMessage(["DRAW_ART",index]);
|
||||
},
|
||||
playCredits: () => {
|
||||
self.players.credits.play();
|
||||
}
|
||||
}
|
||||
|
||||
this.players = {
|
||||
lyrics: new Monkeydo(methods),
|
||||
credits: new Monkeydo(methods)
|
||||
// Open each player
|
||||
if(player.open() === null) return this.windowOpenFailed();
|
||||
}
|
||||
}
|
||||
|
||||
playbackFailed(promiseObject = false) {
|
||||
console.log(promiseObject);
|
||||
}
|
||||
|
||||
// Attempt to open a new window
|
||||
async spawnPlayer(type) {
|
||||
if(!type in this.channels) {
|
||||
throw new Error(`Inavlid player type "${type}"`);
|
||||
}
|
||||
|
||||
return await new Promise((resolve,reject) => {
|
||||
const player = new PlayerWindow(type).open();
|
||||
const channel = this.channels[type];
|
||||
|
||||
// Wait for window to emit ready state message before resolving
|
||||
const ack = channel.addEventListener("message",event => {
|
||||
if(event.data[0] === "WINDOW_READY" || event.data[1] === type) {
|
||||
resolve("WINDOW_READY");
|
||||
}
|
||||
// Window failed to initialize
|
||||
if(event.data[0] === "WINDOW_ERROR" || event.data[1][0] === type) {
|
||||
reject(event.data[1]);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
channel.removeEventListener("message",ack);
|
||||
});
|
||||
}
|
||||
|
||||
async play() {
|
||||
for(const [key,player] of Object.entries(this.players)) {
|
||||
const manifest = new URL(window.location.href + `monkeydo_${key}.json`);
|
||||
|
||||
await player.load(manifest.toString());
|
||||
}
|
||||
this.players.lyrics.play();
|
||||
this.mediaElement.play();
|
||||
}
|
||||
|
||||
// Open player windows and start playback
|
||||
async init() {
|
||||
const art = this.spawnPlayer("#art");
|
||||
const credits = this.spawnPlayer("#credits");
|
||||
const lyrics = this.spawnPlayer("#lyrics");
|
||||
|
||||
const timeout = new Promise(resolve => setTimeout(() => resolve("TIMEOUT")),3000);
|
||||
const windows = await Promise.allSettled([lyrics,credits,art]);
|
||||
|
||||
// Wait for all windows to open and initialize (or timout and fail)
|
||||
const status = Promise.race([windows,timeout]);
|
||||
status.then(promises => {
|
||||
promises.forEach(promiseObject => {
|
||||
if(promiseObject.status !== "fulfilled") {
|
||||
this.playbackFailed(promiseObject);
|
||||
}
|
||||
});
|
||||
|
||||
// Load Monkeydo manifest and start playback
|
||||
this.play();
|
||||
});
|
||||
// Window blocked from opening, show "allow popups" message
|
||||
windowOpenFailed() {
|
||||
// Close all opened windows (some browsers allow one window to open)
|
||||
//for(const player of Object.values(this.players)) {
|
||||
// player.window?.close();
|
||||
//}
|
||||
console.log("failed to open a window");
|
||||
}
|
||||
|
||||
message(event) {
|
||||
const type = event.data[0];
|
||||
const data = event.data[1];
|
||||
|
||||
switch(type) {
|
||||
case "PLAY": console.log("PLAY",event); break;
|
||||
case "WINDOW_CLOSED": console.log("WINDOW_CLOSED",event); break;
|
||||
}
|
||||
console.log(event);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
const windowPositions = {
|
||||
"#lyrics": {
|
||||
"lyrics": {
|
||||
width: window.innerWidth / 2,
|
||||
height: window.innerHeight,
|
||||
top: 0,
|
||||
left: 0
|
||||
},
|
||||
"#credits": {
|
||||
"credits": {
|
||||
width: window.innerWidth / 2,
|
||||
height: window.innerHeight / 2,
|
||||
top: 0,
|
||||
left: window.innerWidth / 2
|
||||
},
|
||||
"#art": {
|
||||
"art": {
|
||||
width: window.innerWidth / 2,
|
||||
height: window.innerHeight / 2,
|
||||
top: window.innerHeight / 2,
|
||||
|
@ -20,7 +20,7 @@ const windowPositions = {
|
|||
}
|
||||
|
||||
export default class PlayerWindow {
|
||||
constructor(name) {
|
||||
constructor(name,manifest = null) {
|
||||
this.features = {
|
||||
menubar: false,
|
||||
location: false,
|
||||
|
@ -28,16 +28,20 @@ export default class PlayerWindow {
|
|||
scrollbar: false,
|
||||
status: false
|
||||
}
|
||||
this.name = name;
|
||||
this.window = null;
|
||||
|
||||
this.url = new URL(window.location.href + "player");
|
||||
this.url.hash = name;
|
||||
|
||||
if(manifest) this.url.searchParams.append("manifest",manifest);
|
||||
|
||||
// Copy window size rect into windowFeatures
|
||||
Object.assign(this.features,windowPositions[this.url.hash]);
|
||||
}
|
||||
|
||||
// Convert windowFeatures object into a CSV DOMString
|
||||
windowFeatures() {
|
||||
getWindowFeatures() {
|
||||
let output = [];
|
||||
for(let [key,value] of Object.entries(this.features)) {
|
||||
if(typeof key === "boolean") {
|
||||
|
@ -48,14 +52,12 @@ export default class PlayerWindow {
|
|||
return output.join(",");
|
||||
}
|
||||
|
||||
// Compile windowFeatures and open the window
|
||||
open() {
|
||||
const features = this.windowFeatures();
|
||||
const open = window.open(this.url.toString(),this.url.hash,features);
|
||||
const features = this.getWindowFeatures();
|
||||
this.window = window.open(this.url.toString(),this.name,features);
|
||||
|
||||
// Window failed to open (usually due to pop-up blocking), tell the WindowManager about this
|
||||
if(!open) {
|
||||
const channel = new BroadcastChannel(this.url.hash);
|
||||
channel.postMessage(["WINDOW_ERROR",[this.url.hash,"BLOCKED"]]);
|
||||
}
|
||||
// Will return null if window failed to open (usually due to popup blocking)
|
||||
return this.window;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Encoded in order from: https://blog.kazitor.com/2014/12/portal-ascii/
|
||||
import { default as Monkeydo } from "./monkeydo/Monkeydo.mjs";
|
||||
|
||||
// URIEncoded ASCII art
|
||||
const artset = [
|
||||
"%20%20%20%20%20%20%20%20%20%20%20%20%20.%2C-%3A%3B%2F%2F%3B%3A%3D%2C%0A%20%20%20%20%20%20%20%20%20.%20%3AH%40%40%40MM%40M%23H%2F.%2C%2B%25%3B%2C%0A%20%20%20%20%20%20%2C%2FX%2B%20%2BM%40%40M%40MM%25%3D%2C-%25HMMM%40X%2F%2C%0A%20%20%20%20%20-%2B%40MM%3B%20%24M%40%40MH%2B-%2C%3BXMMMM%40MMMM%40%2B-%0A%20%20%20%20%3B%40M%40%40M-%20XM%40X%3B.%20-%2BXXXXXHHH%40M%40M%23%40%2F.%0A%20%20%2C%25MM%40%40MH%20%2C%40%25%3D%20%20%20%20%20%20%20%20%20%20%20%20.---%3D-%3D%3A%3D%2C.%0A%20%20-%40%23%40%40%40MX%20.%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%25HX%24%24%25%25%25%2B%3B%0A%20%3D-.%2F%40M%40M%24%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.%3B%40MMMM%40MM%3A%0A%20X%40%2F%20-%24MM%2F%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.%2BMM%40%40%40M%24%0A%2C%40M%40H%3A%20%3A%40%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.%20-X%23%40%40%40%40-%0A%2C%40%40%40MMX%2C%20.%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2FH-%20%3B%40M%40M%3D%0A.H%40%40%40%40M%40%2B%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%25MM%2B..%25%23%24.%0A%20%2FMMMM%40MMH%2F.%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20XM%40MH%3B%20-%3B%0A%20%20%2F%25%2B%25%24XHH%40%24%3D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2C%20.H%40%40%40%40MX%2C%0A%20%20%20.%3D--------.%20%20%20%20%20%20%20%20%20%20%20-%25H.%2C%40%40%40%40%40MX%2C%0A%20%20%20.%25MM%40%40%40HHHXX%24%24%24%25%2B-%20.%3A%24MMX%20-M%40%40MM%25.%0A%20%20%20%20%20%3DXMMM%40MM%40MM%23H%3B%2C-%2BHMM%40M%2B%20%2FMMMX%3D%0A%20%20%20%20%20%20%20%3D%25%40M%40M%23%40%24-.%3D%24%40MM%40%40%40M%3B%20%25M%25%3D%0A%20%20%20%20%20%20%20%20%20%2C%3A%2B%24%2B-%2C%2FH%23MMMMMMM%40-%20-%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%2B%2B%25%25%25%25%2B%2F%3A-.",
|
||||
"%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%2B%24HM%23%23%23%23%40H%25%3B%2C%0A%20%20%20%20%20%20%20%20%20%20%2FH%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23M%24%2C%0A%20%20%20%20%20%20%20%20%20%20%2C%40%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%2B%0A%20%20%20%20%20%20%20%20%20%20%20.H%23%23%23%23%23%23%23%23%23%23%23%23%23%23%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20X%23%23%23%23%23%23%23%23%23%23%23%23%2F%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24%23%23%23%23%23%23%23%23%23%23%2F%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%25%23%23%23%23%23%23%23%23%2F%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2FX%2F%3B%3B%2BX%2F%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-XHHX-%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2C%23%23%23%23%23%23%2C%0A%23%23%23%23%23%23%23%23%23%23%23%23%23X%20%20.M%23%23%23%23M.%20%20X%23%23%23%23%23%23%23%23%23%23%23%23%23%0A%23%23%23%23%23%23%23%23%23%23%23%23%23%23-%20%20%20-%2F%2F-%20%20%20-%23%23%23%23%23%23%23%23%23%23%23%23%23%23%0AX%23%23%23%23%23%23%23%23%23%23%23%23%23%23%25%2C%20%20%20%20%20%20%2C%2B%23%23%23%23%23%23%23%23%23%23%23%23%23%23X%0A-%23%23%23%23%23%23%23%23%23%23%23%23%23%23X%20%20%20%20%20%20%20%20X%23%23%23%23%23%23%23%23%23%23%23%23%23%23-%0A%20%25%23%23%23%23%23%23%23%23%23%23%23%23%25%20%20%20%20%20%20%20%20%20%20%25%23%23%23%23%23%23%23%23%23%23%23%23%25%0A%20%20%25%23%23%23%23%23%23%23%23%23%23%3B%20%20%20%20%20%20%20%20%20%20%20%20%3B%23%23%23%23%23%23%23%23%23%23%25%0A%20%20%20%3B%23%23%23%23%23%23%23M%3D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3DM%23%23%23%23%23%23%23%3B%0A%20%20%20%20.%2BM%23%23%23%40%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2C%40%23%23%23M%2B.%0A%20%20%20%20%20%20%20%3AXH.%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.HX%3A",
|
||||
|
@ -13,39 +15,70 @@ const artset = [
|
|||
];
|
||||
|
||||
export default class StillAlivePlayer {
|
||||
constructor(element) {
|
||||
this.channel = new BroadcastChannel(window.location.hash);
|
||||
constructor(element,name) {
|
||||
const self = this;
|
||||
|
||||
this.name = name;
|
||||
|
||||
// Open a BroadcastChannel to listen to messages for me
|
||||
this.channel = new BroadcastChannel(this.name);
|
||||
this.channel.addEventListener("message",event => this.message(event));
|
||||
|
||||
this.player = element;
|
||||
|
||||
this.channel.postMessage(["WINDOW_READY",window.location.hash]);
|
||||
}
|
||||
|
||||
// Clear the screen from elements
|
||||
blank() {
|
||||
while(this.player.firstChild) {
|
||||
this.player.removeChild(this.player.lastChild);
|
||||
// Monkeydo methods
|
||||
const methods = {
|
||||
// Clear the screen from elements
|
||||
blank: () => {
|
||||
while(this.player.firstChild) {
|
||||
this.player.removeChild(this.player.lastChild);
|
||||
}
|
||||
},
|
||||
// Create a new paragraph and make it the target for textFeed calls
|
||||
lineFeed: () => {
|
||||
this.target = document.createElement("p");
|
||||
this.player.appendChild(this.target);
|
||||
},
|
||||
// Append text to the current target element
|
||||
textFeed: (text) => {
|
||||
this.target.innerText = this.target.innerText + text;
|
||||
},
|
||||
// Decode and draw art from artset by key
|
||||
drawArt: (index) => {
|
||||
this.blank();
|
||||
self.target = document.createElement("pre");
|
||||
self.target.innerText = window.decodeURIComponent(artset[key]);
|
||||
self.player.appendChild(self.target);
|
||||
},
|
||||
playCredits: () => {
|
||||
self.players.credits.play();
|
||||
}
|
||||
}
|
||||
|
||||
// Execute or relay Monkeydo methods
|
||||
const proxiedMethods = this.getMethods(methods);
|
||||
|
||||
console.log(proxiedMethods.lineFeed("lyrics"));
|
||||
this.player = element;
|
||||
}
|
||||
|
||||
// Create a new paragraph and make it the target for textFeed calls
|
||||
lineFeed() {
|
||||
this.target = document.createElement("p");
|
||||
this.player.appendChild(this.target);
|
||||
getMethods(methods) {
|
||||
const handler = {
|
||||
get(target,propKey,receiver) {
|
||||
const origMethod = target[propKey];
|
||||
return function (...args) {
|
||||
console.log(this);
|
||||
let result = origMethod.apply(this, args);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
};
|
||||
return new Proxy(methods,handler);
|
||||
}
|
||||
|
||||
// Append text to the current target element
|
||||
textFeed(text) {
|
||||
this.target.innerText = this.target.innerText + text;
|
||||
}
|
||||
|
||||
// Decode and draw art from artset by key
|
||||
drawArt(key) {
|
||||
this.blank();
|
||||
this.target = document.createElement("pre");
|
||||
this.target.innerText = window.decodeURIComponent(artset[key]);
|
||||
this.player.appendChild(this.target);
|
||||
// Open a channel to a different player to relay a task
|
||||
relay(channelName,message) {
|
||||
const channel = new BroadcastChannel(channelName);
|
||||
channel.postMessage(message);
|
||||
channel.close();
|
||||
}
|
||||
|
||||
message(event) {
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
import { default as Player } from "./modules/StillAlivePlayer.mjs";
|
||||
|
||||
// Go to start page if location.hash is omitted
|
||||
if(!window.location.hash) {
|
||||
// Close this window, if we can
|
||||
window.close();
|
||||
|
||||
// Otherwise redirect to main page
|
||||
if(typeof BroadcastChannel !== "function" || !window.location.hash) {
|
||||
close();
|
||||
const page = window.location.pathname.split("/");
|
||||
const url = window.location.href.replace(page[page.length - 1],"");
|
||||
window.location.replace(url);
|
||||
}
|
||||
};
|
||||
|
||||
// Add location.hash to body classList
|
||||
document.body.classList.add(window.location.hash.substring(1));
|
||||
const name = window.location.hash.substring(1);
|
||||
document.body.className = name;
|
||||
|
||||
const element = document.getElementById("player");
|
||||
const player = new Player(element);
|
||||
const target = document.getElementById("player");
|
||||
new Player(target,name);
|
|
@ -2,20 +2,11 @@ import { default as Player } from "./modules/PlayerManager.mjs";
|
|||
|
||||
const play = document.getElementById("play");
|
||||
|
||||
try {
|
||||
if(typeof BroadcastChannel !== "function") {
|
||||
throw new Error("BroadcastChannel API is not supported");
|
||||
}
|
||||
|
||||
const mediaElement = document.getElementById("still-alive");
|
||||
|
||||
const player = new Player(mediaElement);
|
||||
play.addEventListener("click",() => player.init());
|
||||
} catch(error) {
|
||||
if(typeof BroadcastChannel !== "function") {
|
||||
const message = document.getElementById("message");
|
||||
|
||||
play.classList.add("unsupported");
|
||||
play.innerText = "Your browser can not play this demo";
|
||||
|
||||
message.innerText = error;
|
||||
}
|
||||
}
|
||||
|
||||
play.addEventListener("click",() => new Player());
|
Loading…
Add table
Reference in a new issue