rx-player 3.28.0 → 3.28.1-dev.2022083000

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 (104) hide show
  1. package/.github/workflows/checks.yml +20 -18
  2. package/CHANGELOG.md +11 -0
  3. package/VERSION +1 -1
  4. package/dist/_esm5.processed/compat/eme/load_session.d.ts +5 -6
  5. package/dist/_esm5.processed/compat/eme/load_session.js +5 -6
  6. package/dist/_esm5.processed/core/api/public_api.js +2 -2
  7. package/dist/_esm5.processed/core/decrypt/create_session.js +33 -4
  8. package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.d.ts +14 -0
  9. package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.js +25 -0
  10. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.d.ts +1 -1
  11. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +1 -1
  12. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +110 -38
  13. package/dist/_esm5.processed/core/stream/representation/check_for_discontinuity.js +21 -9
  14. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +21 -29
  15. package/dist/_esm5.processed/errors/encrypted_media_error.d.ts +1 -2
  16. package/dist/_esm5.processed/errors/encrypted_media_error.js +0 -1
  17. package/dist/_esm5.processed/errors/error_codes.d.ts +6 -1
  18. package/dist/_esm5.processed/errors/media_error.d.ts +1 -2
  19. package/dist/_esm5.processed/errors/media_error.js +0 -1
  20. package/dist/_esm5.processed/errors/network_error.d.ts +1 -2
  21. package/dist/_esm5.processed/errors/network_error.js +0 -1
  22. package/dist/_esm5.processed/errors/other_error.d.ts +1 -2
  23. package/dist/_esm5.processed/errors/other_error.js +0 -1
  24. package/dist/_esm5.processed/manifest/manifest.d.ts +1 -1
  25. package/dist/_esm5.processed/manifest/manifest.js +1 -1
  26. package/dist/_esm5.processed/manifest/representation_index/static.d.ts +21 -6
  27. package/dist/_esm5.processed/manifest/representation_index/static.js +26 -8
  28. package/dist/_esm5.processed/manifest/representation_index/types.d.ts +55 -44
  29. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.d.ts +21 -8
  30. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +25 -10
  31. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +26 -12
  32. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +26 -13
  33. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.d.ts +23 -7
  34. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +65 -22
  35. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.d.ts +20 -3
  36. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +57 -7
  37. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/{is_period_fulfilled.d.ts → utils.d.ts} +3 -6
  38. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/{is_period_fulfilled.js → utils.js} +4 -8
  39. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_periods.js +1 -1
  40. package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +5 -8
  41. package/dist/_esm5.processed/parsers/manifest/local/representation_index.d.ts +21 -8
  42. package/dist/_esm5.processed/parsers/manifest/local/representation_index.js +49 -14
  43. package/dist/_esm5.processed/parsers/manifest/local/types.d.ts +16 -0
  44. package/dist/_esm5.processed/parsers/manifest/metaplaylist/representation_index.d.ts +20 -6
  45. package/dist/_esm5.processed/parsers/manifest/metaplaylist/representation_index.js +28 -10
  46. package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +4 -4
  47. package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.d.ts +21 -12
  48. package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.js +39 -14
  49. package/dist/_esm5.processed/parsers/manifest/utils/get_first_time_from_adaptation.js +1 -1
  50. package/dist/_esm5.processed/parsers/manifest/utils/get_last_time_from_adaptation.js +1 -1
  51. package/dist/_esm5.processed/transports/metaplaylist/pipelines.js +0 -2
  52. package/dist/_esm5.processed/transports/smooth/segment_loader.js +1 -1
  53. package/dist/_esm5.processed/transports/types.d.ts +1 -1
  54. package/dist/_esm5.processed/utils/deep_merge.d.ts +1 -1
  55. package/dist/_esm5.processed/utils/deep_merge.js +6 -5
  56. package/dist/_esm5.processed/utils/task_canceller.d.ts +0 -3
  57. package/dist/_esm5.processed/utils/task_canceller.js +0 -3
  58. package/dist/mpd-parser.wasm +0 -0
  59. package/dist/rx-player.js +1382 -1058
  60. package/dist/rx-player.min.js +1 -1
  61. package/jest.config.js +5 -0
  62. package/package.json +31 -30
  63. package/sonar-project.properties +1 -1
  64. package/src/compat/eme/load_session.ts +5 -6
  65. package/src/core/api/public_api.ts +2 -2
  66. package/src/core/decrypt/create_session.ts +28 -2
  67. package/src/core/decrypt/utils/loaded_sessions_store.ts +29 -0
  68. package/src/core/fetchers/segment/segment_fetcher.ts +1 -1
  69. package/src/core/init/content_time_boundaries_observer.ts +116 -42
  70. package/src/core/stream/representation/check_for_discontinuity.ts +28 -10
  71. package/src/core/stream/representation/get_buffer_status.ts +27 -34
  72. package/src/errors/encrypted_media_error.ts +1 -2
  73. package/src/errors/error_codes.ts +2 -2
  74. package/src/errors/media_error.ts +1 -2
  75. package/src/errors/network_error.ts +1 -2
  76. package/src/errors/other_error.ts +1 -2
  77. package/src/manifest/__tests__/adaptation.test.ts +4 -3
  78. package/src/manifest/__tests__/representation.test.ts +4 -3
  79. package/src/manifest/manifest.ts +1 -1
  80. package/src/manifest/representation_index/__tests__/static.test.ts +5 -4
  81. package/src/manifest/representation_index/static.ts +28 -9
  82. package/src/manifest/representation_index/types.ts +62 -46
  83. package/src/parsers/manifest/dash/common/indexes/base.ts +27 -11
  84. package/src/parsers/manifest/dash/common/indexes/list.ts +32 -15
  85. package/src/parsers/manifest/dash/common/indexes/template.ts +73 -27
  86. package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +60 -8
  87. package/src/parsers/manifest/dash/common/indexes/{is_period_fulfilled.ts → utils.ts} +4 -13
  88. package/src/parsers/manifest/dash/common/parse_periods.ts +1 -1
  89. package/src/parsers/manifest/local/parse_local_manifest.ts +8 -20
  90. package/src/parsers/manifest/local/representation_index.ts +51 -16
  91. package/src/parsers/manifest/local/types.ts +13 -0
  92. package/src/parsers/manifest/metaplaylist/representation_index.ts +31 -11
  93. package/src/parsers/manifest/smooth/create_parser.ts +4 -4
  94. package/src/parsers/manifest/smooth/representation_index.ts +40 -15
  95. package/src/parsers/manifest/utils/__tests__/get_first_time_from_adaptations.test.ts +4 -3
  96. package/src/parsers/manifest/utils/__tests__/get_last_time_from_adaptation.test.ts +4 -3
  97. package/src/parsers/manifest/utils/get_first_time_from_adaptation.ts +1 -1
  98. package/src/parsers/manifest/utils/get_last_time_from_adaptation.ts +1 -1
  99. package/src/transports/metaplaylist/pipelines.ts +0 -2
  100. package/src/transports/smooth/segment_loader.ts +1 -1
  101. package/src/transports/types.ts +1 -1
  102. package/src/utils/__tests__/initialization_segment_cache.test.ts +7 -0
  103. package/src/utils/deep_merge.ts +7 -4
  104. package/src/utils/task_canceller.ts +0 -3
