File
Blob: worker/src/server/publisher-metadata.ts
| 1 | import type { DeviceStart } from '@/shared/contracts/signaling.ts'; |
| 2 | import { newerRevision, sameNowPlaying } from '@/shared/contracts/track.ts'; |
| 3 | import type { NowPlaying, Track } from '@/shared/contracts/track.ts'; |
| 4 | import type { Publisher } from './room-state.ts'; |
| 5 | import { demand } from './rpc.ts'; |
| 6 | |
| 7 | function 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 | |
| 11 | export 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 | |
| 25 | export 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. */ |
| 47 | export 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 | } |