Skip to content

Commit

Permalink
Program date time, date range, and fragment parsing optimizations
Browse files Browse the repository at this point in the history
Improve InterstitalEvent object serialization with MediaFragmentRef
  • Loading branch information
robwalch committed Nov 11, 2024
1 parent c97f579 commit 2d91f78
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 122 deletions.
45 changes: 33 additions & 12 deletions api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,13 @@ export class BasePlaylistController extends Logger implements NetworkComponentAP
//
// @public (undocumented)
export class BaseSegment {
constructor(baseurl: string);
constructor(base: Base | string);
// Warning: (ae-forgotten-export) The symbol "Base" needs to be exported by the entry point hls.d.ts
//
// (undocumented)
readonly base: Base;
// (undocumented)
readonly baseurl: string;
get baseurl(): string;
// (undocumented)
get byteRange(): [number, number] | [];
// (undocumented)
Expand All @@ -348,12 +352,20 @@ export class BaseSegment {
// (undocumented)
clearElementaryStreamInfo(): void;
// (undocumented)
elementaryStreams: ElementaryStreams;
get elementaryStreams(): ElementaryStreams;
set elementaryStreams(value: ElementaryStreams);
// (undocumented)
get hasStats(): boolean;
// (undocumented)
get hasStreams(): boolean;
// (undocumented)
relurl?: string;
// (undocumented)
setByteRange(value: string, previous?: BaseSegment): void;
// (undocumented)
get stats(): LoadStats;
set stats(value: LoadStats);
// (undocumented)
get url(): string;
set url(value: string);
}
Expand Down Expand Up @@ -991,8 +1003,10 @@ export class DateRange {
get startDate(): Date;
// (undocumented)
get startTime(): number;
// Warning: (ae-forgotten-export) The symbol "MediaFragmentRef" needs to be exported by the entry point hls.d.ts
//
// (undocumented)
tagAnchor: Fragment | null;
tagAnchor: MediaFragmentRef | null;
// (undocumented)
tagOrder: number;
}
Expand Down Expand Up @@ -1690,10 +1704,12 @@ export interface FragLoadingData {
//
// @public
export class Fragment extends BaseSegment {
constructor(type: PlaylistLevelType, baseurl: string);
constructor(type: PlaylistLevelType, base: Base | string);
// (undocumented)
abortRequests(): void;
// (undocumented)
addStart(value: number): void;
// (undocumented)
bitrateTest: boolean;
// (undocumented)
cc: number;
Expand Down Expand Up @@ -1738,14 +1754,21 @@ export class Fragment extends BaseSegment {
// (undocumented)
playlistOffset: number;
// (undocumented)
programDateTime: number | null;
get programDateTime(): number | null;
set programDateTime(value: number | null);
// (undocumented)
rawProgramDateTime: string | null;
// (undocumented)
get ref(): MediaFragmentRef | null;
// (undocumented)
setDuration(value: number): void;
// (undocumented)
setElementaryStreamInfo(type: ElementaryStreamTypes, startPTS: number, endPTS: number, startDTS: number, endDTS: number, partial?: boolean): void;
// (undocumented)
setKeyFormat(keyFormat: KeySystemFormats): void;
// (undocumented)
setStart(value: number): void;
// (undocumented)
sn: number | 'initSegment';
// (undocumented)
start: number;
Expand All @@ -1754,8 +1777,6 @@ export class Fragment extends BaseSegment {
// (undocumented)
startPTS?: number;
// (undocumented)
stats: LoadStats;
// (undocumented)
tagList: Array<string[]>;
// (undocumented)
title: string | null;
Expand Down Expand Up @@ -2603,7 +2624,7 @@ export class InterstitialEvent {
// (undocumented)
restrictions: PlaybackRestrictions;
// (undocumented)
resumeAnchor?: Fragment;
resumeAnchor?: MediaFragmentRef;
// (undocumented)
resumeOffset: number;
// (undocumented)
Expand Down Expand Up @@ -3763,6 +3784,8 @@ export interface MediaEndedData {
//
// @public (undocumented)
export interface MediaFragment extends Fragment {
// (undocumented)
ref: MediaFragmentRef;
// (undocumented)
sn: number;
}
Expand Down Expand Up @@ -3992,7 +4015,7 @@ export interface ParsedTrack extends BaseTrack {
//
// @public
export class Part extends BaseSegment {
constructor(partAttrs: AttrList, frag: MediaFragment, baseurl: string, index: number, previous?: Part);
constructor(partAttrs: AttrList, frag: MediaFragment, base: Base | string, index: number, previous?: Part);
// (undocumented)
readonly duration: number;
// (undocumented)
Expand All @@ -4013,8 +4036,6 @@ export class Part extends BaseSegment {
readonly relurl: string;
// (undocumented)
get start(): number;
// (undocumented)
stats: LoadStats;
}

// Warning: (ae-missing-release-tag) "PartsLoadedData" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Expand Down
2 changes: 1 addition & 1 deletion src/controller/latency-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default class LatencyController implements ComponentAPI {
if (config.liveMaxLatencyDuration !== undefined) {
return config.liveMaxLatencyDuration;
}
const levelDetails = this.hls.latestLevelDetails;
const levelDetails = this.hls?.latestLevelDetails;
return levelDetails
? config.liveMaxLatencyDurationCount * levelDetails.targetduration
: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
import { ChunkMetadata } from '../types/transmuxer';
import { BufferHelper } from '../utils/buffer-helper';
import { pickMostCompleteCodecName } from '../utils/codecs';
import type Hls from '../hls';
import type { FragmentTracker } from './fragment-tracker';
import type Hls from '../hls';
import type { Fragment, MediaFragment } from '../loader/fragment';
import type KeyLoader from '../loader/key-loader';
import type { LevelDetails } from '../loader/level-details';
Expand Down
4 changes: 2 additions & 2 deletions src/loader/date-range.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AttrList } from '../utils/attr-list';
import { logger } from '../utils/logger';
import type { Fragment } from './fragment';
import type { MediaFragmentRef } from './fragment';

// Avoid exporting const enum so that these values can be inlined
const enum DateRangeAttribute {
Expand Down Expand Up @@ -47,7 +47,7 @@ export function isSCTE35Attribute(attrName: string): boolean {

export class DateRange {
public attr: AttrList;
public tagAnchor: Fragment | null;
public tagAnchor: MediaFragmentRef | null;
public tagOrder: number;
private _startDate: Date;
private _endDate?: Date;
Expand Down
139 changes: 116 additions & 23 deletions src/loader/fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,27 @@ export type ElementaryStreams = Record<
ElementaryStreamInfo | null
>;

export type Base = {
url: string;
};

export class BaseSegment {
private _byteRange: [number, number] | null = null;
private _url: string | null = null;
private _stats: LoadStats | null = null;
private _streams: ElementaryStreams | null = null;

// baseurl is the URL to the playlist
public readonly baseurl: string;
public readonly base: Base;

// relurl is the portion of the URL that comes from inside the playlist.
public relurl?: string;
// Holds the types of data this fragment supports
public elementaryStreams: ElementaryStreams = {
[ElementaryStreamTypes.AUDIO]: null,
[ElementaryStreamTypes.VIDEO]: null,
[ElementaryStreamTypes.AUDIOVIDEO]: null,
};

constructor(baseurl: string) {
this.baseurl = baseurl;
constructor(base: Base | string) {
if (typeof base === 'string') {
base = { url: base };
}
this.base = base;
}

// setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
Expand All @@ -60,8 +64,12 @@ export class BaseSegment {
this._byteRange = [start, parseInt(params[0]) + start];
}

get baseurl(): string {
return this.base.url;
}

get byteRange(): [number, number] | [] {
if (!this._byteRange) {
if (this._byteRange === null) {
return [];
}

Expand All @@ -76,6 +84,40 @@ export class BaseSegment {
return this.byteRange[1];
}

get elementaryStreams(): ElementaryStreams {
if (this._streams === null) {
this._streams = {
[ElementaryStreamTypes.AUDIO]: null,
[ElementaryStreamTypes.VIDEO]: null,
[ElementaryStreamTypes.AUDIOVIDEO]: null,
};
}
return this._streams;
}

set elementaryStreams(value: ElementaryStreams) {
this._streams = value;
}

get hasStats(): boolean {
return this._stats !== null;
}

get hasStreams(): boolean {
return this._streams !== null;
}

get stats(): LoadStats {
if (this._stats === null) {
this._stats = new LoadStats();
}
return this._stats;
}

set stats(value: LoadStats) {
this._stats = value;
}

get url(): string {
if (!this._url && this.baseurl && this.relurl) {
this._url = buildAbsoluteURL(this.baseurl, this.relurl, {
Expand All @@ -99,16 +141,26 @@ export class BaseSegment {

export interface MediaFragment extends Fragment {
sn: number;
ref: MediaFragmentRef;
}

export type MediaFragmentRef = {
base: Base;
start: number;
duration: number;
sn: number;
programDateTime: number | null;
};

/**
* Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
*/
export class Fragment extends BaseSegment {
private _decryptdata: LevelKey | null = null;
private _programDateTime: number | null = null;
private _ref: MediaFragmentRef | null = null;

public rawProgramDateTime: string | null = null;
public programDateTime: number | null = null;
public tagList: Array<string[]> = [];

// EXTINF has to be present for a m3u8 to be considered valid
Expand Down Expand Up @@ -147,8 +199,6 @@ export class Fragment extends BaseSegment {
public maxStartPTS?: number;
// The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
public minEndPTS?: number;
// Load/parse timing information
public stats: LoadStats = new LoadStats();
// Init Segment bytes (unset for media segments)
public data?: Uint8Array;
// A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered
Expand All @@ -164,8 +214,8 @@ export class Fragment extends BaseSegment {
// Deprecated
public urlId: number = 0;

constructor(type: PlaylistLevelType, baseurl: string) {
super(baseurl);
constructor(type: PlaylistLevelType, base: Base | string) {
super(base);
this.type = type;
}

Expand Down Expand Up @@ -203,10 +253,6 @@ export class Fragment extends BaseSegment {
return null;
}

if (!Number.isFinite(this.programDateTime)) {
return null;
}

const duration = !Number.isFinite(this.duration) ? 0 : this.duration;

return this.programDateTime + duration * 1000;
Expand All @@ -225,10 +271,58 @@ export class Fragment extends BaseSegment {
return true;
}
}

return false;
}

get programDateTime(): number | null {
if (this._programDateTime === null && this.rawProgramDateTime) {
this.programDateTime = Date.parse(this.rawProgramDateTime);
}
return this._programDateTime;
}

set programDateTime(value: number | null) {
if (!Number.isFinite(value)) {
this._programDateTime = this.rawProgramDateTime = null;
return;
}
this._programDateTime = value;
}

get ref(): MediaFragmentRef | null {
if (this.sn === 'initSegment') {
return null;
}
if (!this._ref) {
this._ref = {
base: this.base,
start: this.start,
duration: this.duration,
sn: this.sn,
programDateTime: this.programDateTime,
};
}
return this._ref;
}

addStart(value: number) {
this.setStart(this.start + value);
}

setStart(value: number) {
this.start = value;
if (this._ref) {
this._ref.start = value;
}
}

setDuration(value: number) {
this.duration = value;
if (this._ref) {
this._ref.duration = value;
}
}

setKeyFormat(keyFormat: KeySystemFormats) {
if (this.levelkeys) {
const key = this.levelkeys[keyFormat];
Expand Down Expand Up @@ -282,16 +376,15 @@ export class Part extends BaseSegment {
public readonly relurl: string;
public readonly fragment: MediaFragment;
public readonly index: number;
public stats: LoadStats = new LoadStats();

constructor(
partAttrs: AttrList,
frag: MediaFragment,
baseurl: string,
base: Base | string,
index: number,
previous?: Part,
) {
super(baseurl);
super(base);
this.duration = partAttrs.decimalFloatingPoint('DURATION');
this.gap = partAttrs.bool('GAP');
this.independent = partAttrs.bool('INDEPENDENT');
Expand Down
Loading

0 comments on commit 2d91f78

Please sign in to comment.