hls.js 1.5.12-0.canary.10340 → 1.5.12-0.canary.10343

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -90,7 +90,7 @@
90
90
  "babel-plugin-transform-remove-console": "6.9.4",
91
91
  "chai": "4.4.1",
92
92
  "chart.js": "2.9.4",
93
- "chromedriver": "125.0.2",
93
+ "chromedriver": "125.0.3",
94
94
  "doctoc": "2.2.1",
95
95
  "es-check": "7.2.1",
96
96
  "eslint": "8.57.0",
@@ -130,5 +130,5 @@
130
130
  "url-toolkit": "2.2.5",
131
131
  "wrangler": "3.59.0"
132
132
  },
133
- "version": "1.5.12-0.canary.10340"
133
+ "version": "1.5.12-0.canary.10343"
134
134
  }
@@ -4,7 +4,12 @@ import { Bufferable, BufferHelper } from '../utils/buffer-helper';
4
4
  import { FragmentState } from './fragment-tracker';
5
5
  import { Level } from '../types/level';
6
6
  import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
7
- import { Fragment, ElementaryStreamTypes, Part } from '../loader/fragment';
7
+ import {
8
+ Fragment,
9
+ ElementaryStreamTypes,
10
+ Part,
11
+ MediaFragment,
12
+ } from '../loader/fragment';
8
13
  import ChunkCache from '../demux/chunk-cache';
9
14
  import TransmuxerInterface from '../demux/transmuxer-interface';
10
15
  import { ChunkMetadata } from '../types/transmuxer';
@@ -40,7 +45,7 @@ import type { MediaPlaylist } from '../types/media-playlist';
40
45
  const TICK_INTERVAL = 100; // how often to tick in ms
41
46
 
