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
@@ -17,13 +17,40 @@
17
17
  import config from "../../config";
18
18
  import log from "../../log";
19
19
  import Manifest from "../../manifest";
20
+ import isNullOrUndefined from "../../utils/is_null_or_undefined";
20
21
 
21
-
22
- export interface IInitialTimeOptions { position? : number;
23
- wallClockTime? : number;
24
- fromFirstPosition? : number;
25
- fromLastPosition? : number;
26
- percentage? : number; }
22
+ /**
23
+ * All possible initial time options that can be set.
24
+ *
25
+ * Written this way (with many type possibilities) on purpose to avoid issues
26
+ * if the application using the RxPlayer gives undocumented values such as
27
+ * `null`.
28
+ *
29
+ * TODO we shouldn't be that robust, at least not outside the API code, because
30
+ * if we begin to be that liberal here, where should we end?
31
+ * We may instead either want to progressively remove tolerance on the wrong
32
+ * type being given or be better to enforce type-compliancy in the API code.
33
+ */
34
+ export interface IInitialTimeOptions {
35
+ /** If set, we should begin at this position, in seconds. */
36
+ position? : number | null | undefined;
37
+ /** If set, we should begin at this unix timestamp, in seconds. */
38
+ wallClockTime? : number | null | undefined;
39
+ /**
40
+ * If set, we should begin at this position relative to the content's start,
41
+ * in seconds.
42
+ */
43
+ fromFirstPosition? : number | null | undefined;
44
+ /**
45
+ * If set, we should begin at this position relative to the content's end,
46
+ * in seconds.
47
+ */
48
+ fromLastPosition? : number | null | undefined;
49
+ /** If set, we should begin at this position relative to the whole duration of
50
+ * the content, in percentage.
51
+ */
52
+ percentage? : number | null | undefined;
53
+ }
27
54
 
28
55
  /**
29
56
  * Returns the calculated initial time for the content described by the given
@@ -43,33 +70,39 @@ export default function getInitialTime(
43
70
  lowLatencyMode : boolean,
44
71
  startAt? : IInitialTimeOptions
45
72
  ) : number {
46
- if (startAt != null) {
47
- const min = manifest.getMinimumPosition();
48
- const max = manifest.getMaximumPosition();
49
- if (startAt.position != null) {
73
+ if (!isNullOrUndefined(startAt)) {
74
+ const min = manifest.getMinimumSafePosition();
75
+ let max;
76
+ if (manifest.isLive) {
77
+ max = manifest.getLivePosition();
78
+ }
79
+ if (max === undefined) {
80
+ max = manifest.getMaximumSafePosition();
81
+ }
82
+ if (!isNullOrUndefined(startAt.position)) {
50
83
  log.debug("Init: using startAt.minimumPosition");
51
84
  return Math.max(Math.min(startAt.position, max), min);
52
85
  }
53
- else if (startAt.wallClockTime != null) {
86
+ else if (!isNullOrUndefined(startAt.wallClockTime)) {
54
87
  log.debug("Init: using startAt.wallClockTime");
55
- const ast = manifest.availabilityStartTime == null ?
88
+ const ast = manifest.availabilityStartTime === undefined ?
56
89
  0 :
57
90
  manifest.availabilityStartTime;
58
91
  const position = startAt.wallClockTime - ast;
59
92
  return Math.max(Math.min(position, max), min);
60
93
  }
61
- else if (startAt.fromFirstPosition != null) {
94
+ else if (!isNullOrUndefined(startAt.fromFirstPosition)) {
62
95
  log.debug("Init: using startAt.fromFirstPosition");
63
96
  const { fromFirstPosition } = startAt;
64
97
  return fromFirstPosition <= 0 ? min :
65
98
  Math.min(max, min + fromFirstPosition);
66
99
  }
67
- else if (startAt.fromLastPosition != null) {
100
+ else if (!isNullOrUndefined(startAt.fromLastPosition)) {
68
101
  log.debug("Init: using startAt.fromLastPosition");
69
102
  const { fromLastPosition } = startAt;
70
103
  return fromLastPosition >= 0 ? max :
71
104
  Math.max(min, max + fromLastPosition);
72
- } else if (startAt.percentage != null) {
105
+ } else if (!isNullOrUndefined(startAt.percentage)) {
73
106
  log.debug("Init: using startAt.percentage");
74
107
  const { percentage } = startAt;
75
108
  if (percentage > 100) {
@@ -83,22 +116,22 @@ export default function getInitialTime(
83
116
  }
84
117
  }
85
118
 
86
- const minimumPosition = manifest.getMinimumPosition();
119
+ const minimumPosition = manifest.getMinimumSafePosition();
87
120
  if (manifest.isLive) {
88
121
  const { suggestedPresentationDelay,
89
122
  clockOffset } = manifest;
90
- const maximumPosition = manifest.getMaximumPosition();
123
+ const maximumPosition = manifest.getMaximumSafePosition();
91
124
  let liveTime : number;
92
125
  const { DEFAULT_LIVE_GAP } = config.getCurrent();
93
126
 
94
- if (clockOffset == null) {
127
+ if (clockOffset === undefined) {
95
128
  log.info("Init: no clock offset found for a live content, " +
96
129
  "starting close to maximum available position");
97
130
  liveTime = maximumPosition;
98
131
  } else {
99
132
  log.info("Init: clock offset found for a live content, " +
100
133
  "checking if we can start close to it");
101
- const ast = manifest.availabilityStartTime == null ?
134
+ const ast = manifest.availabilityStartTime === undefined ?
102
135
  0 :
103
136
  manifest.availabilityStartTime;
104
137
  const clockRelativeLiveTime = (performance.now() + clockOffset) / 1000 - ast;
@@ -39,11 +39,12 @@ import SegmentBuffersStore from "../segment_buffers";
39
39
  import StreamOrchestrator, {
40
40
  IStreamOrchestratorOptions,
41
41
  } from "../stream";
42
+ import ContentTimeBoundariesObserver from "./content_time_boundaries_observer";
42
43
  import createStreamPlaybackObserver from "./create_stream_playback_observer";
43
- import DurationUpdater from "./duration_updater";
44
44
  import emitLoadedEvent from "./emit_loaded_event";
45
45
  import { maintainEndOfStream } from "./end_of_stream";
46
46
  import initialSeekAndPlay from "./initial_seek_and_play";
47
+ import MediaDurationUpdater from "./media_duration_updater";
47
48
  import StallAvoider, {
48
49
  IDiscontinuityEvent,
49
50
  ILockedStreamEvent,
@@ -104,7 +105,7 @@ export default function createMediaSourceLoader(
104
105
  autoPlay : boolean
105
106
  ) : Observable<IMediaSourceLoaderEvent> {
106
107
  /** Maintains the MediaSource's duration up-to-date with the Manifest */
