/** * Shared client-side types - overlapping copies of server/src/types.ts's * DTO shapes rather than a real shared package (the two projects compile * independently - tsc for the server, esbuild for the client - and a * shared package would be a bigger structural commitment than this app's * size justifies). See docs/architecture.md. */ export interface ItemDto { id: string; name: string; /** Emby's own sort key (articles stripped) when the server requested * it, the display name otherwise - see server/src/types.ts's own * ItemDto.sortName for why alphaIndex.ts buckets by this, not name. */ sortName: string; overview?: string; year?: number; type?: string; canWatchParty: boolean; imageUrl: string; embyUrl: string; durationTicks: number | null; indexNumber: number | null; artistName: string | null; albumName: string | null; } export interface LibraryDto { id: string; name: string; collectionType: string; } export interface SeasonDto { id: string; name: string; indexNumber?: number; } export interface PlaybackTrackOption { index: number; label: string; // Subtitle tracks only (undefined for audio) - false for an image/ // bitmap subtitle (PGS/VobSub/DVB - a Blu-ray/DVD rip's own subtitles, // typically). The player renders subtitles as a native (WebVTT, // converted server-side - see api/embyApi.ts's subtitleUrl and // Player.ts's applySubtitleTrack), which needs actual text; a bitmap // has none to extract. isTextBased?: boolean; } export interface PlaybackOptionsDto { audioTracks: PlaybackTrackOption[]; subtitleTracks: PlaybackTrackOption[]; } /** Emby's own native intro/credits detection - see server/src/types.ts's * own copy of this shape for where the field names come from. Every * field is independently nullable ("not detected", never an error) - * see views/player/Player.ts's updateSkipButton, the only consumer. */ export interface PlaybackMarkersDto { introStartTicks: number | null; introEndTicks: number | null; creditsStartTicks: number | null; } export type MediaType = 'video' | 'music'; export interface PublicMember { userId: string; userName: string; isHost: boolean; isPaused: boolean; positionTicks: number; connected: boolean; } export interface MusicTrack { itemId: string; itemName: string; artistName: string | null; albumName: string | null; itemImageUrl: string; durationSeconds: number | null; addedByUserId: string; addedByUserName: string; } /** What this app's own catalog DTO (ItemDto) looks like once reshaped into * a track add request - see Album.ts's addTrackToQueue calls. */ export interface MusicTrackInput { itemId: string; itemName: string; artistName?: string | null; albumName?: string | null; itemImageUrl: string; durationTicks?: number | null; } export interface MusicCurrentPublic extends MusicTrack { isPaused: boolean; elapsedSeconds: number; } export interface MusicPublicState { queue: MusicTrack[]; current: MusicCurrentPublic | null; skipVotes: number; } export interface PartyPublicState { partyId: string; mediaType: MediaType; itemId: string; itemName: string; itemImageUrl: string; createdUtc: number; hasNext: boolean; queueLength: number; queueIndex: number; hostUserName: string | null; memberCount: number; youAreHost: boolean; members: PublicMember[]; music: MusicPublicState | null; // Video only - the host's current pick for the party's shared burn-in // stream (see server/src/services/TranscodeService.ts), null when the // party is just using its normal party-shared Emby stream instead // (PartyStreamService - also shared, just proxied rather than // transcoded by us). Both null or both set together - see // SocketGateway's own comment. burnInSubtitleIndex: string | null; burnInAudioIndex: string | null; // Video only - the host's current pick for a *text* subtitle, party- // wide now (not each viewer's own local pick any more - that turned // out to be the wrong call once people actually watched together, see // docs/architecture.md). Mutually exclusive with burnInSubtitleIndex - // see SocketGateway's "setTextSubtitle" handler. textSubtitleIndex: string | null; // Video only - the host's current pick for the party's normal shared // stream (PartyStreamService) - null means "Emby's own default". // There's only ever one stream now, so this is party-wide, host-set // state, not a per-viewer local choice the way it was before streams // were shared - see SocketGateway's "setStreamOptions" handler. streamAudioIndex: string | null; streamQuality: string | null; } /** The socket "members" broadcast's shape - smaller than * PartyPublicState.members above (no isPaused/positionTicks) - see * server/src/types.ts's SocketMemberDto for why. */ export interface SocketMember { userId: string; userName: string; isHost: boolean; connected: boolean; } export interface LobbyPartyDto { partyId: string; mediaType: MediaType; itemId: string; itemName: string; itemImageUrl: string; hostUserName: string | null; memberCount: number; createdUtc: number; // Video only - null for music. See Home.ts's updateElapsedLabels() for // how these turn into a live, pause-aware "elapsed" label instead of // just ticking wall-clock time since createdUtc regardless of pauses. positionTicks: number | null; isPaused: boolean | null; positionUpdatedUtc: number | null; /** The static watch party this live session belongs to, if any. */ staticPartyId: string | null; staticPartyName: string | null; } /** Mirrors server/src/types.ts's StaticPartySummaryDto - one static * (persistent) watch party, as the home page lists it. */ export interface StaticPartySummaryDto { id: string; name: string; sourceType: 'series' | 'playlist'; imageUrl: string; itemCount: number; groupWatchedCount: number; nextItemName: string | null; memberCount: number; youAreMember: boolean; youAreCreator: boolean; missedCount: number; livePartyId: string | null; } /** One item of a static watch party - mirrors StaticPartyItemDto. */ export interface StaticPartyItemDto { itemId: string; itemName: string; itemImageUrl: string; groupState: 'watched' | 'next' | 'upcoming'; viewerWatched: boolean; } /** Mirrors server/src/types.ts's StaticPartyDetailDto. */ export interface StaticPartyDetailDto extends StaticPartySummaryDto { items: StaticPartyItemDto[]; missedItemIds: string[]; members: Array<{ userId: string; userName: string; watchedCount: number; isCreator: boolean }>; } export interface ItemChangedMsg { itemId: string; itemName: string; itemImageUrl: string; queueIndex: number; queueLength: number; hasNext: boolean; } export interface PauseRequestedMsg { userId: string; userName: string; } /** Same shape as PauseRequestedMsg, kept as its own named type anyway * (not reused) so a callback's own type signature says which request it's * actually for - see api/party.ts's PartyConnectionCallbacks. */ export interface ResumeRequestedMsg { userId: string; userName: string; } /** "burnInChanged" - see api/party.ts's setBurnInSubtitle/onBurnInChanged * and PartyPublicState.burnInSubtitleIndex above (this is the live-push * half; that's the on-load/rejoin half). Both null together clears the * party's burn-in stream back to its normal party-shared one instead. */ export interface BurnInChangedMsg { subtitleIndex: string | null; audioIndex: string | null; } /** "textSubtitleChanged" - see api/party.ts's setTextSubtitle/ * onTextSubtitleChanged and PartyPublicState.textSubtitleIndex above * (this is the live-push half; that's the on-load/rejoin half). */ export interface TextSubtitleChangedMsg { subtitleIndex: string | null; } /** "streamOptionsChanged" - see api/party.ts's setStreamOptions/ * onStreamOptionsChanged and PartyPublicState.streamAudioIndex/ * streamQuality above (this is the live-push half; that's the * on-load/rejoin half). */ export interface StreamOptionsChangedMsg { audioStreamIndex: string | null; quality: string | null; }