@@ -19,15 +19,16 @@ import {
19
19
  IRepresentationIndex,
20
20
  ISegment,
21
21
  } from "../../../../../manifest";
22
+ import assert from "../../../../../utils/assert";
22
23
  import { IEMSG } from "../../../../containers/isobmff";
23
24
  import ManifestBoundsCalculator from "../manifest_bounds_calculator";
24
25
  import { IResolvedBaseUrl } from "../resolve_base_urls";
25
26
  import getInitSegment from "./get_init_segment";
26
- import isPeriodFulfilled from "./is_period_fulfilled";
27
27
  import {
28
28
  createDashUrlDetokenizer,
29
29
  createIndexURLs,
30
30
  } from "./tokens";
31
+ import { getSegmentTimeRoundingError } from "./utils";
31
32
 
32
33
 
33
34
  /**
@@ -59,6 +60,7 @@ export interface ITemplateIndex {
59
60
  /**
60
61
  * URL base to access any segment.
61
62
  * Can contain token to replace to convert it to real URLs.
63
+ * `null` if no URL exists.
62
64
  */
63
65
  mediaURLs : string[] | null;
64
66
  /**
@@ -150,7 +152,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
150
152
  /** Absolute start of the Period, in seconds. */
151
153
  private _periodStart : number;
152
154
  /** Difference between the end time of the Period and its start time, in timescale. */
153
- private _scaledPeriodEnd : number | undefined;
155
+ private _scaledRelativePeriodEnd : number | undefined;
154
156
  /** Minimum availabilityTimeOffset concerning the segments of this Representation. */
155
157
  private _availabilityTimeOffset : number | undefined;
156
158
  /** Whether the corresponding Manifest can be updated and changed. */
@@ -218,7 +220,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
218
220
  startNumber: index.startNumber };
219
221
  this._isDynamic = isDynamic;
220
222
  this._periodStart = periodStart;
221
- this._scaledPeriodEnd = periodEnd === undefined ?
223
+ this._scaledRelativePeriodEnd = periodEnd === undefined ?
222
224
  undefined :
223
225
  (periodEnd - periodStart) * timescale;
224
226
  this._isEMSGWhitelisted = isEMSGWhitelisted;
@@ -245,7 +247,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
245
247
  mediaURLs } = index;
