rx-player 3.27.0 → 3.27.1-dev.2022041500

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.
Files changed (79) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/core/abr/abr_manager.d.ts +7 -3
  4. package/dist/_esm5.processed/core/abr/abr_manager.js +4 -3
  5. package/dist/_esm5.processed/core/abr/representation_estimator.d.ts +13 -8
  6. package/dist/_esm5.processed/core/abr/representation_estimator.js +4 -4
  7. package/dist/_esm5.processed/core/api/public_api.js +13 -14
  8. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.d.ts +52 -0
  9. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +223 -0
  10. package/dist/_esm5.processed/core/init/create_stream_playback_observer.js +17 -6
  11. package/dist/_esm5.processed/core/init/get_initial_time.d.ts +30 -5
  12. package/dist/_esm5.processed/core/init/get_initial_time.js +20 -13
  13. package/dist/_esm5.processed/core/init/load_on_media_source.js +16 -3
  14. package/dist/_esm5.processed/core/init/media_duration_updater.d.ts +56 -0
  15. package/dist/_esm5.processed/core/init/media_duration_updater.js +188 -0
  16. package/dist/_esm5.processed/core/stream/adaptation/adaptation_stream.d.ts +2 -5
  17. package/dist/_esm5.processed/core/stream/adaptation/create_representation_estimator.d.ts +3 -2
  18. package/dist/_esm5.processed/core/stream/adaptation/create_representation_estimator.js +3 -3
  19. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +2 -22
  20. package/dist/_esm5.processed/core/stream/period/period_stream.d.ts +2 -5
  21. package/dist/_esm5.processed/core/stream/period/period_stream.js +6 -4
  22. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.d.ts +4 -2
  23. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +85 -42
  24. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +3 -3
  25. package/dist/_esm5.processed/manifest/manifest.d.ts +14 -5
  26. package/dist/_esm5.processed/manifest/manifest.js +31 -12
  27. package/dist/_esm5.processed/manifest/period.d.ts +6 -0
  28. package/dist/_esm5.processed/manifest/period.js +9 -0
  29. package/dist/_esm5.processed/manifest/representation_index/types.d.ts +7 -2
  30. package/dist/_esm5.processed/parsers/manifest/dash/common/get_minimum_and_maximum_positions.d.ts +5 -1
  31. package/dist/_esm5.processed/parsers/manifest/dash/common/get_minimum_and_maximum_positions.js +6 -4
  32. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +2 -1
  33. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +4 -0
  34. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +4 -2
  35. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +4 -2
  36. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +3 -3
  37. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_mpd.js +28 -20
  38. package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +3 -2
  39. package/dist/_esm5.processed/parsers/manifest/metaplaylist/metaplaylist_parser.js +9 -3
  40. package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +26 -17
  41. package/dist/_esm5.processed/parsers/manifest/types.d.ts +45 -17
  42. package/dist/_esm5.processed/parsers/manifest/utils/{get_maximum_position.d.ts → get_maximum_positions.d.ts} +4 -1
  43. package/dist/_esm5.processed/parsers/manifest/utils/{get_maximum_position.js → get_maximum_positions.js} +10 -6
  44. package/dist/rx-player.js +854 -351
  45. package/dist/rx-player.min.js +1 -1
  46. package/package.json +1 -1
  47. package/sonar-project.properties +1 -1
  48. package/src/core/abr/abr_manager.ts +11 -3
  49. package/src/core/abr/representation_estimator.ts +17 -10
  50. package/src/core/api/public_api.ts +13 -15
  51. package/src/core/init/content_time_boundaries_observer.ts +312 -0
  52. package/src/core/init/create_stream_playback_observer.ts +18 -7
  53. package/src/core/init/get_initial_time.ts +52 -19
  54. package/src/core/init/load_on_media_source.ts +22 -5
  55. package/src/core/init/media_duration_updater.ts +268 -0
  56. package/src/core/stream/adaptation/adaptation_stream.ts +2 -5
  57. package/src/core/stream/adaptation/create_representation_estimator.ts +6 -3
  58. package/src/core/stream/orchestrator/stream_orchestrator.ts +2 -29
  59. package/src/core/stream/period/period_stream.ts +12 -9
  60. package/src/core/stream/representation/get_buffer_status.ts +102 -41
  61. package/src/core/stream/representation/representation_stream.ts +3 -3
  62. package/src/manifest/__tests__/manifest.test.ts +94 -68
  63. package/src/manifest/manifest.ts +73 -33
  64. package/src/manifest/period.ts +10 -0
  65. package/src/manifest/representation_index/types.ts +7 -2
  66. package/src/parsers/manifest/dash/common/get_minimum_and_maximum_positions.ts +10 -5
  67. package/src/parsers/manifest/dash/common/indexes/base.ts +4 -3
  68. package/src/parsers/manifest/dash/common/indexes/list.ts +8 -1
  69. package/src/parsers/manifest/dash/common/indexes/template.ts +8 -3
  70. package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +7 -7
  71. package/src/parsers/manifest/dash/common/parse_mpd.ts +38 -20
  72. package/src/parsers/manifest/local/parse_local_manifest.ts +3 -2
  73. package/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts +9 -4
  74. package/src/parsers/manifest/smooth/create_parser.ts +36 -18
  75. package/src/parsers/manifest/types.ts +45 -17
  76. package/src/parsers/manifest/utils/{get_maximum_position.ts → get_maximum_positions.ts} +13 -7
  77. package/dist/_esm5.processed/core/init/duration_updater.d.ts +0 -27
  78. package/dist/_esm5.processed/core/init/duration_updater.js +0 -136
  79. package/src/core/init/duration_updater.ts +0 -206
