hls.js 1.6.0-beta.1.0.canary.10743 → 1.6.0-beta.1.0.canary.10746

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
@@ -114,7 +114,7 @@
114
114
  "lint-staged": "15.2.10",
115
115
  "markdown-styles": "3.2.0",
116
116
  "micromatch": "4.0.8",
117
- "mocha": "10.7.3",
117
+ "mocha": "10.8.2",
118
118
  "node-fetch": "3.3.2",
119
119
  "npm-run-all2": "6.2.6",
120
120
  "prettier": "3.3.3",
@@ -130,5 +130,5 @@
130
130
  "url-toolkit": "2.2.5",
131
131
  "wrangler": "3.85.0"
132
132
  },
133
- "version": "1.6.0-beta.1.0.canary.10743"
133
+ "version": "1.6.0-beta.1.0.canary.10746"
134
134
  }
@@ -33,7 +33,7 @@ export default class LatencyController implements ComponentAPI {
33
33
  if (config.liveMaxLatencyDuration !== undefined) {
34
34
  return config.liveMaxLatencyDuration;
35
35
  }
36
- const levelDetails = this.hls.latestLevelDetails;
36
+ const levelDetails = this.hls?.latestLevelDetails;
37
37
  return levelDetails
38
38
  ? config.liveMaxLatencyDurationCount * levelDetails.targetduration
39
39
  : 0;
@@ -11,8 +11,8 @@ import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
11
11
  import { ChunkMetadata } from '../types/transmuxer';
12
12
  import { BufferHelper } from '../utils/buffer-helper';
13
13
  import { pickMostCompleteCodecName } from '../utils/codecs';
14
- import type Hls from '../hls';
15
14
  import type { FragmentTracker } from './fragment-tracker';
15
+ import type Hls from '../hls';
16
16
  import type { Fragment, MediaFragment } from '../loader/fragment';
17
17
  import type KeyLoader from '../loader/key-loader';
18
18
  import type { LevelDetails } from '../loader/level-details';
@@ -1,6 +1,6 @@
1
1
  import { AttrList } from '../utils/attr-list';
2
2
  import { logger } from '../utils/logger';
3
- import type { Fragment } from './fragment';
3
+ import type { MediaFragmentRef } from './fragment';
4
4
 
5
5
  // Avoid exporting const enum so that these values can be inlined
6
6
  const enum DateRangeAttribute {
@@ -47,7 +47,7 @@ export function isSCTE35Attribute(attrName: string): boolean {
47
47
 
48
48
  export class DateRange {
49
49
  public attr: AttrList;
50
- public tagAnchor: Fragment | null;
50
+ public tagAnchor: MediaFragmentRef | null;
51
51
  public tagOrder: number;
52
52
  private _startDate: Date;
53
53
  private _endDate?: Date;
@@ -29,23 +29,27 @@ export type ElementaryStreams = Record<
29
29
  ElementaryStreamInfo | null
30
30
  >;
31
31
 
32
+ export type Base = {
33
+ url: string;
34
+ };
35
+
32
36
  export class BaseSegment {
33
37
  private _byteRange: [number, number] | null = null;
34
38
  private _url: string | null = null;
39
+ private _stats: LoadStats | null = null;
40
+ private _streams: ElementaryStreams | null = null;
35
41
 
36
42
  // baseurl is the URL to the playlist
37
- public readonly baseurl: string;
43
+ public readonly base: Base;
44
+
38
45
  // relurl is the portion of the URL that comes from inside the playlist.
39
46
  public relurl?: string;
40
- // Holds the types of data this fragment supports
41
- public elementaryStreams: ElementaryStreams = {
42
- [ElementaryStreamTypes.AUDIO]: null,
43
- [ElementaryStreamTypes.VIDEO]: null,
44
- [ElementaryStreamTypes.AUDIOVIDEO]: null,
45
- };
46
47
 
47
- constructor(baseurl: string) {
48
- this.baseurl = baseurl;
48
+ constructor(base: Base | string) {
49
+ if (typeof base === 'string') {
50
+ base = { url: base };
51
+ }
52
+ this.base = base;
49
53
  }
50
54
 
51
55
  // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
@@ -60,8 +64,12 @@ export class BaseSegment {
60
64
  this._byteRange = [start, parseInt(params[0]) + start];
61
65
  }
62
66
 
67
+ get baseurl(): string {
68
+ return this.base.url;
69
+ }
70
+
63
71
  get byteRange(): [number, number] | [] {
64
- if (!this._byteRange) {
72
+ if (this._byteRange === null) {
65
73
  return [];
66
74
  }
67
75
 
@@ -76,6 +84,40 @@ export class BaseSegment {
76
84
  return this.byteRange[1];
77
85
  }
78
86
 
87
+ get elementaryStreams(): ElementaryStreams {
88
+ if (this._streams === null) {
89
+ this._streams = {
90
+ [ElementaryStreamTypes.AUDIO]: null,
91
+ [ElementaryStreamTypes.VIDEO]: null,
92
+ [ElementaryStreamTypes.AUDIOVIDEO]: null,
93
+ };
94
+ }
95
+ return this._streams;
96
+ }
97
+
98
+ set elementaryStreams(value: ElementaryStreams) {
99
+ this._streams = value;
100
+ }
101
+
102
+ get hasStats(): boolean {
103
+ return this._stats !== null;
104
+ }
105
+
106
+ get hasStreams(): boolean {
107
+ return this._streams !== null;
108
+ }
109
+
110
+ get stats(): LoadStats {
111
+ if (this._stats === null) {
112
+ this._stats = new LoadStats();
113
+ }
114
+ return this._stats;
115
+ }
116
+
117
+ set stats(value: LoadStats) {
118
+ this._stats = value;
119
+ }
120
+
79
121
  get url(): string {
80
122
  if (!this._url && this.baseurl && this.relurl) {
81
123
  this._url = buildAbsoluteURL(this.baseurl, this.relurl, {
@@ -99,16 +141,26 @@ export class BaseSegment {
99
141
 
100
142
  export interface MediaFragment extends Fragment {
101
143
  sn: number;
144
+ ref: MediaFragmentRef;
102
145
  }
103
146
 
147
+ export type MediaFragmentRef = {
148
+ base: Base;
149
+ start: number;
150
+ duration: number;
151
+ sn: number;
152
+ programDateTime: number | null;
153
+ };
154
+
104
155
  /**
105
156
  * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
106
157
  */
107
158
  export class Fragment extends BaseSegment {
108
159
  private _decryptdata: LevelKey | null = null;
160
+ private _programDateTime: number | null = null;
161
+ private _ref: MediaFragmentRef | null = null;
109
162
 
110
163
  public rawProgramDateTime: string | null = null;
111
- public programDateTime: number | null = null;
112
164
  public tagList: Array<string[]> = [];
113
165
 
114
166
  // EXTINF has to be present for a m3u8 to be considered valid
@@ -147,8 +199,6 @@ export class Fragment extends BaseSegment {
147
199
  public maxStartPTS?: number;
148
200
  // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
149
201
  public minEndPTS?: number;
150
- // Load/parse timing information
151
- public stats: LoadStats = new LoadStats();
152
202
  // Init Segment bytes (unset for media segments)
153
203
  public data?: Uint8Array;
154
204
  // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered
@@ -164,8 +214,8 @@ export class Fragment extends BaseSegment {
164
214
  // Deprecated
165
215
  public urlId: number = 0;
166
216
 
167
- constructor(type: PlaylistLevelType, baseurl: string) {
168
- super(baseurl);
217
+ constructor(type: PlaylistLevelType, base: Base | string) {
218
+ super(base);
169
219
  this.type = type;
170
220
  }
171
221
 
@@ -203,10 +253,6 @@ export class Fragment extends BaseSegment {
203
253
  return null;
204
254
  }
205
255
 
206
- if (!Number.isFinite(this.programDateTime)) {
207
- return null;
208
- }
209
-
210
256
  const duration = !Number.isFinite(this.duration) ? 0 : this.duration;
211
257
 
212
258
  return this.programDateTime + duration * 1000;
@@ -225,10 +271,58 @@ export class Fragment extends BaseSegment {
225
271
  return true;
226
272
  }
227
273
  }
228
-
229
274
  return false;
230
275
  }
231
276
 
277
+ get programDateTime(): number | null {
278
+ if (this._programDateTime === null && this.rawProgramDateTime) {
279
+ this.programDateTime = Date.parse(this.rawProgramDateTime);
280
+ }
281
+ return this._programDateTime;
282
+ }
283
+
284
+ set programDateTime(value: number | null) {
285
+ if (!Number.isFinite(value)) {
286
+ this._programDateTime = this.rawProgramDateTime = null;
287
+ return;
288
+ }
289
+ this._programDateTime = value;
290
+ }
291
+
292
+ get ref(): MediaFragmentRef | null {
293
+ if (this.sn === 'initSegment') {
294
+ return null;
295
+ }
296
+ if (!this._ref) {
297
+ this._ref = {
298
+ base: this.base,
299
+ start: this.start,
300
+ duration: this.duration,
301
+ sn: this.sn,
302
+ programDateTime: this.programDateTime,
303
+ };
304
+ }
305
+ return this._ref;
306
+ }
307
+
308
+ addStart(value: number) {
309
+ this.setStart(this.start + value);
310
+ }
311
+
312
+ setStart(value: number) {
313
+ this.start = value;
314
+ if (this._ref) {
315
+ this._ref.start = value;
316
+ }
317
+ }
318
+
319
+ setDuration(value: number) {
320
+ this.duration = value;
321
+ if (this._ref) {
322
+ this._ref.duration = value;
323
+ }
324
+ }
325
+
232
326
  setKeyFormat(keyFormat: KeySystemFormats) {
233
327
  if (this.levelkeys) {
234
328
  const key = this.levelkeys[keyFormat];
@@ -282,16 +376,15 @@ export class Part extends BaseSegment {
282
376
  public readonly relurl: string;
283
377
  public readonly fragment: MediaFragment;
284
378
  public readonly index: number;
285
- public stats: LoadStats = new LoadStats();
286
379
 
287
380
  constructor(
288
381
  partAttrs: AttrList,
289
382
  frag: MediaFragment,
290
- baseurl: string,
383
+ base: Base | string,
291
384
  index: number,
292
385
  previous?: Part,
293
386
  ) {
294
- super(baseurl);
387
+ super(base);
295
388
  this.duration = partAttrs.decimalFloatingPoint('DURATION');
296
389
  this.gap = partAttrs.bool('GAP');
297
390
  this.independent = partAttrs.bool('INDEPENDENT');
@@ -1,6 +1,6 @@
1
1
  import { hash } from '../utils/hash';
2
2
  import type { DateRange, DateRangeCue } from './date-range';
3
- import type { Fragment } from './fragment';
3
+ import type { MediaFragmentRef } from './fragment';
4
4
  import type { Loader, LoaderContext } from '../types/loader';
5
5
 
6
6
  export const ALIGNED_END_THRESHOLD_SECONDS = 0.02; // 0.1 // 0.2
@@ -74,7 +74,7 @@ export class InterstitialEvent {
74
74
  public assetList: InterstitialAssetItem[] = [];
75
75
  public assetListLoader?: Loader<LoaderContext>;
76
76
  public assetListResponse: AssetListJSON | null = null;
77
- public resumeAnchor?: Fragment;
77
+ public resumeAnchor?: MediaFragmentRef;
78
78
  public error?: Error;
79
79
 
80
80
  constructor(dateRange: DateRange, base: BaseData) {
@@ -261,11 +261,14 @@ export class InterstitialEvent {
261
261
  }
262
262
  }
263
263
 
264
- function getSnapToFragmentTime(time: number, frag: Fragment) {
264
+ function getSnapToFragmentTime(time: number, frag: MediaFragmentRef) {
265
265
  return time - frag.start < frag.duration / 2 &&
266
- !(Math.abs(time - frag.end) < ALIGNED_END_THRESHOLD_SECONDS)
266
+ !(
267
+ Math.abs(time - (frag.start + frag.duration)) <
268
+ ALIGNED_END_THRESHOLD_SECONDS
269
+ )
267
270
  ? frag.start
268
- : frag.end;
271
+ : frag.start + frag.duration;
269
272
  }
270
273
 
271
274
  export function getInterstitialUrl(
@@ -48,8 +48,6 @@ const LEVEL_PLAYLIST_REGEX_FAST = new RegExp(
48
48
  [
49
49
  /#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source, // duration (#EXTINF:<duration>,<title>), group 1 => duration, group 2 => title
50
50
  /(?!#) *(\S[^\r\n]*)/.source, // segment URI, group 3 => the URI (note newline is not eaten)
51
- /#EXT-X-BYTERANGE:*(.+)/.source, // next segment's byterange, group 4 => range spec (x@y)
52
- /#EXT-X-PROGRAM-DATE-TIME:(.+)/.source, // next segment's program date/time group 5 => the datetime spec
53
51
  /#.*/.source, // All other non-segment oriented tags will match with all groups empty
54
52
  ].join('|'),
55
53
  'g',
@@ -58,7 +56,7 @@ const LEVEL_PLAYLIST_REGEX_FAST = new RegExp(
58
56
  const LEVEL_PLAYLIST_REGEX_SLOW = new RegExp(
59
57
  [
60
58
  /#(EXTM3U)/.source,
61
- /#EXT-X-(DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/
59
+ /#EXT-X-(PROGRAM-DATE-TIME|BYTERANGE|DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/
62
60
  .source,
63
61
  /#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/
64
62
  .source,
@@ -306,6 +304,7 @@ export default class M3U8Parser {
306
304
  levelUrlId: number,
307
305
  multivariantVariableList: VariableMap | null,
308
306
  ): LevelDetails {
307
+ const base = { url: baseurl };
309
308
  const level = new LevelDetails(baseurl);
310
309
  const fragments: M3U8ParserFragments = level.fragments;
311
310
  const programDateTimes: MediaFragment[] = [];
@@ -316,7 +315,7 @@ export default class M3U8Parser {
316
315
  let totalduration = 0;
317
316
  let discontinuityCounter = 0;
318
317
  let prevFrag: Fragment | null = null;
319
- let frag: Fragment = new Fragment(type, baseurl);
318
+ let frag: Fragment = new Fragment(type, base);
320
319
  let result: RegExpExecArray | RegExpMatchArray | null;
321
320
  let i: number;
322
321
  let levelkeys: { [key: string]: LevelKey } | undefined;
@@ -333,7 +332,7 @@ export default class M3U8Parser {
333
332
  while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) {
334
333
  if (createNextFrag) {
335
334
  createNextFrag = false;
336
- frag = new Fragment(type, baseurl);
335
+ frag = new Fragment(type, base);
337
336
  // setup the next fragment for part loading
338
337
  frag.playlistOffset = totalduration;
339
338
  frag.start = totalduration;
@@ -342,8 +341,10 @@ export default class M3U8Parser {
342
341
  frag.level = id;
343
342
  if (currentInitSegment) {
344
343
  frag.initSegment = currentInitSegment;
345
- frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
346
- currentInitSegment.rawProgramDateTime = null;
344
+ if (currentInitSegment.rawProgramDateTime) {
345
+ frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
346
+ currentInitSegment.rawProgramDateTime = null;
347
+ }
347
348
  if (nextByteRange) {
348
349
  frag.setByteRange(nextByteRange);
349
350
  nextByteRange = null;
@@ -387,22 +388,6 @@ export default class M3U8Parser {
387
388
  currentPart = 0;
388
389
  createNextFrag = true;
389
390
  }
390
- } else if (result[4]) {
391
- // X-BYTERANGE
392
- const data = (' ' + result[4]).slice(1);
393
- if (prevFrag) {
394
- frag.setByteRange(data, prevFrag);
395
- } else {
396
- frag.setByteRange(data);
397
- }
398
- } else if (result[5]) {
399
- // PROGRAM-DATE-TIME
400
- // avoid sliced strings https://github.com/video-dev/hls.js/issues/939
401
- frag.rawProgramDateTime = (' ' + result[5]).slice(1);
402
- frag.tagList.push(['PROGRAM-DATE-TIME', frag.rawProgramDateTime]);
403
- if (firstPdtIndex === -1) {
404
- firstPdtIndex = fragments.length;
405
- }
406
391
  } else {
407
392
  result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW);
408
393
  if (!result) {
@@ -410,7 +395,7 @@ export default class M3U8Parser {
410
395
  continue;
411
396
  }
412
397
  for (i = 1; i < result.length; i++) {
413
- if (typeof result[i] !== 'undefined') {
398
+ if (result[i] !== undefined) {
414
399
  break;
415
400
  }
416
401
  }
@@ -418,9 +403,24 @@ export default class M3U8Parser {
418
403
  // avoid sliced strings https://github.com/video-dev/hls.js/issues/939
419
404
  const tag = (' ' + result[i]).slice(1);
420
405
  const value1 = (' ' + result[i + 1]).slice(1);
421
- const value2 = result[i + 2] ? (' ' + result[i + 2]).slice(1) : '';
406
+ const value2 = result[i + 2] ? (' ' + result[i + 2]).slice(1) : null;
422
407
 
423
408
  switch (tag) {
409
+ case 'BYTERANGE':
410
+ if (prevFrag) {
411
+ frag.setByteRange(value1, prevFrag);
412
+ } else {
413
+ frag.setByteRange(value1);
414
+ }
415
+ break;
416
+ case 'PROGRAM-DATE-TIME':
417
+ // avoid sliced strings https://github.com/video-dev/hls.js/issues/939
418
+ frag.rawProgramDateTime = value1;
419
+ frag.tagList.push(['PROGRAM-DATE-TIME', value1]);
420
+ if (firstPdtIndex === -1) {
421
+ firstPdtIndex = fragments.length;
422
+ }
423
+ break;
424
424
  case 'PLAYLIST-TYPE':
425
425
  level.type = value1.toUpperCase();
426
426
  break;
@@ -546,7 +546,7 @@ export default class M3U8Parser {
546
546
  // Initial segment tag is after segment duration tag.
547
547
  // #EXTINF: 6.0
548
548
  // #EXT-X-MAP:URI="init.mp4
549
- const init = new Fragment(type, baseurl);
549
+ const init = new Fragment(type, base);
550
550
  setInitSegment(init, mapAttrs, id, levelkeys);
551
551
  currentInitSegment = init;
552
552
  frag.initSegment = currentInitSegment;
@@ -607,7 +607,7 @@ export default class M3U8Parser {
607
607
  const part = new Part(
608
608
  partAttrs,
609
609
  frag as MediaFragment,
610
- baseurl,
610
+ base,
611
611
  index,
612
612
  previousFragmentPart,
613
613
  );
@@ -709,7 +709,7 @@ export function mapDateRanges(
709
709
  for (let i = dateRangeIds.length; i--; ) {
710
710
  const dateRange = details.dateRanges[dateRangeIds[i]];
711
711
  const startDateTime = dateRange.startDate.getTime();
712
- dateRange.tagAnchor = lastProgramDateTime;
712
+ dateRange.tagAnchor = lastProgramDateTime.ref;
713
713
  for (let j = programDateTimeCount; j--; ) {
714
714
  const fragIndex = findFragmentWithStartDate(
715
715
  details,
@@ -719,7 +719,7 @@ export function mapDateRanges(
719
719
  playlistEnd,
720
720
  );
721
721
  if (fragIndex !== -1) {
722
- dateRange.tagAnchor = details.fragments[fragIndex];
722
+ dateRange.tagAnchor = details.fragments[fragIndex].ref;
723
723
  break;
724
724
  }
725
725
  }
@@ -736,31 +736,30 @@ function findFragmentWithStartDate(
736
736
  const pdtFragment = programDateTimes[index];
737
737
  if (pdtFragment) {
738
738
  // find matching range between PDT tags
739
- const durationBetweenPdt =
740
- (programDateTimes[index + 1]?.start || endTime) - pdtFragment.start;
741
739
  const pdtStart = pdtFragment.programDateTime as number;
742
- if (
743
- (startDateTime >= pdtStart || index === 0) &&
744
- startDateTime <= pdtStart + durationBetweenPdt * 1000
745
- ) {
746
- // map to fragment with date-time range
747
- const startIndex = programDateTimes[index].sn - details.startSN;
748
- const fragments = details.fragments;
749
- if (fragments.length > programDateTimes.length) {
750
- const endSegment =
751
- programDateTimes[index + 1] || fragments[fragments.length - 1];
752
- const endIndex = endSegment.sn - details.startSN;
753
- for (let i = endIndex; i > startIndex; i--) {
754
- const fragStartDateTime = fragments[i].programDateTime as number;
755
- if (
756
- startDateTime >= fragStartDateTime &&
757
- startDateTime < fragStartDateTime + fragments[i].duration * 1000
758
- ) {
759
- return i;
740
+ if (startDateTime >= pdtStart || index === 0) {
741
+ const durationBetweenPdt =
742
+ (programDateTimes[index + 1]?.start || endTime) - pdtFragment.start;
743
+ if (startDateTime <= pdtStart + durationBetweenPdt * 1000) {
744
+ // map to fragment with date-time range
745
+ const startIndex = programDateTimes[index].sn - details.startSN;
746
+ const fragments = details.fragments;
747
+ if (fragments.length > programDateTimes.length) {
748
+ const endSegment =
749
+ programDateTimes[index + 1] || fragments[fragments.length - 1];
750
+ const endIndex = endSegment.sn - details.startSN;
751
+ for (let i = endIndex; i > startIndex; i--) {
752
+ const fragStartDateTime = fragments[i].programDateTime as number;
753
+ if (
754
+ startDateTime >= fragStartDateTime &&
755
+ startDateTime < fragStartDateTime + fragments[i].duration * 1000
756
+ ) {
757
+ return i;
758
+ }
760
759
  }
761
760
  }
761
+ return startIndex;
762
762
  }
763
- return startIndex;
764
763
  }
765
764
  }
766
765
  return -1;
@@ -862,12 +861,6 @@ export function assignProgramDateTime(
862
861
  programDateTimes: MediaFragment[],
863
862
  ) {
864
863
  if (frag.rawProgramDateTime) {
865
- frag.programDateTime = Date.parse(frag.rawProgramDateTime);
866
- if (!Number.isFinite(frag.programDateTime)) {
867
- frag.programDateTime = null;
868
- frag.rawProgramDateTime = null;
869
- return;
870
- }
871
864
  programDateTimes.push(frag);
872
865
  } else if (prevFrag?.programDateTime) {
873
866
  frag.programDateTime = prevFrag.endProgramDateTime;
@@ -38,19 +38,19 @@ function updateFromToPTS(fragFrom: MediaFragment, fragTo: MediaFragment) {
38
38
  frag = fragTo;
39
39
  }
40
40
  if (frag.duration !== duration) {
41
- frag.duration = duration;
41
+ frag.setDuration(duration);
42
42
  }
43
43
  // we dont know startPTS[toIdx]
44
44
  } else if (fragTo.sn > fragFrom.sn) {
45
45
  const contiguous = fragFrom.cc === fragTo.cc;
46
46
  // TODO: With part-loading end/durations we need to confirm the whole fragment is loaded before using (or setting) minEndPTS
47
47
  if (contiguous && fragFrom.minEndPTS) {
48
- fragTo.start = fragFrom.start + (fragFrom.minEndPTS - fragFrom.start);
48
+ fragTo.setStart(fragFrom.start + (fragFrom.minEndPTS - fragFrom.start));
49
49
  } else {
50
- fragTo.start = fragFrom.start + fragFrom.duration;
50
+ fragTo.setStart(fragFrom.start + fragFrom.duration);
51
51
  }
52
52
  } else {
53
- fragTo.start = Math.max(fragFrom.start - fragTo.duration, 0);
53
+ fragTo.setStart(Math.max(fragFrom.start - fragTo.duration, 0));
54
54
  }
55
55
  }
56
56
 
@@ -92,9 +92,9 @@ export function updateFragPTSDTS(
92
92
 
93
93
  const drift = startPTS - frag.start;
94
94
  if (frag.start !== 0) {
95
- frag.start = startPTS;
95
+ frag.setStart(startPTS);
96
96
  }
97
- frag.duration = endPTS - frag.start;
97
+ frag.setDuration(endPTS - frag.start);
98
98
  frag.startPTS = startPTS;
99
99
  frag.maxStartPTS = maxStartPTS;
100
100
  frag.startDTS = startDTS;
@@ -170,15 +170,14 @@ export function mergeDetails(
170
170
  Number.isFinite(oldFrag.startPTS) &&
171
171
  Number.isFinite(oldFrag.endPTS)
172
172
  ) {
173
- newFrag.start = newFrag.startPTS = oldFrag.startPTS as number;
173
+ newFrag.setStart((newFrag.startPTS = oldFrag.startPTS!));
174
174
  newFrag.startDTS = oldFrag.startDTS;
175
175
  newFrag.maxStartPTS = oldFrag.maxStartPTS;
176
176
 
177
177
  newFrag.endPTS = oldFrag.endPTS;
178
178
  newFrag.endDTS = oldFrag.endDTS;
179
179
  newFrag.minEndPTS = oldFrag.minEndPTS;
180
- newFrag.duration =
181
- (oldFrag.endPTS as number) - (oldFrag.startPTS as number);
180
+ newFrag.setDuration(oldFrag.endPTS! - oldFrag.startPTS!);
182
181
 
183
182
  if (newFrag.duration) {
184
183
  PTSFrag = newFrag;
@@ -187,9 +186,17 @@ export function mergeDetails(
187
186
  // PTS is known when any segment has startPTS and endPTS
188
187
  newDetails.PTSKnown = newDetails.alignedSliding = true;
189
188
  }
190
- newFrag.elementaryStreams = oldFrag.elementaryStreams;
189
+
190
+ if (oldFrag.hasStreams) {
191
+ newFrag.elementaryStreams = oldFrag.elementaryStreams;
192
+ }
193
+
191
194
  newFrag.loader = oldFrag.loader;
192
- newFrag.stats = oldFrag.stats;
195
+
196
+ if (oldFrag.hasStats) {
197
+ newFrag.stats = oldFrag.stats;
198
+ }
199
+
193
200
  if (oldFrag.initSegment) {
194
201
  newFrag.initSegment = oldFrag.initSegment;
195
202
  currentInitSegment = oldFrag.initSegment;
@@ -432,14 +439,14 @@ export function adjustSliding(
432
439
  addSliding(newDetails, sliding);
433
440
  }
434
441
 
435
- export function addSliding(details: LevelDetails, start: number) {
436
- if (start) {
442
+ export function addSliding(details: LevelDetails, sliding: number) {
443
+ if (sliding) {
437
444
  const fragments = details.fragments;
438
445
  for (let i = details.skippedSegments; i < fragments.length; i++) {
439
- fragments[i].start += start;
446
+ fragments[i].addStart(sliding);
440
447
  }
441
448
  if (details.fragmentHint) {
442
- details.fragmentHint.start += start;
449
+ details.fragmentHint.addStart(sliding);
443
450
  }
444
451
  }
445
452
  }