rx-player 3.28.0-dev.2022061700 → 3.28.0-dev.2022062700

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 (49) hide show
  1. package/CHANGELOG.md +5 -3
  2. package/VERSION +1 -1
  3. package/appveyor.yml +1 -1
  4. package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.d.ts +32 -11
  5. package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +19 -26
  6. package/dist/_esm5.processed/core/api/playback_observer.d.ts +47 -35
  7. package/dist/_esm5.processed/core/api/playback_observer.js +120 -117
  8. package/dist/_esm5.processed/core/api/public_api.d.ts +4 -6
  9. package/dist/_esm5.processed/core/api/public_api.js +18 -18
  10. package/dist/_esm5.processed/core/decrypt/content_decryptor.js +60 -31
  11. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +1 -1
  12. package/dist/_esm5.processed/core/init/create_stream_playback_observer.d.ts +1 -1
  13. package/dist/_esm5.processed/core/init/create_stream_playback_observer.js +23 -6
  14. package/dist/_esm5.processed/core/init/initial_seek_and_play.js +3 -3
  15. package/dist/_esm5.processed/core/init/initialize_directfile.js +1 -1
  16. package/dist/_esm5.processed/core/init/load_on_media_source.js +1 -1
  17. package/dist/_esm5.processed/core/init/stall_avoider.js +12 -8
  18. package/dist/_esm5.processed/core/stream/adaptation/create_representation_estimator.js +1 -1
  19. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +5 -4
  20. package/dist/_esm5.processed/core/stream/period/create_empty_adaptation_stream.js +1 -1
  21. package/dist/_esm5.processed/core/stream/period/period_stream.js +21 -5
  22. package/dist/_esm5.processed/core/stream/reload_after_switch.js +1 -1
  23. package/dist/_esm5.processed/core/stream/representation/append_segment_to_buffer.js +1 -1
  24. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +1 -1
  25. package/dist/_esm5.processed/utils/reference.d.ts +29 -19
  26. package/dist/_esm5.processed/utils/reference.js +21 -5
  27. package/dist/rx-player.js +366 -257
  28. package/dist/rx-player.min.js +1 -1
  29. package/package.json +2 -2
  30. package/sonar-project.properties +1 -1
  31. package/src/README.md +84 -68
  32. package/src/core/adaptive/adaptive_representation_selector.ts +47 -40
  33. package/src/core/api/playback_observer.ts +185 -173
  34. package/src/core/api/public_api.ts +23 -21
  35. package/src/core/decrypt/content_decryptor.ts +57 -23
  36. package/src/core/init/content_time_boundaries_observer.ts +1 -1
  37. package/src/core/init/create_stream_playback_observer.ts +69 -47
  38. package/src/core/init/initial_seek_and_play.ts +3 -3
  39. package/src/core/init/initialize_directfile.ts +1 -1
  40. package/src/core/init/load_on_media_source.ts +1 -1
  41. package/src/core/init/stall_avoider.ts +12 -9
  42. package/src/core/stream/adaptation/create_representation_estimator.ts +6 -6
  43. package/src/core/stream/orchestrator/stream_orchestrator.ts +5 -4
  44. package/src/core/stream/period/create_empty_adaptation_stream.ts +1 -1
  45. package/src/core/stream/period/period_stream.ts +33 -14
  46. package/src/core/stream/reload_after_switch.ts +1 -1
  47. package/src/core/stream/representation/append_segment_to_buffer.ts +1 -1
  48. package/src/core/stream/representation/representation_stream.ts +1 -1
  49. package/src/utils/reference.ts +40 -28
@@ -13,21 +13,13 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
17
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
18
- if (ar || !(i in from)) {
19
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
20
- ar[i] = from[i];
21
- }
22
- }
23
- return to.concat(ar || Array.prototype.slice.call(from));
24
- };
25
- import { defer as observableDefer, fromEvent as observableFromEvent, interval as observableInterval, map, merge as observableMerge, share, shareReplay, skip, startWith, } from "rxjs";
26
16
  import config from "../../config";
27
17
  import log from "../../log";
28
18
  import noop from "../../utils/noop";
29
19
  import objectAssign from "../../utils/object_assign";
30
20
  import { getRange } from "../../utils/ranges";
21
+ import createSharedReference from "../../utils/reference";
22
+ import TaskCanceller from "../../utils/task_canceller";
31
23
  /**
32
24
  * HTMLMediaElement Events for which playback observations are calculated and
33
25
  * emitted.
@@ -56,6 +48,12 @@ var SCANNED_MEDIA_ELEMENTS_EVENTS = ["canplay",
56
48
  */
