Skip to content

Commit 0cd55b8

Browse files
authored
Interstitials live start, buffering, and manager fixes and docs update (#7030)
* Interstitials improvements - Add interstitialPlayer to InterstitialsManager and simplify interface - Fix and cover expected playback state in interstitial event callbacks (where currentTime is derived from transitional controller state) - Fix live start in append-in-place interstitial seeking to second start position when primary starts buffering after starting in interstitial - Simplify playout-limit handling in asset player - Add padding to flush front buffer call (prevents start of audio segment from being removed in Safari) - Fix an issue where buffering an interstitial in place gets stuck when the video buffer is full, but the audio buffer is not (use combined buffer in stream-controller when there is no alt-audio source) - Fix typos (Interstitals > Interstitials) * Add `interstitialsManager.interstitialPlayer.assetPlayers` tests - verify playingIndex player is present for relevant asset events
1 parent a172344 commit 0cd55b8

15 files changed

+712
-165
lines changed

.eslintrc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ module.exports = {
1818
__USE_VARIABLE_SUBSTITUTION__: true,
1919
__USE_M2TS_ADVANCED_CODECS__: true,
2020
__USE_MEDIA_CAPABILITIES__: true,
21-
__USE_INTERSTITALS__: true,
21+
__USE_INTERSTITIALS__: true,
2222
},
2323
// see https://github.com/standard/eslint-config-standard
2424
// 'prettier' (https://github.com/prettier/eslint-config-prettier) must be last

api-extractor/report/hls.js.api.md

+20-7
Original file line numberDiff line numberDiff line change
@@ -2145,6 +2145,8 @@ export class HlsAssetPlayer {
21452145
// (undocumented)
21462146
resumeBuffering(): void;
21472147
// (undocumented)
2148+
get startOffset(): number;
2149+
// (undocumented)
21482150
get timelineOffset(): number;
21492151
set timelineOffset(value: number);
21502152
// (undocumented)
@@ -2711,6 +2713,22 @@ export interface InterstitialEventWithAssetList extends InterstitialEvent {
27112713
// @public (undocumented)
27122714
export type InterstitialId = string;
27132715

2716+
// Warning: (ae-missing-release-tag) "InterstitialPlayer" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
2717+
//
2718+
// @public (undocumented)
2719+
export interface InterstitialPlayer {
2720+
// (undocumented)
2721+
assetPlayers: (HlsAssetPlayer | null)[];
2722+
// (undocumented)
2723+
currentTime: number;
2724+
// (undocumented)
2725+
duration: number;
2726+
// (undocumented)
2727+
playingIndex: number;
2728+
// (undocumented)
2729+
scheduleItem: InterstitialScheduleEventItem | null;
2730+
}
2731+
27142732
// Warning: (ae-missing-release-tag) "InterstitialsBufferedToBoundaryData" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
27152733
//
27162734
// @public (undocumented)
@@ -2805,12 +2823,12 @@ export interface InterstitialsManager {
28052823
// (undocumented)
28062824
bufferingItem: InterstitialScheduleItem | null;
28072825
// (undocumented)
2808-
bufferingPlayer: HlsAssetPlayer | null;
2809-
// (undocumented)
28102826
events: InterstitialEvent[];
28112827
// (undocumented)
28122828
integrated: PlayheadTimes;
28132829
// (undocumented)
2830+
interstitialPlayer: InterstitialPlayer | null;
2831+
// (undocumented)
28142832
playerQueue: HlsAssetPlayer[];
28152833
// (undocumented)
28162834
playingAsset: InterstitialAssetItem | null;
@@ -2819,15 +2837,11 @@ export interface InterstitialsManager {
28192837
// (undocumented)
28202838
playingItem: InterstitialScheduleItem | null;
28212839
// (undocumented)
2822-
playout: PlayheadTimes;
2823-
// (undocumented)
28242840
primary: PlayheadTimes;
28252841
// (undocumented)
28262842
schedule: InterstitialScheduleItem[];
28272843
// (undocumented)
28282844
skip: () => void;
2829-
// (undocumented)
2830-
waitingIndex: number;
28312845
}
28322846

28332847
// Warning: (ae-missing-release-tag) "InterstitialsPrimaryResumed" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -4131,7 +4145,6 @@ export type PlayheadTimes = {
41314145
currentTime: number;
41324146
duration: number;
41334147
seekableStart: number;
4134-
seekTo: (time: number) => void;
41354148
};
41364149

41374150
// Warning: (ae-missing-release-tag) "PlaylistContextType" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)

build-config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const buildConstants = (type, additional = {}) => ({
7373
__USE_MEDIA_CAPABILITIES__: JSON.stringify(
7474
type === BUILD_TYPE.full || addMediaCapabilitiesSupport,
7575
),
76-
__USE_INTERSTITALS__: JSON.stringify(
76+
__USE_INTERSTITIALS__: JSON.stringify(
7777
type === BUILD_TYPE.full || addInterstitialSupport,
7878
),
7979

docs/API.md

+55-10
Original file line numberDiff line numberDiff line change
@@ -2019,34 +2019,79 @@ HLS.js supports playback of X-ASSET-URI and X-ASSET-LIST m3u8 playlists schedule
20192019
20202020
The data includes the list of Interstitial events with their asset lists, the schedule of event and primary segment items, information about which items and assets are buffering and playing, the player instance currently buffering media, and the queue of players responsible for the streaming of assets.
20212021
2022-
Use `skip()` to skip the current interstitial. Use `primary`, `playout`, and `integrated` to get `currentTime`, `duration` and to seek along the respective timeline.
2022+
Use `skip()` to skip the current interstitial.
2023+
Use `primary` and `integrated` playhead objects to get `currentTime`, `duration` and to seek along the respective timeline.
2024+
Use `interstitialPlayer` to get active intersititial info (playing or upcoming buffering break) like `currentTime`, `duration`, and `playingIndex` within an Interstital break.
20232025
20242026
```ts
20252027
interface InterstitialsManager {
2026-
events: InterstitialEvent[]; // An array of Interstitials (events) parsed from the latest media playlist update
20272028
schedule: InterstitialScheduleItem[]; // An array of primary and event items with start and end times representing the scheduled program
2028-
playerQueue: HlsAssetPlayer[]; // And array of child Hls instances created to preload and stream Interstitial asset content
2029-
bufferingPlayer: HlsAssetPlayer | null; // The child Hls instance assigned to streaming media at the edge of the forward buffer
2029+
integrated: PlayheadTimes; // playhead mapping and control that applies the X-TIMELINE-OCCUPIES attribute to each event item
2030+
primary: PlayheadTimes; // playhead mapping and control based on the primary content
2031+
2032+
interstitialPlayer: InterstitialPlayer | null; // interface for interstitial playback state
2033+
20302034
bufferingAsset: InterstitialAssetItem | null; // The Interstitial asset currently being streamed
20312035
bufferingItem: InterstitialScheduleItem | null; // The primary item or event item currently being streamed
20322036
bufferingIndex: number; // The index of `bufferingItem` in the `schedule` array
2037+
20332038
playingAsset: InterstitialAssetItem | null; // The Interstitial asset currently being streamed
20342039
playingItem: InterstitialScheduleItem | null; // The primary item or event item currently being played
20352040
playingIndex: number; // The index of `playingItem` in the `schedule` array
2036-
waitingIndex: number; // The index of the item whose asset list is being loaded in the `schedule` array
2037-
primary: PlayheadTimes; // playhead mapping and seekTo method based on the primary content
2038-
playout: PlayheadTimes; // playhead mapping and seekTo method based on playout of all items in the `schedule` array
2039-
integrated: PlayheadTimes; // playhead mapping and seekTo method that applies the X-TIMELINE-OCCUPIES attribute to each event item
2041+
2042+
events: InterstitialEvent[]; // An array of Interstitials (events) parsed from the latest media playlist update
2043+
playerQueue: HlsAssetPlayer[]; // And array of child Hls instances created to preload and stream Interstitial asset content
2044+
20402045
skip: () => void; // A method for skipping the currently playing event item, provided it is not jump restricted
20412046
}
20422047

20432048
type PlayheadTimes = {
20442049
bufferedEnd: number; // The buffer end time relative to the playhead in the scheduled program
2045-
currentTime: number; // The current playhead time in the scheduled program
2050+
currentTime: number; // (get/set) The current playhead time in the scheduled program
20462051
duration: number; // The time at the end of the scheduled program
20472052
seekableStart: number; // The earliest available time where media is available (maps to the start of the first segment in primary media playlists)
2048-
seekTo: (time: number) => void; // A method for seeking to the designated time the scheduled program
20492053
};
2054+
2055+
interface InterstitialPlayer {
2056+
currentTime: number; // (get/set) The current playhead time within the interstitial break (no-op prior to playback)
2057+
duration: number; // the playout duration of the interstitial break
2058+
assetPlayers: (HlsAssetPlayer | null)[]; // The asset players assigned to the break asset list
2059+
playingIndex: number; // The index of the currently playing asset (or -1 prior to playback)
2060+
scheduleItem: InterstitialScheduleEventItem | null; // The interstitial schedule item for the break
2061+
}
2062+
```
2063+
2064+
The interstials manager can be used to get various apects of interstitial playback.
2065+
2066+
Time remaining in interstial event break:
2067+
2068+
```js
2069+
const interstitialPlayer = hls.interstitialsManager.interstitialPlayer;
2070+
// Is the interstitialPlayer playing an asset?
2071+
if (interstitialPlayer && interstitialPlayer.playingIndex > -1) {
2072+
const timeRemaining = Math.ceil(
2073+
interstitialPlayer.duration - interstitialPlayer.currentTime,
2074+
);
2075+
}
2076+
```
2077+
2078+
The last watched position of primary content:
2079+
2080+
```js
2081+
const primaryLastWatched = hls.interstitialsManager.primary.currentTime;
2082+
```
2083+
2084+
Integrated timeline position and time ranges:
2085+
2086+
```js
2087+
const currentTime = hls.interstitialsManager.integrated.currentTime;
2088+
const timelineRanges = hls.interstitialsManager.schedule.map((item) => {
2089+
return {
2090+
interstitial: item.event,
2091+
start: item.integrated.start,
2092+
end: item.integrated.end,
2093+
};
2094+
});
20502095
```
20512096
20522097
### Interstitial Events

src/config.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ export type HlsConfig = {
308308
interstitialsController?: typeof InterstitialsController;
309309
// Option to disable internal playback handling of Interstitials (set to false to disable Interstitials playback without disabling parsing and schedule events)
310310
enableInterstitialPlayback: boolean;
311-
// Option to disable appending Interstitals inline on same timeline and MediaSource as Primary media
311+
// Option to disable appending Interstitials inline on same timeline and MediaSource as Primary media
312312
interstitialAppendInPlace: boolean;
313313
// How many seconds past the end of a live playlist to preload Interstitial assets
314314
interstitialLiveLookAhead: number;
@@ -440,7 +440,7 @@ export const hlsDefaultConfig: HlsConfig = {
440440
enableEmsgMetadataCues: true,
441441
enableEmsgKLVMetadata: false,
442442
enableID3MetadataCues: true,
443-
enableInterstitialPlayback: __USE_INTERSTITALS__,
443+
enableInterstitialPlayback: __USE_INTERSTITIALS__,
444444
interstitialAppendInPlace: true,
445445
interstitialLiveLookAhead: 10,
446446
useMediaCapabilities: __USE_MEDIA_CAPABILITIES__,
@@ -533,7 +533,7 @@ export const hlsDefaultConfig: HlsConfig = {
533533
: defaultLoadPolicy,
534534
},
535535
interstitialAssetListLoadPolicy: {
536-
default: __USE_INTERSTITALS__
536+
default: __USE_INTERSTITIALS__
537537
? {
538538
maxTimeToFirstByteMs: 10000,
539539
maxLoadTimeMs: 30000,
@@ -582,7 +582,7 @@ export const hlsDefaultConfig: HlsConfig = {
582582
contentSteeringController: __USE_CONTENT_STEERING__
583583
? ContentSteeringController
584584
: undefined,
585-
interstitialsController: __USE_INTERSTITALS__
585+
interstitialsController: __USE_INTERSTITIALS__
586586
? InterstitialsController
587587
: undefined,
588588
};

src/controller/base-stream-controller.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ export default class BaseStreamController
454454
) {
455455
const config = this.hls.config;
456456
if (
457-
__USE_INTERSTITALS__ &&
457+
__USE_INTERSTITIALS__ &&
458458
config.interstitialsController &&
459459
config.enableInterstitialPlayback !== false &&
460460
frag.type !== PlaylistLevelType.SUBTITLE
@@ -488,7 +488,7 @@ export default class BaseStreamController
488488
}
489489
}
490490
}
491-
// Skip loading of fragments that overlap completely with appendInPlace interstitals
491+
// Skip loading of fragments that overlap completely with appendInPlace interstitials
492492
const playerQueue = interstitials?.playerQueue;
493493
if (playerQueue) {
494494
for (let i = playerQueue.length; i--; ) {

src/controller/id3-track-controller.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ class ID3TrackController implements ComponentAPI {
461461
this.id3Track.addCue(cue);
462462
cues[key] = cue;
463463
if (
464-
__USE_INTERSTITALS__ &&
464+
__USE_INTERSTITIALS__ &&
465465
this.hls.config.interstitialsController
466466
) {
467467
if (key === 'X-ASSET-LIST' || key === 'X-ASSET-URL') {

src/controller/interstitial-player.ts

+15-5
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@ import {
99
} from '../loader/interstitial-event';
1010
import { BufferHelper } from '../utils/buffer-helper';
1111
import type { HlsConfig } from '../config';
12+
import type { InterstitialScheduleEventItem } from '../controller/interstitials-schedule';
1213
import type Hls from '../hls';
1314
import type { BufferCodecsData, MediaAttachingData } from '../types/events';
1415

16+
export interface InterstitialPlayer {
17+
currentTime: number;
18+
duration: number;
19+
assetPlayers: (HlsAssetPlayer | null)[];
20+
playingIndex: number;
21+
scheduleItem: InterstitialScheduleEventItem | null;
22+
}
1523
export class HlsAssetPlayer {
1624
public readonly hls: Hls;
1725
public readonly interstitial: InterstitialEvent;
1826
public readonly assetItem: InterstitialAssetItem;
1927
public tracks: Partial<BufferCodecsData> | null = null;
2028
private hasDetails: boolean = false;
2129
private mediaAttached: HTMLMediaElement | null = null;
22-
private playoutOffset: number = 0;
2330

2431
constructor(
2532
HlsPlayerClass: typeof Hls,
@@ -49,8 +56,6 @@ export class HlsAssetPlayer {
4956
this.mediaAttached = media;
5057
const event = this.interstitial;
5158
if (event.playoutLimit) {
52-
this.playoutOffset =
53-
event.assetList[event.assetList.indexOf(assetItem)]?.startOffset || 0;
5459
media.addEventListener('timeupdate', this.checkPlayout);
5560
}
5661
});
@@ -59,7 +64,8 @@ export class HlsAssetPlayer {
5964
private checkPlayout = () => {
6065
const interstitial = this.interstitial;
6166
const playoutLimit = interstitial.playoutLimit;
62-
if (this.playoutOffset + this.currentTime >= playoutLimit) {
67+
const currentTime = this.currentTime;
68+
if (this.startOffset + currentTime >= playoutLimit) {
6369
this.hls.trigger(Events.PLAYOUT_LIMIT_REACHED, {});
6470
}
6571
};
@@ -98,7 +104,7 @@ export class HlsAssetPlayer {
98104
}
99105

100106
get duration(): number {
101-
const duration = this.assetItem?.duration;
107+
const duration = this.assetItem.duration;
102108
if (!duration) {
103109
return 0;
104110
}
@@ -113,6 +119,10 @@ export class HlsAssetPlayer {
113119
return Math.max(0, duration - this.currentTime);
114120
}
115121

122+
get startOffset(): number {
123+
return this.assetItem.startOffset;
124+
}
125+
116126
get timelineOffset(): number {
117127
return this.hls?.config.timelineOffset || 0;
118128
}

0 commit comments

Comments
 (0)