import { isResolvedTrack } from "../types/index.js"; //#region src/media/utils/tracks.ts /** * Get the tracks of the given type from a presentation's first switching set. * * Returns `[]` when the presentation is unresolved, when no selection set of * `type` exists, or when its first switching set is empty. Returned tracks may * be partially resolved (URL only) or fully resolved (with segments) — callers * narrow as needed. * * The "first switching set" assumption matches the rest of the codebase * (HLS typically has one switching set per type); multi-group / multi-period * support would generalize this. */ function getTracksByType(presentation, type) { return presentation.selectionSets?.find(({ type: t }) => t === type)?.switchingSets[0]?.tracks ?? []; } /** * Find a track of the given type and id within a presentation. * * Returns the matching track from the first switching set of the matching * selection set, or `undefined` if either is missing. The returned track may * be partially resolved (URL only) or fully resolved (with segments) — callers * narrow as needed. */ function findTrack(presentation, type, trackId) { return getTracksByType(presentation, type).find(({ id }) => id === trackId); } /** * Find a track by id across all selection sets in a presentation, without * knowing its type up front. Used when the caller has a track id obtained * from a downstream consumer (e.g. `SourceBufferActor.initTrackId`) and * needs to locate the corresponding track in the presentation. * * Track ids are unique within a presentation per the HLS spec; the first * match wins. */ function findTrackById(presentation, trackId) { for (const selectionSet of presentation.selectionSets ?? []) { const track = selectionSet.switchingSets[0]?.tracks.find(({ id }) => id === trackId); if (track) return track; } } /** * Find a text track of the given id within a presentation and narrow it to * the fully-resolved `TextTrack` shape (segments populated). Returns * `undefined` if no track matches the id, the matching track isn't a text * track, or it hasn't been resolved yet. * * The segments-non-empty check stays at the call site — a resolved track * with zero segments is a valid state, distinct from "ready to load." */ function findResolvedTextTrack(presentation, trackId) { if (!presentation || !trackId) return void 0; const track = findTrack(presentation, "text", trackId); if (track?.type !== "text" || !isResolvedTrack(track)) return void 0; return track; } function findResolvedVideoTrack(presentation, trackId) { if (!presentation || !trackId) return void 0; const track = findTrack(presentation, "video", trackId); if (track?.type !== "video" || !isResolvedTrack(track)) return void 0; return track; } function findResolvedAudioTrack(presentation, trackId) { if (!presentation || !trackId) return void 0; const track = findTrack(presentation, "audio", trackId); if (track?.type !== "audio" || !isResolvedTrack(track)) return void 0; return track; } /** * Whether a track carries a non-empty `codecs` array. Both partially- * resolved and fully-resolved tracks may carry codecs — they come from * the multivariant playlist's `EXT-X-STREAM-INF` line, not from the * per-type media playlist — so this works at either resolution stage. * * `TextTrack` doesn't declare a `codecs` field; the `'codecs' in track` * check narrows it out for the false branch. */ function hasCodecs(track) { return !!track && "codecs" in track && !!track.codecs?.length; } /** * Set `mimeType` on every track of one `type` (immutably). Used to propagate a * detected container across a type's renditions: an ABR ladder is the same * content at different bitrates, so one rendition's container holds for all of * them — capability probing + SourceBuffer setup then get the right MIME for the * whole type from a single resolved media playlist, without fetching the rest. * * Scoped to one type on purpose: propagating *across* audio/video would be wrong * for mixed-container sources (e.g. muxed-TS video + raw-`.aac` audio) and races * concurrent per-type resolution. Same-type writes are disjoint and safe. * Idempotent — tracks already at `mimeType` are left as-is. */ function applyContainerMimeType(presentation, type, mimeType) { return { ...presentation, selectionSets: presentation.selectionSets.map((selectionSet) => selectionSet.type === type ? { ...selectionSet, switchingSets: selectionSet.switchingSets.map((switchingSet) => ({ ...switchingSet, tracks: switchingSet.tracks.map((track) => track.mimeType === mimeType ? track : { ...track, mimeType }) })) } : selectionSet) }; } /** * Updates a track within a presentation (immutably). Generic — works for * video, audio, or text tracks. */ function updateTrackInPresentation(presentation, resolvedTrack) { const trackId = resolvedTrack.id; return { ...presentation, selectionSets: presentation.selectionSets.map((selectionSet) => ({ ...selectionSet, switchingSets: selectionSet.switchingSets.map((switchingSet) => ({ ...switchingSet, tracks: switchingSet.tracks.map((track) => track.id === trackId ? resolvedTrack : track) })) })) }; } //#endregion export { applyContainerMimeType, findResolvedAudioTrack, findResolvedTextTrack, findResolvedVideoTrack, findTrack, findTrackById, getTracksByType, hasCodecs, updateTrackInPresentation }; //# sourceMappingURL=tracks.js.map