Skip to content

Commit d5f789e

Browse files
committed
Add autoplay URL param and Copy share link button
A presence-only ?play param triggers playback on page load. Modern browsers block AudioContext.resume outside of a user gesture, so the implementation tries immediately AND attaches a one-time fallback on the first click or keypress, with the fallback no-opping if audio is already running. The flag has to be captured before refresh runs, since refresh calls updateUrlParams which strips presence-only params via replaceState. The new Copy share link button in the transport row composes the current URL and appends play, copies it to the clipboard, and shows brief Copied feedback. Recipients land on the same envelope and sound with autoplay queued.
1 parent ea3569a commit d5f789e

4 files changed

Lines changed: 53 additions & 3 deletions

File tree

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ <h2>Visualisation</h2>
4646
<div class="transport">
4747
<button id="play">Play</button>
4848
<button id="stop">Stop</button>
49+
<button id="share-link" title="Copy a shareable URL with auto-play that reproduces the current envelope and sound">Copy share link</button>
4950
<button id="run-emulator" title="Open bbc.xania.org with the current ENVELOPE and SOUND auto-running">Run in BBC emulator ↗</button>
5051
</div>
5152
<h3>Presets</h3>

src/audio.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ export function stop(): void {
7272
playDuration = null;
7373
}
7474

75+
/** True if the AudioContext exists and is actively producing audio. */
76+
export function audioContextIsRunning(): boolean {
77+
return ctx?.state === "running";
78+
}
79+
7580
/**
7681
* Fraction (0..1) of the way through the currently-playing note, or null
7782
* when nothing is playing. Used to drive the visualiser playhead.

src/main.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type Envelope,
1010
} from "./envelope";
1111
import { render } from "./visualizer";
12-
import { play, playheadFraction, stop } from "./audio";
12+
import { audioContextIsRunning, play, playheadFraction, stop } from "./audio";
1313
import { PRESETS, type Preset } from "./presets";
1414

1515
interface FieldSpec {
@@ -61,6 +61,9 @@ const SOUND_FIELDS: { key: keyof SoundParams; code: string; label: string; min:
6161
const env: Envelope = { ...DEFAULT_ENVELOPE };
6262
const sound: SoundParams = { ...DEFAULT_SOUND };
6363

64+
// Capture before refresh() rewrites the URL via history.replaceState (which
65+
// strips presence-only params like `play`).
66+
const autoplayRequested = new URLSearchParams(location.search).has("play");
6467
loadFromUrlParams();
6568

6669
const canvas = document.getElementById("viz") as HTMLCanvasElement;
@@ -311,6 +314,25 @@ document.getElementById("stop")!.addEventListener("click", () => {
311314
render(canvas, currentSamples, sound.pitch, null);
312315
});
313316

317+
document.getElementById("share-link")!.addEventListener("click", async (e) => {
318+
// Build a URL that reproduces current state and triggers autoplay on land.
319+
// location.search already has the live env/sound params (refresh keeps it
320+
// in sync); we just append the presence-only `play`.
321+
const search = location.search ? `${location.search}&play` : "?play";
322+
const shareUrl = `${location.origin}${location.pathname}${search}`;
323+
const btn = e.currentTarget as HTMLButtonElement;
324+
const originalText = btn.textContent;
325+
try {
326+
await navigator.clipboard.writeText(shareUrl);
327+
btn.textContent = "Copied!";
328+
} catch {
329+
// Fallback: prompt so the user can copy manually.
330+
window.prompt("Share URL:", shareUrl);
331+
return;
332+
}
333+
setTimeout(() => { btn.textContent = originalText; }, 1500);
334+
});
335+
314336
document.getElementById("run-emulator")!.addEventListener("click", () => {
315337
// jsbeeb (bbc.xania.org) takes URL-encoded BASIC via embedBasic, with
316338
// &autorun to type RUN after tokenising. Two numbered lines are enough:
@@ -338,3 +360,23 @@ for (const btn of document.querySelectorAll<HTMLButtonElement>(".copy-btn")) {
338360
}
339361

340362
refresh();
363+
364+
// `?play` (presence-only) starts playback automatically. Modern browsers
365+
// block AudioContext.resume() outside of a user gesture, so we both try
366+
// immediately AND attach a one-time fallback that fires on the first user
367+
// interaction. The fallback no-ops if the immediate attempt already kicked
368+
// audio into the running state.
369+
if (autoplayRequested) {
370+
play(currentSamples, sound.pitch);
371+
animatePlayhead();
372+
const playOnGesture = (): void => {
373+
if (!audioContextIsRunning()) {
374+
play(currentSamples, sound.pitch);
375+
animatePlayhead();
376+
}
377+
document.removeEventListener("click", playOnGesture);
378+
document.removeEventListener("keydown", playOnGesture);
379+
};
380+
document.addEventListener("click", playOnGesture);
381+
document.addEventListener("keydown", playOnGesture);
382+
}

src/style.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ code { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; }
113113
}
114114
.field input:focus { outline: 1px solid #58a6ff; }
115115

116-
#play, #stop, #run-emulator {
116+
#play, #stop, #share-link, #run-emulator {
117117
color: white;
118118
border: none;
119119
border-radius: 4px;
@@ -126,7 +126,9 @@ code { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; }
126126
#play:hover { background: #2ea043; }
127127
#stop { background: #6e7681; }
128128
#stop:hover { background: #8b949e; }
129-
#run-emulator { background: #1f6feb; margin-left: auto; margin-right: 0; }
129+
#share-link { background: #30363d; margin-left: auto; }
130+
#share-link:hover { background: #484f58; }
131+
#run-emulator { background: #1f6feb; margin-right: 0; }
130132
#run-emulator:hover { background: #388bfd; }
131133

132134
.basic-row {

0 commit comments

Comments
 (0)