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
@@ -263,10 +263,15 @@ export interface IParsedManifest {
263
263
  */
264
264
  timeBounds : {
265
265
  /**
266
- * The minimum time, in seconds, available in this Manifest.
267
- * `undefined` if that value is unknown.
266
+ * This is the theoretical minimum playable position on the content
267
+ * regardless of the current Adaptation chosen, as estimated at parsing
268
+ * time.
269
+ * `undefined` if unknown.
270
+ *
271
+ * More technically, the `minimumSafePosition` is the maximum between all
272
+ * the minimum positions reachable in any of the audio and video Adaptation.
268
273
  */
269
- absoluteMinimumTime? : number | undefined;
274
+ minimumSafePosition? : number | undefined;
270
275
  /**
271
276
  * Some dynamic contents have the concept of a "window depth" (or "buffer
272
277
  * depth") which allows to set a minimum position for all reachable
@@ -283,27 +288,50 @@ export interface IParsedManifest {
283
288
  timeshiftDepth : number | null;
284
289
  /** Data allowing to calculate the maximum position at any given time. */
285
290
  maximumTimeData : {
286
- /** Maximum seekable time in milliseconds calculated at `time`. */
287
- value : number;
288
291
  /**
289
- * `Performance.now()` output at the time `value` was calculated.
290
- * This can be used to retrieve the maximum position from `value` when it
291
- * linearly evolves over time (see `isLinear` property).
292
+ * Current position representing live content.
293
+ * Only makes sense for un-ended live contents.
294
+ *
295
+ * `undefined` if unknown or if it doesn't make sense in the current context.
292
296
  */
293
- time : number;
297
+ livePosition : number | undefined;
294
298
  /**
295
- * Whether the maximum seekable position evolves linearly over time.
299
+ * Whether the maximum positions should evolve linearly over time.
296
300
  *
297
- * If set to `false`, `value` indicates the constant maximum position.
298
- *
299
- * If set to `true`, the maximum seekable time continuously increase at
301
+ * If set to `true`, the maximum seekable position continuously increase at
300
302
  * the same rate than the time since `time` does.
301
- * For example, a `value` of 50000 (50 seconds) will indicate a maximum time
302
- * of 51 seconds after 1 second have passed, of 56 seconds after 6 seconds
303
- * have passed (we know how many seconds have passed since the initial
304
- * calculation of value by checking the `time` property) etc.
303
+ *
304
+ * For example, a `maximumSafePosition` of 50000 (50 seconds) will
305
+ * indicate a maximum time of 51 seconds after 1 second have passed, of 56
306
+ * seconds after 6 seconds have passed (we know how many seconds have
307
+ * passed since the initial calculation of the maximum position by
308
+ * checking the `time` property) etc.
305
309
  */
306
310
  isLinear: boolean;
311
+ /**
312
+ * This is the theoretical maximum playable position on the content,
313
+ * regardless of the current Adaptation chosen, as estimated at parsing
314
+ * time.
315
+ *
316
+ * More technically, the `maximumSafePosition` is the minimum between all
317
+ * attributes indicating the duration of the content in the Manifest.
318
+ *
319
+ * That is the minimum between:
320
+ * - The Manifest original attributes relative to its duration
321
+ * - The minimum between all known maximum audio positions
322
+ * - The minimum between all known maximum video positions
323
+ *
324
+ * This can for example be understood as the safe maximum playable
325
+ * position through all possible tacks.
326
+ */
327
+ maximumSafePosition : number;
328
+ /**
329
+ * `Performance.now()` output at the time both `maximumSafePosition` and
330
+ * `maximumPositionWithMediaData` were calculated.
331
+ * This can be used to retrieve a new maximum position from them when they
332
+ * linearly evolves over time (see `isLinear` property).
333
+ */
334
+ time : number;
307
335
  };
308
336
  };
309
337
  /**
@@ -24,7 +24,9 @@ import getLastPositionFromAdaptation from "./get_last_time_from_adaptation";
24
24
  */
25
25
  export default function getMaximumPosition(
26
26
  periods : IParsedPeriod[]
27
- ) : number | undefined {
27
+ ) : { safe : number | undefined;
28
+ unsafe : number | undefined; }
29
+ {
28
30
  for (let i = periods.length - 1; i >= 0; i--) {
29
31
  const periodAdaptations = periods[i].adaptations;
30
32
  const firstAudioAdaptationFromPeriod = periodAdaptations.audio === undefined ?
@@ -44,7 +46,7 @@ export default function getMaximumPosition(
44
46
  const lastPosition =
45
47
  getLastPositionFromAdaptation(firstAudioAdaptationFromPeriod);
46
48
  if (lastPosition === undefined) {
47
- return undefined;
49
+ return { safe: undefined, unsafe: undefined };
48
50
  }
49
51
  maximumAudioPosition = lastPosition;
50
52
  }
@@ -52,7 +54,7 @@ export default function getMaximumPosition(
52
54
  const lastPosition =
53
55
  getLastPositionFromAdaptation(firstVideoAdaptationFromPeriod);
54
56
  if (lastPosition === undefined) {
55
- return undefined;
57
+ return { safe: undefined, unsafe: undefined };
56
58
  }
57
59
  maximumVideoPosition = lastPosition;
58
60
  }
@@ -64,18 +66,22 @@ export default function getMaximumPosition(
64
66
  ) {
65
67
  log.info("Parser utils: found Period with no segment. ",
66
68
  "Going to previous one to calculate last position");
67
- return undefined;
69
+ return { safe: undefined, unsafe: undefined };
68
70
  }
69
71
 
70
72
  if (maximumVideoPosition !== null) {
71
73
  if (maximumAudioPosition !== null) {
72
- return Math.min(maximumAudioPosition, maximumVideoPosition);
74
+ return { safe: Math.min(maximumAudioPosition, maximumVideoPosition),
75
+ unsafe: Math.max(maximumAudioPosition, maximumVideoPosition) };
73
76
  }
74
- return maximumVideoPosition;
77
+ return { safe: maximumVideoPosition,
78
+ unsafe: maximumVideoPosition };
75
79
  }
76
80
  if (maximumAudioPosition !== null) {
77
- return maximumAudioPosition;
81
+ return { safe: maximumAudioPosition,
82
+ unsafe: maximumAudioPosition };
78
83
  }
79
84
  }
80
85
  }
86
+ return { safe: undefined, unsafe: undefined };
81
87
  }
