import {type EbmlCuePointTagType, type EbmlCuesTagType, EbmlTagIdEnum} from "konoebml"; import {CuePointSchema, type CuePointType, type CueTrackPositionsType} from "../schema"; import {maxBy} from "lodash-es"; import type {SegmentComponent} from "./segment"; import {SegmentComponentSystemTrait} from "./segment-component"; export class CueSystem extends SegmentComponentSystemTrait< EbmlCuePointTagType, typeof CuePointSchema > { override get schema() { return CuePointSchema; } cues: SegmentComponent[] = []; prepareCuesWithTag(tag: EbmlCuesTagType) { this.cues = tag.children .filter((c) => c.id === EbmlTagIdEnum.CuePoint) .map(this.componentFromTag.bind(this)); return this; } findClosestCue(seekTime: number): CuePointType | undefined { const cues = this.cues; if (!cues || cues.length === 0) { return undefined; } let left = 0; let right = cues.length - 1; if (seekTime <= cues[0].CueTime) { return cues[0]; } if (seekTime >= cues[right].CueTime) { return cues[right]; } while (left <= right) { const mid = Math.floor((left + right) / 2); if (cues[mid].CueTime === seekTime) { return cues[mid]; } if (cues[mid].CueTime < seekTime) { left = mid + 1; } else { right = mid - 1; } } const before = cues[right]; const after = cues[left]; return Math.abs((before.CueTime as number) - seekTime) < Math.abs((after.CueTime as number) - seekTime) ? before : after; } getCueTrackPositions( cuePoint: CuePointType, track?: number ): CueTrackPositionsType { let cueTrackPositions: CueTrackPositionsType | undefined; if (track! >= 0) { cueTrackPositions = cuePoint.CueTrackPositions.find( (c) => c.CueTrack === track ); } if (!cueTrackPositions) { cueTrackPositions = maxBy( cuePoint.CueTrackPositions, (c) => c.CueClusterPosition )!; } return cueTrackPositions; } get prepared(): boolean { return this.cues.length > 0; } }