Skip to content
File

Blob: worker/src/server/publisher-metadata.ts

typescript58 lines
1import type { DeviceStart } from '@/shared/contracts/signaling.ts';
2import { newerRevision, sameNowPlaying } from '@/shared/contracts/track.ts';
3import type { NowPlaying, Track } from '@/shared/contracts/track.ts';
4import type { Publisher } from './room-state.ts';
5import { demand } from './rpc.ts';
6 
7function sameTrack(a: Track | null | undefined, b: Track | null | undefined): boolean {
8 return a?.title === b?.title && a?.artist === b?.artist && a?.durationMs === b?.durationMs;
9}
10 
11export function startupMetadata(input: DeviceStart) {
12 if (input.nowPlaying && input.track !== undefined) {
13 demand(
14 sameTrack(input.track, input.nowPlaying.track),
15 400,
16 'Initial song metadata must agree.',
17 );
18 }
19 return {
20 track: (input.nowPlaying ? input.nowPlaying.track : input.track) ?? undefined,
21 nowPlaying: input.nowPlaying,
22 };
23}
24 
25export function checkStartupMetadata(
26 existing: Publisher,
27 input: ReturnType<typeof startupMetadata>,
28): void {
29 const initialTrack = existing.initialNowPlaying
30 ? existing.initialNowPlaying.track
31 : existing.track;
32 demand(
33 sameTrack(initialTrack, input.track),
34 409,
35 'Startup track information cannot change within a device boot.',
36 );
37 demand(
38 existing.initialNowPlaying
39 ? !!input.nowPlaying && sameNowPlaying(existing.initialNowPlaying, input.nowPlaying)
40 : !input.nowPlaying,
41 409,
42 'Startup playlist information cannot change within a device boot.',
43 );
44}
45 
46/** Reject conflicts, ignore delayed revisions, and retain the accepted immutable value. */
47export function heartbeatMetadata(previous: NowPlaying | undefined, next: NowPlaying): NowPlaying {
48 demand(
49 !previous || previous.trackCount === next.trackCount,
50 409,
51 'Playlist size cannot change within a device boot.',
52 );
53 if (previous?.revision === next.revision) {
54 demand(sameNowPlaying(previous, next), 409, 'A playback revision cannot describe two songs.');
55 }
56 return !previous || newerRevision(next.revision, previous.revision) ? next : previous;
57}