@@ -248,10 +248,13 @@ export default class Manifest extends EventEmitter<IManifestEvents> {
248
248
  */
249
249
  private _timeBounds : {
250
250
  /**
251
- * The minimum time, in seconds, that was available the last time the
252
- * Manifest was fetched.
251
+ * This is the theoretical minimum playable position on the content
252
+ * regardless of the current Adaptation chosen, as estimated at parsing
253
+ * time.
254
+ * `undefined` if unknown.
253
255
  *
254
- * `undefined` if that value is unknown.
256
+ * More technically, the `minimumSafePosition` is the maximum between all
257
+ * the minimum positions reachable in any of the audio and video Adaptation.
255
258
  *
256
259
  * Together with `timeshiftDepth` and the `maximumTimeData` object, this
257
260
  * value allows to compute at any time the minimum seekable time:
@@ -259,17 +262,17 @@ export default class Manifest extends EventEmitter<IManifestEvents> {
259
262
  * - if `timeshiftDepth` is not set, the minimum seekable time is a
260
263
  * constant that corresponds to this value.
261
264
  *
262
- * - if `timeshiftDepth` is set, `absoluteMinimumTime` will act as the
265
+ * - if `timeshiftDepth` is set, `minimumSafePosition` will act as the
263
266
  * absolute minimum seekable time we can never seek below, even when
264
267
  * `timeshiftDepth` indicates a possible lower position.
265
268
  * This becomes useful for example when playing live contents which -
266
269
  * despite having a large window depth - just begun and as such only
267
270
  * have a few segment available for now.
268
- * Here, `absoluteMinimumTime` would be the start time of the initial
271
+ * Here, `minimumSafePosition` would be the start time of the initial
269
272
  * segment, and `timeshiftDepth` would be the whole depth that will
270
273
  * become available once enough segments have been generated.
271
274
  */
272
- absoluteMinimumTime? : number | undefined;
275
+ minimumSafePosition? : number | undefined;
273
276
  /**
274
277
  * Some dynamic contents have the concept of a "window depth" (or "buffer
275
278
  * depth") which allows to set a minimum position for all reachable
@@ -287,29 +290,46 @@ export default class Manifest extends EventEmitter<IManifestEvents> {
287
290
  * If set to `null`, this content has no concept of a "window depth".
288
291
  */
289
292
  timeshiftDepth : number | null;
290
- /** Data allowing to calculate the maximum position at any given time. */
293
+ /** Data allowing to calculate the maximum playable position at any given time. */
291
294
  maximumTimeData : {
292
- /** Maximum seekable time in milliseconds calculated at `time`. */
293
- value : number;
294
295
  /**
295
- * `Performance.now()` output at the time `value` was calculated.
296
- * This can be used to retrieve the maximum position from `value` when it
297
- * linearly evolves over time (see `isLinear` property).
296
+ * Current position representing live content.
297
+ * Only makes sense for un-ended live contents.
298
+ *
299
+ * `undefined` if unknown or if it doesn't make sense in the current context.
298
300
  */
299
- time : number;
301
+ livePosition : number | undefined;
300
302
  /**
301
- * Whether the maximum seekable position evolves linearly over time.
302
- *
303
- * If set to `false`, `value` indicates the constant maximum position.
303
+ * Whether the maximum positions should evolve linearly over time.
304
304
  *
305
- * If set to `true`, the maximum seekable time continuously increase at
305
+ * If set to `true`, the maximum seekable position continuously increase at
306
306
  * the same rate than the time since `time` does.
307
- * For example, a `value` of 50000 (50 seconds) will indicate a maximum time
308
- * of 51 seconds after 1 second have passed, of 56 seconds after 6 seconds
309
- * have passed (we know how many seconds have passed since the initial
310
- * calculation of value by checking the `time` property) etc.
311
307
  */
312
308
  isLinear: boolean;
309
+ /**
310
+ * This is the theoretical maximum playable position on the content,
311
+ * regardless of the current Adaptation chosen, as estimated at parsing
312
+ * time.
313
+ *
314
+ * More technically, the `maximumSafePosition` is the minimum between all
315
+ * attributes indicating the duration of the content in the Manifest.
316
+ *
317
+ * That is the minimum between:
318
+ * - The Manifest original attributes relative to its duration
319
+ * - The minimum between all known maximum audio positions
320
+ * - The minimum between all known maximum video positions
321
+ *
322
+ * This can for example be understood as the safe maximum playable
323
+ * position through all possible tacks.
324
+ */
325
+ maximumSafePosition : number;
326
+ /**
327
+ * `Performance.now()` output at the time both `maximumSafePosition` and
328
+ * `livePosition` were calculated.
329
+ * This can be used to retrieve a new maximum position from them when they
330
+ * linearly evolves over time (see `isLinear` property).
331
+ */
332
+ time : number;
313
333
  };
314
334
  };
315
335
 
@@ -459,38 +479,58 @@ export default class Manifest extends EventEmitter<IManifestEvents> {
459
479
  }
460
480
 
461
481
  /**
462
- * Get the minimum position currently defined by the Manifest, in seconds.
482
+ * Returns the theoretical minimum playable position on the content
483
+ * regardless of the current Adaptation chosen, as estimated at parsing
484
+ * time.
463
485
  * @returns {number}
464
486
  */
465
- public getMinimumPosition() : number {
487
+ public getMinimumSafePosition() : number {
466
488
  const windowData = this._timeBounds;
467
489
  if (windowData.timeshiftDepth === null) {
468
- return windowData.absoluteMinimumTime ?? 0;
490
+ return windowData.minimumSafePosition ?? 0;
469
491
  }
470
492
 
471
493
  const { maximumTimeData } = windowData;
472
494
  let maximumTime : number;
473
495
  if (!windowData.maximumTimeData.isLinear) {
474
- maximumTime = maximumTimeData.value;
496
+ maximumTime = maximumTimeData.maximumSafePosition;
475
497
  } else {
476
498
  const timeDiff = performance.now() - maximumTimeData.time;
477
- maximumTime = maximumTimeData.value + timeDiff / 1000;
499
+ maximumTime = maximumTimeData.maximumSafePosition + timeDiff / 1000;
478
500
  }
479
501
  const theoricalMinimum = maximumTime - windowData.timeshiftDepth;
480
- return Math.max(windowData.absoluteMinimumTime ?? 0, theoricalMinimum);
502
+ return Math.max(windowData.minimumSafePosition ?? 0, theoricalMinimum);
481
503
  }
482
504
 
483
505
  /**
484
- * Get the maximum position currently defined by the Manifest, in seconds.
485
- * @returns {number}
506
+ * Get the position of the live edge - that is, the position of what is
507
+ * currently being broadcasted, in seconds.
508
+ * @returns {number|undefined}
509
+ */
510
+ public getLivePosition() : number | undefined {
511
+ const { maximumTimeData } = this._timeBounds;
512
+ if (!this.isLive || maximumTimeData.livePosition === undefined) {
513
+ return undefined;
514
+ }
515
+ if (!maximumTimeData.isLinear) {
516
+ return maximumTimeData.livePosition;
517
+ }
518
+ const timeDiff = performance.now() - maximumTimeData.time;
519
+ return maximumTimeData.livePosition + timeDiff / 1000;
520
+ }
521
+
522
+ /**
523
+ * Returns the theoretical maximum playable position on the content
524
+ * regardless of the current Adaptation chosen, as estimated at parsing
525
+ * time.
486
526
  */
487
- public getMaximumPosition() : number {
527
+ public getMaximumSafePosition() : number {
488
528
  const { maximumTimeData } = this._timeBounds;
489
529
  if (!maximumTimeData.isLinear) {
490
- return maximumTimeData.value;
530
+ return maximumTimeData.maximumSafePosition;
491
531
  }
492
532
  const timeDiff = performance.now() - maximumTimeData.time;
493
- return maximumTimeData.value + timeDiff / 1000;
533
+ return maximumTimeData.maximumSafePosition + timeDiff / 1000;
494
534
  }
495
535
 
496
536
  /**
@@ -690,7 +730,7 @@ export default class Manifest extends EventEmitter<IManifestEvents> {
690
730
  // Partial updates do not remove old Periods.
691
731
  // This can become a memory problem when playing a content long enough.
692
732
  // Let's clean manually Periods behind the minimum possible position.
693
- const min = this.getMinimumPosition();
733
+ const min = this.getMinimumSafePosition();
694
734
  while (this.periods.length > 0) {
695
735
  const period = this.periods[0];
696
736
  if (period.end === undefined || period.end > min) {
@@ -185,4 +185,14 @@ export default class Period {
185
185
  return ada.isSupported;
186
186
  });
187
187
  }
188
+
189
+ /**
190
+ * Returns true if the give time is in the time boundaries of this `Period`.
191
+ * @param {number} time
192
+ * @returns {boolean}
193
+ */
194
+ containsTime(time : number) : boolean {
195
+ return time >= this.start && (this.end === undefined ||
196
+ time < this.end);
197
+ }
188
198
  }
@@ -268,10 +268,15 @@ export interface IRepresentationIndex {
268
268
  getFirstPosition() : number | null | undefined;
269
269
 
270
270
  /**
271
- * Returns the ending time, in seconds, of the last segment currently
272
- * available in this index.
271
+ * Returns the ending time, in seconds, of the last playable position
272
+ * currently available in this index.
273
273
  * Returns `null` if nothing is in the index
274
274
  * Returns `undefined` if we cannot know this value.
275
+ *
276
+ * The last playable position is generally equivalent to the end of the last
277
+ * segment associated to this index, excepted when it goes over the end limit
278
+ * of the corresponding `Period`, in which case it will be equal to this end
279
+ * instead.
275
280
  * @returns {Number|null|undefined}
276
281
  */
277
282
  getLastPosition() : number | null | undefined;
@@ -15,20 +15,25 @@
15
15
  */
16
16
 
17
17
  import { IParsedPeriod } from "../../types";
18
- import getMaximumPosition from "../../utils/get_maximum_position";
18
+ import getMaximumPositions from "../../utils/get_maximum_positions";
19
19
  import getMinimumPosition from "../../utils/get_minimum_position";
20
20
 
21
21
  /**
22
22
  * @param {Object} periods
23
23
  * @returns {Array.<number>}
24
24
  */
25
- export default function getMinimumAndMaximumPosition(
25
+ export default function getMinimumAndMaximumPositions(
26
26
  periods: IParsedPeriod[]
27
- ) : [number|undefined, number|undefined] {
27
+ ) : { minimumSafePosition : number | undefined;
28
+ maximumSafePosition : number | undefined;
29
+ maximumUnsafePosition : number | undefined; } {
28
30
  if (periods.length === 0) {
29
31
  throw new Error("DASH Parser: no period available for a dynamic content");
30
32
  }
31
33
 
32
- return [ getMinimumPosition(periods),
33
- getMaximumPosition(periods) ];
34
+ const minimumSafePosition = getMinimumPosition(periods);
35
+ const maxPositions = getMaximumPositions(periods);
36
+ return { minimumSafePosition,
37
+ maximumSafePosition: maxPositions.safe,
38
+ maximumUnsafePosition: maxPositions.unsafe };
34
39
  }
@@ -288,9 +288,10 @@ export default class BaseRepresentationIndex implements IRepresentationIndex {
288
288
  return null;
289
289
  }
290
290
  const lastTimelineElement = timeline[timeline.length - 1];
291
- const lastTime = getIndexSegmentEnd(lastTimelineElement,
292
- null,
293
- this._scaledPeriodEnd);
291
+ const lastTime = Math.min(getIndexSegmentEnd(lastTimelineElement,
292
+ null,
293
+ this._scaledPeriodEnd),
294
+ this._scaledPeriodEnd ?? Infinity);
294
295
  return fromIndexTime(lastTime, this._index);
295
296
  }
296
297
 
@@ -106,6 +106,8 @@ export interface IListIndexIndexArgument {
106
106
  export interface IListIndexContextArgument {
107
107
  /** Start of the period concerned by this RepresentationIndex, in seconds. */
108
108
  periodStart : number;
109
+ /** End of the period concerned by this RepresentationIndex, in seconds. */
110
+ periodEnd : number | undefined;
109
111
  /** Base URL for the Representation concerned. */
110
112
  representationBaseURLs : IResolvedBaseUrl[];
111
113
  /** ID of the Representation concerned. */
@@ -121,6 +123,8 @@ export default class ListRepresentationIndex implements IRepresentationIndex {
121
123
  private _index : IListIndex;
122
124
  /** Start of the period concerned by this RepresentationIndex, in seconds. */
123
125
  protected _periodStart : number;
126
+ /** End of the period concerned by this RepresentationIndex, in seconds. */
127
+ protected _periodEnd : number | undefined;
124
128
  /* Function that tells if an EMSG is whitelisted by the manifest */
125
129
  private _isEMSGWhitelisted: (inbandEvent: IEMSG) => boolean;
126
130
 
@@ -134,12 +138,14 @@ export default class ListRepresentationIndex implements IRepresentationIndex {
134
138
  }
135
139
 
136
140
  const { periodStart,
141
+ periodEnd,
137
142
  representationBaseURLs,
138
143
  representationId,
139
144
  representationBitrate,
140
145
  isEMSGWhitelisted } = context;
141
146
  this._isEMSGWhitelisted = isEMSGWhitelisted;
142
147
  this._periodStart = periodStart;
148
+ this._periodEnd = periodEnd;
143
149
  const presentationTimeOffset =
144
150
  index.presentationTimeOffset != null ? index.presentationTimeOffset :
145
151
  0;
@@ -248,7 +254,8 @@ export default class ListRepresentationIndex implements IRepresentationIndex {
248
254
  getLastPosition() : number {
249
255
  const index = this._index;
250
256
  const { duration, list } = index;
251
- return ((list.length * duration) / index.timescale) + this._periodStart;
257
+ return Math.min(((list.length * duration) / index.timescale) + this._periodStart,
258
+ this._periodEnd ?? Infinity);
252
259
  }
253
260
 
254
261
  /**
@@ -218,8 +218,9 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
218
218
  startNumber: index.startNumber };
219
219
  this._isDynamic = isDynamic;
220
220
  this._periodStart = periodStart;
221
- this._scaledPeriodEnd = periodEnd == null ? undefined :
222
- (periodEnd - periodStart) * timescale;
221
+ this._scaledPeriodEnd = periodEnd === undefined ?
222
+ undefined :
223
+ (periodEnd - periodStart) * timescale;
223
224
  this._isEMSGWhitelisted = isEMSGWhitelisted;
224
225
  }
225
226
 
@@ -334,7 +335,11 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
334
335
  // the two functions. So, we return the result of the latter.
335
336
  return lastSegmentStart;
336
337
  }
337
- const lastSegmentEnd = lastSegmentStart + this._index.duration;
338
+
339
+ const lastSegmentEnd = Math.min(
340
+ lastSegmentStart + this._index.duration,
341
+ this._scaledPeriodEnd ?? Infinity
342
+ );
338
343
  return (lastSegmentEnd / this._index.timescale) + this._periodStart;
339
344
  }
340
345
 
@@ -47,7 +47,6 @@ import constructTimelineFromElements from "./construct_timeline_from_elements";
47
47
  // eslint-disable-next-line max-len
48
48
  import constructTimelineFromPreviousTimeline from "./construct_timeline_from_previous_timeline";
49
49
 
50
-
51
50
  /**
52
51
  * Index property defined for a SegmentTimeline RepresentationIndex
53
52
  * This object contains every property needed to generate an ISegment for a
@@ -307,8 +306,8 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
307
306
  timeline: index.timeline ?? null,
308
307
  timescale };
309
308
  this._scaledPeriodStart = toIndexTime(periodStart, this._index);
310
- this._scaledPeriodEnd = periodEnd == null ? undefined :
311
- toIndexTime(periodEnd, this._index);
309
+ this._scaledPeriodEnd = periodEnd === undefined ? undefined :
310
+ toIndexTime(periodEnd, this._index);
312
311
  }
313
312
 
314
313
  /**
@@ -389,7 +388,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
389
388
  this._index.timeline = this._getTimeline();
390
389
  }
391
390
  const lastTime = TimelineRepresentationIndex.getIndexEnd(this._index.timeline,
392
- this._scaledPeriodStart);
391
+ this._scaledPeriodEnd);
393
392
  return lastTime === null ? null :
394
393
  fromIndexTime(lastTime, this._index);
395
394
  }
@@ -566,9 +565,10 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
566
565
  if (timeline.length <= 0) {
567
566
  return null;
568
567
  }
569
- return getIndexSegmentEnd(timeline[timeline.length - 1],
570
- null,
571
- scaledPeriodEnd);
568
+ return Math.min(getIndexSegmentEnd(timeline[timeline.length - 1],
569
+ null,
570
+ scaledPeriodEnd),
571
+ scaledPeriodEnd ?? Infinity);
572
572
  }
573
573
 
574
574
  /**
@@ -28,7 +28,7 @@ import { IResponseData } from "../parsers_types";
28
28
  // eslint-disable-next-line max-len
29
29
  import getClockOffset from "./get_clock_offset";
30
30
  import getHTTPUTCTimingURL from "./get_http_utc-timing_url";
31
- import getMinimumAndMaximumPosition from "./get_minimum_and_maximum_positions";
31
+ import getMinimumAndMaximumPositions from "./get_minimum_and_maximum_positions";
32
32
  import parseAvailabilityStartTime from "./parse_availability_start_time";
33
33
  import parsePeriods, {
34
34
  IXLinkInfos,
@@ -270,7 +270,10 @@ function parseCompleteIntermediateRepresentation(
270
270
  let lifetime : number | undefined;
271
271
  let minimumTime : number | undefined;
272
272
  let timeshiftDepth : number | null = null;
273
- let maximumTimeData : { isLinear : boolean; value : number; time : number };
273
+ let maximumTimeData : { isLinear : boolean;
274
+ maximumSafePosition : number;
275
+ livePosition : number | undefined;
276
+ time : number; };
274
277
 
275
278
  if (rootAttributes.minimumUpdatePeriod !== undefined &&
276
279
  rootAttributes.minimumUpdatePeriod >= 0)
@@ -280,58 +283,73 @@ function parseCompleteIntermediateRepresentation(
280
283
  rootAttributes.minimumUpdatePeriod;
281
284
  }
282
285
 
283
- const [contentStart, contentEnd] = getMinimumAndMaximumPosition(parsedPeriods);
286
+ const { minimumSafePosition,
287
+ maximumSafePosition,
288
+ maximumUnsafePosition } = getMinimumAndMaximumPositions(parsedPeriods);
284
289
  const now = performance.now();
285
290
 
286
291
  if (!isDynamic) {
287
- minimumTime = contentStart !== undefined ? contentStart :
292
+ minimumTime = minimumSafePosition !== undefined ? minimumSafePosition :
288
293
  parsedPeriods[0]?.start !== undefined ? parsedPeriods[0].start :
289
294
  0;
290
- let maximumTime = mediaPresentationDuration ?? Infinity;
295
+ let finalMaximumSafePosition = mediaPresentationDuration ?? Infinity;
291
296
  if (parsedPeriods[parsedPeriods.length - 1] !== undefined) {
292
297
  const lastPeriod = parsedPeriods[parsedPeriods.length - 1];
293
298
  const lastPeriodEnd = lastPeriod.end ??
294
299
  (lastPeriod.duration !== undefined ?
295
300
  lastPeriod.start + lastPeriod.duration :
296
301
  undefined);
297
- if (lastPeriodEnd !== undefined && lastPeriodEnd < maximumTime) {
298
- maximumTime = lastPeriodEnd;
302
+ if (lastPeriodEnd !== undefined && lastPeriodEnd < finalMaximumSafePosition) {
303
+ finalMaximumSafePosition = lastPeriodEnd;
299
304
  }
300
305
  }
301
- if (contentEnd !== undefined && contentEnd < maximumTime) {
302
- maximumTime = contentEnd;
306
+ if (maximumSafePosition !== undefined &&
307
+ maximumSafePosition < finalMaximumSafePosition)
308
+ {
309
+ finalMaximumSafePosition = maximumSafePosition;
303
310
  }
304
311
 
305
312
  maximumTimeData = { isLinear: false,
306
- value: maximumTime,
313
+ maximumSafePosition: finalMaximumSafePosition,
314
+ livePosition: undefined,
307
315
  time: now };
308
316
  } else {
309
- minimumTime = contentStart;
317
+ minimumTime = minimumSafePosition;
310
318
  timeshiftDepth = timeShiftBufferDepth ?? null;
311
- let maximumTime : number;
312
- if (contentEnd !== undefined) {
313
- maximumTime = contentEnd;
319
+ let finalMaximumSafePosition : number;
320
+ let livePosition;
321
+
322
+ if (maximumUnsafePosition !== undefined) {
323
+ livePosition = maximumUnsafePosition;
324
+ }
325
+
326
+ if (maximumSafePosition !== undefined) {
327
+ finalMaximumSafePosition = maximumSafePosition;
314
328
  } else {
315
329
  const ast = availabilityStartTime ?? 0;
316
330
  const { externalClockOffset } = args;
317
331
  if (externalClockOffset === undefined) {
318
332
  log.warn("DASH Parser: use system clock to define maximum position");
319
- maximumTime = (Date.now() / 1000) - ast;
333
+ finalMaximumSafePosition = (Date.now() / 1000) - ast;
320
334
  } else {
321
335
  const serverTime = performance.now() + externalClockOffset;
322
- maximumTime = (serverTime / 1000) - ast;
336
+ finalMaximumSafePosition = (serverTime / 1000) - ast;
323
337
  }
324
338
  }
339
+ if (livePosition === undefined) {
340
+ livePosition = finalMaximumSafePosition;
341
+ }
325
342
  maximumTimeData = { isLinear: true,
326
- value: maximumTime,
343
+ maximumSafePosition: finalMaximumSafePosition,
344
+ livePosition,
327
345
  time: now };
328
346
 
329
347
  // if the minimum calculated time is even below the buffer depth, perhaps we
330
348
  // can go even lower in terms of depth
331
349
  if (timeshiftDepth !== null && minimumTime !== undefined &&
332
- maximumTime - minimumTime > timeshiftDepth)
350
+ finalMaximumSafePosition - minimumTime > timeshiftDepth)
333
351
  {
334
- timeshiftDepth = maximumTime - minimumTime;
352
+ timeshiftDepth = finalMaximumSafePosition - minimumTime;
335
353
  }
336
354
  }
337
355
 
@@ -358,7 +376,7 @@ function parseCompleteIntermediateRepresentation(
358
376
  publishTime: rootAttributes.publishTime,
359
377
  suggestedPresentationDelay: rootAttributes.suggestedPresentationDelay,
360
378
  transportType: "dash",
361
- timeBounds: { absoluteMinimumTime: minimumTime,
379
+ timeBounds: { minimumSafePosition: minimumTime,
362
380
  timeshiftDepth,
363
381
  maximumTimeData },
364
382
  lifetime,
@@ -61,10 +61,11 @@ export default function parseLocalManifest(
61
61
  isLastPeriodKnown: isFinished,
62
62
  isLive: false,
63
63
  uris: [],
64
- timeBounds: { absoluteMinimumTime: minimumPosition ?? 0,
64
+ timeBounds: { minimumSafePosition: minimumPosition ?? 0,
65
65
  timeshiftDepth: null,
66
66
  maximumTimeData: { isLinear: false,
67
- value: maximumPosition,
67
+ maximumSafePosition: maximumPosition,
68
+ livePosition: undefined,
68
69
  time: performance.now() } },
69
70
  periods: parsedPeriods };
70
71
  }
@@ -308,11 +308,16 @@ function createManifest(
308
308
  isLastPeriodKnown,
309
309
  uris: url == null ? [] :
310
310
  [url],
311
- timeBounds: { minimumTime,
311
+
312
+ // TODO more precize time bounds?
313
+ timeBounds: { minimumSafePosition: minimumTime,
312
314
  timeshiftDepth: null,
313
- maximumTimeData: { isLinear: false,
314
- value: maximumTime,
315
- time } },
315
+ maximumTimeData: {
316
+ isLinear: false,
317
+ maximumSafePosition: maximumTime,
318
+ livePosition: undefined,
319
+ time,
320
+ } },
316
321
  lifetime: mplData.pollInterval };
317
322
 
318
323
  return manifest;
@@ -533,7 +533,10 @@ function createSmoothStreamingParser(
533
533
  let availabilityStartTime : number|undefined;
534
534
  let minimumTime : number | undefined;
535
535
  let timeshiftDepth : number | null = null;
536
- let maximumTimeData : { isLinear : boolean; value : number; time : number };
536
+ let maximumTimeData : { isLinear : boolean;
537
+ maximumSafePosition: number;
538
+ livePosition : number | undefined;
539
+ time : number; };
537
540
 
538
541
  const firstVideoAdaptation = adaptations.video !== undefined ?
539
542
  adaptations.video[0] :
@@ -541,8 +544,15 @@ function createSmoothStreamingParser(
541
544
  const firstAudioAdaptation = adaptations.audio !== undefined ?
542
545
  adaptations.audio[0] :
543
546
  undefined;
544
- let firstTimeReference : number|undefined;
545
- let lastTimeReference : number|undefined;
547
+
548
+ /** Minimum time that can be reached regardless of the StreamIndex chosen. */
549
+ let safeMinimumTime : number|undefined;
550
+
551
+ /** Maximum time that can be reached regardless of the StreamIndex chosen. */
552
+ let safeMaximumTime : number|undefined;
553
+
554
+ /** Maximum time that can be reached in absolute on the content. */
555
+ let unsafeMaximumTime : number|undefined;
546
556
 
547
557
  if (firstVideoAdaptation !== undefined || firstAudioAdaptation !== undefined) {
548
558
  const firstTimeReferences : number[] = [];
@@ -585,44 +595,52 @@ function createSmoothStreamingParser(
585
595
  }
586
596
 
587
597
  if (firstTimeReferences.length > 0) {
588
- firstTimeReference = Math.max(...firstTimeReferences);
598
+ safeMinimumTime = Math.max(...firstTimeReferences);
589
599
  }
590
600
 
591
601
  if (lastTimeReferences.length > 0) {
592
- lastTimeReference = Math.min(...lastTimeReferences);
602
+ safeMaximumTime = Math.min(...lastTimeReferences);
603
+ unsafeMaximumTime = Math.max(...lastTimeReferences);
593
604
  }
594
605
  }
595
606
 
596
607
  const manifestDuration = root.getAttribute("Duration");
597
- const duration = (manifestDuration != null && +manifestDuration !== 0) ?
608
+ const duration = (manifestDuration !== null && +manifestDuration !== 0) ?
598
609
  (+manifestDuration / timescale) : undefined;
599
610
 
600
611
  if (isLive) {
601
612
  suggestedPresentationDelay = parserOptions.suggestedPresentationDelay;
602
613
  availabilityStartTime = referenceDateTime;
603
614
 
604
- minimumTime = firstTimeReference ?? availabilityStartTime;
605
- const maximumTime = lastTimeReference != null ?
606
- lastTimeReference :
607
- (Date.now() / 1000 - availabilityStartTime);
615
+ minimumTime = safeMinimumTime ?? availabilityStartTime;
616
+ let livePosition = unsafeMaximumTime;
617
+ if (livePosition === undefined) {
618
+ livePosition = (Date.now() / 1000 - availabilityStartTime);
619
+ }
620
+ let maximumSafePosition = safeMaximumTime;
621
+ if (maximumSafePosition === undefined) {
622
+ maximumSafePosition = livePosition;
623
+ }
608
624
  maximumTimeData = { isLinear: true,
609
- value: maximumTime,
625
+ maximumSafePosition,
626
+ livePosition,
610
627
  time: performance.now() };
611
628
  timeshiftDepth = timeShiftBufferDepth ?? null;
612
629
  } else {
613
- minimumTime = firstTimeReference ?? 0;
614
- const maximumTime = lastTimeReference !== undefined ? lastTimeReference :
615
- duration !== undefined ? minimumTime + duration :
616
- Infinity;
630
+ minimumTime = safeMinimumTime ?? 0;
631
+ const maximumTime = safeMaximumTime !== undefined ? safeMaximumTime :
632
+ duration !== undefined ? minimumTime + duration :
633
+ Infinity;
617
634
  maximumTimeData = { isLinear: false,
618
- value: maximumTime,
635
+ maximumSafePosition: maximumTime,
636
+ livePosition: undefined,
619
637
  time: performance.now() };
620
638
  }
621
639
 
622
640
  const periodStart = isLive ? 0 :
623
641
  minimumTime;
624
642
  const periodEnd = isLive ? undefined :
625
- maximumTimeData.value;
643
+ maximumTimeData.maximumSafePosition;
626
644
  const manifest = {
627
645
  availabilityStartTime: availabilityStartTime === undefined ?
628
646
  0 :
@@ -631,7 +649,7 @@ function createSmoothStreamingParser(
631
649
  isLive,
632
650
  isDynamic: isLive,
633
651
  isLastPeriodKnown: true,
634
- timeBounds: { absoluteMinimumTime: minimumTime,
652
+ timeBounds: { minimumSafePosition: minimumTime,
635
653
  timeshiftDepth,
636
654
  maximumTimeData },
637
655
  periods: [{ adaptations,