@@ -1,27 +0,0 @@
1
- /**
2
- * Copyright 2015 CANAL+ Group
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import { Observable } from "rxjs";
17
- import Manifest from "../../manifest";
18
- /**
19
- * Keep the MediaSource duration up-to-date with the Manifest one on
20
- * subscription:
21
- * Set the current duration initially and then update if needed after
22
- * each Manifest updates.
23
- * @param {Object} manifest
24
- * @param {MediaSource} mediaSource
25
- * @returns {Observable}
26
- */
27
- export default function DurationUpdater(manifest: Manifest, mediaSource: MediaSource): Observable<never>;
@@ -1,136 +0,0 @@
1
- /**
2
- * Copyright 2015 CANAL+ Group
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import { catchError, combineLatest as observableCombineLatest, defer as observableDefer, distinctUntilChanged, EMPTY, filter, fromEvent as observableFromEvent, ignoreElements, interval as observableInterval, map, merge as observableMerge, mergeMap, of as observableOf, startWith, switchMap, take, tap, } from "rxjs";
17
- import { onSourceOpen$, onSourceClose$, onSourceEnded$, } from "../../compat/event_listeners";
18
- import log from "../../log";
19
- import { fromEvent } from "../../utils/event_emitter";
20
- /** Number of seconds in a regular year. */
21
- var YEAR_IN_SECONDS = 365 * 24 * 3600;
22
- /**
23
- * Keep the MediaSource duration up-to-date with the Manifest one on
24
- * subscription:
25
- * Set the current duration initially and then update if needed after
26
- * each Manifest updates.
27
- * @param {Object} manifest
28
- * @param {MediaSource} mediaSource
29
- * @returns {Observable}
30
- */
31
- export default function DurationUpdater(manifest, mediaSource) {
32
- return observableDefer(function () {
33
- var lastDurationUpdate;
34
- return setMediaSourceDuration(mediaSource, manifest).pipe(mergeMap(function (initialDurationUpdate) {
35
- // only update `lastDurationUpdate` if the MediaSource's duration has
36
- // been updated.
37
- if (initialDurationUpdate !== null) {
38
- lastDurationUpdate = initialDurationUpdate;
39
- }
40
- return fromEvent(manifest, "manifestUpdate").pipe(switchMap(function () { return setMediaSourceDuration(mediaSource, manifest, lastDurationUpdate); }), tap(function (durationUpdate) {
41
- if (durationUpdate !== null) {
42
- lastDurationUpdate = durationUpdate;
43
- }
44
- }));
45
- }),
46
- // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default
47
- // first type parameter as `any` instead of the perfectly fine `unknown`,
48
- // leading to linter issues, as it forbids the usage of `any`.
49
- // This is why we're disabling the eslint rule.
50
- /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */
51
- ignoreElements());
52
- });
53
- }
54
- /**
55
- * Checks that duration can be updated on the MediaSource, and then
56
- * sets it.
57
- *
58
- * Returns either:
59
- * - the new duration it has been updated to if it has
60
- * - `null` if it hasn'nt been updated
61
- *
62
- * @param {MediaSource} mediaSource
63
- * @param {Object} manifest
64
- * @param {number | undefined} lastSetDuration
65
- * @returns {Observable.<number | null>}
66
- */
67
- function setMediaSourceDuration(mediaSource, manifest, lastSetDuration) {
68
- return isMediaSourceOpened$(mediaSource).pipe(switchMap(function (isMediaSourceOpened) {
69
- if (!isMediaSourceOpened) {
70
- return EMPTY;
71
- }
72
- return whenSourceBuffersEndedUpdates$(mediaSource.sourceBuffers);
73
- }), take(1), map(function () {
74
- var maximumPosition = manifest.getMaximumPosition();
75
- var isLive = manifest.isLive;
76
- // Some targets poorly support setting a very high number for durations.
77
- // Yet, in live contents, we would prefer setting a value as high as possible
78
- // to still be able to seek anywhere we want to (even ahead of the Manifest if
79
- // we want to). As such, we put it at a safe default value of 2^32 excepted
80
- // when the maximum position is already relatively close to that value, where
81
- // we authorize exceptionally going over it.
82
- var newDuration = !isLive ? maximumPosition :
83
- Math.max(Math.pow(2, 32), maximumPosition + YEAR_IN_SECONDS);
84
- if (mediaSource.duration >= newDuration ||
85
- // Even if the MediaSource duration is different than the duration that
86
- // we want to set now, the last duration we wanted to set may be the same,
87
- // as the MediaSource duration may have been changed by the browser.
88
- //
89
- // In that case, we do not want to update it.
90
- //
91
- newDuration === lastSetDuration) {
92
- return null;
93
- }
94
- if (isNaN(mediaSource.duration) || !isFinite(mediaSource.duration) ||
95
- newDuration - mediaSource.duration > 0.01) {
96
- log.info("Init: Updating duration", newDuration);
97
- mediaSource.duration = newDuration;
98
- return newDuration;
99
- }
100
- return null;
101
- }), catchError(function (err) {
102
- log.warn("Duration Updater: Can't update duration on the MediaSource.", err);
103
- return observableOf(null);
104
- }));
105
- }
106
- /**
107
- * Returns an Observable which will emit only when all the SourceBuffers ended
108
- * all pending updates.
109
- * @param {SourceBufferList} sourceBuffers
110
- * @returns {Observable}
111
- */
112
- function whenSourceBuffersEndedUpdates$(sourceBuffers) {
113
- if (sourceBuffers.length === 0) {
114
- return observableOf(undefined);
115
- }
116
- var sourceBufferUpdatingStatuses = [];
117
- var _loop_1 = function (i) {
118
- var sourceBuffer = sourceBuffers[i];
119
- sourceBufferUpdatingStatuses.push(observableMerge(observableFromEvent(sourceBuffer, "updatestart").pipe(map(function () { return true; })), observableFromEvent(sourceBuffer, "update").pipe(map(function () { return false; })), observableInterval(500).pipe(map(function () { return sourceBuffer.updating; }))).pipe(startWith(sourceBuffer.updating), distinctUntilChanged()));
120
- };
121
- for (var i = 0; i < sourceBuffers.length; i++) {
122
- _loop_1(i);
123
- }
124
- return observableCombineLatest(sourceBufferUpdatingStatuses).pipe(filter(function (areUpdating) {
125
- return areUpdating.every(function (isUpdating) { return !isUpdating; });
126
- }), map(function () { return undefined; }));
127
- }
128
- /**
129
- * Emit a boolean that tells if the media source is opened or not.
130
- * @param {MediaSource} mediaSource
131
- * @returns {Object}
132
- */
133
- function isMediaSourceOpened$(mediaSource) {
134
- return observableMerge(onSourceOpen$(mediaSource).pipe(map(function () { return true; })), onSourceEnded$(mediaSource).pipe(map(function () { return false; })), onSourceClose$(mediaSource).pipe(map(function () { return false; }))).pipe(startWith(mediaSource.readyState === "open"), distinctUntilChanged());
135
- }
136
- /* eslint-enable @typescript-eslint/no-unsafe-argument */
@@ -1,206 +0,0 @@
1
- /**
2
- * Copyright 2015 CANAL+ Group
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- import {
18
- catchError,
19
- combineLatest as observableCombineLatest,
20
- defer as observableDefer,
21
- distinctUntilChanged,
22
- EMPTY,
23
- filter,
24
- fromEvent as observableFromEvent,
25
- ignoreElements,
26
- interval as observableInterval,
27
- map,
28
- merge as observableMerge,
29
- mergeMap,
30
- Observable,
31
- of as observableOf,
32
- startWith,
33
- switchMap,
34
- take,
35
- tap,
36
- } from "rxjs";
37
- import {
38
- onSourceOpen$,
39
- onSourceClose$,
40
- onSourceEnded$,
41
- } from "../../compat/event_listeners";
42
- import log from "../../log";
43
- import Manifest from "../../manifest";
44
- import { fromEvent } from "../../utils/event_emitter";
45
-
46
- /** Number of seconds in a regular year. */
47
- const YEAR_IN_SECONDS = 365 * 24 * 3600;
48
-
49
- /**
50
- * Keep the MediaSource duration up-to-date with the Manifest one on
51
- * subscription:
52
- * Set the current duration initially and then update if needed after
53
- * each Manifest updates.
54
- * @param {Object} manifest
55
- * @param {MediaSource} mediaSource
56
- * @returns {Observable}
57
- */
58
- export default function DurationUpdater(
59
- manifest : Manifest,
60
- mediaSource : MediaSource
61
- ) : Observable<never> {
62
- return observableDefer(() => {
63
- let lastDurationUpdate: number | undefined;
64
- return setMediaSourceDuration(mediaSource, manifest).pipe(
65
- mergeMap((initialDurationUpdate) => {
66
- // only update `lastDurationUpdate` if the MediaSource's duration has
67
- // been updated.
68
- if (initialDurationUpdate !== null) {
69
- lastDurationUpdate = initialDurationUpdate;
70
- }
71
-
72
- return fromEvent(manifest, "manifestUpdate").pipe(
73
- switchMap(() => setMediaSourceDuration(mediaSource,
74
- manifest,
75
- lastDurationUpdate)),
76
- tap((durationUpdate) => {
77
- if (durationUpdate !== null) {
78
- lastDurationUpdate = durationUpdate;
79
- }
80
- })
81
- );
82
- }),
83
- // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default
84
- // first type parameter as `any` instead of the perfectly fine `unknown`,
85
- // leading to linter issues, as it forbids the usage of `any`.
86
- // This is why we're disabling the eslint rule.
87
- /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */
88
- ignoreElements()
89
- );
90
- });
91
- }
92
-
93
- /**
94
- * Checks that duration can be updated on the MediaSource, and then
95
- * sets it.
96
- *
97
- * Returns either:
98
- * - the new duration it has been updated to if it has
99
- * - `null` if it hasn'nt been updated
100
- *
101
- * @param {MediaSource} mediaSource
102
- * @param {Object} manifest
103
- * @param {number | undefined} lastSetDuration
104
- * @returns {Observable.<number | null>}
105
- */
106
- function setMediaSourceDuration(
107
- mediaSource: MediaSource,
108
- manifest: Manifest,
109
- lastSetDuration?: number
110
- ): Observable<number | null> {
111
- return isMediaSourceOpened$(mediaSource).pipe(
112
- switchMap((isMediaSourceOpened) => {
113
- if (!isMediaSourceOpened) {
114
- return EMPTY;
115
- }
116
- return whenSourceBuffersEndedUpdates$(mediaSource.sourceBuffers);
117
- }),
118
- take(1),
119
- map(() : number | null => {
120
- const maximumPosition = manifest.getMaximumPosition();
121
- const { isLive } = manifest;
122
- // Some targets poorly support setting a very high number for durations.
123
- // Yet, in live contents, we would prefer setting a value as high as possible
124
- // to still be able to seek anywhere we want to (even ahead of the Manifest if
125
- // we want to). As such, we put it at a safe default value of 2^32 excepted
126
- // when the maximum position is already relatively close to that value, where
127
- // we authorize exceptionally going over it.
128
- const newDuration = !isLive ? maximumPosition :
129
- Math.max(Math.pow(2, 32),
130
- maximumPosition + YEAR_IN_SECONDS);
131
-
132
- if (mediaSource.duration >= newDuration ||
133
- // Even if the MediaSource duration is different than the duration that
134
- // we want to set now, the last duration we wanted to set may be the same,
135
- // as the MediaSource duration may have been changed by the browser.
136
- //
137
- // In that case, we do not want to update it.
138
- //
139
- newDuration === lastSetDuration) {
140
- return null;
141
- }
142
- if (isNaN(mediaSource.duration) || !isFinite(mediaSource.duration) ||
143
- newDuration - mediaSource.duration > 0.01) {
144
- log.info("Init: Updating duration", newDuration);
145
- mediaSource.duration = newDuration;
146
- return newDuration;
147
- }
148
- return null;
149
- }),
150
- catchError((err) => {
151
- log.warn("Duration Updater: Can't update duration on the MediaSource.", err);
152
- return observableOf(null);
153
- })
154
- );
155
- }
156
-
157
- /**
158
- * Returns an Observable which will emit only when all the SourceBuffers ended
159
- * all pending updates.
160
- * @param {SourceBufferList} sourceBuffers
161
- * @returns {Observable}
162
- */
163
- function whenSourceBuffersEndedUpdates$(
164
- sourceBuffers: SourceBufferList
165
- ) : Observable<undefined> {
166
- if (sourceBuffers.length === 0) {
167
- return observableOf(undefined);
168
- }
169
- const sourceBufferUpdatingStatuses : Array<Observable<boolean>> = [];
170
-
171
- for (let i = 0; i < sourceBuffers.length; i++) {
172
- const sourceBuffer = sourceBuffers[i];
173
- sourceBufferUpdatingStatuses.push(
174
- observableMerge(
175
- observableFromEvent(sourceBuffer, "updatestart").pipe(map(() => true)),
176
- observableFromEvent(sourceBuffer, "update").pipe(map(() => false)),
177
- observableInterval(500).pipe(map(() => sourceBuffer.updating))
178
- ).pipe(
179
- startWith(sourceBuffer.updating),
180
- distinctUntilChanged()
181
- )
182
- );
183
- }
184
- return observableCombineLatest(sourceBufferUpdatingStatuses).pipe(
185
- filter((areUpdating) => {
186
- return areUpdating.every((isUpdating) => !isUpdating);
187
- }),
188
- map(() => undefined)
189
- );
190
- }
191
-
192
- /**
193
- * Emit a boolean that tells if the media source is opened or not.
194
- * @param {MediaSource} mediaSource
195
- * @returns {Object}
196
- */
197
- function isMediaSourceOpened$(mediaSource: MediaSource): Observable<boolean> {
198
- return observableMerge(onSourceOpen$(mediaSource).pipe(map(() => true)),
199
- onSourceEnded$(mediaSource).pipe(map(() => false)),
200
- onSourceClose$(mediaSource).pipe(map(() => false))
201
- ).pipe(
202
- startWith(mediaSource.readyState === "open"),
203
- distinctUntilChanged()
204
- );
205
- }
206
- /* eslint-enable @typescript-eslint/no-unsafe-argument */