107
- const durationUpdater$ = DurationUpdater(manifest, mediaSource);
108
+ const mediaDurationUpdater = new MediaDurationUpdater(manifest, mediaSource);
108
109
 
109
110
  const initialPeriod = manifest.getPeriodForTime(initialTime) ??
110
111
  manifest.getNextPeriod(initialTime);
@@ -154,7 +155,7 @@ export default function createMediaSourceLoader(
154
155
  segmentFetcherCreator,
155
156
  bufferOptions
156
157
  ).pipe(
157
- mergeMap((evt) : Observable<IMediaSourceLoaderEvent> => {
158
+ mergeMap((evt) => {
158
159
  switch (evt.type) {
159
160
  case "end-of-stream":
160
161
  log.debug("Init: end-of-stream order received.");
@@ -181,6 +182,21 @@ export default function createMediaSourceLoader(
181
182
  })
182
183
  );
183
184
 
185
+ const contentTimeObserver = ContentTimeBoundariesObserver(manifest,
186
+ streams$,
187
+ streamObserver)
188
+ .pipe(
189
+ mergeMap((evt) => {
190
+ switch (evt.type) {
191
+ case "contentDurationUpdate":
192
+ log.debug("Init: Duration has to be updated.", evt.value);
193
+ mediaDurationUpdater.updateKnownDuration(evt.value);
194
+ return EMPTY;
195
+ default:
196
+ return observableOf(evt);
197
+ }
198
+ }));
199
+
184
200
  /**
185
201
  * On subscription, keep the playback speed synchronized to the speed set by
186
202
  * the user on the media element and force a speed of `0` when the buffer is
@@ -209,13 +225,14 @@ export default function createMediaSourceLoader(
209
225
  observableOf(evt) :
210
226
  emitLoadedEvent(observation$, mediaElement, segmentBuffersStore, false)));
211
227
 
212
- return observableMerge(durationUpdater$,
213
- loadingEvts$,
228
+ return observableMerge(loadingEvts$,
214
229
  playbackRate$,
215
230
  stallAvoider$,
216
231
  streams$,
232
+ contentTimeObserver,
217
233
  streamEvents$
218
234
  ).pipe(finalize(() => {
235
+ mediaDurationUpdater.stop();
219
236
  // clean-up every created SegmentBuffers
220
237
  segmentBuffersStore.disposeAll();
221
238
  }));
@@ -0,0 +1,268 @@
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
+ combineLatest as observableCombineLatest,
19
+ distinctUntilChanged,
20
+ EMPTY,
21
+ fromEvent as observableFromEvent,
22
+ interval as observableInterval,
23
+ map,
24
+ merge as observableMerge,
25
+ mergeMap,
26
+ Observable,
27
+ of as observableOf,
28
+ startWith,
29
+ Subscription,
30
+ switchMap,
31
+ timer,
32
+ } from "rxjs";
33
+ import {
34
+ onSourceOpen$,
35
+ onSourceClose$,
36
+ onSourceEnded$,
37
+ } from "../../compat/event_listeners";
38
+ import log from "../../log";
39
+ import Manifest from "../../manifest";
40
+ import { fromEvent } from "../../utils/event_emitter";
41
+ import createSharedReference, {
42
+ ISharedReference,
43
+ } from "../../utils/reference";
44
+
45
+ /** Number of seconds in a regular year. */
46
+ const YEAR_IN_SECONDS = 365 * 24 * 3600;
47
+
48
+ /**
49
+ * Keep the MediaSource's duration up-to-date with what is being played.
50
+ * @class MediaDurationUpdater
51
+ */
52
+ export default class MediaDurationUpdater {
53
+ private _subscription : Subscription;
54
+ /**
55
+ * The last known audio Adaptation (i.e. track) chosen for the last Period.
56
+ * Useful to determinate the duration of the current content.
57
+ * `undefined` if the audio track for the last Period has never been known yet.
58
+ * `null` if there are no chosen audio Adaptation.
59
+ */
60
+ private _lastKnownDuration : ISharedReference<number | undefined>;
61
+
62
+ /**
63
+ * Create a new `MediaDurationUpdater` that will keep the given MediaSource's
64
+ * duration as soon as possible.
65
+ * This duration will be updated until the `stop` method is called.
66
+ * @param {Object} manifest - The Manifest currently played.
67
+ * For another content, you will have to create another `MediaDurationUpdater`.
68
+ * @param {MediaSource} mediaSource - The MediaSource on which the content is
69
+ * pushed.
70
+ */
71
+ constructor(manifest : Manifest, mediaSource : MediaSource) {
72
+ this._lastKnownDuration = createSharedReference(undefined);
73
+ this._subscription = isMediaSourceOpened$(mediaSource).pipe(
74
+ switchMap((canUpdate) =>
75
+ canUpdate ? observableCombineLatest([this._lastKnownDuration.asObservable(),
76
+ fromEvent(manifest, "manifestUpdate")
77
+ .pipe(startWith(null))]) :
78
+ EMPTY
79
+ ),
80
+ switchMap(([lastKnownDuration]) =>
81
+ areSourceBuffersUpdating$(mediaSource.sourceBuffers).pipe(
82
+ switchMap((areSBUpdating) => {
83
+ return areSBUpdating ? EMPTY :
84
+ recursivelyTryUpdatingDuration();
85
+ function recursivelyTryUpdatingDuration() : Observable<never> {
86
+ const res = setMediaSourceDuration(mediaSource,
87
+ manifest,
88
+ lastKnownDuration);
89
+ if (res === MediaSourceDurationUpdateStatus.Success) {
90
+ return EMPTY;
91
+ }
92
+ return timer(2000)
93
+ .pipe(mergeMap(() => recursivelyTryUpdatingDuration()));
94
+ }
95
+ })))).subscribe();
96
+ }
97
+
98
+ /**
99
+ * By default, the `MediaDurationUpdater` only set a safe estimate for the
100
+ * MediaSource's duration.
101
+ * A more precize duration can be set by communicating to it a more precize
102
+ * media duration through `updateKnownDuration`.
103
+ * If the duration becomes unknown, `undefined` can be given to it so the
104
+ * `MediaDurationUpdater` goes back to a safe estimate.
105
+ * @param {number | undefined} newDuration
106
+ */
107
+ public updateKnownDuration(
108
+ newDuration : number | undefined
109
+ ) : void {
110
+ this._lastKnownDuration.setValue(newDuration);
111
+ }
112
+
113
+ /**
114
+ * Stop the `MediaDurationUpdater` from updating and free its resources.
115
+ * Once stopped, it is not possible to start it again, beside creating another
116
+ * `MediaDurationUpdater`.
117
+ */
118
+ public stop() {
119
+ this._subscription.unsubscribe();
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Checks that duration can be updated on the MediaSource, and then
125
+ * sets it.
126
+ *
127
+ * Returns either:
128
+ * - the new duration it has been updated to if it has
129
+ * - `null` if it hasn'nt been updated
130
+ *
131
+ * @param {MediaSource} mediaSource
132
+ * @param {Object} manifest
133
+ * @returns {string}
134
+ */
135
+ function setMediaSourceDuration(
136
+ mediaSource: MediaSource,
137
+ manifest: Manifest,
138
+ knownDuration : number | undefined
139
+ ) : MediaSourceDurationUpdateStatus {
140
+ let newDuration = knownDuration;
141
+
142
+ if (newDuration === undefined) {
143
+ if (manifest.isDynamic) {
144
+ const maxPotentialPos = manifest.getLivePosition() ??
145
+ manifest.getMaximumSafePosition();
146
+ // Some targets poorly support setting a very high number for durations.
147
+ // Yet, in dynamic contents, we would prefer setting a value as high as possible
148
+ // to still be able to seek anywhere we want to (even ahead of the Manifest if
149
+ // we want to). As such, we put it at a safe default value of 2^32 excepted
150
+ // when the maximum position is already relatively close to that value, where
151
+ // we authorize exceptionally going over it.
152
+ newDuration = Math.max(Math.pow(2, 32), maxPotentialPos + YEAR_IN_SECONDS);
153
+ } else {
154
+ newDuration = manifest.getMaximumSafePosition();
155
+ }
156
+ }
157
+
158
+ let maxBufferedEnd : number = 0;
159
+ for (let i = 0; i < mediaSource.sourceBuffers.length; i++) {
160
+ const sourceBuffer = mediaSource.sourceBuffers[i];
161
+ const sbBufferedLen = sourceBuffer.buffered.length;
162
+ if (sbBufferedLen > 0) {
163
+ maxBufferedEnd = Math.max(sourceBuffer.buffered.end(sbBufferedLen - 1));
164
+ }
165
+ }
166
+
167
+ if (newDuration === mediaSource.duration) {
168
+ return MediaSourceDurationUpdateStatus.Success;
169
+ } else if (maxBufferedEnd > newDuration) {
170
+ // We already buffered further than the duration we want to set.
171
+ // Keep the duration that was set at that time as a security.
172
+ if (maxBufferedEnd < mediaSource.duration) {
173
+ try {
174
+ log.info("Init: Updating duration to what is currently buffered", maxBufferedEnd);
175
+ mediaSource.duration = newDuration;
176
+ } catch (err) {
177
+ log.warn("Duration Updater: Can't update duration on the MediaSource.", err);
178
+ return MediaSourceDurationUpdateStatus.Failed;
179
+ }
180
+ }
181
+ return MediaSourceDurationUpdateStatus.Partial;
182
+ } else {
183
+ const oldDuration = mediaSource.duration;
184
+ try {
185
+ log.info("Init: Updating duration", newDuration);
186
+ mediaSource.duration = newDuration;
187
+ } catch (err) {
188
+ log.warn("Duration Updater: Can't update duration on the MediaSource.", err);
189
+ return MediaSourceDurationUpdateStatus.Failed;
190
+ }
191
+ const deltaToExpected = Math.abs(mediaSource.duration - newDuration);
192
+ if (deltaToExpected >= 0.1) {
193
+ const deltaToBefore = Math.abs(mediaSource.duration - oldDuration);
194
+ return deltaToExpected < deltaToBefore ? MediaSourceDurationUpdateStatus.Partial :
195
+ MediaSourceDurationUpdateStatus.Failed;
196
+ }
197
+ return MediaSourceDurationUpdateStatus.Success;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * String describing the result of the process of updating a MediaSource's
203
+ * duration.
204
+ */
205
+ const enum MediaSourceDurationUpdateStatus {
206
+ /** The MediaSource's duration has been updated to the asked duration. */
207
+ Success = "success",
208
+ /**
209
+ * The MediaSource's duration has been updated and is now closer to the asked
210
+ * duration but is not yet the actually asked duration.
211
+ */
212
+ Partial = "partial",
213
+ /**
214
+ * The MediaSource's duration could not have been updated due to an issue or
215
+ * has been updated but to a value actually further from the asked duration
216
+ * from what it was before.
217
+ */
218
+ Failed = "failed",
219
+ }
220
+
221
+ /**
222
+ * Returns an Observable which will emit only when all the SourceBuffers ended
223
+ * all pending updates.
224
+ * @param {SourceBufferList} sourceBuffers
225
+ * @returns {Observable}
226
+ */
227
+ function areSourceBuffersUpdating$(
228
+ sourceBuffers: SourceBufferList
229
+ ) : Observable<boolean> {
230
+ if (sourceBuffers.length === 0) {
231
+ return observableOf(false);
232
+ }
233
+ const sourceBufferUpdatingStatuses : Array<Observable<boolean>> = [];
234
+
235
+ for (let i = 0; i < sourceBuffers.length; i++) {
236
+ const sourceBuffer = sourceBuffers[i];
237
+ sourceBufferUpdatingStatuses.push(
238
+ observableMerge(
239
+ observableFromEvent(sourceBuffer, "updatestart").pipe(map(() => true)),
240
+ observableFromEvent(sourceBuffer, "update").pipe(map(() => false)),
241
+ observableInterval(500).pipe(map(() => sourceBuffer.updating))
242
+ ).pipe(
243
+ startWith(sourceBuffer.updating),
244
+ distinctUntilChanged()
245
+ )
246
+ );
247
+ }
248
+ return observableCombineLatest(sourceBufferUpdatingStatuses).pipe(
249
+ map((areUpdating) => {
250
+ return areUpdating.some((isUpdating) => isUpdating);
251
+ }),
252
+ distinctUntilChanged());
253
+ }
254
+
255
+ /**
256
+ * Emit a boolean that tells if the media source is opened or not.
257
+ * @param {MediaSource} mediaSource
258
+ * @returns {Object}
259
+ */
260
+ function isMediaSourceOpened$(mediaSource: MediaSource): Observable<boolean> {
261
+ return observableMerge(onSourceOpen$(mediaSource).pipe(map(() => true)),
262
+ onSourceEnded$(mediaSource).pipe(map(() => false)),
263
+ onSourceClose$(mediaSource).pipe(map(() => false))
264
+ ).pipe(
265
+ startWith(mediaSource.readyState === "open"),
266
+ distinctUntilChanged()
267
+ );
268
+ }
@@ -91,11 +91,8 @@ export interface IAdaptationStreamPlaybackObservation extends
91
91
  isPaused: boolean;
92
92
  /** Last "playback rate" asked by the user. */
93
93
  speed : number;
94
- /**
95
- * Only set for live contents.
96
- * Difference between the live edge and the current position, in seconds.
97
- */
98
- liveGap : number | undefined;
94
+ /** Theoretical maximum position on the content that can currently be played. */
95
+ maximumPosition : number;
99
96
  }
100
97
 
101
98
  /** Arguments given when creating a new `AdaptationStream`. */
@@ -26,6 +26,7 @@ import {
26
26
  import { MediaError } from "../../../errors";
27
27
  import Manifest, {
28
28
  Adaptation,
29
+ Period,
29
30
  Representation,
30
31
  } from "../../../manifest";
31
32
  import { fromEvent } from "../../../utils/event_emitter";
@@ -53,13 +54,15 @@ import ABRManager, {
53
54
  * @returns {Object}
54
55
  */
55
56
  export default function createRepresentationEstimator(
56
- { manifest, adaptation } : { manifest : Manifest;
57
- adaptation : Adaptation; },
57
+ content : { manifest : Manifest;
58
+ period : Period;
59
+ adaptation : Adaptation; },
58
60
  abrManager : ABRManager,
59
61
  observation$ : Observable<IABRManagerPlaybackObservation>
60
62
  ) : { estimator$ : Observable<IABREstimate>;
61
63
  abrFeedbacks$ : Subject<IABRStreamEvents>; }
62
64
  {
65
+ const { manifest, adaptation } = content;
63
66
  const abrFeedbacks$ = new Subject<IABRStreamEvents>();
64
67
  const estimator$ = observableMerge(
65
68
  // subscribe "first" (hack as it is a merge here) to event
@@ -90,7 +93,7 @@ export default function createRepresentationEstimator(
90
93
  return true;
91
94
  }),
92
95
  switchMap((playableRepresentations) =>
93
- abrManager.get$(adaptation.type,
96
+ abrManager.get$(content,
94
97
  playableRepresentations,
95
98
  observation$,
96
99
  abrFeedbacks$)));
@@ -64,7 +64,6 @@ import {
64
64
  IMultiplePeriodStreamsEvent,
65
65
  IPeriodStreamEvent,
66
66
  IStreamOrchestratorEvent,
67
- IStreamWarningEvent,
68
67
  } from "../types";
69
68
  import ActivePeriodEmitter from "./active_period_emitter";
70
69
  import areStreamsComplete from "./are_streams_complete";
@@ -151,31 +150,8 @@ export default function StreamOrchestrator(
151
150
  });
152
151
  });
153
152
 
154
- // trigger warnings when the wanted time is before or after the manifest's
155
- // segments
156
- const outOfManifest$ = playbackObserver.observe(true).pipe(
157
- filterMap<IStreamOrchestratorPlaybackObservation, IStreamWarningEvent, null>((
158
- { position, wantedTimeOffset }
159
- ) => {
160
- const offsetedPosition = wantedTimeOffset + position;
161
- if (offsetedPosition < manifest.getMinimumPosition()) {
162
- const warning = new MediaError("MEDIA_TIME_BEFORE_MANIFEST",
163
- "The current position is behind the " +
164
- "earliest time announced in the Manifest.");
165
- return EVENTS.warning(warning);
166
- } else if (offsetedPosition > manifest.getMaximumPosition()) {
167
- const warning = new MediaError("MEDIA_TIME_AFTER_MANIFEST",
168
- "The current position is after the latest " +
169
- "time announced in the Manifest.");
170
- return EVENTS.warning(warning);
171
- }
172
- return null;
173
- }, null));
174
-
175
- const bufferTypes = segmentBuffersStore.getBufferTypes();
176
-
177
153
  // Every PeriodStreams for every possible types
178
- const streamsArray = bufferTypes.map((bufferType) => {
154
+ const streamsArray = segmentBuffersStore.getBufferTypes().map((bufferType) => {
179
155
  return manageEveryStreams(bufferType, initialPeriod)
180
156
  .pipe(deferSubscriptions(), share());
181
157
  });
@@ -203,10 +179,7 @@ export default function StreamOrchestrator(
203
179
  map((emitEndOfStream) =>
204
180
  emitEndOfStream ? EVENTS.endOfStream() : EVENTS.resumeStream()));
205
181
 
206
- return observableMerge(...streamsArray,
207
- activePeriodChanged$,
208
- endOfStream$,
209
- outOfManifest$);
182
+ return observableMerge(...streamsArray, activePeriodChanged$, endOfStream$);
210
183
 
211
184
  /**
212
185
  * Manage creation and removal of Streams for every Periods for a given type.
@@ -30,7 +30,10 @@ import {
30
30
  switchMap,
31
31
  } from "rxjs";
32
32
  import config from "../../../config";
33
- import { formatError } from "../../../errors";
33
+ import {
34
+ formatError,
35
+ MediaError,
36
+ } from "../../../errors";
34
37
  import log from "../../../log";
35
38
  import Manifest, {
36
39
  Adaptation,
@@ -81,11 +84,8 @@ export interface IPeriodStreamPlaybackObservation {
81
84
  * which we actually want to download segments for.
82
85
  */
83
86
  wantedTimeOffset : number;
84
- /**
85
- * Only set for live contents.
86
- * Difference between the live edge and the current position, in seconds.
87
- */
88
- liveGap : number | undefined;
87
+ /** Theoretical maximum position on the content that can currently be played. */
88
+ maximumPosition : number;
89
89
  }
90
90
 
91
91
  /** Arguments required by the `PeriodStream`. */
@@ -332,9 +332,12 @@ function createOrReuseSegmentBuffer(
332
332
  * @returns {string}
333
333
  */
334
334
  function getFirstDeclaredMimeType(adaptation : Adaptation) : string {
335
- const { representations } = adaptation;
336
- if (representations[0] == null) {
337
- return "";
335
+ const representations = adaptation.getPlayableRepresentations();
336
+ if (representations.length === 0) {
337
+ const noRepErr = new MediaError("NO_PLAYABLE_REPRESENTATION",
338
+ "No Representation in the chosen " +
339
+ adaptation.type + " Adaptation can be played");
340
+ throw noRepErr;
338
341
  }
339
342
  return representations[0].getMimeTypeString();
340
343
  }