rx-player 3.28.0-dev.2022062300 → 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 (42) hide show
  1. package/CHANGELOG.md +2 -1
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +1 -6
  4. package/dist/_esm5.processed/core/api/playback_observer.d.ts +47 -35
  5. package/dist/_esm5.processed/core/api/playback_observer.js +120 -117
  6. package/dist/_esm5.processed/core/api/public_api.d.ts +4 -1
  7. package/dist/_esm5.processed/core/api/public_api.js +10 -5
  8. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +1 -1
  9. package/dist/_esm5.processed/core/init/create_stream_playback_observer.d.ts +1 -1
  10. package/dist/_esm5.processed/core/init/create_stream_playback_observer.js +23 -6
  11. package/dist/_esm5.processed/core/init/initial_seek_and_play.js +3 -3
  12. package/dist/_esm5.processed/core/init/initialize_directfile.js +1 -1
  13. package/dist/_esm5.processed/core/init/load_on_media_source.js +1 -1
  14. package/dist/_esm5.processed/core/init/stall_avoider.js +12 -8
  15. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +5 -4
  16. package/dist/_esm5.processed/core/stream/period/create_empty_adaptation_stream.js +1 -1
  17. package/dist/_esm5.processed/core/stream/period/period_stream.js +21 -5
  18. package/dist/_esm5.processed/core/stream/reload_after_switch.js +1 -1
  19. package/dist/_esm5.processed/core/stream/representation/append_segment_to_buffer.js +1 -1
  20. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +1 -1
  21. package/dist/_esm5.processed/utils/reference.d.ts +2 -2
  22. package/dist/_esm5.processed/utils/reference.js +5 -4
  23. package/dist/rx-player.js +241 -179
  24. package/dist/rx-player.min.js +1 -1
  25. package/package.json +2 -2
  26. package/sonar-project.properties +1 -1
  27. package/src/core/adaptive/adaptive_representation_selector.ts +1 -7
  28. package/src/core/api/playback_observer.ts +185 -173
  29. package/src/core/api/public_api.ts +15 -6
  30. package/src/core/init/content_time_boundaries_observer.ts +1 -1
  31. package/src/core/init/create_stream_playback_observer.ts +69 -47
  32. package/src/core/init/initial_seek_and_play.ts +3 -3
  33. package/src/core/init/initialize_directfile.ts +1 -1
  34. package/src/core/init/load_on_media_source.ts +1 -1
  35. package/src/core/init/stall_avoider.ts +12 -9
  36. package/src/core/stream/orchestrator/stream_orchestrator.ts +5 -4
  37. package/src/core/stream/period/create_empty_adaptation_stream.ts +1 -1
  38. package/src/core/stream/period/period_stream.ts +33 -14
  39. package/src/core/stream/reload_after_switch.ts +1 -1
  40. package/src/core/stream/representation/append_segment_to_buffer.ts +1 -1
  41. package/src/core/stream/representation/representation_stream.ts +1 -1
  42. package/src/utils/reference.ts +7 -5
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.28.0-dev.2022062300",
4
+ "version": "3.28.0-dev.2022062700",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -32,7 +32,7 @@
32
32
  "build:modular": "./scripts/build/generate_build.js",
33
33
  "build:report": "webpack --progress --config webpack.config.js --env production --env reportSize",
34
34
  "build:rxp:all": "npm run build && npm run build:min && npm run build:modular",
35
- "build:watch": "webpack --progress --config webpack.config.js -w -env production",
35
+ "build:watch": "webpack --progress --config webpack.config.js -w --env production",
36
36
  "build:min:watch": "webpack --progress --config webpack.config.js -w --env production --env minify",
37
37
  "build:wasm:debug": "cd ./src/parsers/manifest/dash/wasm-parser && cargo build --target wasm32-unknown-unknown && cp target/wasm32-unknown-unknown/debug/mpd_node_parser.wasm ../../../../../dist/mpd-parser.wasm",
