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.
- package/.github/workflows/checks.yml +20 -18
- package/CHANGELOG.md +11 -0
- package/VERSION +1 -1
- package/dist/_esm5.processed/compat/eme/load_session.d.ts +5 -6
- package/dist/_esm5.processed/compat/eme/load_session.js +5 -6
- package/dist/_esm5.processed/core/api/public_api.js +2 -2
- package/dist/_esm5.processed/core/decrypt/create_session.js +33 -4
- package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.d.ts +14 -0
- package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.js +25 -0
- package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.d.ts +1 -1
- package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +1 -1
- package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +110 -38
- package/dist/_esm5.processed/core/stream/representation/check_for_discontinuity.js +21 -9
- package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +21 -29
- package/dist/_esm5.processed/errors/encrypted_media_error.d.ts +1 -2
- package/dist/_esm5.processed/errors/encrypted_media_error.js +0 -1
- package/dist/_esm5.processed/errors/error_codes.d.ts +6 -1
- package/dist/_esm5.processed/errors/media_error.d.ts +1 -2
- package/dist/_esm5.processed/errors/media_error.js +0 -1
- package/dist/_esm5.processed/errors/network_error.d.ts +1 -2
- package/dist/_esm5.processed/errors/network_error.js +0 -1
- package/dist/_esm5.processed/errors/other_error.d.ts +1 -2
- package/dist/_esm5.processed/errors/other_error.js +0 -1
- package/dist/_esm5.processed/manifest/manifest.d.ts +1 -1
- package/dist/_esm5.processed/manifest/manifest.js +1 -1
- package/dist/_esm5.processed/manifest/representation_index/static.d.ts +21 -6
- package/dist/_esm5.processed/manifest/representation_index/static.js +26 -8
- package/dist/_esm5.processed/manifest/representation_index/types.d.ts +55 -44
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.d.ts +21 -8
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +25 -10
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +26 -12
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +26 -13
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.d.ts +23 -7
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +65 -22
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.d.ts +20 -3
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +57 -7
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/{is_period_fulfilled.d.ts → utils.d.ts} +3 -6
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/{is_period_fulfilled.js → utils.js} +4 -8
- package/dist/_esm5.processed/parsers/manifest/dash/common/parse_periods.js +1 -1
- package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +5 -8
- package/dist/_esm5.processed/parsers/manifest/local/representation_index.d.ts +21 -8
- package/dist/_esm5.processed/parsers/manifest/local/representation_index.js +49 -14
- package/dist/_esm5.processed/parsers/manifest/local/types.d.ts +16 -0
- package/dist/_esm5.processed/parsers/manifest/metaplaylist/representation_index.d.ts +20 -6
- package/dist/_esm5.processed/parsers/manifest/metaplaylist/representation_index.js +28 -10
- package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +4 -4
- package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.d.ts +21 -12
- package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.js +39 -14
- package/dist/_esm5.processed/parsers/manifest/utils/get_first_time_from_adaptation.js +1 -1
- package/dist/_esm5.processed/parsers/manifest/utils/get_last_time_from_adaptation.js +1 -1
- package/dist/_esm5.processed/transports/metaplaylist/pipelines.js +0 -2
- package/dist/_esm5.processed/transports/smooth/segment_loader.js +1 -1
- package/dist/_esm5.processed/transports/types.d.ts +1 -1
- package/dist/_esm5.processed/utils/deep_merge.d.ts +1 -1
- package/dist/_esm5.processed/utils/deep_merge.js +6 -5
- package/dist/_esm5.processed/utils/task_canceller.d.ts +0 -3
- package/dist/_esm5.processed/utils/task_canceller.js +0 -3
- package/dist/mpd-parser.wasm +0 -0
- package/dist/rx-player.js +1382 -1058
- package/dist/rx-player.min.js +1 -1
- package/jest.config.js +5 -0
- package/package.json +31 -30
- package/sonar-project.properties +1 -1
- package/src/compat/eme/load_session.ts +5 -6
- package/src/core/api/public_api.ts +2 -2
- package/src/core/decrypt/create_session.ts +28 -2
- package/src/core/decrypt/utils/loaded_sessions_store.ts +29 -0
- package/src/core/fetchers/segment/segment_fetcher.ts +1 -1
- package/src/core/init/content_time_boundaries_observer.ts +116 -42
- package/src/core/stream/representation/check_for_discontinuity.ts +28 -10
- package/src/core/stream/representation/get_buffer_status.ts +27 -34
- package/src/errors/encrypted_media_error.ts +1 -2
- package/src/errors/error_codes.ts +2 -2
- package/src/errors/media_error.ts +1 -2
- package/src/errors/network_error.ts +1 -2
- package/src/errors/other_error.ts +1 -2
- package/src/manifest/__tests__/adaptation.test.ts +4 -3
- package/src/manifest/__tests__/representation.test.ts +4 -3
- package/src/manifest/manifest.ts +1 -1
- package/src/manifest/representation_index/__tests__/static.test.ts +5 -4
- package/src/manifest/representation_index/static.ts +28 -9
- package/src/manifest/representation_index/types.ts +62 -46
- package/src/parsers/manifest/dash/common/indexes/base.ts +27 -11
- package/src/parsers/manifest/dash/common/indexes/list.ts +32 -15
- package/src/parsers/manifest/dash/common/indexes/template.ts +73 -27
- package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +60 -8
- package/src/parsers/manifest/dash/common/indexes/{is_period_fulfilled.ts → utils.ts} +4 -13
- package/src/parsers/manifest/dash/common/parse_periods.ts +1 -1
- package/src/parsers/manifest/local/parse_local_manifest.ts +8 -20
- package/src/parsers/manifest/local/representation_index.ts +51 -16
- package/src/parsers/manifest/local/types.ts +13 -0
- package/src/parsers/manifest/metaplaylist/representation_index.ts +31 -11
- package/src/parsers/manifest/smooth/create_parser.ts +4 -4
- package/src/parsers/manifest/smooth/representation_index.ts +40 -15
- package/src/parsers/manifest/utils/__tests__/get_first_time_from_adaptations.test.ts +4 -3
- package/src/parsers/manifest/utils/__tests__/get_last_time_from_adaptation.test.ts +4 -3
- package/src/parsers/manifest/utils/get_first_time_from_adaptation.ts +1 -1
- package/src/parsers/manifest/utils/get_last_time_from_adaptation.ts +1 -1
- package/src/transports/metaplaylist/pipelines.ts +0 -2
- package/src/transports/smooth/segment_loader.ts +1 -1
- package/src/transports/types.ts +1 -1
- package/src/utils/__tests__/initialization_segment_cache.test.ts +7 -0
- package/src/utils/deep_merge.ts +7 -4
- 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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
332
|
+
getLastAvailablePosition() : number|null|undefined {
|
|
331
333
|
const lastSegmentStart = this._getLastSegmentStart();
|
|
332
334
|
if (lastSegmentStart == null) {
|
|
333
|
-
// In that case (null or undefined),
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
503
|
-
this.
|
|
548
|
+
if (this._scaledRelativePeriodEnd != null &&
|
|
549
|
+
this._scaledRelativePeriodEnd <
|
|
504
550
|
(lastPos + agressiveModeOffset - this._periodStart) * this._index.timescale) {
|
|
505
|
-
if (this.
|
|
551
|
+
if (this._scaledRelativePeriodEnd < duration) {
|
|
506
552
|
return null;
|
|
507
553
|
}
|
|
508
|
-
return (Math.floor(this.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
25
|
-
*
|
|
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
|
|
33
|
-
timescale
|
|
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.
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
116
|
-
const wrappedFirstPosition = this._wrappedIndex.
|
|
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
|
|
127
|
-
const wrappedLastPosition = this._wrappedIndex.
|
|
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
|
*/
|