42
47
  type WaitingForPTSData = {
43
- frag: Fragment;
48
+ frag: MediaFragment;
44
49
  part: Part | null;
45
50
  cache: ChunkCache;
46
51
  complete: boolean;
@@ -128,7 +133,9 @@ class AudioStreamController
128
133
  if (id === 'main') {
129
134
  const cc = frag.cc;
130
135
  this.initPTS[frag.cc] = { baseTime: initPTS, timescale };
131
- this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`);
136
+ this.log(
137
+ `InitPTS for cc: ${cc} found from main: ${initPTS}/${timescale}`,
138
+ );
132
139
  this.videoTrackCC = cc;
133
140
  // If we are waiting, tick immediately to unblock audio fragment transmuxing
134
141
  if (this.state === State.WAITING_INIT_PTS) {
@@ -561,7 +568,8 @@ class AudioStreamController
561
568
  }
562
569
 
563
570
  _handleFragmentLoadProgress(data: FragLoadedData) {
564
- const { frag, part, payload } = data;
571
+ const frag = data.frag as MediaFragment;
572
+ const { part, payload } = data;
565
573
  const { config, trackId, levels } = this;
566
574
  if (!levels) {
567
575
  this.warn(
@@ -606,7 +614,7 @@ class AudioStreamController
606
614
  const partial = partIndex !== -1;
607
615
  const chunkMeta = new ChunkMetadata(
608
616
  frag.level,
609
- frag.sn as number,
617
+ frag.sn,
610
618
  frag.stats.chunkCount,
611
619
  payload.byteLength,
612
620
  partIndex,
@@ -22,7 +22,7 @@ import {
22
22
  updateFragPTSDTS,
23
23
  } from '../utils/level-helper';
24
24
  import TransmuxerInterface from '../demux/transmuxer-interface';
25
- import { Fragment, Part } from '../loader/fragment';
25
+ import { Fragment, MediaFragment, Part } from '../loader/fragment';
26
26
  import FragmentLoader, {
27
27
  FragmentLoadProgressCallback,
28
28
  LoadError,
@@ -954,22 +954,24 @@ export default class BaseStreamController
954
954
  part.stats.parsing.end = now;
955
955
  }
956
956
  // See if part loading should be disabled/enabled based on buffer and playback position.
957
- if (frag.sn !== 'initSegment') {
958
- const levelDetails = this.getLevelDetails();
959
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
960
- const shouldLoadParts =
961
- loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
962
- if (shouldLoadParts !== this.loadingParts) {
963
- this.log(
964
- `LL-Part loading ${
965
- shouldLoadParts ? 'ON' : 'OFF'
966
- } after parsing segment ending @${frag.end.toFixed(2)}`,
967
- );
968
- this.loadingParts = shouldLoadParts;
969
- }
957
+ const levelDetails = this.getLevelDetails();
958
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
959
+ const shouldLoadParts =
960
+ loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
961
+ if (shouldLoadParts !== this.loadingParts) {
962
+ this.log(
963
+ `LL-Part loading ${
964
+ shouldLoadParts ? 'ON' : 'OFF'
965
+ } after parsing segment ending @${frag.end.toFixed(2)}`,
966
+ );
967
+ this.loadingParts = shouldLoadParts;
970
968
  }
971
-
972
- this.updateLevelTiming(frag, part, level, chunkMeta.partial);
969
+ this.updateLevelTiming(
970
+ frag as MediaFragment,
971
+ part,
972
+ level,
973
+ chunkMeta.partial,
974
+ );
973
975
  }
974
976
 
975
977
  private shouldLoadParts(
@@ -999,7 +1001,7 @@ export default class BaseStreamController
999
1001
 
1000
1002
  protected getCurrentContext(
1001
1003
  chunkMeta: ChunkMetadata,
1002
- ): { frag: Fragment; part: Part | null; level: Level } | null {
1004
+ ): { frag: MediaFragment; part: Part | null; level: Level } | null {
1003
1005
  const { levels, fragCurrent } = this;
1004
1006
  const { level: levelIndex, sn, part: partIndex } = chunkMeta;
1005
1007
  if (!levels?.[levelIndex]) {
@@ -1183,7 +1185,7 @@ export default class BaseStreamController
1183
1185
  const { config } = this;
1184
1186
  const start = fragments[0].start;
1185
1187
  const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
1186
- let frag: Fragment | null = null;
1188
+ let frag: MediaFragment | null = null;
1187
1189
 
1188
1190
  if (levelDetails.live) {
1189
1191
  const initialLiveManifestSize = config.initialLiveManifestSize;
@@ -1326,10 +1328,10 @@ export default class BaseStreamController
1326
1328
  */
1327
1329
  protected getInitialLiveFragment(
1328
1330
  levelDetails: LevelDetails,
1329
- fragments: Array<Fragment>,
1330
- ): Fragment | null {
1331
+ fragments: MediaFragment[],
1332
+ ): MediaFragment | null {
1331
1333
  const fragPrevious = this.fragPrevious;
1332
- let frag: Fragment | null = null;
1334
+ let frag: MediaFragment | null = null;
1333
1335
  if (fragPrevious) {
1334
1336
  if (levelDetails.hasProgramDateTime) {
1335
1337
  // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
@@ -1393,7 +1395,7 @@ export default class BaseStreamController
1393
1395
  bufferEnd: number,
1394
1396
  end: number,
1395
1397
  levelDetails: LevelDetails,
1396
- ): Fragment | null {
1398
+ ): MediaFragment | null {
1397
1399
  const { config } = this;
1398
1400
  let { fragPrevious } = this;
1399
1401
  let { fragments, endSN } = levelDetails;
@@ -1409,10 +1411,10 @@ export default class BaseStreamController
1409
1411
  if (loadingParts && fragmentHint && !this.bitrateTest) {
1410
1412
  // Include incomplete fragment with parts at end
1411
1413
  fragments = fragments.concat(fragmentHint);
1412
- endSN = fragmentHint.sn as number;
1414
+ endSN = fragmentHint.sn;
1413
1415
  }
1414
1416
 
1415
- let frag;
1417
+ let frag: MediaFragment | null;
1416
1418
  if (bufferEnd < end) {
1417
1419
  const lookupTolerance =
1418
1420
  bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
@@ -1649,7 +1651,7 @@ export default class BaseStreamController
1649
1651
  }
1650
1652
  const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
1651
1653
  if (gapTagEncountered) {
1652
- this.fragmentTracker.fragBuffered(frag, true);
1654
+ this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
1653
1655
  }
1654
1656
  // keep retrying until the limit will be reached
1655
1657
  const errorAction = data.errorAction;
@@ -1813,7 +1815,7 @@ export default class BaseStreamController
1813
1815
  }
1814
1816
 
1815
1817
  private updateLevelTiming(
1816
- frag: Fragment,
1818
+ frag: MediaFragment,
1817
1819
  part: Part | null,
1818
1820
  level: Level,
1819
1821
  partial: boolean,
@@ -1,5 +1,5 @@
1
1
  import BinarySearch from '../utils/binary-search';
2
- import { Fragment } from '../loader/fragment';
2
+ import type { Fragment, MediaFragment } from '../loader/fragment';
3
3
 
4
4
  /**
5
5
  * Returns first fragment whose endPdt value exceeds the given PDT, or null.
@@ -8,10 +8,10 @@ import { Fragment } from '../loader/fragment';
8
8
  * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
9
9
  */
10
10
  export function findFragmentByPDT(
11
- fragments: Array<Fragment>,
11
+ fragments: MediaFragment[],
12
12
  PDTValue: number | null,
13
13
  maxFragLookUpTolerance: number,
14
- ): Fragment | null {
14
+ ): MediaFragment | null {
15
15
  if (
16
16
  PDTValue === null ||
17
17
  !Array.isArray(fragments) ||
@@ -55,19 +55,17 @@ export function findFragmentByPDT(
55
55
  */
56
56
  export function findFragmentByPTS(
57
57
  fragPrevious: Fragment | null,
58
- fragments: Array<Fragment>,
58
+ fragments: MediaFragment[],
59
59
  bufferEnd: number = 0,
60
60
  maxFragLookUpTolerance: number = 0,
61
61
  nextFragLookupTolerance: number = 0.005,
62
- ): Fragment | null {
63
- let fragNext: Fragment | null = null;
62
+ ): MediaFragment | null {
63
+ let fragNext: MediaFragment | null = null;
64
64
  if (fragPrevious) {
65
65
  fragNext =
66
- fragments[
67
- (fragPrevious.sn as number) - (fragments[0].sn as number) + 1
68
- ] || null;
66
+ fragments[(fragPrevious.sn as number) - fragments[0].sn + 1] || null;
69
67
  // check for buffer-end rounding error
70
- const bufferEdgeError = fragPrevious.endDTS - bufferEnd;
68
+ const bufferEdgeError = (fragPrevious.endDTS as number) - bufferEnd;
71
69
  if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
72
70
  bufferEnd += 0.0000015;
73
71
  }
@@ -135,7 +133,7 @@ function fragmentWithinFastStartSwitch(
135
133
  export function fragmentWithinToleranceTest(
136
134
  bufferEnd = 0,
137
135
  maxFragLookUpTolerance = 0,
138
- candidate: Fragment,
136
+ candidate: MediaFragment,
139
137
  ) {
140
138
  // eagerly accept an accurate match (no tolerance)
141
139
  if (
@@ -189,7 +187,7 @@ export function fragmentWithinToleranceTest(
189
187
  export function pdtWithinToleranceTest(
190
188
  pdtBufferEnd: number,
191
189
  maxFragLookUpTolerance: number,
192
- candidate: Fragment,
190
+ candidate: MediaFragment,
193
191
  ): boolean {
194
192
  const candidateLookupTolerance =
195
193
  Math.min(
@@ -203,9 +201,9 @@ export function pdtWithinToleranceTest(
203
201
  }
204
202
 
205
203
  export function findFragWithCC(
206
- fragments: Fragment[],
204
+ fragments: MediaFragment[],
207
205
  cc: number,
208
- ): Fragment | null {
206
+ ): MediaFragment | null {
209
207
  return BinarySearch.search(fragments, (candidate) => {
210
208
  if (candidate.cc < cc) {
211
209
  return 1;
@@ -1,5 +1,5 @@
1
1
  import { Events } from '../events';
2
- import { Fragment, Part } from '../loader/fragment';
2
+ import { Fragment, MediaFragment, Part } from '../loader/fragment';
3
3
  import { PlaylistLevelType } from '../types/loader';
4
4
  import type { SourceBufferName } from '../types/buffer';
5
5
  import type {
@@ -107,7 +107,7 @@ export class FragmentTracker implements ComponentAPI {
107
107
  public getBufferedFrag(
108
108
  position: number,
109
109
  levelType: PlaylistLevelType,
110
- ): Fragment | null {
110
+ ): MediaFragment | null {
111
111
  return this.getFragAtPos(position, levelType, true);
112
112
  }
113
113
 
@@ -115,7 +115,7 @@ export class FragmentTracker implements ComponentAPI {
115
115
  position: number,
116
116
  levelType: PlaylistLevelType,
117
117
  buffered?: boolean,
118
- ): Fragment | null {
118
+ ): MediaFragment | null {
119
119
  const { fragments } = this;
120
120
  const keys = Object.keys(fragments);
121
121
  for (let i = keys.length; i--; ) {
@@ -149,13 +149,13 @@ export class FragmentTracker implements ComponentAPI {
149
149
  }
150
150
  // Check if any flagged fragments have been unloaded
151
151
  // excluding anything newer than appendedPartSn
152
- const appendedPartSn = (appendedPart?.fragment.sn || -1) as number;
152
+ const appendedPartSn = appendedPart?.fragment.sn || -1;
153
153
  Object.keys(this.fragments).forEach((key) => {
154
154
  const fragmentEntity = this.fragments[key];
155
155
  if (!fragmentEntity) {
156
156
  return;
157
157
  }
158
- if (appendedPartSn >= (fragmentEntity.body.sn as number)) {
158
+ if (appendedPartSn >= fragmentEntity.body.sn) {
159
159
  return;
160
160
  }
161
161
  if (!fragmentEntity.buffered && !fragmentEntity.loaded) {
@@ -189,11 +189,11 @@ export class FragmentTracker implements ComponentAPI {
189
189
  */
190
190
  public detectPartialFragments(data: FragBufferedData) {
191
191
  const timeRanges = this.timeRanges;
192
- const { frag, part } = data;
193
- if (!timeRanges || frag.sn === 'initSegment') {
192
+ if (!timeRanges || data.frag.sn === 'initSegment') {
194
193
  return;
195
194
  }
196
195
 
196
+ const frag = data.frag as MediaFragment;
197
197
  const fragKey = getFragmentKey(frag);
198
198
  const fragmentEntity = this.fragments[fragKey];
199
199
  if (!fragmentEntity || (fragmentEntity.buffered && frag.gap)) {
@@ -209,7 +209,7 @@ export class FragmentTracker implements ComponentAPI {
209
209
  const partial = isFragHint || streamInfo.partial === true;
210
210
  fragmentEntity.range[elementaryStream] = this.getBufferedTimes(
211
211
  frag,
212
- part,
212
+ data.part,
213
213
  partial,
214
214
  timeRange,
215
215
  );
@@ -224,7 +224,7 @@ export class FragmentTracker implements ComponentAPI {
224
224
  }
225
225
  if (!isPartial(fragmentEntity)) {
226
226
  // Remove older fragment parts from lookup after frag is tracked as buffered
227
- this.removeParts((frag.sn as number) - 1, frag.type);
227
+ this.removeParts(frag.sn - 1, frag.type);
228
228
  }
229
229
  } else {
230
230
  // remove fragment if nothing was appended
@@ -238,11 +238,11 @@ export class FragmentTracker implements ComponentAPI {
238
238
  return;
239
239
  }
240
240
  this.activePartLists[levelType] = activeParts.filter(
241
- (part) => (part.fragment.sn as number) >= snToKeep,
241
+ (part) => part.fragment.sn >= snToKeep,
242
242
  );
243
243
  }
244
244
 
245
- public fragBuffered(frag: Fragment, force?: true) {
245
+ public fragBuffered(frag: MediaFragment, force?: true) {
246
246
  const fragKey = getFragmentKey(frag);
247
247
  let fragmentEntity = this.fragments[fragKey];
248
248
  if (!fragmentEntity && force) {
@@ -388,15 +388,15 @@ export class FragmentTracker implements ComponentAPI {
388
388
  }
389
389
 
390
390
  private onFragLoaded(event: Events.FRAG_LOADED, data: FragLoadedData) {
391
- const { frag, part } = data;
392
391
  // don't track initsegment (for which sn is not a number)
393
392
  // don't track frags used for bitrateTest, they're irrelevant.
394
- if (frag.sn === 'initSegment' || frag.bitrateTest) {
393
+ if (data.frag.sn === 'initSegment' || data.frag.bitrateTest) {
395
394
  return;
396
395
  }
397
396
 
397
+ const frag = data.frag as MediaFragment;
398
398
  // Fragment entity `loaded` FragLoadedData is null when loading parts
399
- const loaded = part ? null : data;
399
+ const loaded = data.part ? null : data;
400
400
 
401
401
  const fragKey = getFragmentKey(frag);
402
402
  this.fragments[fragKey] = {
@@ -9,10 +9,12 @@ import {
9
9
  isDateRangeCueAttribute,
10
10
  isSCTE35Attribute,
11
11
  } from '../loader/date-range';
12
+ import { LevelDetails } from '../loader/level-details';
12
13
  import { MetadataSchema } from '../types/demuxer';
13
14
  import type {
14
15
  BufferFlushingData,
15
16
  FragParsingMetadataData,
17
+ LevelPTSUpdatedData,
16
18
  LevelUpdatedData,
17
19
  MediaAttachedData,
18
20
  } from '../types/events';
@@ -69,10 +71,6 @@ const MAX_CUE_ENDTIME = (() => {
69
71
  return Number.POSITIVE_INFINITY;
70
72
  })();
71
73
 
72
- function dateRangeDateToTimelineSeconds(date: Date, offset: number): number {
73
- return date.getTime() / 1000 - offset;
74
- }
75
-
76
74
  function hexToArrayBuffer(str): ArrayBuffer {
77
75
  return Uint8Array.from(
78
76
  str
@@ -100,7 +98,7 @@ class ID3TrackController implements ComponentAPI {
100
98
  this._registerListeners();
101
99
  }
102
100
 
103
- destroy() {
101
+ public destroy() {
104
102
  this._unregisterListeners();
105
103
  this.id3Track = null;
106
104
  this.media = null;
@@ -117,6 +115,7 @@ class ID3TrackController implements ComponentAPI {
117
115
  hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
118
116
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
119
117
  hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
118
+ hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
120
119
  }
121
120
 
122
121
  private _unregisterListeners() {
@@ -127,17 +126,18 @@ class ID3TrackController implements ComponentAPI {
127
126
  hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
128
127
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
129
128
  hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
129
+ hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
130
130
  }
131
131
 
132
132
  // Add ID3 metatadata text track.
133
- protected onMediaAttached(
133
+ private onMediaAttached(
134
134
  event: Events.MEDIA_ATTACHED,
135
135
  data: MediaAttachedData,
136
136
  ): void {
137
137
  this.media = data.media;
138
138
  }
139
139
 
140
- protected onMediaDetaching(): void {
140
+ private onMediaDetaching(): void {
141
141
  if (this.id3Track) {
142
142
  clearCurrentCues(this.id3Track);
143
143
  this.id3Track = null;
@@ -150,13 +150,13 @@ class ID3TrackController implements ComponentAPI {
150
150
  this.dateRangeCuesAppended = {};
151
151
  }
152
152
 
153
- createTrack(media: HTMLMediaElement): TextTrack {
153
+ private createTrack(media: HTMLMediaElement): TextTrack {
154
154
  const track = this.getID3Track(media.textTracks) as TextTrack;
155
155
  track.mode = 'hidden';
156
156
  return track;
157
157
  }
158
158
 
159
- getID3Track(textTracks: TextTrackList): TextTrack | void {
159
+ private getID3Track(textTracks: TextTrackList): TextTrack | void {
160
160
  if (!this.media) {
161
161
  return;
162
162
  }
@@ -173,7 +173,7 @@ class ID3TrackController implements ComponentAPI {
173
173
  return this.media.addTextTrack('metadata', 'id3');
174
174
  }
175
175
 
176
- onFragParsingMetadata(
176
+ private onFragParsingMetadata(
177
177
  event: Events.FRAG_PARSING_METADATA,
178
178
  data: FragParsingMetadataData,
179
179
  ) {
@@ -247,7 +247,7 @@ class ID3TrackController implements ComponentAPI {
247
247
  }
248
248
  }
249
249
 
250
- updateId3CueEnds(startTime: number, type: MetadataSchema) {
250
+ private updateId3CueEnds(startTime: number, type: MetadataSchema) {
251
251
  const cues = this.id3Track?.cues;
252
252
  if (cues) {
253
253
  for (let i = cues.length; i--; ) {
@@ -263,7 +263,7 @@ class ID3TrackController implements ComponentAPI {
263
263
  }
264
264
  }
265
265
 
266
- onBufferFlushing(
266
+ private onBufferFlushing(
267
267
  event: Events.BUFFER_FLUSHING,
268
268
  { startOffset, endOffset, type }: BufferFlushingData,
269
269
  ) {
@@ -295,7 +295,23 @@ class ID3TrackController implements ComponentAPI {
295
295
  }
296
296
  }
297
297
 
298
- onLevelUpdated(event: Events.LEVEL_UPDATED, { details }: LevelUpdatedData) {
298
+ private onLevelUpdated(
299
+ event: Events.LEVEL_UPDATED,
300
+ { details }: LevelUpdatedData,
301
+ ) {
302
+ this.updateDateRangeCues(details, true);
303
+ }
304
+
305
+ private onLevelPtsUpdated(
306
+ event: Events.LEVEL_PTS_UPDATED,
307
+ data: LevelPTSUpdatedData,
308
+ ) {
309
+ if (Math.abs(data.drift) > 0.01) {
310
+ this.updateDateRangeCues(data.details);
311
+ }
312
+ }
313
+
314
+ private updateDateRangeCues(details: LevelDetails, removeOldCues?: true) {
299
315
  if (
300
316
  !this.media ||
301
317
  !details.hasProgramDateTime ||
@@ -307,7 +323,7 @@ class ID3TrackController implements ComponentAPI {
307
323
  const { dateRanges } = details;
308
324
  const ids = Object.keys(dateRanges);
309
325
  // Remove cues from track not found in details.dateRanges
310
- if (id3Track) {
326
+ if (id3Track && removeOldCues) {
311
327
  const idsToRemove = Object.keys(dateRangeCuesAppended).filter(
312
328
  (id) => !ids.includes(id),
313
329
  );
@@ -329,26 +345,20 @@ class ID3TrackController implements ComponentAPI {
329
345
  this.id3Track = this.createTrack(this.media);
330
346
  }
331
347
 
332
- const dateTimeOffset =
333
- (lastFragment.programDateTime as number) / 1000 - lastFragment.start;
334
348
  const Cue = getCueClass();
335
-
336
349
  for (let i = 0; i < ids.length; i++) {
337
350
  const id = ids[i];
338
351
  const dateRange = dateRanges[id];
339
- const startTime = dateRangeDateToTimelineSeconds(
340
- dateRange.startDate,
341
- dateTimeOffset,
342
- );
352
+ const startTime = dateRange.startTime;
343
353
 
344
354
  // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)
345
355
  const appendedDateRangeCues = dateRangeCuesAppended[id];
346
356
  const cues = appendedDateRangeCues?.cues || {};
347
357
  let durationKnown = appendedDateRangeCues?.durationKnown || false;
348
358
  let endTime = MAX_CUE_ENDTIME;
349
- const endDate = dateRange.endDate;
350
- if (endDate) {
351
- endTime = dateRangeDateToTimelineSeconds(endDate, dateTimeOffset);
359
+ const { duration, endDate } = dateRange;
360
+ if (endDate && duration !== null) {
361
+ endTime = startTime + duration;
352
362
  durationKnown = true;
353
363
  } else if (dateRange.endOnNext && !durationKnown) {
354
364
  const nextDateRangeWithSameClass = ids.reduce(
@@ -369,10 +379,7 @@ class ID3TrackController implements ComponentAPI {
369
379
  null,
370
380
  );
371
381
  if (nextDateRangeWithSameClass) {
372
- endTime = dateRangeDateToTimelineSeconds(
373
- nextDateRangeWithSameClass.startDate,
374
- dateTimeOffset,
375
- );
382
+ endTime = nextDateRangeWithSameClass.startTime;
376
383
  durationKnown = true;
377
384
  }
378
385
  }
@@ -389,6 +396,9 @@ class ID3TrackController implements ComponentAPI {
389
396
  if (cue) {
390
397
  if (durationKnown && !appendedDateRangeCues.durationKnown) {
391
398
  cue.endTime = endTime;
399
+ } else if (Math.abs(cue.startTime - startTime) > 0.01) {
400
+ cue.startTime = startTime;
401
+ cue.endTime = endTime;
392
402
  }
393
403
  } else if (Cue) {
394
404
  let data = dateRange.attr[key];
@@ -4,7 +4,11 @@ import { Events } from '../events';
4
4
  import { BufferHelper, BufferInfo } from '../utils/buffer-helper';
5
5
  import { FragmentState } from './fragment-tracker';
6
6
  import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
7
- import { ElementaryStreamTypes, Fragment } from '../loader/fragment';
7
+ import {
8
+ ElementaryStreamTypes,
9
+ Fragment,
10
+ MediaFragment,
11
+ } from '../loader/fragment';
8
12
  import TransmuxerInterface from '../demux/transmuxer-interface';
9
13
  import { ChunkMetadata } from '../types/transmuxer';
10
14
  import GapController, { MAX_START_GAP_JUMP } from './gap-controller';
@@ -691,7 +695,8 @@ export default class StreamController
691
695
  }
692
696
 
693
697
  protected _handleFragmentLoadProgress(data: FragLoadedData) {
694
- const { frag, part, payload } = data;
698
+ const frag = data.frag as MediaFragment;
699
+ const { part, payload } = data;
695
700
  const { levels } = this;
696
701
  if (!levels) {
697
702
  this.warn(
@@ -729,7 +734,7 @@ export default class StreamController
729
734
  const partial = partIndex !== -1;
730
735
  const chunkMeta = new ChunkMetadata(
731
736
  frag.level,
732
- frag.sn as number,
737
+ frag.sn,
733
738
  frag.stats.chunkCount,
734
739
  payload.byteLength,
735
740
  partIndex,
@@ -1113,7 +1118,7 @@ export default class StreamController
1113
1118
  }
1114
1119
 
1115
1120
  // Avoid buffering if backtracking this fragment
1116
- if (video && details && frag.sn !== 'initSegment') {
1121
+ if (video && details) {
1117
1122
  const prevFrag = details.fragments[frag.sn - 1 - details.startSN];
1118
1123
  const isFirstFragment = frag.sn === details.startSN;
1119
1124
  const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
@@ -18,7 +18,7 @@ import type Hls from '../hls';
18
18
  import type { FragmentTracker } from './fragment-tracker';
19
19
  import type KeyLoader from '../loader/key-loader';
20
20
  import type { LevelDetails } from '../loader/level-details';
21
- import type { Fragment } from '../loader/fragment';
21
+ import type { Fragment, MediaFragment } from '../loader/fragment';
22
22
  import type {
23
23
  ErrorData,
24
24
  FragLoadedData,
@@ -156,7 +156,7 @@ export class SubtitleStreamController
156
156
  };
157
157
  buffered.push(timeRange);
158
158
  }
159
- this.fragmentTracker.fragBuffered(frag);
159
+ this.fragmentTracker.fragBuffered(frag as MediaFragment);
160
160
  this.fragBufferedComplete(frag, null);
161
161
  }
162
162
 
@@ -206,7 +206,7 @@ export class SubtitleStreamController
206
206
 
207
207
  if (frag?.type === PlaylistLevelType.SUBTITLE) {
208
208
  if (data.details === ErrorDetails.FRAG_GAP) {
209
- this.fragmentTracker.fragBuffered(frag, true);
209
+ this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
210
210
  }
211
211
  if (this.fragCurrent) {
212
212
  this.fragCurrent.abortRequests();
@@ -13,7 +13,7 @@ import Transmuxer, {
13
13
  import { logger } from '../utils/logger';
14
14
  import { ErrorTypes, ErrorDetails } from '../errors';
15
15
  import { EventEmitter } from 'eventemitter3';
16
- import { Fragment, Part } from '../loader/fragment';
16
+ import { MediaFragment, Part } from '../loader/fragment';
17
17
  import { getM2TSSupportedAudioTypes } from '../utils/codecs';
18
18
  import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
19
19
  import type Hls from '../hls';
@@ -26,7 +26,7 @@ export default class TransmuxerInterface {
26
26
  private hls: Hls;
27
27
  private id: PlaylistLevelType;
28
28
  private observer: HlsEventEmitter;
29
- private frag: Fragment | null = null;
29
+ private frag: MediaFragment | null = null;
30
30
  private part: Part | null = null;
31
31
  private useWorker: boolean;
32
32
  private workerContext: WorkerContext | null = null;
@@ -173,7 +173,7 @@ export default class TransmuxerInterface {
173
173
  initSegmentData: Uint8Array | undefined,
174
174
  audioCodec: string | undefined,
175
175
  videoCodec: string | undefined,
176
- frag: Fragment,
176
+ frag: MediaFragment,
177
177
  part: Part | null,
178
178
  duration: number,
179
179
  accurateTimeOffset: boolean,
@@ -189,7 +189,7 @@ export default class TransmuxerInterface {
189
189
 
190
190
  const discontinuity = !(lastFrag && frag.cc === lastFrag.cc);
191
191
  const trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level);
192
- const snDiff = lastFrag ? chunkMeta.sn - (lastFrag.sn as number) : -1;
192
+ const snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1;
193
193
  const partDiff = this.part ? chunkMeta.part - this.part.index : -1;
194
194
  const progressive =
195
195
  snDiff === 0 &&
package/src/hls.ts CHANGED
@@ -1068,7 +1068,7 @@ export type {
1068
1068
  KeySystems,
1069
1069
  KeySystemFormats,
1070
1070
  } from './utils/mediakeys-helper';
1071
- export type { DateRange } from './loader/date-range';
1071
+ export type { DateRange, DateRangeCue } from './loader/date-range';
1072
1072
  export type { LoadStats } from './loader/load-stats';
1073
1073
  export type { LevelKey } from './loader/level-key';
1074
1074
  export type { LevelDetails } from './loader/level-details';
@@ -1118,6 +1118,7 @@ export type { ChunkMetadata } from './types/transmuxer';
1118
1118
  export type {
1119
1119
  BaseSegment,
1120
1120
  Fragment,
1121
+ MediaFragment,
1121
1122
  Part,
1122
1123
  ElementaryStreams,
1123
1124
  ElementaryStreamTypes,
@@ -1184,3 +1185,4 @@ export type {
1184
1185
  IErrorAction,
1185
1186
  } from './controller/error-controller';
1186
1187
  export type { AttrList } from './utils/attr-list';
1188
+ export type { ParsedMultivariantPlaylist } from './loader/m3u8-parser';