fixed API and stopping delay
This commit is contained in:
@@ -272,11 +272,20 @@ function handleWs(msg) {
|
||||
if (msg.you?.kind && msg.you.kind !== 'display') {
|
||||
state.np.error = `This room already has a display (${countDisplays(msg.peers)} active). You were joined as ${msg.you.kind}.`;
|
||||
}
|
||||
// Resume room state when (re-)connecting: play whatever the room
|
||||
// thinks is current, unless we're already on it.
|
||||
// Resume room state when (re-)connecting: only auto-start audio
|
||||
// if the room was actually playing — a user who hit Stop and
|
||||
// reloaded should NOT get audio back. We still adopt the station
|
||||
// metadata for the card so the UI shows what's "selected".
|
||||
const rs = msg.state;
|
||||
if (rs?.station_id && rs.station && rs.station_id !== state.np.stationId) {
|
||||
playStation(rs.station, { silent: true });
|
||||
if (rs.playing) {
|
||||
playStation(rs.station, { silent: true });
|
||||
} else {
|
||||
state.np.station = rs.station;
|
||||
state.np.stationId = rs.station_id;
|
||||
state.np.playing = false;
|
||||
state.np.loading = false;
|
||||
}
|
||||
}
|
||||
if (typeof rs?.volume === 'number') {
|
||||
player.setVolume(rs.volume);
|
||||
@@ -321,12 +330,12 @@ function handleCommand(msg) {
|
||||
case 'play': {
|
||||
const id = Number(msg.stationId);
|
||||
if (!Number.isFinite(id)) return;
|
||||
// Idempotency: ignore a play for the station already current OR
|
||||
// already pending. Without these guards a flood of commands (e.g.
|
||||
// multiple kiosks racing, or repeated clicks) would call
|
||||
// playStation() in a loop, each one doing stop()+play() and
|
||||
// producing audible start/stop thrashing.
|
||||
if (id === state.np.stationId) return;
|
||||
// Idempotency: only skip if we're already on this station AND
|
||||
// audio is actually playing or loading it. If a previous play
|
||||
// attempt failed (gesture refused, resolve error), state.np was
|
||||
// cleared by clearNowPlaying() so this guard correctly lets the
|
||||
// retry through.
|
||||
if (id === state.np.stationId && (state.np.playing || state.np.loading)) return;
|
||||
if (id === _pendingStationId) return;
|
||||
_pendingStationId = id;
|
||||
const gen = ++_cmdGen;
|
||||
@@ -345,11 +354,16 @@ function handleCommand(msg) {
|
||||
if (state.np.playing) player.togglePause();
|
||||
return;
|
||||
case 'stop':
|
||||
if (!state.np.stationId) return;
|
||||
player.stop();
|
||||
// Always idempotent: even if local state thinks nothing is
|
||||
// playing, the command may be a retry from a kiosk that saw a
|
||||
// stale "playing" — stop unconditionally so the room converges.
|
||||
try { player.stop(); } catch { /* ignore */ }
|
||||
endCurrentSession();
|
||||
state.np.playing = false;
|
||||
state.np.stationId = null;
|
||||
state.np.station = null;
|
||||
state.np.loading = false;
|
||||
_pendingStationId = null;
|
||||
sendState();
|
||||
render();
|
||||
return;
|
||||
@@ -388,6 +402,8 @@ async function playStation(station, { silent } = {}) {
|
||||
endCurrentSession();
|
||||
state.np.station = station;
|
||||
state.np.stationId = station.id;
|
||||
state.np.loading = true;
|
||||
state.np.error = null;
|
||||
state.voteStats = {
|
||||
up: station.up || 0, down: station.down || 0,
|
||||
plays: station.plays || 0, score: station.score || 0
|
||||
@@ -397,8 +413,21 @@ async function playStation(station, { silent } = {}) {
|
||||
// would otherwise call audio.play() without one and iOS/Chromium
|
||||
// would refuse. ensureGesture short-circuits once unlocked.
|
||||
const ok = await ensureGesture(station.name, silent ? 'Tap Start to resume the group audio.' : 'Tap Start to play.');
|
||||
if (!ok) return;
|
||||
await player.play(station);
|
||||
if (!ok) {
|
||||
// Gesture refused: don't leave state pointing at a station we never
|
||||
// actually played, otherwise the dedupe in handleCommand('play')
|
||||
// would wedge — every future command for this id would be skipped.
|
||||
// Only clear if we're still the active station; a newer playStation
|
||||
// may have superseded us during the modal await.
|
||||
if (state.np.stationId === station.id) clearNowPlaying();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await player.play(station);
|
||||
} catch {
|
||||
if (state.np.stationId === station.id) clearNowPlaying();
|
||||
return;
|
||||
}
|
||||
if (player.audio.paused) {
|
||||
// Gesture confirmed but browser hasn't started yet — try once more.
|
||||
player.audio.play().catch(() => { });
|
||||
@@ -421,6 +450,21 @@ async function playStation(station, { silent } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Wipe local now-playing state and broadcast the cleared state to the room.
|
||||
// Used when a play attempt fails (gesture refused, resolve error) so the
|
||||
// dedupe in handleCommand doesn't trap us pointing at a station we never
|
||||
// actually played.
|
||||
function clearNowPlaying() {
|
||||
state.np.stationId = null;
|
||||
state.np.station = null;
|
||||
state.np.playing = false;
|
||||
state.np.loading = false;
|
||||
state.voteStats = null;
|
||||
try { player?.stop(); } catch { /* ignore */ }
|
||||
sendState();
|
||||
render();
|
||||
}
|
||||
|
||||
// Close whichever session is currently open. Idempotent.
|
||||
function endCurrentSession({ beacon = false } = {}) {
|
||||
const s = state.session;
|
||||
|
||||
Reference in New Issue
Block a user