57
49
  var PlaybackObserver = /** @class */ (function () {
58
50
  /**
51
+ * Create a new `PlaybackObserver`, which allows to produce new "playback
52
+ * observations" on various media events and intervals.
53
+ *
54
+ * Note that creating a `PlaybackObserver` lead to the usage of resources,
55
+ * such as event listeners which will only be freed once the `stop` method is
56
+ * called.
59
57
  * @param {HTMLMediaElement} mediaElement
60
58
  * @param {Object} options
61
59
  */
@@ -64,9 +62,22 @@ var PlaybackObserver = /** @class */ (function () {
64
62
  this._mediaElement = mediaElement;
65
63
  this._withMediaSource = options.withMediaSource;
66
64
  this._lowLatencyMode = options.lowLatencyMode;
67
- this._lastObservation = null;
68
- this._observation$ = null;
65
+ this._canceller = new TaskCanceller();
66
+ this._observationRef = this._createSharedReference();
69
67
  }
68
+ /**
69
+ * Stop the `PlaybackObserver` from emitting playback observations and free all
70
+ * resources reserved to emitting them such as event listeners, intervals and
71
+ * subscribing callbacks.
72
+ *
73
+ * Once `stop` is called, no new playback observation will ever be emitted.
74
+ *
75
+ * Note that it is important to call stop once the `PlaybackObserver` is no
76
+ * more needed to avoid unnecessarily leaking resources.
77
+ */
78
+ PlaybackObserver.prototype.stop = function () {
79
+ this._canceller.cancel();
80
+ };
70
81
  /**
71
82
  * Returns the current position advertised by the `HTMLMediaElement`, in
72
83
  * seconds.
@@ -107,31 +118,17 @@ var PlaybackObserver = /** @class */ (function () {
107
118
  return this._mediaElement.readyState;
108
119
  };
109
120
  /**
110
- * Returns an Observable regularly emitting playback observation, optionally
111
- * starting with the last one.
121
+ * Returns an `IReadOnlySharedReference` storing the last playback observation
122
+ * produced by the `PlaybackObserver` and updated each time a new one is
123
+ * produced.
112
124
  *
113
- * Note that this Observable is shared and unique, so that multiple `observe`
114
- * call will return the exact same Observable and multiple concurrent
115
- * `subscribe` will receive the same events at the same time.
116
- * This was done for performance and simplicity reasons.
125
+ * This value can then be for example subscribed to to be notified of future
126
+ * playback observations.
117
127
  *
118
- * @param {boolean} includeLastObservation
119
- * @returns {Observable}
128
+ * @returns {Object}
120
129
  */
121
- PlaybackObserver.prototype.observe = function (includeLastObservation) {
122
- var _this = this;
123
- return observableDefer(function () {
124
- if (_this._observation$ === null || _this._lastObservation === null) {
125
- _this._lastObservation = _this._generateInitialObservation();
126
- _this._observation$ = _this._createInnerObservable().pipe(share());
127
- return _this.observe(includeLastObservation);
128
- }
129
- else {
130
- return includeLastObservation ?
131
- _this._observation$.pipe(startWith(_this._lastObservation)) :
132
- _this._observation$;
133
- }
134
- });
130
+ PlaybackObserver.prototype.getReference = function () {
131
+ return this._observationRef;
135
132
  };
136
133
  /**
137
134
  * Register a callback so it regularly receives playback observations.
@@ -141,22 +138,16 @@ var PlaybackObserver = /** @class */ (function () {
141
138
  * be first emitted synchronously.
142
139
  * - `clearSignal`: If set, the callback will be unregistered when this
143
140
  * CancellationSignal emits.
144
- * @returns {Function} - Allows to easily unregister the callback
145
141
  */
146
142
  PlaybackObserver.prototype.listen = function (cb, options) {
147
- var _a, _b, _c, _d;
148
- if (((_a = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _a === void 0 ? void 0 : _a.isCancelled) === true) {
143
+ var _a;
144
+ if (this._canceller.isUsed || ((_a = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _a === void 0 ? void 0 : _a.isCancelled) === true) {
149
145
  return noop;
150
146
  }
151
- var sub = this.observe((_b = options === null || options === void 0 ? void 0 : options.includeLastObservation) !== null && _b !== void 0 ? _b : false)
152
- .subscribe(cb);
153
- var unregister = (_d = (_c = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _c === void 0 ? void 0 : _c.register(function () {
154
- sub.unsubscribe();
155
- })) !== null && _d !== void 0 ? _d : noop;
156
- return function () {
157
- unregister();
158
- sub.unsubscribe();
159
- };
147
+ this._observationRef.onUpdate(cb, {
148
+ clearSignal: options === null || options === void 0 ? void 0 : options.clearSignal,
149
+ emitCurrentValue: options === null || options === void 0 ? void 0 : options.includeLastObservation,
150
+ });
160
151
  };
161
152
  /**
162
153
  * Generate a new playback observer which can listen to other
@@ -169,63 +160,84 @@ var PlaybackObserver = /** @class */ (function () {
169
160
  *
170
161
  * As argument, this method takes a function which will allow to produce
171
162
  * the new set of properties to be present on each observation.
172
- * @param {Function} mapObservable
163
+ * @param {Function} transform
173
164
  * @returns {Object}
174
165
  */
175
- PlaybackObserver.prototype.deriveReadOnlyObserver = function (mapObservable) {
176
- return generateReadOnlyObserver(this, mapObservable);
166
+ PlaybackObserver.prototype.deriveReadOnlyObserver = function (transform) {
167
+ return generateReadOnlyObserver(this, transform, this._canceller.signal);
177
168
  };
178
169
  /**
179
- * Creates the observable that will generate playback observations.
170
+ * Creates the `IReadOnlySharedReference` that will generate playback
171
+ * observations.
180
172
  * @returns {Observable}
181
173
  */
182
- PlaybackObserver.prototype._createInnerObservable = function () {
174
+ PlaybackObserver.prototype._createSharedReference = function () {
183
175
  var _this = this;
184
- return observableDefer(function () {
185
- var _a = config.getCurrent(), SAMPLING_INTERVAL_MEDIASOURCE = _a.SAMPLING_INTERVAL_MEDIASOURCE, SAMPLING_INTERVAL_LOW_LATENCY = _a.SAMPLING_INTERVAL_LOW_LATENCY, SAMPLING_INTERVAL_NO_MEDIASOURCE = _a.SAMPLING_INTERVAL_NO_MEDIASOURCE;
186
- var getCurrentObservation = function (event) {
187
- var _a;
188
- var tmpEvt = event;
189
- if (tmpEvt === "seeking" && _this._internalSeekingEventsIncomingCounter > 0) {
190
- tmpEvt = "internal-seeking";
191
- _this._internalSeekingEventsIncomingCounter -= 1;
192
- }
193
- var lastObservation = (_a = _this._lastObservation) !== null && _a !== void 0 ? _a : _this._generateInitialObservation();
194
- var mediaTimings = getMediaInfos(_this._mediaElement, tmpEvt);
195
- var internalSeeking = mediaTimings.seeking &&
196
- // We've just received the event for internally seeking
197
- (tmpEvt === "internal-seeking" ||
198
- // or We're still waiting on the previous internal-seek
199
- (lastObservation.internalSeeking && tmpEvt !== "seeking"));
200
- var rebufferingStatus = getRebufferingStatus(lastObservation, mediaTimings, { lowLatencyMode: _this._lowLatencyMode,
201
- withMediaSource: _this._withMediaSource });
202
- var freezingStatus = getFreezingStatus(lastObservation, mediaTimings);
203
- var timings = objectAssign({}, { rebuffering: rebufferingStatus,
204
- freezing: freezingStatus, internalSeeking: internalSeeking }, mediaTimings);
205
- if (log.hasLevel("DEBUG")) {
206
- log.debug("API: current media element state tick", "event", timings.event, "position", timings.position, "seeking", timings.seeking, "internalSeeking", timings.internalSeeking, "rebuffering", timings.rebuffering !== null, "freezing", timings.freezing !== null, "ended", timings.ended, "paused", timings.paused, "playbackRate", timings.playbackRate, "readyState", timings.readyState);
207
- }
208
- return timings;
176
+ if (this._observationRef !== undefined) {
177
+ return this._observationRef;
178
+ }
179
+ var lastObservation;
180
+ var _a = config.getCurrent(), SAMPLING_INTERVAL_MEDIASOURCE = _a.SAMPLING_INTERVAL_MEDIASOURCE, SAMPLING_INTERVAL_LOW_LATENCY = _a.SAMPLING_INTERVAL_LOW_LATENCY, SAMPLING_INTERVAL_NO_MEDIASOURCE = _a.SAMPLING_INTERVAL_NO_MEDIASOURCE;
181
+ var getCurrentObservation = function (event) {
182
+ var tmpEvt = event;
183
+ if (tmpEvt === "seeking" && _this._internalSeekingEventsIncomingCounter > 0) {
184
+ tmpEvt = "internal-seeking";
185
+ _this._internalSeekingEventsIncomingCounter -= 1;
186
+ }
187
+ var _lastObservation = lastObservation !== null && lastObservation !== void 0 ? lastObservation : _this._generateInitialObservation();
188
+ var mediaTimings = getMediaInfos(_this._mediaElement, tmpEvt);
189
+ var internalSeeking = mediaTimings.seeking &&
190
+ // We've just received the event for internally seeking
191
+ (tmpEvt === "internal-seeking" ||
192
+ // or We're still waiting on the previous internal-seek
193
+ (_lastObservation.internalSeeking && tmpEvt !== "seeking"));
194
+ var rebufferingStatus = getRebufferingStatus(_lastObservation, mediaTimings, { lowLatencyMode: _this._lowLatencyMode,
195
+ withMediaSource: _this._withMediaSource });
196
+ var freezingStatus = getFreezingStatus(_lastObservation, mediaTimings);
197
+ var timings = objectAssign({}, { rebuffering: rebufferingStatus,
198
+ freezing: freezingStatus, internalSeeking: internalSeeking }, mediaTimings);
199
+ if (log.hasLevel("DEBUG")) {
200
+ log.debug("API: current media element state tick", "event", timings.event, "position", timings.position, "seeking", timings.seeking, "internalSeeking", timings.internalSeeking, "rebuffering", timings.rebuffering !== null, "freezing", timings.freezing !== null, "ended", timings.ended, "paused", timings.paused, "playbackRate", timings.playbackRate, "readyState", timings.readyState);
201
+ }
202
+ return timings;
203
+ };
204
+ var returnedSharedReference = createSharedReference(getCurrentObservation("init"));
205
+ var generateObservationForEvent = function (event) {
206
+ var newObservation = getCurrentObservation(event);
207
+ if (log.hasLevel("DEBUG")) {
208
+ log.debug("API: current playback timeline:\n" +
209
+ prettyPrintBuffered(newObservation.buffered, newObservation.position), "\n".concat(event));
210
+ }
211
+ lastObservation = newObservation;
212
+ returnedSharedReference.setValue(newObservation);
213
+ };
214
+ var interval = this._lowLatencyMode ? SAMPLING_INTERVAL_LOW_LATENCY :
215
+ this._withMediaSource ? SAMPLING_INTERVAL_MEDIASOURCE :
216
+ SAMPLING_INTERVAL_NO_MEDIASOURCE;
217
+ var intervalId = setInterval(onInterval, interval);
218
+ var removeEventListeners = SCANNED_MEDIA_ELEMENTS_EVENTS.map(function (eventName) {
219
+ _this._mediaElement.addEventListener(eventName, onMediaEvent);
220
+ function onMediaEvent() {
221
+ restartInterval();
222
+ generateObservationForEvent(eventName);
223
+ }
224
+ return function () {
225
+ _this._mediaElement.removeEventListener(eventName, onMediaEvent);
209
226
  };
210
- var eventObs = SCANNED_MEDIA_ELEMENTS_EVENTS.map(function (eventName) {
211
- return observableFromEvent(_this._mediaElement, eventName)
212
- .pipe(map(function () { return eventName; }));
213
- });
214
- var interval = _this._lowLatencyMode ? SAMPLING_INTERVAL_LOW_LATENCY :
215
- _this._withMediaSource ? SAMPLING_INTERVAL_MEDIASOURCE :
216
- SAMPLING_INTERVAL_NO_MEDIASOURCE;
217
- var interval$ = observableInterval(interval)
218
- .pipe(map(function () { return "timeupdate"; }));
219
- return observableMerge.apply(void 0, __spreadArray([interval$], eventObs, false)).pipe(map(function (event) {
220
- var newObservation = getCurrentObservation(event);
221
- if (log.hasLevel("DEBUG")) {
222
- log.debug("API: current playback timeline:\n" +
223
- prettyPrintBuffered(newObservation.buffered, newObservation.position), "\n".concat(event));
224
- }
225
- _this._lastObservation = newObservation;
226
- return newObservation;
227
- }));
228
227
  });
228
+ this._canceller.signal.register(function () {
229
+ clearInterval(intervalId);
230
+ removeEventListeners.forEach(function (cb) { return cb(); });
231
+ returnedSharedReference.finish();
232
+ });
233
+ return returnedSharedReference;
234
+ function onInterval() {
235
+ generateObservationForEvent("timeupdate");
236
+ }
237
+ function restartInterval() {
238
+ clearInterval(intervalId);
239
+ intervalId = setInterval(onInterval, interval);
240
+ }
229
241
  };
230
242
  PlaybackObserver.prototype._generateInitialObservation = function () {
231
243
  return objectAssign(getMediaInfos(this._mediaElement, "init"), { rebuffering: null,
@@ -480,13 +492,11 @@ function prettyPrintBuffered(buffered, currentTime) {
480
492
  * Create `IReadOnlyPlaybackObserver` from a source `IReadOnlyPlaybackObserver`
481
493
  * and a mapping function.
482
494
  * @param {Object} src
483
- * @param {Function} mapObservable
495
+ * @param {Function} transform
484
496
  * @returns {Object}
485
497
  */
486
- function generateReadOnlyObserver(src, mapObservable) {
487
- var newObs = observableDefer(function () {
488
- return mapObservable(src.observe(true));
489
- }).pipe(shareReplay({ bufferSize: 1, refCount: true }));
498
+ function generateReadOnlyObserver(src, transform, cancellationSignal) {
499
+ var mappedRef = transform(src.getReference(), cancellationSignal);
490
500
  return {
491
501
  getCurrentTime: function () {
492
502
  return src.getCurrentTime();
@@ -497,28 +507,21 @@ function generateReadOnlyObserver(src, mapObservable) {
497
507
  getIsPaused: function () {
498
508
  return src.getIsPaused();
499
509
  },
500
- observe: function (includeLastObservation) {
501
- return includeLastObservation ? newObs :
502
- newObs.pipe(skip(1));
510
+ getReference: function () {
511
+ return mappedRef;
503
512
  },
504
513
  listen: function (cb, options) {
505
- var _a, _b, _c;
506
- if (((_a = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _a === void 0 ? void 0 : _a.isCancelled) === true) {
507
- return noop;
514
+ var _a;
515
+ if (cancellationSignal.isCancelled || ((_a = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _a === void 0 ? void 0 : _a.isCancelled) === true) {
516
+ return;
508
517
  }
509
- var obs = (options === null || options === void 0 ? void 0 : options.includeLastObservation) === true ? newObs :
510
- newObs.pipe(skip(1));
511
- var sub = obs.subscribe(cb);
512
- var unregister = (_c = (_b = options === null || options === void 0 ? void 0 : options.clearSignal) === null || _b === void 0 ? void 0 : _b.register(function () {
513
- sub.unsubscribe();
514
- })) !== null && _c !== void 0 ? _c : noop;
515
- return function () {
516
- unregister();
517
- sub.unsubscribe();
518
- };
518
+ mappedRef.onUpdate(cb, {
519
+ clearSignal: options === null || options === void 0 ? void 0 : options.clearSignal,
520
+ emitCurrentValue: options === null || options === void 0 ? void 0 : options.includeLastObservation,
521
+ });
519
522
  },
520
- deriveReadOnlyObserver: function (newUdateObserver) {
521
- return generateReadOnlyObserver(this, newUdateObserver);
523
+ deriveReadOnlyObserver: function (newTransformFn) {
524
+ return generateReadOnlyObserver(this, newTransformFn, cancellationSignal);
522
525
  },
523
526
  };
524
527
  }
@@ -29,7 +29,10 @@ declare class Player extends EventEmitter<IPublicAPIEvent> {
29
29
  static version: string;
30
30
  /** Current version of the RxPlayer. */
31
31
  readonly version: string;
32
- /** Media element attached to the RxPlayer. */
32
+ /**
33
+ * Media element attached to the RxPlayer.
34
+ * Set to `null` when the RxPlayer is disposed.
35
+ */
33
36
  videoElement: HTMLMediaElement | null;
34
37
  /** Logger the RxPlayer uses. */
35
38
  readonly log: Logger;
@@ -693,11 +696,6 @@ declare class Player extends EventEmitter<IPublicAPIEvent> {
693
696
  * @param {Error} error
694
697
  */
695
698
  private _priv_onPlaybackError;
696
- /**
697
- * Triggered when the playback Observable completes.
698
- * Clean-up ressources and signal that the content has ended.
699
- */
700
- private _priv_onPlaybackFinished;
701
699
  /**
702
700
  * Triggered when we received a warning event during playback.
703
701
  * Trigger the right API event.
@@ -88,7 +88,7 @@ var Player = /** @class */ (function (_super) {
88
88
  // Workaround to support Firefox autoplay on FF 42.
89
89
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
90
90
  videoElement.preload = "auto";
91
- _this.version = /* PLAYER_VERSION */ "3.28.0-dev.2022061700";
91
+ _this.version = /* PLAYER_VERSION */ "3.28.0-dev.2022062700";
92
92
  _this.log = log;
93
93
  _this.state = "STOPPED";
94
94
  _this.videoElement = videoElement;
@@ -359,6 +359,9 @@ var Player = /** @class */ (function (_super) {
359
359
  withMediaSource: !isDirectFile,
360
360
  lowLatencyMode: lowLatencyMode,
361
361
  });
362
+ currentContentCanceller.signal.register(function () {
363
+ playbackObserver.stop();
364
+ });
362
365
  /** Emit playback events. */
363
366
  var playback$;
364
367
  if (!isDirectFile) {
@@ -534,7 +537,7 @@ var Player = /** @class */ (function (_super) {
534
537
  return evt.type === "reloading-media-source";
535
538
  }), share());
536
539
  /** Emit when the media element emits a "seeking" event. */
537
- var observation$ = playbackObserver.observe(true);
540
+ var observation$ = playbackObserver.getReference().asObservable();
538
541
  var stateChangingEvent$ = observation$.pipe(filter(function (o) {
539
542
  return o.event === "seeking" || o.event === "ended" ||
540
543
  o.event === "play" || o.event === "pause";
@@ -598,7 +601,14 @@ var Player = /** @class */ (function (_super) {
598
601
  playback$.subscribe({
599
602
  next: function (x) { return _this._priv_onPlaybackEvent(x); },
600
603
  error: function (err) { return _this._priv_onPlaybackError(err); },
601
- complete: function () { return _this._priv_onPlaybackFinished(); },
604
+ complete: function () {
605
+ if (!contentInfos.currentContentCanceller.isUsed) {
606
+ log.info("API: Previous playback finished. Stopping and cleaning-up...");
607
+ contentInfos.currentContentCanceller.cancel();
608
+ _this._priv_cleanUpCurrentContentState();
609
+ _this._priv_setPlayerState(PLAYER_STATES.STOPPED);
610
+ }
611
+ },
602
612
  });
603
613
  // initialize the content only when the lock is inactive
604
614
  this._priv_contentLock.asObservable()
@@ -1758,8 +1768,10 @@ var Player = /** @class */ (function (_super) {
1758
1768
  this._priv_contentEventsMemory = {};
1759
1769
  // DRM-related clean-up
1760
1770
  var freeUpContentLock = function () {
1761
- log.debug("Unlocking `contentLock`. Next content can begin.");
1762
- _this._priv_contentLock.setValue(false);
1771
+ if (_this.videoElement !== null) { // If not disposed
1772
+ log.debug("Unlocking `contentLock`. Next content can begin.");
1773
+ _this._priv_contentLock.setValue(false);
1774
+ }
1763
1775
  };
1764
1776
  if (!isNullOrUndefined(this.videoElement)) {
1765
1777
  clearOnStop(this.videoElement).then(function () {
@@ -1878,18 +1890,6 @@ var Player = /** @class */ (function (_super) {
1878
1890
  this.trigger("error", formattedError);
1879
1891
  }
1880
1892
  };
1881
- /**
1882
- * Triggered when the playback Observable completes.
1883
- * Clean-up ressources and signal that the content has ended.
1884
- */
1885
- Player.prototype._priv_onPlaybackFinished = function () {
1886
- log.info("API: Previous playback finished. Stopping and cleaning-up...");
1887
- if (this._priv_contentInfos !== null) {
1888
- this._priv_contentInfos.currentContentCanceller.cancel();
1889
- }
1890
- this._priv_cleanUpCurrentContentState();
1891
- this._priv_setPlayerState(PLAYER_STATES.ENDED);
1892
- };
1893
1893
  /**
1894
1894
  * Triggered when we received a warning event during playback.
1895
1895
  * Trigger the right API event.
@@ -2295,5 +2295,5 @@ var Player = /** @class */ (function (_super) {
2295
2295
  };
2296
2296
  return Player;
2297
2297
  }(EventEmitter));
2298
- Player.version = /* PLAYER_VERSION */ "3.28.0-dev.2022061700";
2298
+ Player.version = /* PLAYER_VERSION */ "3.28.0-dev.2022062700";
2299
2299
  export default Player;
@@ -428,18 +428,7 @@ var ContentDecryptor = /** @class */ (function (_super) {
428
428
  _this.trigger("warning", evt.value);
429
429
  return;
430
430
  }
431
- var linkedKeys;
432
- if (sessionInfo.source === "created-session" /* MediaKeySessionLoadingType.Created */) {
433
- // When the license has been fetched, there might be implicit key ids
434
- // linked to the session depending on the `singleLicensePer` option.
435
- linkedKeys = getFetchedLicenseKeysInfo(initializationData, options.singleLicensePer, evt.value.whitelistedKeyIds, evt.value.blacklistedKeyIDs);
436
- }
437
- else {
438
- // When the MediaKeySession is just a cached/persisted one, we don't
439
- // have any concept of "implicit key id".
440
- linkedKeys = { whitelisted: evt.value.whitelistedKeyIds,
441
- blacklisted: evt.value.blacklistedKeyIDs };
442
- }
431
+ var linkedKeys = getKeyIdsLinkedToSession(initializationData, sessionInfo.record, options.singleLicensePer, sessionInfo.source === "created-session" /* MediaKeySessionLoadingType.Created */, evt.value.whitelistedKeyIds, evt.value.blacklistedKeyIDs);
443
432
  sessionInfo.record.associateKeyIds(linkedKeys.whitelisted);
444
433
  sessionInfo.record.associateKeyIds(linkedKeys.blacklisted);
445
434
  sessionInfo.keyStatuses = { whitelisted: linkedKeys.whitelisted,
@@ -758,26 +747,63 @@ export var ContentDecryptorState;
758
747
  ContentDecryptorState[ContentDecryptorState["Disposed"] = 4] = "Disposed";
759
748
  })(ContentDecryptorState || (ContentDecryptorState = {}));
760
749
  /**
761
- * Returns information on all keys - explicit or implicit - that are linked to
762
- * a loaded license.
750
+ * Returns set of all usable and unusable keys - explicit or implicit - that are
751
+ * linked to a `MediaKeySession`.
763
752
  *
764
753
  * In the RxPlayer, there is a concept of "explicit" key ids, which are key ids
765
754
  * found in a license whose status can be known through the `keyStatuses`
766
755
  * property from a `MediaKeySession`, and of "implicit" key ids, which are key
767
756
  * ids which were expected to be in a fetched license, but apparently weren't.
768
- * @param {Object} initializationData
769
- * @param {string|undefined} singleLicensePer
770
- * @param {Array.<Uint8Array>} usableKeyIds
771
- * @param {Array.<Uint8Array>} unusableKeyIds
772
- * @returns {Object}
757
+ *
758
+ * @param {Object} initializationData - Initialization data object used to make
759
+ * the request for the current license.
760
+ * @param {Object} keySessionRecord - The `KeySessionRecord` associated with the
761
+ * session that has been loaded. It might give supplementary information on
762
+ * keys implicitly linked to the license.
763
+ * @param {string|undefined} singleLicensePer - Setting allowing to indicate the
764
+ * scope a given license should have.
765
+ * @param {boolean} isCurrentLicense - If `true` the license has been fetched
766
+ * especially for the current content.
767
+ *
768
+ * Knowing this allows to determine that if decryption keys that should have
769
+ * been referenced in the fetched license (according to the `singleLicensePer`
770
+ * setting) are missing, then the keys surely must have been voluntarly
771
+ * removed from the license.
772
+ *
773
+ * If it is however set to `false`, it means that the license is an older
774
+ * license that might have been linked to another content, thus we cannot make
775
+ * that assumption.
776
+ * @param {Array.<Uint8Array>} usableKeyIds - Key ids that are present in the
777
+ * license and can be used.
778
+ * @param {Array.<Uint8Array>} unusableKeyIds - Key ids that are present in the
779
+ * license yet cannot be used.
780
+ * @returns {Object} - Returns an object with the following properties:
781
+ * - `whitelisted`: Array of key ids for keys that are known to be usable
782
+ * - `blacklisted`: Array of key ids for keys that are considered unusable.
783
+ * The qualities linked to those keys should not be played.
773
784
  */
774
- function getFetchedLicenseKeysInfo(initializationData, singleLicensePer, usableKeyIds, unusableKeyIds) {
785
+ function getKeyIdsLinkedToSession(initializationData, keySessionRecord, singleLicensePer, isCurrentLicense, usableKeyIds, unusableKeyIds) {
775
786
  var _a;
776
787
  /**
777
788
  * Every key id associated with the MediaKeySession, starting with
778
789
  * whitelisted ones.
779
790
  */
780
791
  var associatedKeyIds = __spreadArray(__spreadArray([], usableKeyIds, true), unusableKeyIds, true);
792
+ // Add all key ids associated to the `KeySessionRecord` yet not in
793
+ // `usableKeyIds` nor in `unusableKeyIds`
794
+ var allKnownKeyIds = keySessionRecord.getAssociatedKeyIds();
795
+ var _loop_2 = function (kid) {
796
+ if (!associatedKeyIds.some(function (ak) { return areKeyIdsEqual(ak, kid); })) {
797
+ if (log.hasLevel("DEBUG")) {
798
+ log.debug("DRM: KeySessionRecord's key missing in the license, blacklisting it", bytesToHex(kid));
799
+ }
800
+ associatedKeyIds.push(kid);
801
+ }
802
+ };
803
+ for (var _i = 0, allKnownKeyIds_1 = allKnownKeyIds; _i < allKnownKeyIds_1.length; _i++) {
804
+ var kid = allKnownKeyIds_1[_i];
805
+ _loop_2(kid);
806
+ }
781
807
  if (singleLicensePer !== undefined && singleLicensePer !== "init-data") {
782
808
  // We want to add the current key ids in the blacklist if it is
783
809
  // not already there.
@@ -798,24 +824,27 @@ function getFetchedLicenseKeysInfo(initializationData, singleLicensePer, usableK
798
824
  return !associatedKeyIds.some(function (k) { return areKeyIdsEqual(k, expected); });
799
825
  });
800
826
  if (missingKeyIds.length > 0) {
827
+ if (log.hasLevel("DEBUG")) {
828
+ log.debug("DRM: init data keys missing in the license, blacklisting them", missingKeyIds.map(function (m) { return bytesToHex(m); }).join(", "));
829
+ }
801
830
  associatedKeyIds.push.apply(associatedKeyIds, missingKeyIds);
802
831
  }
803
832
  }
804
- if (content !== undefined) {
833
+ if (isCurrentLicense && content !== undefined) {
805
834
  if (singleLicensePer === "content") {
806
835
  // Put it in a Set to automatically filter out duplicates (by ref)
807
836
  var contentKeys = new Set();
808
837
  var manifest = content.manifest;
809
- for (var _i = 0, _b = manifest.periods; _i < _b.length; _i++) {
810
- var period = _b[_i];
838
+ for (var _b = 0, _c = manifest.periods; _b < _c.length; _b++) {
839
+ var period = _c[_b];
811
840
  addKeyIdsFromPeriod(contentKeys, period);
812
841
  }
813
842
  mergeKeyIdSetIntoArray(contentKeys, associatedKeyIds);
814
843
  }
815
844
  else if (singleLicensePer === "periods") {
816
845
  var manifest = content.manifest;
817
- for (var _c = 0, _d = manifest.periods; _c < _d.length; _c++) {
818
- var period = _d[_c];
846
+ for (var _d = 0, _e = manifest.periods; _d < _e.length; _d++) {
847
+ var period = _e[_d];
819
848
  var periodKeys = new Set();
820
849
  addKeyIdsFromPeriod(periodKeys, period);
821
850
  if (((_a = initializationData.content) === null || _a === void 0 ? void 0 : _a.period.id) === period.id) {
@@ -823,16 +852,16 @@ function getFetchedLicenseKeysInfo(initializationData, singleLicensePer, usableK
823
852
  }
824
853
  else {
825
854
  var periodKeysArr = Array.from(periodKeys);
826
- var _loop_2 = function (kid) {
855
+ var _loop_3 = function (kid) {
827
856
  var isFound = associatedKeyIds.some(function (k) { return areKeyIdsEqual(k, kid); });
828
857
  if (isFound) {
829
858
  mergeKeyIdSetIntoArray(periodKeys, associatedKeyIds);
830
859
  return "break";
831
860
  }
832
861
  };
833
- for (var _e = 0, periodKeysArr_3 = periodKeysArr; _e < periodKeysArr_3.length; _e++) {
834
- var kid = periodKeysArr_3[_e];
835
- var state_2 = _loop_2(kid);
862
+ for (var _f = 0, periodKeysArr_3 = periodKeysArr; _f < periodKeysArr_3.length; _f++) {
863
+ var kid = periodKeysArr_3[_f];
864
+ var state_2 = _loop_3(kid);
836
865
  if (state_2 === "break")
837
866
  break;
838
867
  }
@@ -847,7 +876,7 @@ function getFetchedLicenseKeysInfo(initializationData, singleLicensePer, usableK
847
876
  }
848
877
  function mergeKeyIdSetIntoArray(set, arr) {
849
878
  var setArr = Array.from(set.values());
850
- var _loop_3 = function (kid) {
879
+ var _loop_4 = function (kid) {
851
880
  var isFound = arr.some(function (k) { return areKeyIdsEqual(k, kid); });
852
881
  if (!isFound) {
853
882
  arr.push(kid);
@@ -855,7 +884,7 @@ function mergeKeyIdSetIntoArray(set, arr) {
855
884
  };
856
885
  for (var _i = 0, setArr_1 = setArr; _i < setArr_1.length; _i++) {
857
886
  var kid = setArr_1[_i];
858
- _loop_3(kid);
887
+ _loop_4(kid);
859
888
  }
860
889
  }
861
890
  function addKeyIdsFromPeriod(set, period) {
@@ -45,7 +45,7 @@ export default function ContentTimeBoundariesObserver(manifest, streams, playbac
45
45
  var maximumPositionCalculator = new MaximumPositionCalculator(manifest);
46
46
  // trigger warnings when the wanted time is before or after the manifest's
47
47
  // segments
48
- var outOfManifest$ = playbackObserver.observe(true).pipe(filterMap(function (_a) {
48
+ var outOfManifest$ = playbackObserver.getReference().asObservable().pipe(filterMap(function (_a) {
49
49
  var _b;
50
50
  var position = _a.position;
51
51
  var wantedPosition = (_b = position.pending) !== null && _b !== void 0 ? _b : position.last;
@@ -35,6 +35,6 @@ export interface IStreamPlaybackObserverArguments {
35
35
  * @param {Object} manifest
36
36
  * @param {Object} playbackObserver
37
37
  * @param {Object} args
38
- * @returns {Observable}
38
+ * @returns {Object}
39
39
  */
40
40
  export default function createStreamPlaybackObserver(manifest: Manifest, playbackObserver: PlaybackObserver, { autoPlay, initialPlayPerformed, initialSeekPerformed, speed, startTime }: IStreamPlaybackObserverArguments): IReadOnlyPlaybackObserver<IStreamOrchestratorPlaybackObservation>;