Buckets:
ktongue/docker_container / simsite /frontend /node_modules /hls.js /src /loader /interstitial-event.ts
| import { hash } from '../utils/hash'; | |
| import type { DateRange, DateRangeCue } from './date-range'; | |
| import type { MediaFragmentRef } from './fragment'; | |
| import type { Loader, LoaderContext } from '../types/loader'; | |
| export const ALIGNED_END_THRESHOLD_SECONDS = 0.025; | |
| export type PlaybackRestrictions = { | |
| skip: boolean; | |
| jump: boolean; | |
| }; | |
| export type SnapOptions = { | |
| out: boolean; | |
| in: boolean; | |
| }; | |
| export enum TimelineOccupancy { | |
| Point, | |
| Range, | |
| } | |
| export type AssetListJSON = { | |
| ASSETS: Array<{ URI: string; DURATION: string }>; | |
| }; | |
| export interface InterstitialEventWithAssetList extends InterstitialEvent { | |
| assetListUrl: string; | |
| } | |
| export type BaseData = { | |
| url: string; | |
| }; | |
| export type InterstitialId = string; | |
| export type InterstitialAssetId = string; | |
| export type InterstitialAssetItem = { | |
| parentIdentifier: InterstitialId; | |
| identifier: InterstitialAssetId; | |
| duration: number | null; | |
| startOffset: number; // asset start offset from start of interstitial event | |
| timelineStart: number; // asset start on media element timeline | |
| uri: string; | |
| error?: Error; | |
| }; | |
| export function generateAssetIdentifier( | |
| interstitial: InterstitialEvent, | |
| uri: string, | |
| assetListIndex: number, | |
| ): string { | |
| return `${interstitial.identifier}-${assetListIndex + 1}-${hash(uri)}`; | |
| } | |
| export class InterstitialEvent { | |
| private base: BaseData; | |
| private _duration: number | null = null; | |
| private _timelineStart: number | null = null; | |
| private appendInPlaceDisabled?: boolean; | |
| public appendInPlaceStarted?: boolean; | |
| public dateRange: DateRange; | |
| public hasPlayed: boolean = false; | |
| public cumulativeDuration: number = 0; | |
| public resumeOffset: number = NaN; | |
| public playoutLimit: number = NaN; | |
| public restrictions: PlaybackRestrictions = { | |
| skip: false, | |
| jump: false, | |
| }; | |
| public snapOptions: SnapOptions = { | |
| out: false, | |
| in: false, | |
| }; | |
| public assetList: InterstitialAssetItem[] = []; | |
| public assetListLoader?: Loader<LoaderContext>; | |
| public assetListResponse: AssetListJSON | null = null; | |
| public resumeAnchor?: MediaFragmentRef; | |
| public error?: Error; | |
| public resetOnResume?: boolean; | |
| constructor(dateRange: DateRange, base: BaseData) { | |
| this.base = base; | |
| this.dateRange = dateRange; | |
| this.setDateRange(dateRange); | |
| } | |
| public setDateRange(dateRange: DateRange) { | |
| this.dateRange = dateRange; | |
| this.resumeOffset = dateRange.attr.optionalFloat( | |
| 'X-RESUME-OFFSET', | |
| this.resumeOffset, | |
| ); | |
| this.playoutLimit = dateRange.attr.optionalFloat( | |
| 'X-PLAYOUT-LIMIT', | |
| this.playoutLimit, | |
| ); | |
| this.restrictions = dateRange.attr.enumeratedStringList( | |
| 'X-RESTRICT', | |
| this.restrictions, | |
| ); | |
| this.snapOptions = dateRange.attr.enumeratedStringList( | |
| 'X-SNAP', | |
| this.snapOptions, | |
| ); | |
| } | |
| public reset() { | |
| this.appendInPlaceStarted = false; | |
| this.assetListLoader?.destroy(); | |
| this.assetListLoader = undefined; | |
| if (!this.supplementsPrimary) { | |
| this.assetListResponse = null; | |
| this.assetList = []; | |
| this._duration = null; | |
| } | |
| // `error?` is reset when seeking back over interstitial `startOffset` | |
| // using `schedule.resetErrorsInRange(start, end)`. | |
| } | |
| public isAssetPastPlayoutLimit(assetIndex: number): boolean { | |
| if (assetIndex > 0 && assetIndex >= this.assetList.length) { | |
| return true; | |
| } | |
| const playoutLimit = this.playoutLimit; | |
| if (assetIndex <= 0 || isNaN(playoutLimit)) { | |
| return false; | |
| } | |
| if (playoutLimit === 0) { | |
| return true; | |
| } | |
| const assetOffset = this.assetList[assetIndex]?.startOffset || 0; | |
| return assetOffset > playoutLimit; | |
| } | |
| public findAssetIndex(asset: InterstitialAssetItem): number { | |
| const index = this.assetList.indexOf(asset); | |
| return index; | |
| } | |
| get identifier(): InterstitialId { | |
| return this.dateRange.id; | |
| } | |
| get startDate(): Date { | |
| return this.dateRange.startDate; | |
| } | |
| get startTime(): number { | |
| // Primary media timeline start time | |
| const startTime = this.dateRange.startTime; | |
| if (this.snapOptions.out) { | |
| const frag = this.dateRange.tagAnchor; | |
| if (frag) { | |
| return getSnapToFragmentTime(startTime, frag); | |
| } | |
| } | |
| return startTime; | |
| } | |
| get startOffset(): number { | |
| return this.cue.pre ? 0 : this.startTime; | |
| } | |
| get startIsAligned(): boolean { | |
| if (this.startTime === 0 || this.snapOptions.out) { | |
| return true; | |
| } | |
| const frag = this.dateRange.tagAnchor; | |
| if (frag) { | |
| const startTime = this.dateRange.startTime; | |
| const snappedStart = getSnapToFragmentTime(startTime, frag); | |
| return startTime - snappedStart < 0.1; | |
| } | |
| return false; | |
| } | |
| get resumptionOffset(): number { | |
| const resumeOffset = this.resumeOffset; | |
| const offset = Number.isFinite(resumeOffset) ? resumeOffset : this.duration; | |
| return this.cumulativeDuration + offset; | |
| } | |
| get resumeTime(): number { | |
| // Primary media timeline resumption time | |
| const resumeTime = this.startOffset + this.resumptionOffset; | |
| if (this.snapOptions.in) { | |
| const frag = this.resumeAnchor; | |
| if (frag) { | |
| return getSnapToFragmentTime(resumeTime, frag); | |
| } | |
| } | |
| return resumeTime; | |
| } | |
| get appendInPlace(): boolean { | |
| if (this.appendInPlaceStarted) { | |
| return true; | |
| } | |
| if (this.appendInPlaceDisabled) { | |
| return false; | |
| } | |
| if ( | |
| !this.cue.once && | |
| !this.cue.pre && // preroll starts at startPosition before startPosition is known (live) | |
| this.startIsAligned && | |
| ((isNaN(this.playoutLimit) && isNaN(this.resumeOffset)) || | |
| (this.resumeOffset && | |
| this.duration && | |
| Math.abs(this.resumeOffset - this.duration) < | |
| ALIGNED_END_THRESHOLD_SECONDS)) | |
| ) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| set appendInPlace(value: boolean) { | |
| if (this.appendInPlaceStarted) { | |
| this.resetOnResume = !value; | |
| return; | |
| } | |
| this.appendInPlaceDisabled = !value; | |
| } | |
| // Extended timeline start time | |
| get timelineStart(): number { | |
| if (this._timelineStart !== null) { | |
| return this._timelineStart; | |
| } | |
| return this.startTime; | |
| } | |
| set timelineStart(value: number) { | |
| this._timelineStart = value; | |
| } | |
| get duration(): number { | |
| const playoutLimit = this.playoutLimit; | |
| let duration: number; | |
| if (this._duration !== null) { | |
| duration = this._duration; | |
| } else if (this.dateRange.duration) { | |
| duration = this.dateRange.duration; | |
| } else { | |
| duration = this.dateRange.plannedDuration || 0; | |
| } | |
| if (!isNaN(playoutLimit) && playoutLimit < duration) { | |
| duration = playoutLimit; | |
| } | |
| return duration; | |
| } | |
| set duration(value: number) { | |
| this._duration = value; | |
| } | |
| get cue(): DateRangeCue { | |
| return this.dateRange.cue; | |
| } | |
| get timelineOccupancy() { | |
| if (this.dateRange.attr['X-TIMELINE-OCCUPIES'] === 'RANGE') { | |
| return TimelineOccupancy.Range; | |
| } | |
| return TimelineOccupancy.Point; | |
| } | |
| get supplementsPrimary(): boolean { | |
| return this.dateRange.attr['X-TIMELINE-STYLE'] === 'PRIMARY'; | |
| } | |
| get contentMayVary(): boolean { | |
| return this.dateRange.attr['X-CONTENT-MAY-VARY'] !== 'NO'; | |
| } | |
| get assetUrl(): string | undefined { | |
| return this.dateRange.attr['X-ASSET-URI']; | |
| } | |
| get assetListUrl(): string | undefined { | |
| return this.dateRange.attr['X-ASSET-LIST']; | |
| } | |
| get baseUrl(): string { | |
| return this.base.url; | |
| } | |
| get assetListLoaded(): boolean { | |
| return this.assetList.length > 0 || this.assetListResponse !== null; | |
| } | |
| toString(): string { | |
| return eventToString(this); | |
| } | |
| } | |
| function getSnapToFragmentTime(time: number, frag: MediaFragmentRef) { | |
| return time - frag.start < frag.duration / 2 && | |
| !( | |
| Math.abs(time - (frag.start + frag.duration)) < | |
| ALIGNED_END_THRESHOLD_SECONDS | |
| ) | |
| ? frag.start | |
| : frag.start + frag.duration; | |
| } | |
| export function getInterstitialUrl( | |
| uri: string, | |
| sessionId: string, | |
| baseUrl?: string, | |
| ): URL | never { | |
| const url = new self.URL(uri, baseUrl); | |
| if (url.protocol !== 'data:') { | |
| url.searchParams.set('_HLS_primary_id', sessionId); | |
| } | |
| return url; | |
| } | |
| export function getNextAssetIndex( | |
| interstitial: InterstitialEvent, | |
| assetListIndex: number, | |
| ): number { | |
| while (interstitial.assetList[++assetListIndex]?.error) { | |
| /* no-op */ | |
| } | |
| return assetListIndex; | |
| } | |
| function eventToString(interstitial: InterstitialEvent): string { | |
| return `["${interstitial.identifier}" ${interstitial.cue.pre ? '<pre>' : interstitial.cue.post ? '<post>' : ''}${interstitial.timelineStart.toFixed(2)}-${interstitial.resumeTime.toFixed(2)}]`; | |
| } | |
| export function eventAssetToString(asset: InterstitialAssetItem): string { | |
| const start = asset.timelineStart; | |
| const duration = asset.duration || 0; | |
| return `["${asset.identifier}" ${start.toFixed(2)}-${(start + duration).toFixed(2)}]`; | |
| } | |
Xet Storage Details
- Size:
- 8.88 kB
- Xet hash:
- 5ccb38b97b9079b483200d6df6b4bba74d134b4d4465b113684f51ed847d86f4
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.