246
248
 
247
249
  const scaledStart = this._periodStart * timescale;
248
- const scaledEnd = this._scaledPeriodEnd;
250
+ const scaledEnd = this._scaledRelativePeriodEnd;
249
251
 
250
252
  // Convert the asked position to the right timescales, and consider them
251
253
  // relatively to the Period's start.
@@ -315,7 +317,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
315
317
  * Returns first possible position in the index, in seconds.
316
318
  * @returns {number|null|undefined}
317
319
  */
318
- getFirstPosition() : number | null | undefined {
320
+ getFirstAvailablePosition() : number | null | undefined {
319
321
  const firstSegmentStart = this._getFirstSegmentStart();
320
322
  if (firstSegmentStart == null) {
321
323
  return firstSegmentStart; // return undefined or null
@@ -327,22 +329,70 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
327
329
  * Returns last possible position in the index, in seconds.
328
330
  * @returns {number|null}
329
331
  */
330
- getLastPosition() : number|null|undefined {
332
+ getLastAvailablePosition() : number|null|undefined {
331
333
  const lastSegmentStart = this._getLastSegmentStart();
332
334
  if (lastSegmentStart == null) {
333
- // In that case (null or undefined), getLastPosition should reflect
335
+ // In that case (null or undefined), getLastAvailablePosition should reflect
334
336
  // the result of getLastSegmentStart, as the meaning is the same for
335
337
  // the two functions. So, we return the result of the latter.
336
338
  return lastSegmentStart;
337
339
  }
338
340
 
339
- const lastSegmentEnd = Math.min(
340
- lastSegmentStart + this._index.duration,
341
- this._scaledPeriodEnd ?? Infinity
342
- );
341
+ const lastSegmentEnd = Math.min(lastSegmentStart + this._index.duration,
342
+ this._scaledRelativePeriodEnd ?? Infinity);
343
343
  return (lastSegmentEnd / this._index.timescale) + this._periodStart;
344
344
  }
345
345
 
346
+ /**
347
+ * Returns the absolute end in seconds this RepresentationIndex can reach once
348
+ * all segments are available.
349
+ * @returns {number|null|undefined}
350
+ */
351
+ getEnd(): number | null | undefined {
352
+ if (!this._isDynamic) {
353
+ return this.getLastAvailablePosition();
354
+ }
355
+ if (this._scaledRelativePeriodEnd === undefined) {
356
+ return undefined;
357
+ }
358
+ const { timescale } = this._index;
359
+ const absoluteScaledPeriodEnd = (this._scaledRelativePeriodEnd +
360
+ this._periodStart * timescale);
361
+ return absoluteScaledPeriodEnd / this._index.timescale;
362
+ }
363
+
364
+ /**
365
+ * Returns:
366
+ * - `true` if in the given time interval, at least one new segment is
367
+ * expected to be available in the future.
368
+ * - `false` either if all segments in that time interval are already
369
+ * available for download or if none will ever be available for it.
370
+ * - `undefined` when it is not possible to tell.
371
+ *
372
+ * Always `false` in a `BaseRepresentationIndex` because all segments should
373
+ * be directly available.
374
+ * @returns {boolean}
375
+ */
376
+ awaitSegmentBetween(start: number, end: number): boolean | undefined {
377
+ assert(start <= end);
378
+ if (!this._isDynamic) {
379
+ return false;
380
+ }
381
+
382
+ const { timescale } = this._index;
383
+ const segmentTimeRounding = getSegmentTimeRoundingError(timescale);
384
+ const scaledPeriodStart = this._periodStart * timescale;
385
+ const scaledRelativeEnd = end * timescale - scaledPeriodStart;
386
+ if (this._scaledRelativePeriodEnd === undefined) {
387
+ return (scaledRelativeEnd + segmentTimeRounding) >= 0;
388
+ }
389
+
390
+ const scaledRelativePeriodEnd = this._scaledRelativePeriodEnd;
391
+ const scaledRelativeStart = start * timescale - scaledPeriodStart;
392
+ return (scaledRelativeStart - segmentTimeRounding) < scaledRelativePeriodEnd &&
393
+ (scaledRelativeEnd + segmentTimeRounding) >= 0;
394
+ }
395
+
346
396
  /**
347
397
  * Returns true if, based on the arguments, the index should be refreshed.
348
398
  * We never have to refresh a SegmentTemplate-based manifest.
@@ -360,13 +410,6 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
360
410
  return null;
361
411
  }
362
412
 
363
- /**
364
- * @returns {Boolean}
365
- */
366
- areSegmentsChronologicallyGenerated() : true {
367
- return true;
368
- }
369
-
370
413
  /**
371
414
  * Returns `true` if the given segment should still be available as of now
372
415
  * (not removed since and still request-able).
@@ -403,7 +446,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
403
446
  if (!this._isDynamic) {
404
447
  return true;
405
448
  }
406
- if (this._scaledPeriodEnd === undefined) {
449
+ if (this._scaledRelativePeriodEnd === undefined) {
407
450
  return false;
408
451
  }
409
452
 
@@ -416,7 +459,8 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
416
459
  return false;
417
460
  }
418
461
  const lastSegmentEnd = lastSegmentStart + this._index.duration;
419
- return isPeriodFulfilled(timescale, lastSegmentEnd, this._scaledPeriodEnd);
462
+ const segmentTimeRounding = getSegmentTimeRoundingError(timescale);
463
+ return (lastSegmentEnd + segmentTimeRounding) >= this._scaledRelativePeriodEnd;
420
464
  }
421
465
 
422
466
  /**
@@ -434,7 +478,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
434
478
  this._aggressiveMode = newIndex._aggressiveMode;
435
479
  this._isDynamic = newIndex._isDynamic;
436
480
  this._periodStart = newIndex._periodStart;
437
- this._scaledPeriodEnd = newIndex._scaledPeriodEnd;
481
+ this._scaledRelativePeriodEnd = newIndex._scaledRelativePeriodEnd;
438
482
  this._manifestBoundsCalculator = newIndex._manifestBoundsCalculator;
439
483
  }
440
484
 
@@ -458,7 +502,9 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
458
502
  }
459
503
 
460
504
  // 1 - check that this index is already available
461
- if (this._scaledPeriodEnd === 0 || this._scaledPeriodEnd === undefined) {
505
+ if (this._scaledRelativePeriodEnd === 0 ||
506
+ this._scaledRelativePeriodEnd === undefined)
507
+ {
462
508
  // /!\ The scaled max position augments continuously and might not
463
509
  // reflect exactly the real server-side value. As segments are
464
510
  // generated discretely.
@@ -499,13 +545,13 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
499
545
  }
500
546
  const agressiveModeOffset = this._aggressiveMode ? (duration / timescale) :
501
547
  0;
502
- if (this._scaledPeriodEnd != null &&
503
- this._scaledPeriodEnd <
548
+ if (this._scaledRelativePeriodEnd != null &&
549
+ this._scaledRelativePeriodEnd <
504
550
  (lastPos + agressiveModeOffset - this._periodStart) * this._index.timescale) {
505
- if (this._scaledPeriodEnd < duration) {
551
+ if (this._scaledRelativePeriodEnd < duration) {
506
552
  return null;
507
553
  }
508
- return (Math.floor(this._scaledPeriodEnd / duration) - 1) * duration;
554
+ return (Math.floor(this._scaledRelativePeriodEnd / duration) - 1) * duration;
509
555
  }
510
556
  // /!\ The scaled last position augments continuously and might not
511
557
  // reflect exactly the real server-side value. As segments are
@@ -529,7 +575,7 @@ export default class TemplateRepresentationIndex implements IRepresentationIndex
529
575
  null :
530
576
  (numberOfSegmentsAvailable - 1) * duration;
531
577
  } else {
532
- const maximumTime = this._scaledPeriodEnd ?? 0;
578
+ const maximumTime = this._scaledRelativePeriodEnd ?? 0;
533
579
  const numberIndexedToZero = Math.ceil(maximumTime / duration) - 1;
534
580
  const regularLastSegmentStart = numberIndexedToZero * duration;
535
581
 
@@ -23,6 +23,7 @@ import {
23
23
  Representation,
24
24
  } from "../../../../../../manifest";
25
25
  import { IPlayerError } from "../../../../../../public_types";
26
+ import assert from "../../../../../../utils/assert";
26
27
  import { IEMSG } from "../../../../../containers/isobmff";
27
28
  import clearTimelineFromPosition from "../../../../utils/clear_timeline_from_position";
28
29
  import {
@@ -39,8 +40,8 @@ import ManifestBoundsCalculator from "../../manifest_bounds_calculator";
39
40
  import { IResolvedBaseUrl } from "../../resolve_base_urls";
40
41
  import getInitSegment from "../get_init_segment";
41
42
  import getSegmentsFromTimeline from "../get_segments_from_timeline";
42
- import isPeriodFulfilled from "../is_period_fulfilled";
43
43
  import { createIndexURLs } from "../tokens";
44
+ import { getSegmentTimeRoundingError } from "../utils";
44
45
  import constructTimelineFromElements from "./construct_timeline_from_elements";
45
46
  // eslint-disable-next-line max-len
46
47
  import constructTimelineFromPreviousTimeline from "./construct_timeline_from_previous_timeline";
@@ -363,7 +364,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
363
364
  * Returns null if nothing is in the index
364
365
  * @returns {Number|null}
365
366
  */
366
- getFirstPosition() : number|null {
367
+ getFirstAvailablePosition(): number | null {
367
368
  this._refreshTimeline();
368
369
  if (this._index.timeline === null) {
369
370
  this._index.timeline = this._getTimeline();
@@ -381,7 +382,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
381
382
  * Returns null if nothing is in the index
382
383
  * @returns {Number|null}
383
384
  */
384
- getLastPosition() : number|null {
385
+ getLastAvailablePosition(): number | null {
385
386
  this._refreshTimeline();
386
387
  if (this._index.timeline === null) {
387
388
  this._index.timeline = this._getTimeline();
@@ -392,6 +393,60 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
392
393
  fromIndexTime(lastTime, this._index);
393
394
  }
394
395
 
396
+ /**
397
+ * Returns the absolute end in seconds this RepresentationIndex can reach once
398
+ * all segments are available.
399
+ * @returns {number|null|undefined}
400
+ */
401
+ getEnd(): number | undefined | null {
402
+ if (!this._isDynamic || !this._isLastPeriod) { // @see isFinished
403
+ return this.getLastAvailablePosition();
404
+ }
405
+ return undefined;
406
+ }
407
+
408
+ /**
409
+ * Returns:
410
+ * - `true` if in the given time interval, at least one new segment is
411
+ * expected to be available in the future.
412
+ * - `false` either if all segments in that time interval are already
413
+ * available for download or if none will ever be available for it.
414
+ * - `undefined` when it is not possible to tell.
415
+ * @param {number} start
416
+ * @param {number} end
417
+ * @returns {boolean|undefined}
418
+ */
419
+ awaitSegmentBetween(start: number, end: number): boolean | undefined {
420
+ assert(start <= end);
421
+ if (!this._isDynamic || !this._isLastPeriod) {
422
+ return false;
423
+ }
424
+ this._refreshTimeline();
425
+ if (this._index.timeline === null) {
426
+ this._index.timeline = this._getTimeline();
427
+ }
428
+ const { timeline, timescale } = this._index;
429
+ const segmentTimeRounding = getSegmentTimeRoundingError(timescale);
430
+ const scaledEnd = toIndexTime(end, this._index);
431
+ if (timeline.length > 0) {
432
+ const lastTimelineElement = timeline[timeline.length - 1];
433
+ const lastSegmentEnd = getIndexSegmentEnd(lastTimelineElement,
434
+ null,
435
+ this._scaledPeriodEnd);
436
+ const roundedEnd = lastSegmentEnd + segmentTimeRounding;
437
+ if (roundedEnd >= Math.min(scaledEnd, this._scaledPeriodEnd ?? Infinity)) {
438
+ return false; // already loaded
439
+ }
440
+ }
441
+ if (this._scaledPeriodEnd === undefined) {
442
+ return (scaledEnd + segmentTimeRounding) > this._scaledPeriodStart ? undefined :
443
+ false;
444
+ }
445
+ const scaledStart = toIndexTime(start, this._index);
446
+ return (scaledStart - segmentTimeRounding) < this._scaledPeriodEnd &&
447
+ (scaledEnd + segmentTimeRounding) > this._scaledPeriodStart;
448
+ }
449
+
395
450
  /**
396
451
  * Returns true if a Segment returned by this index is still considered
397
452
  * available.
@@ -446,10 +501,6 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
446
501
  error.isHttpError(404);
447
502
  }
448
503
 
449
- areSegmentsChronologicallyGenerated() : boolean {
450
- return true;
451
- }
452
-
453
504
  /**
454
505
  * Replace this RepresentationIndex with one from a new version of the
455
506
  * Manifest.
@@ -518,7 +569,8 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
518
569
  const lastTime = getIndexSegmentEnd(lastTimelineElement,
519
570
  null,
520
571
  this._scaledPeriodEnd);
521
- return isPeriodFulfilled(this._index.timescale, lastTime, this._scaledPeriodEnd);
572
+ const segmentTimeRounding = getSegmentTimeRoundingError(this._index.timescale);
573
+ return (lastTime + segmentTimeRounding) >= this._scaledPeriodEnd;
522
574
  }
523
575
 
524
576
  /**
@@ -21,21 +21,12 @@ import config from "../../../../../config";
21
21
  * In Javascript, numbers are encoded in a way that a floating number may be
22
22
  * represented internally with a rounding error.
23
23
  *
24
- * As the period end is the result of a multiplication between a floating or integer
25
- * number (period end * timescale), this function takes into account the potential
26
- * rounding error to tell if the period is fulfilled with content.
24
+ * This function returns a small number allowing to accound for rounding many
25
+ * rounding errors.
27
26
  * @param {number} timescale
28
- * @param {number} lastSegmentEnd
29
- * @param {number} periodEnd
30
27
  * @returns {boolean}
31
28
  */
32
- export default function isPeriodFulfilled(
33
- timescale: number,
34
- lastSegmentEnd: number,
35
- periodEnd: number
36
- ): boolean {
37
- const scaledRoundingError =
38
- config.getCurrent().DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR * timescale;
39
- return (lastSegmentEnd + scaledRoundingError) >= periodEnd;
29
+ export function getSegmentTimeRoundingError(timescale: number): number {
30
+ return config.getCurrent().DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR * timescale;
40
31
  }
41
32
 
@@ -250,7 +250,7 @@ function getMaximumLastPosition(
250
250
  for (const adaptation of allAdaptations) {
251
251
  const representations = adaptation.representations;
252
252
  for (const representation of representations) {
253
- const position = representation.index.getLastPosition();
253
+ const position = representation.index.getLastAvailablePosition();
254
254
  if (position !== null) {
255
255
  allIndexAreEmpty = false;
256
256
  if (typeof position === "number") {
@@ -51,14 +51,13 @@ export default function parseLocalManifest(
51
51
  maximumPosition,
52
52
  isFinished } = localManifest;
53
53
  const parsedPeriods = localManifest.periods
54
- .map(period => parsePeriod(period, { periodIdGenerator,
55
- isFinished }));
54
+ .map(period => parsePeriod(period, { periodIdGenerator }));
56
55
 
57
56
  return { availabilityStartTime: 0,
58
57
  expired: localManifest.expired,
59
58
  transportType: "local",
60
59
  isDynamic: !isFinished,
61
- isLastPeriodKnown: isFinished,
60
+ isLastPeriodKnown: true,
62
61
  isLive: false,
63
62
  uris: [],
64
63
  timeBounds: { minimumSafePosition: minimumPosition ?? 0,
@@ -77,11 +76,8 @@ export default function parseLocalManifest(
77
76
  */
78
77
  function parsePeriod(
79
78
  period : ILocalPeriod,
80
- ctxt : { periodIdGenerator : () => string; /* Generate next Period's id */
81
- /** If true, the local Manifest has been fully downloaded. */
82
- isFinished : boolean; }
79
+ ctxt : { periodIdGenerator : () => string /* Generate next Period's id */ }
83
80
  ) : IParsedPeriod {
84
- const { isFinished } = ctxt;
85
81
  const adaptationIdGenerator = idGenerator();
86
82
  return {
87
83
  id: "period-" + ctxt.periodIdGenerator(),
@@ -97,8 +93,7 @@ function parsePeriod(
97
93
  adaps = [];
98
94
  acc[type] = adaps;
99
95
  }
100
- adaps.push(parseAdaptation(ada, { adaptationIdGenerator,
101
- isFinished }));
96
+ adaps.push(parseAdaptation(ada, { adaptationIdGenerator }));
102
97
  return acc;
103
98
  }, {}),
104
99
  };
@@ -111,11 +106,8 @@ function parsePeriod(
111
106
  */
112
107
  function parseAdaptation(
113
108
  adaptation : ILocalAdaptation,
114
- ctxt : { adaptationIdGenerator : () => string; /* Generate next Adaptation's id */
115
- /** If true, the local Manifest has been fully downloaded. */
116
- isFinished : boolean; }
109
+ ctxt : { adaptationIdGenerator : () => string /* Generate next Adaptation's id */ }
117
110
  ) : IParsedAdaptation {
118
- const { isFinished } = ctxt;
119
111
  const representationIdGenerator = idGenerator();
120
112
  return {
121
113
  id: "adaptation-" + ctxt.adaptationIdGenerator(),
@@ -124,8 +116,7 @@ function parseAdaptation(
124
116
  closedCaption: adaptation.closedCaption,
125
117
  language: adaptation.language,
126
118
  representations: adaptation.representations.map((representation) =>
127
- parseRepresentation(representation, { representationIdGenerator,
128
- isFinished })),
119
+ parseRepresentation(representation, { representationIdGenerator })),
129
120
  };
130
121
  }
131
122
 
@@ -135,11 +126,8 @@ function parseAdaptation(
135
126
  */
136
127
  function parseRepresentation(
137
128
  representation : ILocalRepresentation,
138
- ctxt : { representationIdGenerator : () => string;
139
- /** If true, the local Manifest has been fully downloaded. */
140
- isFinished : boolean; }
129
+ ctxt : { representationIdGenerator : () => string }
141
130
  ) : IParsedRepresentation {
142
- const { isFinished } = ctxt;
143
131
  const id = "representation-" + ctxt.representationIdGenerator();
144
132
  const contentProtections = representation.contentProtections === undefined ?
145
133
  undefined :
@@ -150,7 +138,7 @@ function parseRepresentation(
150
138
  width: representation.width,
151
139
  codecs: representation.codecs,
152
140
  mimeType: representation.mimeType,
153
- index: new LocalRepresentationIndex(representation.index, id, isFinished),
141
+ index: new LocalRepresentationIndex(representation.index, id),
154
142
  contentProtections };
155
143
  }
156
144
 
@@ -27,11 +27,9 @@ import {
27
27
  export default class LocalRepresentationIndex implements IRepresentationIndex {
28
28
  private _index : ILocalIndex;
29
29
  private _representationId : string;
30
- private _isFinished : boolean;
31
- constructor(index : ILocalIndex, representationId : string, isFinished : boolean) {
30
+ constructor(index : ILocalIndex, representationId : string) {
32
31
  this._index = index;
33
32
  this._representationId = representationId;
34
- this._isFinished = isFinished;
35
33
  }
36
34
 
37
35
  /**
@@ -96,7 +94,7 @@ export default class LocalRepresentationIndex implements IRepresentationIndex {
96
94
  /**
97
95
  * @returns {Number|undefined}
98
96
  */
99
- getFirstPosition() : number|undefined {
97
+ getFirstAvailablePosition() : number|undefined {
100
98
  if (this._index.segments.length === 0) {
101
99
  return undefined;
102
100
  }
@@ -107,12 +105,58 @@ export default class LocalRepresentationIndex implements IRepresentationIndex {
107
105
  /**
108
106
  * @returns {Number|undefined}
109
107
  */
110
- getLastPosition() : number|undefined {
108
+ getLastAvailablePosition() : number|undefined {
111
109
  if (this._index.segments.length === 0) {
112
110
  return undefined;
113
111
  }
114
112
  const lastSegment = this._index.segments[this._index.segments.length - 1];
115
- return lastSegment.time;
113
+ return lastSegment.time + lastSegment.duration;
114
+ }
115
+
116
+ /**
117
+ * Returns the expected ending position of this RepresentationIndex.
118
+ * `undefined` if unknown.
119
+ * @returns {number|undefined}
120
+ */
121
+ getEnd() : number | undefined {
122
+ if (this._index.isFinished) {
123
+ return this.getLastAvailablePosition();
124
+ }
125
+
126
+ const { incomingRanges, segments } = this._index;
127
+ if (incomingRanges === undefined || incomingRanges.length === 0) {
128
+ // If incomingRanges is empty but not finished... It's ambiguous.
129
+ return undefined;
130
+ }
131
+ const lastIncomingRange = incomingRanges[incomingRanges.length - 1];
132
+ const futureEnd = lastIncomingRange.end;
133
+ if (segments.length === 0) {
134
+ return futureEnd;
135
+ }
136
+ const lastSegment = this._index.segments[this._index.segments.length - 1];
137
+ return Math.max(lastSegment.time + lastSegment.duration, futureEnd);
138
+ }
139
+
140
+ /**
141
+ * Returns:
142
+ * - `true` if in the given time interval, at least one new segment is
143
+ * expected to be available in the future.
144
+ * - `false` either if all segments in that time interval are already
145
+ * available for download or if none will ever be available for it.
146
+ * - `undefined` when it is not possible to tell.
147
+ * @param {number} start
148
+ * @param {number} end
149
+ * @returns {boolean|undefined}
150
+ */
151
+ awaitSegmentBetween(start: number, end: number): boolean | undefined {
152
+ if (this.isFinished()) {
153
+ return false;
154
+ }
155
+ if (this._index.incomingRanges === undefined) {
156
+ return undefined;
157
+ }
158
+ return this._index.incomingRanges.some((range) =>
159
+ range.start < end && range.end > start);
116
160
  }
117
161
 
118
162
  /**
@@ -130,7 +174,7 @@ export default class LocalRepresentationIndex implements IRepresentationIndex {
130
174
  }
131
175
 
132
176
  isFinished() : boolean {
133
- return this._isFinished;
177
+ return this._index.isFinished;
134
178
  }
135
179
 
136
180
  /**
@@ -147,13 +191,6 @@ export default class LocalRepresentationIndex implements IRepresentationIndex {
147
191
  return null;
148
192
  }
149
193
 
150
- /**
151
- * @returns {boolean}
152
- */
153
- areSegmentsChronologicallyGenerated() : boolean {
154
- return false;
155
- }
156
-
157
194
  /**
158
195
  * @returns {Boolean}
159
196
  */
@@ -162,14 +199,12 @@ export default class LocalRepresentationIndex implements IRepresentationIndex {
162
199
  }
163
200
 
164
201
  _replace(newIndex : LocalRepresentationIndex) : void {
165
- this._isFinished = newIndex._isFinished;
166
202
  this._index.segments = newIndex._index.segments;
167
203
  this._index.loadSegment = newIndex._index.loadSegment;
168
204
  this._index.loadInitSegment = newIndex._index.loadInitSegment;
169
205
  }
170
206
 
171
207
  _update(newIndex : LocalRepresentationIndex) : void {
172
- this._isFinished = newIndex._isFinished;
173
208
  const newSegments = newIndex._index.segments;
174
209
  if (newSegments.length <= 0) {
175
210
  return;
@@ -153,6 +153,8 @@ export interface ILocalIndexSegment {
153
153
 
154
154
  /** "Index" of a `ILocalRepresentation`. Allow to declare available segments. */
155
155
  export interface ILocalIndex {
156
+ /** Every segments in that `ILocalIndex` has been downloaded. */
157
+ isFinished : boolean;
156
158
  /** Callback used to retrieve the initialization segment of a `ILocalRepresentation`. */
157
159
  loadInitSegment : ILocalManifestInitSegmentLoader;
158
160
  /** Callback used to retrieve a media segment of a `ILocalRepresentation`. */
@@ -162,6 +164,11 @@ export interface ILocalIndex {
162
164
  * Doesn't include the initialization segment.
163
165
  */
164
166
  segments : ILocalIndexSegment[];
167
+ /**
168
+ * List of not yet available time ranges, that should become available once
169
+ * the content is fully loaded.
170
+ */
171
+ incomingRanges?: Array<{ start : number; end : number }>;
165
172
  }
166
173
 
167
174
  /** A quality for a given "local" Manifest track (`ILocalAdaptation`). */
@@ -244,6 +251,12 @@ export interface ILocalManifest {
244
251
  * `start` and `duration` from a ILocalPeriod and `time` and
245
252
  * `duration` from a `ILocalIndexSegment` are now in seconds
246
253
  * instead of milliseconds.
254
+ *
255
+ * - "0.3": optional `futureSegments` has been added to `ILocalIndex`
256
+ * `duration` has been removed.
257
+ *
258
+ * A mandatory `isFinished` boolean should now be set on a
259
+ * `ILocalIndex`.
247
260
  */
248
261
  version : string;
249
262
  /**
@@ -112,8 +112,8 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
112
112
  * `undefined` if we do not know this value.
113
113
  * @return {Number|undefined}
114
114
  */
115
- public getFirstPosition(): number|undefined {
116
- const wrappedFirstPosition = this._wrappedIndex.getFirstPosition();
115
+ public getFirstAvailablePosition(): number|undefined {
116
+ const wrappedFirstPosition = this._wrappedIndex.getFirstAvailablePosition();
117
117
  return wrappedFirstPosition != null ? wrappedFirstPosition + this._timeOffset :
118
118
  undefined;
119
119
  }
@@ -123,12 +123,39 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
123
123
  * `undefined` if we do not know this value.
124
124
  * @return {Number|undefined}
125
125
  */
126
- public getLastPosition(): number|undefined {
127
- const wrappedLastPosition = this._wrappedIndex.getLastPosition();
126
+ public getLastAvailablePosition(): number|undefined {
127
+ const wrappedLastPosition = this._wrappedIndex.getLastAvailablePosition();
128
128
  return wrappedLastPosition != null ? wrappedLastPosition + this._timeOffset :
129
129
  undefined;
130
130
  }
131
131
 
132
+ /**
133
+ * Returns the absolute end in seconds this RepresentationIndex can reach once
134
+ * all segments are available.
135
+ * @returns {number|null|undefined}
136
+ */
137
+ public getEnd(): number|undefined|null {
138
+ const wrappedEnd = this._wrappedIndex.getEnd();
139
+ return wrappedEnd != null ? wrappedEnd + this._timeOffset :
140
+ undefined;
141
+ }
142
+
143
+ /**
144
+ * Returns:
145
+ * - `true` if in the given time interval, at least one new segment is
146
+ * expected to be available in the future.
147
+ * - `false` either if all segments in that time interval are already
148
+ * available for download or if none will ever be available for it.
149
+ * - `undefined` when it is not possible to tell.
150
+ * @param {number} start
151
+ * @param {number} end
152
+ * @returns {boolean|undefined}
153
+ */
154
+ public awaitSegmentBetween(start: number, end: number): boolean | undefined {
155
+ return this._wrappedIndex.awaitSegmentBetween(start - this._timeOffset,
156
+ end - this._timeOffset);
157
+ }
158
+
132
159
  /**
133
160
  * Returns `false` if that segment is not currently available in the Manifest
134
161
  * (e.g. it corresponds to a segment which is before the current buffer
@@ -166,13 +193,6 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
166
193
  return this._wrappedIndex.checkDiscontinuity(time - this._timeOffset);
167
194
  }
168
195
 
169
- /**
170
- * @returns {Boolean}
171
- */
172
- public areSegmentsChronologicallyGenerated(): boolean {
173
- return this._wrappedIndex.areSegmentsChronologicallyGenerated();
174
- }
175
-
176
196
  /**
177
197
  * @returns {Boolean}
178
198
  */