window.devpageVideo = (() => {
const TYPE = "video/mp4;codecs=avc1,mp4a.40.2";
const WORKLET = `
class Player extends AudioWorkletProcessor {
constructor() {
super();
this.queue = []; this.offset = 0; this.empty = true;
this.port.onmessage = (e) => { this.queue.push(e.data); this.empty = false; };
}
process(_, outputs) {
const out = outputs[0][0];
for (let i = 0; i < out.length; i++) {
const head = this.queue[0];
if (!head) { out[i] = 0; continue; }
out[i] = head[this.offset++];
if (this.offset >= head.length) { this.queue.shift(); this.offset = 0; }
}
if (!this.queue.length && !this.empty) { this.empty = true; this.port.postMessage("empty"); }
return true;
}
}
registerProcessor("player", Player);`;
let screen, context, player, stream, recorder, chunks, done, recording;
let empty = true;
let emptied = () => {};
const events = [];
return {
async start(rate) {
const style = document.createElement("style");
style.textContent = "#devpage-corner, #devpage-comments-toggle { display: none !important }";
document.head.append(style);
const pixel = document.createElement("div");
pixel.style.cssText =
"position:fixed;left:0;top:0;width:1px;height:1px;pointer-events:none;z-index:2147483647";
document.body.append(pixel);
let tick = 0;
const repaint = () => {
pixel.style.opacity = tick++ % 2 ? "0.01" : "0.02";
requestAnimationFrame(repaint);
};
repaint();
screen = await navigator.mediaDevices.getDisplayMedia({
video: { frameRate: 30, width: 1920, height: 1080 },
audio: false,
preferCurrentTab: true,
});
screen.getVideoTracks()[0].onended = () => events.push("track ended");
context = new AudioContext({ sampleRate: rate });
const module = URL.createObjectURL(new Blob([WORKLET], { type: "text/javascript" }));
await context.audioWorklet.addModule(module);
player = new AudioWorkletNode(context, "player");
const voice = context.createMediaStreamDestination();
player.connect(voice);
player.port.onmessage = (e) => {
if (e.data !== "empty") return;
empty = true;
emptied();
};
stream = new MediaStream([...screen.getVideoTracks(), ...voice.stream.getAudioTracks()]);
},
size() {
const track = screen?.getVideoTracks()[0].getSettings() ?? {};
return [innerWidth, innerHeight, track.width ?? 0, track.height ?? 0];
},
async arm() {
for (let attempt = 1; attempt <= 5; attempt++) {
recorder = new MediaRecorder(stream, {
mimeType: TYPE,
videoBitsPerSecond: 6_000_000,
audioBitsPerSecond: 160_000,
});
chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
let failed = false;
recorder.onerror = (e) => {
failed = true;
events.push("error " + e.error?.name + " " + e.error?.message);
};
const these = chunks;
done = new Promise((r) => (recorder.onstop = () => r(new Blob(these, { type: TYPE }))));
recorder.start(1000);
await new Promise((r) => setTimeout(r, 700));
if (!failed && recorder.state === "recording") {
recorder.pause();
return attempt;
}
if (recorder.state !== "inactive") recorder.stop();
}
throw new Error("the recorder never started: " + events.join("; "));
},
push(base64) {
const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
const pcm = new Int16Array(bytes.buffer, 0, bytes.length >> 1);
const data = new Float32Array(pcm.length);
for (let i = 0; i < pcm.length; i++) data[i] = pcm[i] / 32768;
empty = false;
player.port.postMessage(data, [data.buffer]);
},
play() {
recorder.resume();
},
pause() {
recorder.pause();
},
drain() {
return new Promise((r) => {
emptied = () => {
emptied = () => {};
r(null);
};
if (empty) emptied();
setTimeout(
() => r({ stuck: true, audio: context.state, recorder: recorder.state, events }),
8000,
);
});
},
async stop() {
recorder.stop();
screen.getTracks().forEach((t) => t.stop());
recording = new Uint8Array(await (await done).arrayBuffer());
return recording.length;
},
chunk(at, length) {
const bytes = recording.subarray(at, at + length);
let s = "";
for (let i = 0; i < bytes.length; i += 0x8000) {
s += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
}
return btoa(s);
},
shown() {
return [...document.querySelectorAll(".slide.shown")].map((s) => s.dataset.slide).join(",");
},
};
})();