38
38
  "build:wasm:release": "cd ./src/parsers/manifest/dash/wasm-parser && cargo build --target wasm32-unknown-unknown --release && wasm-opt -O3 -o ../../../../../dist/mpd-parser.wasm target/wasm32-unknown-unknown/release/mpd_node_parser.wasm && cd ../../../../../ && npm run wasm-strip",
@@ -1,7 +1,7 @@
1
1
  sonar.projectKey=rx-player
2
2
  sonar.organization=rx-player
3
3
  sonar.projectName=rx-player
4
- sonar.projectVersion=3.28.0-dev.2022062300
4
+ sonar.projectVersion=3.28.0-dev.2022062700
5
5
  sonar.sources=./src,./demo,./tests
6
6
  sonar.exclusions=demo/full/bundle.js,demo/standalone/lib.js,demo/bundle.js
7
7
  sonar.host.url=https://sonarcloud.io
@@ -268,14 +268,8 @@ function getEstimateReference(
268
268
  */
269
269
  const guessBasedChooser = new GuessBasedChooser(scoreCalculator, prevEstimate);
270
270
 
271
- let lastPlaybackObservation : IRepresentationEstimatorPlaybackObservation;
272
-
273
271
  // get initial observation for initial estimate
274
- const unregisterInitial = playbackObserver.listen(obs => {
275
- lastPlaybackObservation = obs;
276
- }, { includeLastObservation: true });
277
- unregisterInitial(); // The initial is emitted synchronously, we can now remove it
278
- // TODO cleaner playbackObserver.getLast() or something?
272
+ let lastPlaybackObservation = playbackObserver.getReference().getValue();
279
273
 
280
274
  /** Reference through which estimates are emitted. */
281
275
  const innerEstimateRef = createSharedReference<IABREstimate>(getCurrentEstimate());
@@ -14,25 +14,17 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import {
18
- defer as observableDefer,
19
- fromEvent as observableFromEvent,
20
- interval as observableInterval,
21
- map,
22
- merge as observableMerge,
23
- Observable,
24
- share,
25
- shareReplay,
26
- skip,
27
- startWith,
28
- } from "rxjs";
29
17
  import config from "../../config";
30
18
  import log from "../../log";
31
19
  import noop from "../../utils/noop";
32
20
  import objectAssign from "../../utils/object_assign";
33
21
  import { getRange } from "../../utils/ranges";
34
- import { CancellationSignal } from "../../utils/task_canceller";
35
-
22
+ import createSharedReference, {
23
+ IReadOnlySharedReference,
24
+ } from "../../utils/reference";
25
+ import TaskCanceller, {
26
+ CancellationSignal,
27
+ } from "../../utils/task_canceller";
36
28
 
37
29
  /**
38
30
  * HTMLMediaElement Events for which playback observations are calculated and
@@ -93,19 +85,23 @@ export default class PlaybackObserver {
93
85
  private _internalSeekingEventsIncomingCounter : number;
94
86
 
95
87
  /**
96
- * Last playback observation made by the `PlaybackObserver`.
97
- *
98
- * `null` if no observation has been made yet.
88
+ * Stores the last playback observation produced by the `PlaybackObserver`.:
99
89
  */
100
- private _lastObservation : IPlaybackObservation | null;
90
+ private _observationRef : IReadOnlySharedReference<IPlaybackObservation>;
101
91
 
102
92
  /**
103
- * Lazily-created shared Observable that will emit playback observations.
104
- * Set to `null` until the first time it is generated.
93
+ * `TaskCanceller` allowing to free all resources and stop producing playback
94
+ * observations.
105
95
  */
106
- private _observation$ : Observable<IPlaybackObservation> | null;
96
+ private _canceller : TaskCanceller;
107
97
 
108
98
  /**
99
+ * Create a new `PlaybackObserver`, which allows to produce new "playback
100
+ * observations" on various media events and intervals.
101
+ *
102
+ * Note that creating a `PlaybackObserver` lead to the usage of resources,
103
+ * such as event listeners which will only be freed once the `stop` method is
104
+ * called.
109
105
  * @param {HTMLMediaElement} mediaElement
110
106
  * @param {Object} options
111
107
  */
@@ -114,8 +110,22 @@ export default class PlaybackObserver {
114
110
  this._mediaElement = mediaElement;
115
111
  this._withMediaSource = options.withMediaSource;
116
112
  this._lowLatencyMode = options.lowLatencyMode;
117
- this._lastObservation = null;
118
- this._observation$ = null;
113
+ this._canceller = new TaskCanceller();
114
+ this._observationRef = this._createSharedReference();
115
+ }
116
+
117
+ /**
118
+ * Stop the `PlaybackObserver` from emitting playback observations and free all
119
+ * resources reserved to emitting them such as event listeners, intervals and
120
+ * subscribing callbacks.
121
+ *
122
+ * Once `stop` is called, no new playback observation will ever be emitted.
123
+ *
124
+ * Note that it is important to call stop once the `PlaybackObserver` is no
125
+ * more needed to avoid unnecessarily leaking resources.
126
+ */
127
+ public stop() {
128
+ this._canceller.cancel();
119
129
  }
120
130
 
121
131
  /**
@@ -162,29 +172,17 @@ export default class PlaybackObserver {
162
172
  }
163
173
 
164
174
  /**
165
- * Returns an Observable regularly emitting playback observation, optionally
166
- * starting with the last one.
175
+ * Returns an `IReadOnlySharedReference` storing the last playback observation
176
+ * produced by the `PlaybackObserver` and updated each time a new one is
177
+ * produced.
167
178
  *
168
- * Note that this Observable is shared and unique, so that multiple `observe`
169
- * call will return the exact same Observable and multiple concurrent
170
- * `subscribe` will receive the same events at the same time.
171
- * This was done for performance and simplicity reasons.
179
+ * This value can then be for example subscribed to to be notified of future
180
+ * playback observations.
172
181
  *
173
- * @param {boolean} includeLastObservation
174
- * @returns {Observable}
182
+ * @returns {Object}
175
183
  */
176
- public observe(includeLastObservation : boolean) : Observable<IPlaybackObservation> {
177
- return observableDefer(() : Observable<IPlaybackObservation> => {
178
- if (this._observation$ === null || this._lastObservation === null) {
179
- this._lastObservation = this._generateInitialObservation();
180
- this._observation$ = this._createInnerObservable().pipe(share());
181
- return this.observe(includeLastObservation);
182
- } else {
183
- return includeLastObservation ?
184
- this._observation$.pipe(startWith(this._lastObservation)) :
185
- this._observation$;
186
- }
187
- });
184
+ public getReference() : IReadOnlySharedReference<IPlaybackObservation> {
185
+ return this._observationRef;
188
186
  }
189
187
 
190
188
  /**
@@ -195,26 +193,19 @@ export default class PlaybackObserver {
195
193
  * be first emitted synchronously.
196
194
  * - `clearSignal`: If set, the callback will be unregistered when this
197
195
  * CancellationSignal emits.
198
- * @returns {Function} - Allows to easily unregister the callback
199
196
  */
200
197
  public listen(
201
198
  cb : (observation : IPlaybackObservation) => void,
202
199
  options? : { includeLastObservation? : boolean | undefined;
203
200
  clearSignal? : CancellationSignal | undefined; }
204
- ) : () => void {
205
- if (options?.clearSignal?.isCancelled === true) {
201
+ ) {
202
+ if (this._canceller.isUsed || options?.clearSignal?.isCancelled === true) {
206
203
  return noop;
207
204
  }
208
- const sub = this.observe(options?.includeLastObservation ?? false)
209
- .subscribe(cb);
210
- const unregister = options?.clearSignal?.register(() => {
211
- sub.unsubscribe();
212
- }) ?? noop;
213
-
214
- return () => {
215
- unregister();
216
- sub.unsubscribe();
217
- };
205
+ this._observationRef.onUpdate(cb, {
206
+ clearSignal: options?.clearSignal,
207
+ emitCurrentValue: options?.includeLastObservation,
208
+ });
218
209
  }
219
210
 
220
211
  /**
@@ -228,99 +219,122 @@ export default class PlaybackObserver {
228
219
  *
229
220
  * As argument, this method takes a function which will allow to produce
230
221
  * the new set of properties to be present on each observation.
231
- * @param {Function} mapObservable
222
+ * @param {Function} transform
232
223
  * @returns {Object}
233
224
  */
234
225
  public deriveReadOnlyObserver<TDest>(
235
- mapObservable : (
236
- observation$ : Observable<IPlaybackObservation>
237
- ) => Observable<TDest>
226
+ transform : (
227
+ observationRef : IReadOnlySharedReference<IPlaybackObservation>,
228
+ cancellationSignal : CancellationSignal
229
+ ) => IReadOnlySharedReference<TDest>
238
230
  ) : IReadOnlyPlaybackObserver<TDest> {
239
- return generateReadOnlyObserver(this, mapObservable);
231
+ return generateReadOnlyObserver(this, transform, this._canceller.signal);
240
232
  }
241
233
 
242
234
  /**
243
- * Creates the observable that will generate playback observations.
235
+ * Creates the `IReadOnlySharedReference` that will generate playback
236
+ * observations.
244
237
  * @returns {Observable}
245
238
  */
246
- private _createInnerObservable() : Observable<IPlaybackObservation> {
247
-
248
-
249
- return observableDefer(() : Observable<IPlaybackObservation> => {
250
- const { SAMPLING_INTERVAL_MEDIASOURCE,
251
- SAMPLING_INTERVAL_LOW_LATENCY,
252
- SAMPLING_INTERVAL_NO_MEDIASOURCE } = config.getCurrent();
253
- const getCurrentObservation = (
254
- event : IPlaybackObserverEventType
255
- ) : IPlaybackObservation => {
256
- let tmpEvt: IPlaybackObserverEventType = event;
257
- if (tmpEvt === "seeking" && this._internalSeekingEventsIncomingCounter > 0) {
258
- tmpEvt = "internal-seeking";
259
- this._internalSeekingEventsIncomingCounter -= 1;
260
- }
261
- const lastObservation = this._lastObservation ??
262
- this._generateInitialObservation();
263
- const mediaTimings = getMediaInfos(this._mediaElement, tmpEvt);
264
- const internalSeeking = mediaTimings.seeking &&
265
- // We've just received the event for internally seeking
266
- (tmpEvt === "internal-seeking" ||
267
- // or We're still waiting on the previous internal-seek
268
- (lastObservation.internalSeeking && tmpEvt !== "seeking"));
269
- const rebufferingStatus = getRebufferingStatus(
270
- lastObservation,
271
- mediaTimings,
272
- { lowLatencyMode: this._lowLatencyMode,
273
- withMediaSource: this._withMediaSource });
274
-
275
- const freezingStatus = getFreezingStatus(lastObservation, mediaTimings);
276
- const timings = objectAssign(
277
- {},
278
- { rebuffering: rebufferingStatus,
279
- freezing: freezingStatus,
280
- internalSeeking },
281
- mediaTimings);
282
- if (log.hasLevel("DEBUG")) {
283
- log.debug("API: current media element state tick",
284
- "event", timings.event,
285
- "position", timings.position,
286
- "seeking", timings.seeking,
287
- "internalSeeking", timings.internalSeeking,
288
- "rebuffering", timings.rebuffering !== null,
289
- "freezing", timings.freezing !== null,
290
- "ended", timings.ended,
291
- "paused", timings.paused,
292
- "playbackRate", timings.playbackRate,
293
- "readyState", timings.readyState);
294
- }
295
- return timings;
239
+ private _createSharedReference() : IReadOnlySharedReference<IPlaybackObservation> {
240
+ if (this._observationRef !== undefined) {
241
+ return this._observationRef;
242
+ }
243
+
244
+ let lastObservation : IPlaybackObservation | null;
245
+ const { SAMPLING_INTERVAL_MEDIASOURCE,
246
+ SAMPLING_INTERVAL_LOW_LATENCY,
247
+ SAMPLING_INTERVAL_NO_MEDIASOURCE } = config.getCurrent();
248
+
249
+ const getCurrentObservation = (
250
+ event : IPlaybackObserverEventType
251
+ ) : IPlaybackObservation => {
252
+ let tmpEvt: IPlaybackObserverEventType = event;
253
+ if (tmpEvt === "seeking" && this._internalSeekingEventsIncomingCounter > 0) {
254
+ tmpEvt = "internal-seeking";
255
+ this._internalSeekingEventsIncomingCounter -= 1;
256
+ }
257
+ const _lastObservation = lastObservation ?? this._generateInitialObservation();
258
+ const mediaTimings = getMediaInfos(this._mediaElement, tmpEvt);
259
+ const internalSeeking = mediaTimings.seeking &&
260
+ // We've just received the event for internally seeking
261
+ (tmpEvt === "internal-seeking" ||
262
+ // or We're still waiting on the previous internal-seek
263
+ (_lastObservation.internalSeeking && tmpEvt !== "seeking"));
264
+ const rebufferingStatus = getRebufferingStatus(
265
+ _lastObservation,
266
+ mediaTimings,
267
+ { lowLatencyMode: this._lowLatencyMode,
268
+ withMediaSource: this._withMediaSource });
269
+
270
+ const freezingStatus = getFreezingStatus(_lastObservation, mediaTimings);
271
+ const timings = objectAssign(
272
+ {},
273
+ { rebuffering: rebufferingStatus,
274
+ freezing: freezingStatus,
275
+ internalSeeking },
276
+ mediaTimings);
277
+ if (log.hasLevel("DEBUG")) {
278
+ log.debug("API: current media element state tick",
279
+ "event", timings.event,
280
+ "position", timings.position,
281
+ "seeking", timings.seeking,
282
+ "internalSeeking", timings.internalSeeking,
283
+ "rebuffering", timings.rebuffering !== null,
284
+ "freezing", timings.freezing !== null,
285
+ "ended", timings.ended,
286
+ "paused", timings.paused,
287
+ "playbackRate", timings.playbackRate,
288
+ "readyState", timings.readyState);
289
+ }
290
+ return timings;
291
+ };
292
+
293
+ const returnedSharedReference = createSharedReference(getCurrentObservation("init"));
294
+
295
+ const generateObservationForEvent = (event : IPlaybackObserverEventType) => {
296
+ const newObservation = getCurrentObservation(event);
297
+ if (log.hasLevel("DEBUG")) {
298
+ log.debug("API: current playback timeline:\n" +
299
+ prettyPrintBuffered(newObservation.buffered,
300
+ newObservation.position),
301
+ `\n${event}`);
302
+ }
303
+ lastObservation = newObservation;
304
+ returnedSharedReference.setValue(newObservation);
305
+ };
306
+
307
+ const interval = this._lowLatencyMode ? SAMPLING_INTERVAL_LOW_LATENCY :
308
+ this._withMediaSource ? SAMPLING_INTERVAL_MEDIASOURCE :
309
+ SAMPLING_INTERVAL_NO_MEDIASOURCE;
310
+ let intervalId = setInterval(onInterval, interval);
311
+ const removeEventListeners = SCANNED_MEDIA_ELEMENTS_EVENTS.map((eventName) => {
312
+ this._mediaElement.addEventListener(eventName, onMediaEvent);
313
+ function onMediaEvent() {
314
+ restartInterval();
315
+ generateObservationForEvent(eventName);
316
+ }
317
+ return () => {
318
+ this._mediaElement.removeEventListener(eventName, onMediaEvent);
296
319
  };
320
+ });
297
321
 
298
- const eventObs : Array< Observable< IPlaybackObserverEventType > > =
299
- SCANNED_MEDIA_ELEMENTS_EVENTS.map((eventName) =>
300
- observableFromEvent(this._mediaElement, eventName)
301
- .pipe(map(() => eventName)));
302
-
303
- const interval = this._lowLatencyMode ? SAMPLING_INTERVAL_LOW_LATENCY :
304
- this._withMediaSource ? SAMPLING_INTERVAL_MEDIASOURCE :
305
- SAMPLING_INTERVAL_NO_MEDIASOURCE;
306
-
307
- const interval$ : Observable<"timeupdate"> =
308
- observableInterval(interval)
309
- .pipe(map(() => "timeupdate"));
310
-
311
- return observableMerge(interval$, ...eventObs).pipe(
312
- map((event : IPlaybackObserverEventType) => {
313
- const newObservation = getCurrentObservation(event);
314
- if (log.hasLevel("DEBUG")) {
315
- log.debug("API: current playback timeline:\n" +
316
- prettyPrintBuffered(newObservation.buffered,
317
- newObservation.position),
318
- `\n${event}`);
319
- }
320
- this._lastObservation = newObservation;
321
- return newObservation;
322
- }));
322
+ this._canceller.signal.register(() => {
323
+ clearInterval(intervalId);
324
+ removeEventListeners.forEach(cb => cb());
325
+ returnedSharedReference.finish();
323
326
  });
327
+
328
+ return returnedSharedReference;
329
+
330
+ function onInterval() {
331
+ generateObservationForEvent("timeupdate");
332
+ }
333
+
334
+ function restartInterval() {
335
+ clearInterval(intervalId);
336
+ intervalId = setInterval(onInterval, interval);
337
+ }
324
338
  }
325
339
 
326
340
  private _generateInitialObservation() : IPlaybackObservation {
@@ -472,18 +486,16 @@ export interface IReadOnlyPlaybackObserver<TObservationType> {
472
486
  */
473
487
  getIsPaused() : boolean;
474
488
  /**
475
- * Returns an Observable regularly emitting playback observation, optionally
476
- * starting with the last one.
489
+ * Returns an `IReadOnlySharedReference` storing the last playback observation
490
+ * produced by the `IReadOnlyPlaybackObserver` and updated each time a new one
491
+ * is produced.
477
492
  *
478
- * Note that this Observable is shared and unique, so that multiple `observe`
479
- * call will return the exact same Observable and multiple concurrent
480
- * `subscribe` will receive the same events at the same time.
481
- * This was done for performance and simplicity reasons.
493
+ * This value can then be for example subscribed to to be notified of future
494
+ * playback observations.
482
495
  *
483
- * @param {boolean} includeLastObservation
484
- * @returns {Observable}
496
+ * @returns {Object}
485
497
  */
486
- observe(includeLastObservation : boolean) : Observable<TObservationType>;
498
+ getReference() : IReadOnlySharedReference<TObservationType>;
487
499
  /**
488
500
  * Register a callback so it regularly receives playback observations.
489
501
  * @param {Function} cb
@@ -498,18 +510,20 @@ export interface IReadOnlyPlaybackObserver<TObservationType> {
498
510
  cb : (observation : TObservationType) => void,
499
511
  options? : { includeLastObservation? : boolean | undefined;
500
512
  clearSignal? : CancellationSignal | undefined; }
501
- ) : () => void;
513
+ ) : void;
502
514
  /**
503
515
  * Generate a new `IReadOnlyPlaybackObserver` from this one.
504
516
  *
505
517
  * As argument, this method takes a function which will allow to produce
506
518
  * the new set of properties to be present on each observation.
507
- * @param {Function} mapObservable
519
+ * @param {Function} transform
508
520
  * @returns {Object}
509
521
  */
510
522
  deriveReadOnlyObserver<TDest>(
511
- mapObservable : (observation$ : Observable<TObservationType>) => Observable<TDest>,
512
- mapObservation : (observation : TObservationType) => TDest
523
+ transform : (
524
+ observationRef : IReadOnlySharedReference<TObservationType>,
525
+ cancellationSignal : CancellationSignal
526
+ ) => IReadOnlySharedReference<TDest>
513
527
  ) : IReadOnlyPlaybackObserver<TDest>;
514
528
  }
515
529
 
@@ -826,16 +840,18 @@ function prettyPrintBuffered(
826
840
  * Create `IReadOnlyPlaybackObserver` from a source `IReadOnlyPlaybackObserver`
827
841
  * and a mapping function.
828
842
  * @param {Object} src
829
- * @param {Function} mapObservable
843
+ * @param {Function} transform
830
844
  * @returns {Object}
831
845
  */
832
846
  function generateReadOnlyObserver<TSource, TDest>(
833
847
  src : IReadOnlyPlaybackObserver<TSource>,
834
- mapObservable : (observation$ : Observable<TSource>) => Observable<TDest>
848
+ transform : (
849
+ observationRef : IReadOnlySharedReference<TSource>,
850
+ cancellationSignal : CancellationSignal
851
+ ) => IReadOnlySharedReference<TDest>,
852
+ cancellationSignal : CancellationSignal
835
853
  ) : IReadOnlyPlaybackObserver<TDest> {
836
- const newObs = observableDefer(() =>
837
- mapObservable(src.observe(true))
838
- ).pipe(shareReplay({ bufferSize: 1, refCount: true }));
854
+ const mappedRef = transform(src.getReference(), cancellationSignal);
839
855
  return {
840
856
  getCurrentTime() {
841
857
  return src.getCurrentTime();
@@ -846,33 +862,29 @@ function generateReadOnlyObserver<TSource, TDest>(
846
862
  getIsPaused() {
847
863
  return src.getIsPaused();
848
864
  },
849
- observe(includeLastObservation : boolean) : Observable<TDest> {
850
- return includeLastObservation ? newObs :
851
- newObs.pipe(skip(1));
865
+ getReference() : IReadOnlySharedReference<TDest> {
866
+ return mappedRef;
852
867
  },
853
868
  listen(
854
869
  cb : (observation : TDest) => void,
855
870
  options? : { includeLastObservation? : boolean | undefined;
856
871
  clearSignal? : CancellationSignal | undefined; }
857
- ) : () => void {
858
- if (options?.clearSignal?.isCancelled === true) {
859
- return noop;
872
+ ) : void {
873
+ if (cancellationSignal.isCancelled || options?.clearSignal?.isCancelled === true) {
874
+ return ;
860
875
  }
861
- const obs = options?.includeLastObservation === true ? newObs :
862
- newObs.pipe(skip(1));
863
- const sub = obs.subscribe(cb);
864
- const unregister = options?.clearSignal?.register(() => {
865
- sub.unsubscribe();
866
- }) ?? noop;
867
- return () => {
868
- unregister();
869
- sub.unsubscribe();
870
- };
876
+ mappedRef.onUpdate(cb, {
877
+ clearSignal: options?.clearSignal,
878
+ emitCurrentValue: options?.includeLastObservation,
879
+ });
871
880
  },
872
881
  deriveReadOnlyObserver<TNext>(
873
- newUdateObserver : (observation$ : Observable<TDest>) => Observable<TNext>
882
+ newTransformFn : (
883
+ observationRef : IReadOnlySharedReference<TDest>,
884
+ signal : CancellationSignal
885
+ ) => IReadOnlySharedReference<TNext>
874
886
  ) : IReadOnlyPlaybackObserver<TNext> {
875
- return generateReadOnlyObserver(this, newUdateObserver);
887
+ return generateReadOnlyObserver(this, newTransformFn, cancellationSignal);
876
888
  },
877
889
  };
878
890
  }
@@ -170,7 +170,10 @@ class Player extends EventEmitter<IPublicAPIEvent> {
170
170
  /** Current version of the RxPlayer. */
171
171
  public readonly version : string;
172
172
 
173
- /** Media element attached to the RxPlayer. */
173
+ /**
174
+ * Media element attached to the RxPlayer.
175
+ * Set to `null` when the RxPlayer is disposed.
176
+ */
174
177
  public videoElement : HTMLMediaElement|null; // null on dispose
175
178
 
176
179
  /** Logger the RxPlayer uses. */
@@ -429,7 +432,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
429
432
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
430
433
  videoElement.preload = "auto";
431
434
 
432
- this.version = /* PLAYER_VERSION */"3.28.0-dev.2022062300";
435
+ this.version = /* PLAYER_VERSION */"3.28.0-dev.2022062700";
433
436
  this.log = log;
434
437
  this.state = "STOPPED";
435
438
  this.videoElement = videoElement;
@@ -720,6 +723,10 @@ class Player extends EventEmitter<IPublicAPIEvent> {
720
723
  lowLatencyMode,
721
724
  });
722
725
 
726
+ currentContentCanceller.signal.register(() => {
727
+ playbackObserver.stop();
728
+ });
729
+
723
730
  /** Emit playback events. */
724
731
  let playback$ : Connectable<IInitEvent>;
725
732
 
@@ -971,7 +978,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
971
978
  share());
972
979
 
973
980
  /** Emit when the media element emits a "seeking" event. */
974
- const observation$ = playbackObserver.observe(true);
981
+ const observation$ = playbackObserver.getReference().asObservable();
975
982
 
976
983
  const stateChangingEvent$ = observation$.pipe(filter(o => {
977
984
  return o.event === "seeking" || o.event === "ended" ||
@@ -2325,8 +2332,10 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2325
2332
 
2326
2333
  // DRM-related clean-up
2327
2334
  const freeUpContentLock = () => {
2328
- log.debug("Unlocking `contentLock`. Next content can begin.");
2329
- this._priv_contentLock.setValue(false);
2335
+ if (this.videoElement !== null) { // If not disposed
2336
+ log.debug("Unlocking `contentLock`. Next content can begin.");
2337
+ this._priv_contentLock.setValue(false);
2338
+ }
2330
2339
  };
2331
2340
 
2332
2341
  if (!isNullOrUndefined(this.videoElement)) {
@@ -2934,7 +2943,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2934
2943
  return activeRepresentations[currentPeriod.id];
2935
2944
  }
2936
2945
  }
2937
- Player.version = /* PLAYER_VERSION */"3.28.0-dev.2022062300";
2946
+ Player.version = /* PLAYER_VERSION */"3.28.0-dev.2022062700";
2938
2947
 
2939
2948
  /** Every events sent by the RxPlayer's public API. */
2940
2949
  interface IPublicAPIEvent {
@@ -73,7 +73,7 @@ export default function ContentTimeBoundariesObserver(
73
73
 
74
74
  // trigger warnings when the wanted time is before or after the manifest's
75
75
  // segments
76
- const outOfManifest$ = playbackObserver.observe(true).pipe(
76
+ const outOfManifest$ = playbackObserver.getReference().asObservable().pipe(
77
77
  filterMap<IContentTimeObserverPlaybackObservation, IWarningEvent, null>((
78
78
  { position }
79
79
  ) => {