export interface Config { availableTimelines: string[]; } export interface Room { id: number; userCount: number; isLeader: boolean; running: boolean; startTime?: number; seekTime: number; timelineName: string; readyToParticipate: boolean; speedFactor: number; ticker: string[]; users?: { id: string; readyToParticipate: boolean }[]; } export class RoomOptions { seekTime: number; timelineName: string; constructor(seekTime: number, timelineName: string) { this.seekTime = seekTime; this.timelineName = timelineName; } } export class Timeline { name: string; songFile: string; feed: TimelineItem[]; constructor(obj: any) { this.name = obj.name; this.songFile = obj.songFile; this.feed = obj.feed; this.feed = this.feed.sort((a, b) => a.timestamp - b.timestamp); // Add string ids to the feed to uniquely identify them in the various // reactive components. for (let i = 0; i < this.feed.length; i++) { this.feed[i].id = i.toString(); for (let j = 0; j < this.feed[i].events.length; j++) { this.feed[i].events[j].id = `${i}:${j}`; } } } getTotalShotCount(): number { let maxShot = 0; for (const item of this.feed) { for (const event of item.events) { if (event.type === "shot") { maxShot = Math.max( maxShot, (event as TimestampEventShot).shotCount || 0 ); } } } return maxShot; } itemAfterTime(time: number, type?: EventType): TimelineItem | undefined { const feedToSearch = type ? this.feed.filter((i) => i.events.some((j) => j.type === type)) : this.feed; return feedToSearch.find((item) => item.timestamp * 1000 > time); } itemBeforeTime(time: number, type?: EventType): TimelineItem | undefined { const feedToSearch = type ? this.feed.filter((i) => i.events.some((j) => j.type === type)) : this.feed; return feedToSearch.reverse().find((item) => item.timestamp * 1000 < time); } eventAfterTime(time: number, type: "shot"): TimestampEventShot | undefined; eventAfterTime(time: number, type: EventType): TimestampEvent | undefined { const item = this.itemAfterTime(time, type); if (!item || !item.events.length) { return undefined; } return item.events.find((ev) => ev.type === type); } eventBeforeTime(time: number, type: "shot"): TimestampEventShot | undefined; eventBeforeTime(time: number, type: EventType): TimestampEvent | undefined { const item = this.itemBeforeTime(time, type); if (!item || !item.events.length) { return undefined; } return item.events.find((ev) => ev.type === type); } } export interface TimelineItem { id: string; timestamp: number; events: TimestampEvent[]; } export const EVENT_PRIORITY: EventType[] = ["shot", "talk", "time", "song"]; export type EventType = "talk" | "shot" | "song" | "time"; interface TimestampEventBase { id: string; type: EventType; text: string[]; } interface TimestampEventShot extends TimestampEventBase { type: "shot"; shotCount: number; } export type TimestampEvent = TimestampEventBase | TimestampEventShot;