mirror of
https://codeberg.org/vlw/still-alive.git
synced 2025-09-14 00:13:41 +02:00
Fix Monkeydo events and #3
Monkeydo events will now be executed through the Proxy created in 8b659066cc
This commit also implements a solution for #3 by having each window report to its PlayerManager when it has been closed.
This commit is contained in:
parent
8b659066cc
commit
7a6ed67d92
8 changed files with 1784 additions and 1702 deletions
|
@ -3,9 +3,9 @@ import { default as Player } from "./PlayerWindow.mjs";
|
||||||
export default class PlayerManager {
|
export default class PlayerManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.players = {
|
this.players = {
|
||||||
"lyrics": new Player("lyrics","monkeydo_lyrics.json")//,
|
"lyrics": new Player("lyrics","monkeydo_lyrics.json"),
|
||||||
//"credits": new Player("credits","monkeydo_credits.json"),
|
"credits": new Player("credits","monkeydo_credits.json"),
|
||||||
//"art": new Player("art")
|
"art": new Player("art")
|
||||||
};
|
};
|
||||||
|
|
||||||
this.channels = new WeakMap();
|
this.channels = new WeakMap();
|
||||||
|
@ -20,16 +20,20 @@ export default class PlayerManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close all opened windows (some browsers allow one window to open)
|
||||||
|
closeAll() {
|
||||||
|
for(const player of Object.values(this.players)) {
|
||||||
|
player.window?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Window blocked from opening, show "allow popups" message
|
// Window blocked from opening, show "allow popups" message
|
||||||
windowOpenFailed() {
|
windowOpenFailed() {
|
||||||
// Close all opened windows (some browsers allow one window to open)
|
this.closeAll();
|
||||||
//for(const player of Object.values(this.players)) {
|
throw new Error("WINDOW_OPEN_FAILED");
|
||||||
// player.window?.close();
|
|
||||||
//}
|
|
||||||
console.log("failed to open a window");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message(event) {
|
message(event) {
|
||||||
console.log(event);
|
if(event.data === "PLAYER_CLOSED") return this.closeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -34,16 +34,18 @@ export default class PlayerWindow {
|
||||||
this.url = new URL(window.location.href + "player");
|
this.url = new URL(window.location.href + "player");
|
||||||
this.url.hash = name;
|
this.url.hash = name;
|
||||||
|
|
||||||
|
// Path to Monkeydo manifest to load in this window
|
||||||
if(manifest) this.url.searchParams.append("manifest",manifest);
|
if(manifest) this.url.searchParams.append("manifest",manifest);
|
||||||
|
|
||||||
// Copy window size rect into windowFeatures
|
// Copy window size rect into windowFeatures
|
||||||
Object.assign(this.features,windowPositions[this.url.hash]);
|
Object.assign(this.features,windowPositions[name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert windowFeatures object into a CSV DOMString
|
// Convert windowFeatures object into a CSV DOMString
|
||||||
getWindowFeatures() {
|
getWindowFeatures() {
|
||||||
let output = [];
|
let output = [];
|
||||||
for(let [key,value] of Object.entries(this.features)) {
|
for(let [key,value] of Object.entries(this.features)) {
|
||||||
|
// Coercive boolean
|
||||||
if(typeof key === "boolean") {
|
if(typeof key === "boolean") {
|
||||||
value = value ? "yes" : "no";
|
value = value ? "yes" : "no";
|
||||||
}
|
}
|
||||||
|
@ -52,6 +54,11 @@ export default class PlayerWindow {
|
||||||
return output.join(",");
|
return output.join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close this window
|
||||||
|
close() {
|
||||||
|
return this.window?.close() ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
// Compile windowFeatures and open the window
|
// Compile windowFeatures and open the window
|
||||||
open() {
|
open() {
|
||||||
const features = this.getWindowFeatures();
|
const features = this.getWindowFeatures();
|
||||||
|
|
|
@ -19,6 +19,7 @@ export default class StillAlivePlayer {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.player = element;
|
||||||
|
|
||||||
// Open a BroadcastChannel to listen to messages for me
|
// Open a BroadcastChannel to listen to messages for me
|
||||||
this.channel = new BroadcastChannel(this.name);
|
this.channel = new BroadcastChannel(this.name);
|
||||||
|
@ -28,45 +29,98 @@ export default class StillAlivePlayer {
|
||||||
const methods = {
|
const methods = {
|
||||||
// Clear the screen from elements
|
// Clear the screen from elements
|
||||||
blank: () => {
|
blank: () => {
|
||||||
while(this.player.firstChild) {
|
while(self.firstChild) {
|
||||||
this.player.removeChild(this.player.lastChild);
|
self.removeChild(self.lastChild);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Create a new paragraph and make it the target for textFeed calls
|
// Create a new paragraph and make it the target for textFeed calls
|
||||||
lineFeed: () => {
|
lineFeed: () => {
|
||||||
this.target = document.createElement("p");
|
self.target = document.createElement("p");
|
||||||
this.player.appendChild(this.target);
|
self.player.appendChild(self.target);
|
||||||
},
|
},
|
||||||
// Append text to the current target element
|
// Append text to the current target element
|
||||||
textFeed: (text) => {
|
textFeed: (text) => {
|
||||||
this.target.innerText = this.target.innerText + text;
|
self.target.innerText = self.target.innerText + text;
|
||||||
},
|
},
|
||||||
// Decode and draw art from artset by key
|
// Decode and draw art from artset by index key
|
||||||
drawArt: (index) => {
|
drawArt: (idx) => {
|
||||||
this.blank();
|
this.blank();
|
||||||
self.target = document.createElement("pre");
|
self.target = document.createElement("pre");
|
||||||
self.target.innerText = window.decodeURIComponent(artset[key]);
|
self.target.innerText = window.decodeURIComponent(artset[idx]);
|
||||||
self.player.appendChild(self.target);
|
self.player.appendChild(self.target);
|
||||||
},
|
},
|
||||||
playCredits: () => {
|
play: () => {
|
||||||
self.players.credits.play();
|
if(!self.monkey) return console.warn("This player has no manifest loaded");
|
||||||
|
self.monkey.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute or relay Monkeydo methods
|
// Execute or relay Monkeydo methods
|
||||||
const proxiedMethods = this.getMethods(methods);
|
const proxiedMethods = this.getMethods(methods);
|
||||||
|
|
||||||
console.log(proxiedMethods.lineFeed("lyrics"));
|
// Monkeydo
|
||||||
this.player = element;
|
const manifest = this.getManifest();
|
||||||
|
if(manifest) {
|
||||||
|
this.monkey = new Monkeydo(proxiedMethods);
|
||||||
|
this.monkey.load(manifest).then(() => {
|
||||||
|
// Start playback of the first manifest
|
||||||
|
if(this.name === "lyrics") this.host();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get parent directory of this file
|
||||||
|
getParentURL() {
|
||||||
|
let pathname = window.location.pathname.split("/");
|
||||||
|
pathname.pop();
|
||||||
|
pathname = pathname[pathname.length - 1];
|
||||||
|
return pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This player is the "host", it will play the music and send events to other players
|
||||||
|
host() {
|
||||||
|
const parent = this.getParentURL();
|
||||||
|
const pathname = [parent,"assets","media","still-alive.webm"].join("/");
|
||||||
|
|
||||||
|
const musicURL = new URL(window.location.origin);
|
||||||
|
musicURL.pathname = pathname;
|
||||||
|
|
||||||
|
// Load and play the Still Alive when ready
|
||||||
|
const stillalive = new Audio(musicURL.toString());
|
||||||
|
stillalive.addEventListener("canplaythrough",() => {
|
||||||
|
this.monkey.play(); // Start the visuals
|
||||||
|
stillalive.play(); // Start the music
|
||||||
|
},{ once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
getManifest() {
|
||||||
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
if(!searchParams.has("manifest")) return false;
|
||||||
|
|
||||||
|
// Relative pathname to manifest JSON
|
||||||
|
const parent = this.getParentURL();
|
||||||
|
const pathname = [parent,searchParams.get("manifest")].join("/");
|
||||||
|
|
||||||
|
// Create a URL to the manifest
|
||||||
|
const manifest = new URL(window.location.origin);
|
||||||
|
manifest.pathname = pathname;
|
||||||
|
|
||||||
|
return manifest.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
getMethods(methods) {
|
getMethods(methods) {
|
||||||
const handler = {
|
const handler = {
|
||||||
|
name: this.name,
|
||||||
|
relay: (...args) => this.relay(...args),
|
||||||
|
// Get and call methods from object
|
||||||
get(target,propKey,receiver) {
|
get(target,propKey,receiver) {
|
||||||
const origMethod = target[propKey];
|
const origMethod = target[propKey];
|
||||||
return function (...args) {
|
return function (...args) {
|
||||||
console.log(this);
|
// Relay function call if it's not for me
|
||||||
let result = origMethod.apply(this, args);
|
const channel = args[args.length - 1] ?? null;
|
||||||
|
if(channel && channel !== handler.name) return handler.relay(args);
|
||||||
|
|
||||||
|
let result = origMethod.apply(this,args);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -74,7 +128,7 @@ export default class StillAlivePlayer {
|
||||||
return new Proxy(methods,handler);
|
return new Proxy(methods,handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a channel to a different player to relay a task
|
// Open a channel to a different player to forward a task
|
||||||
relay(channelName,message) {
|
relay(channelName,message) {
|
||||||
const channel = new BroadcastChannel(channelName);
|
const channel = new BroadcastChannel(channelName);
|
||||||
channel.postMessage(message);
|
channel.postMessage(message);
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 22eda9780099b77bfd755f214534bba24a881894
|
Subproject commit 7ca095a0d7a91aff50be42efb380fdbda05b2320
|
|
@ -11,4 +11,7 @@ const name = window.location.hash.substring(1);
|
||||||
document.body.className = name;
|
document.body.className = name;
|
||||||
|
|
||||||
const target = document.getElementById("player");
|
const target = document.getElementById("player");
|
||||||
new Player(target,name);
|
const player = new Player(target,name);
|
||||||
|
|
||||||
|
// Notify PlayerManager that I was closed
|
||||||
|
window.addEventListener("beforeunload",() => player.channel.postMessage("PLAYER_CLOSED"));
|
|
@ -2,11 +2,24 @@ import { default as Player } from "./modules/PlayerManager.mjs";
|
||||||
|
|
||||||
const play = document.getElementById("play");
|
const play = document.getElementById("play");
|
||||||
|
|
||||||
if(typeof BroadcastChannel !== "function") {
|
function info(text = "") {
|
||||||
const message = document.getElementById("message");
|
const element = document.getElementById("message");
|
||||||
play.classList.add("unsupported");
|
element.innerText = text;
|
||||||
play.innerText = "Your browser can not play this demo";
|
|
||||||
message.innerText = error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
play.addEventListener("click",() => new Player());
|
if(typeof BroadcastChannel !== "function") {
|
||||||
|
play.classList.add("unsupported");
|
||||||
|
info("Your browser can not play this demo");
|
||||||
|
}
|
||||||
|
|
||||||
|
play.addEventListener("click",() => {
|
||||||
|
try {
|
||||||
|
info();
|
||||||
|
new Player();
|
||||||
|
}
|
||||||
|
catch(except) {
|
||||||
|
if(except.message === "WINDOW_OPEN_FAILED") return info("Yikes! Player windows could not be created. Allow this page to open pop-ups, it will be worth it!");
|
||||||
|
// Cannot handle this exception, rethrow
|
||||||
|
throw except;
|
||||||
|
}
|
||||||
|
});
|
|
@ -7,6 +7,7 @@
|
||||||
<meta name="description" content="End credits from 'Portal' - the video game, recreated with JS and browser windows.">
|
<meta name="description" content="End credits from 'Portal' - the video game, recreated with JS and browser windows.">
|
||||||
<title>Still Alive</title>
|
<title>Still Alive</title>
|
||||||
<link rel="stylesheet" href="assets/css/style.css">
|
<link rel="stylesheet" href="assets/css/style.css">
|
||||||
|
<style>body { text-align: center; }</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
|
|
3336
monkeydo_lyrics.json
3336
monkeydo_lyrics.json
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue