rx-player 3.29.0-dev.2022103100 → 3.29.0-dev.2022110200

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 (27) hide show
  1. package/CHANGELOG.md +2 -3
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/core/api/playback_observer.d.ts +5 -0
  4. package/dist/_esm5.processed/core/api/playback_observer.js +7 -0
  5. package/dist/_esm5.processed/core/api/public_api.js +2 -2
  6. package/dist/_esm5.processed/core/init/initialize_directfile.js +3 -8
  7. package/dist/_esm5.processed/core/init/load_on_media_source.js +3 -11
  8. package/dist/_esm5.processed/core/init/{stall_avoider.d.ts → rebuffering_controller.d.ts} +5 -2
  9. package/dist/_esm5.processed/core/init/{stall_avoider.js → rebuffering_controller.js} +93 -4
  10. package/dist/_esm5.processed/core/init/update_playback_rate.d.ts +0 -32
  11. package/dist/_esm5.processed/core/init/update_playback_rate.js +1 -46
  12. package/dist/_esm5.processed/default_config.js +1 -1
  13. package/dist/_esm5.processed/parsers/texttracks/ttml/html/apply_extent.js +1 -8
  14. package/dist/_esm5.processed/parsers/texttracks/ttml/html/apply_origin.js +1 -9
  15. package/dist/rx-player.js +193 -182
  16. package/dist/rx-player.min.js +1 -1
  17. package/package.json +1 -1
  18. package/sonar-project.properties +1 -1
  19. package/src/core/api/playback_observer.ts +8 -0
  20. package/src/core/api/public_api.ts +2 -2
  21. package/src/core/init/initialize_directfile.ts +3 -11
  22. package/src/core/init/load_on_media_source.ts +12 -23
  23. package/src/core/init/{stall_avoider.ts → rebuffering_controller.ts} +106 -3
  24. package/src/core/init/update_playback_rate.ts +0 -70
  25. package/src/default_config.ts +1 -1
  26. package/src/parsers/texttracks/ttml/html/apply_extent.ts +1 -8
  27. package/src/parsers/texttracks/ttml/html/apply_origin.ts +1 -9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.29.0-dev.2022103100",
4
+ "version": "3.29.0-dev.2022110200",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -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.29.0-dev.2022103100
4
+ sonar.projectVersion=3.29.0-dev.2022110200
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
@@ -171,6 +171,14 @@ export default class PlaybackObserver {
171
171
  this._mediaElement.currentTime = time;
172
172
  }
173
173
 
174
+ /**
175
+ * Update the playback rate of the `HTMLMediaElement`.
176
+ * @param {number} playbackRate
177
+ */
178
+ public setPlaybackRate(playbackRate: number) : void {
179
+ this._mediaElement.playbackRate = playbackRate;
180
+ }
181
+
174
182
  /**
175
183
  * Returns the current `readyState` advertised by the `HTMLMediaElement`.
176
184
  * @returns {number}
@@ -460,7 +460,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
460
460
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
461
461
  videoElement.preload = "auto";
462
462
 
463
- this.version = /* PLAYER_VERSION */"3.29.0-dev.2022103100";
463
+ this.version = /* PLAYER_VERSION */"3.29.0-dev.2022110200";
464
464
  this.log = log;
465
465
  this.state = "STOPPED";
466
466
  this.videoElement = videoElement;
@@ -3002,7 +3002,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3002
3002
  return activeRepresentations[currentPeriod.id];
3003
3003
  }
3004
3004
  }
3005
- Player.version = /* PLAYER_VERSION */"3.29.0-dev.2022103100";
3005
+ Player.version = /* PLAYER_VERSION */"3.29.0-dev.2022110200";
3006
3006
 
3007
3007
  /** Every events sent by the RxPlayer's public API. */
3008
3008
  interface IPublicAPIEvent {
@@ -44,10 +44,9 @@ import emitLoadedEvent from "./emit_loaded_event";
44
44
  import { IInitialTimeOptions } from "./get_initial_time";
45
45
  import initialSeekAndPlay from "./initial_seek_and_play";
46
46
  import linkDrmAndContent from "./link_drm_and_content";
47
- import StallAvoider from "./stall_avoider";
47
+ import RebufferingController from "./rebuffering_controller";
48
48
  import throwOnMediaError from "./throw_on_media_error";
49
49
  import { IDirectfileEvent } from "./types";
50
- import updatePlaybackRate from "./update_playback_rate";
51
50
 
52
51
  // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default
53
52
  // first type parameter as `any` instead of the perfectly fine `unknown`,
@@ -160,17 +159,11 @@ export default function initializeDirectfileContent({
160
159
 
161
160
  const observation$ = playbackObserver.getReference().asObservable();
162
161
 
163
- // Set the speed set by the user on the media element while pausing a
164
- // little longer while the buffer is empty.
165
- const playbackRate$ =
166
- updatePlaybackRate(mediaElement, speed, observation$)
167
- .pipe(ignoreElements());
168
-
169
162
  /**
170
163
  * Observable trying to avoid various stalling situations, emitting "stalled"
171
164
  * events when it cannot, as well as "unstalled" events when it get out of one.
172
165
  */
173
- const stallAvoider$ = StallAvoider(playbackObserver, null, speed, EMPTY, EMPTY);
166
+ const rebuffer$ = RebufferingController(playbackObserver, null, speed, EMPTY, EMPTY);
174
167
 
175
168
  /**
176
169
  * Emit a "loaded" events once the initial play has been performed and the
@@ -192,8 +185,7 @@ export default function initializeDirectfileContent({
192
185
  return observableMerge(loadingEvts$,
193
186
  drmEvents$.pipe(ignoreElements()),
194
187
  mediaError$,
195
- playbackRate$,
196
- stallAvoider$);
188
+ rebuffer$);
197
189
  }
198
190
 
199
191
  export { IDirectfileEvent };
@@ -46,13 +46,12 @@ import emitLoadedEvent from "./emit_loaded_event";
46
46
  import { maintainEndOfStream } from "./end_of_stream";
47
47
  import initialSeekAndPlay from "./initial_seek_and_play";
48
48
  import MediaDurationUpdater from "./media_duration_updater";
49
- import StallAvoider, {
49
+ import RebufferingController, {
50
50
  IDiscontinuityEvent,
51
51
  ILockedStreamEvent,
52
- } from "./stall_avoider";
52
+ } from "./rebuffering_controller";
53
53
  import streamEventsEmitter from "./stream_events_emitter";
54
54
  import { IMediaSourceLoaderEvent } from "./types";
55
- import updatePlaybackRate from "./update_playback_rate";
56
55
 
57
56
  // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default
58
57
  // first type parameter as `any` instead of the perfectly fine `unknown`,
@@ -206,24 +205,15 @@ export default function createMediaSourceLoader(
206
205
  }
207
206
  }));
208
207
 
209
- /**
210
- * On subscription, keep the playback speed synchronized to the speed set by
211
- * the user on the media element and force a speed of `0` when the buffer is
212
- * empty, so it can build back buffer.
213
- */
214
- const playbackRate$ =
215
- updatePlaybackRate(mediaElement, speed, observation$)
216
- .pipe(ignoreElements());
217
-
218
- /**
219
- * Observable trying to avoid various stalling situations, emitting "stalled"
220
- * events when it cannot, as well as "unstalled" events when it get out of one.
221
- */
222
- const stallAvoider$ = StallAvoider(playbackObserver,
223
- manifest,
224
- speed,
225
- lockedStream$,
226
- discontinuityUpdate$);
208
+ /**
209
+ * Observable trying to avoid various stalling situations, emitting "stalled"
210
+ * events when it cannot, as well as "unstalled" events when it get out of one.
211
+ */
212
+ const rebuffer$ = RebufferingController(playbackObserver,
213
+ manifest,
214
+ speed,
215
+ lockedStream$,
216
+ discontinuityUpdate$);
227
217
 
228
218
  /**
229
219
  * Emit a "loaded" events once the initial play has been performed and the
@@ -236,8 +226,7 @@ export default function createMediaSourceLoader(
236
226
  emitLoadedEvent(observation$, mediaElement, segmentBuffersStore, false)));
237
227
 
238
228
  return observableMerge(loadingEvts$,
239
- playbackRate$,
240
- stallAvoider$,
229
+ rebuffer$,
241
230
  streams$,
242
231
  contentTimeObserver,
243
232
  streamEvents$
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import {
18
+ finalize,
18
19
  ignoreElements,
19
20
  map,
20
21
  merge as observableMerge,
@@ -32,6 +33,7 @@ import Manifest, {
32
33
  } from "../../manifest";
33
34
  import { getNextRangeGap } from "../../utils/ranges";
34
35
  import { IReadOnlySharedReference } from "../../utils/reference";
36
+ import TaskCanceller from "../../utils/task_canceller";
35
37
  import {
36
38
  IPlaybackObservation,
37
39
  PlaybackObserver,
@@ -117,7 +119,10 @@ interface IDiscontinuityStoredInfo {
117
119
  }
118
120
 
119
121
  /**
120
- * Monitor situations where playback is stalled and try to get out of those.
122
+ * Monitor playback, trying to avoid stalling situation.
123
+ * If stopping the player to build buffer is needed, temporarily set the
124
+ * playback rate (i.e. speed) at `0` until enough buffer is available again.
125
+ *
121
126
  * Emit "stalled" then "unstalled" respectively when an unavoidable stall is
122
127
  * encountered and exited.
123
128
  * @param {object} playbackObserver - emit the current playback conditions.
@@ -129,7 +134,7 @@ interface IDiscontinuityStoredInfo {
129
134
  * discontinuities for loaded Period and buffer types.
130
135
  * @returns {Observable}
131
136
  */
132
- export default function StallAvoider(
137
+ export default function RebufferingController(
133
138
  playbackObserver : PlaybackObserver,
134
139
  manifest: Manifest | null,
135
140
  speed : IReadOnlySharedReference<number>,
@@ -214,6 +219,8 @@ export default function StallAvoider(
214
219
  ignoreElements()
215
220
  );
216
221
 
222
+ const playbackRateUpdater = new PlaybackRateUpdater(playbackObserver, speed);
223
+
217
224
  const stall$ = playbackObserver.getReference().asObservable().pipe(
218
225
  withLatestFrom(discontinuitiesStore$),
219
226
  map(([observation, discontinuitiesStore]) => {
@@ -260,6 +267,11 @@ export default function StallAvoider(
260
267
  }
261
268
 
262
269
  if (now - freezing.timestamp > FREEZING_STALLED_DELAY) {
270
+ if (rebuffering === null || ignoredStallTimeStamp !== null) {
271
+ playbackRateUpdater.stopRebuffering();
272
+ } else {
273
+ playbackRateUpdater.startRebuffering();
274
+ }
263
275
  return { type: "stalled" as const,
264
276
  value: "freezing" as const };
265
277
  }
@@ -268,6 +280,7 @@ export default function StallAvoider(
268
280
  }
269
281
 
270
282
  if (rebuffering === null) {
283
+ playbackRateUpdater.stopRebuffering();
271
284
  if (readyState === 1) {
272
285
  // With a readyState set to 1, we should still not be able to play:
273
286
  // Return that we're stalled
@@ -295,6 +308,7 @@ export default function StallAvoider(
295
308
  if (ignoredStallTimeStamp !== null) {
296
309
  const now = performance.now();
297
310
  if (now - ignoredStallTimeStamp < FORCE_DISCONTINUITY_SEEK_DELAY) {
311
+ playbackRateUpdater.stopRebuffering();
298
312
  log.debug("Init: letting the device get out of a stall by itself");
299
313
  return { type: "stalled" as const,
300
314
  value: stalledReason };
@@ -305,6 +319,7 @@ export default function StallAvoider(
305
319
  }
306
320
 
307
321
  ignoredStallTimeStamp = null;
322
+ playbackRateUpdater.startRebuffering();
308
323
 
309
324
  if (manifest === null) {
310
325
  return { type: "stalled" as const,
@@ -380,7 +395,11 @@ export default function StallAvoider(
380
395
  return { type: "stalled" as const,
381
396
  value: stalledReason };
382
397
  }));
383
- return observableMerge(unlock$, stall$);
398
+
399
+ return observableMerge(unlock$, stall$)
400
+ .pipe(finalize(() => {
401
+ playbackRateUpdater.dispose();
402
+ }));
384
403
  }
385
404
 
386
405
  /**
@@ -516,3 +535,87 @@ function generateDiscontinuityError(
516
535
  String(stalledPosition) + ", seeked at position " +
517
536
  String(seekTo));
518
537
  }
538
+
539
+ /**
540
+ * Manage playback speed, allowing to force a playback rate of `0` when
541
+ * rebuffering is wanted.
542
+ *
543
+ * Only one `PlaybackRateUpdater` should be created per HTMLMediaElement.
544
+ * Note that the `PlaybackRateUpdater` reacts to playback event and wanted
545
+ * speed change. You should call its `dispose` method once you don't need it
546
+ * anymore.
547
+ * @class PlaybackRateUpdater
548
+ */
549
+ class PlaybackRateUpdater {
550
+ private _playbackObserver : PlaybackObserver;
551
+ private _speed : IReadOnlySharedReference<number>;
552
+ private _speedUpdateCanceller : TaskCanceller;
553
+ private _isRebuffering : boolean;
554
+ private _isDisposed : boolean;
555
+
556
+ /**
557
+ * Create a new `PlaybackRateUpdater`.
558
+ * @param {Object} playbackObserver
559
+ * @param {Object} speed
560
+ */
561
+ constructor(
562
+ playbackObserver : PlaybackObserver,
563
+ speed : IReadOnlySharedReference<number>
564
+ ) {
565
+ this._speedUpdateCanceller = new TaskCanceller();
566
+ this._isRebuffering = false;
567
+ this._playbackObserver = playbackObserver;
568
+ this._isDisposed = false;
569
+ this._speed = speed;
570
+ this._updateSpeed();
571
+ }
572
+
573
+ /**
574
+ * Force the playback rate to `0`, to start a rebuffering phase.
575
+ *
576
+ * You can call `stopRebuffering` when you want the rebuffering phase to end.
577
+ */
578
+ public startRebuffering() : void {
579
+ if (this._isRebuffering || this._isDisposed) {
580
+ return;
581
+ }
582
+ this._isRebuffering = true;
583
+ this._speedUpdateCanceller.cancel();
584
+ log.info("Init: Pause playback to build buffer");
585
+ this._playbackObserver.setPlaybackRate(0);
586
+ }
587
+
588
+ /**
589
+ * If in a rebuffering phase (during which the playback rate is forced to
590
+ * `0`), exit that phase to apply the wanted playback rate instead.
591
+ *
592
+ * Do nothing if not in a rebuffering phase.
593
+ */
594
+ public stopRebuffering() {
595
+ if (!this._isRebuffering || this._isDisposed) {
596
+ return;
597
+ }
598
+ this._isRebuffering = false;
599
+ this._speedUpdateCanceller = new TaskCanceller();
600
+ this._updateSpeed();
601
+ }
602
+
603
+ /**
604
+ * The `PlaybackRateUpdater` allocate resources to for example listen to
605
+ * wanted speed changes and react to it.
606
+ *
607
+ * Consequently, you should call the `dispose` method, when you don't want the
608
+ * `PlaybackRateUpdater` to have an effect anymore.
609
+ */
610
+ public dispose() {
611
+ this._speedUpdateCanceller.cancel();
612
+ this._isDisposed = true;
613
+ }
614
+
615
+ private _updateSpeed() {
616
+ this._speed.onUpdate((lastSpeed) => {
617
+ log.info("Init: Resume playback speed", lastSpeed);
618
+ this._playbackObserver.setPlaybackRate(lastSpeed);
619
+ }, { clearSignal: this._speedUpdateCanceller.signal, emitCurrentValue: true });
620
+ }
621
+ }
@@ -1,70 +0,0 @@
1
- /**
2
- * Copyright 2015 CANAL+ Group
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- import {
18
- defer as observableDefer,
19
- distinctUntilChanged,
20
- map,
21
- Observable,
22
- of as observableOf,
23
- startWith,
24
- switchMap,
25
- tap,
26
- } from "rxjs";
27
- import log from "../../log";
28
- import { IReadOnlySharedReference } from "../../utils/reference";
29
- import { IPlaybackObservation } from "../api";
30
-
31
- export interface IPlaybackRateOptions { pauseWhenRebuffering? : boolean }
32
-
33
- /**
34
- * Manage playback speed.
35
- * Set playback rate set by the user, pause playback when the player appear to
36
- * rebuffering and restore the speed once it appears to exit rebuffering status.
37
- *
38
- * @param {HTMLMediaElement} mediaElement
39
- * @param {Observable} speed - last speed set by the user
40
- * @param {Observable} observation$ - Current playback conditions
41
- * @returns {Observable}
42
- */
43
- export default function updatePlaybackRate(
44
- mediaElement : HTMLMediaElement,
45
- speed : IReadOnlySharedReference<number>,
46
- observation$ : Observable<IPlaybackObservation>
47
- ) : Observable<number> {
48
- const forcePause$ = observation$
49
- .pipe(
50
- map((observation) => observation.rebuffering !== null),
51
- startWith(false),
52
- distinctUntilChanged()
53
- );
54
-
55
- return forcePause$
56
- .pipe(switchMap(shouldForcePause => {
57
- if (shouldForcePause) {
58
- return observableDefer(() => {
59
- log.info("Init: Pause playback to build buffer");
60
- mediaElement.playbackRate = 0;
61
- return observableOf(0);
62
- });
63
- }
64
- return speed.asObservable()
65
- .pipe(tap((lastSpeed) => {
66
- log.info("Init: Resume playback speed", lastSpeed);
67
- mediaElement.playbackRate = lastSpeed;
68
- }));
69
- }));
70
- }
@@ -342,7 +342,7 @@ const DEFAULT_CONFIG = {
342
342
  * small enough so this (arguably rare) situation won't lead to too much
343
343
  * waiting time.
344
344
  */
345
- FORCE_DISCONTINUITY_SEEK_DELAY: 3000,
345
+ FORCE_DISCONTINUITY_SEEK_DELAY: 5000,
346
346
 
347
347
  /**
348
348
  * Ratio used to know if an already loaded segment should be re-buffered.
@@ -54,14 +54,7 @@ export default function applyExtent(
54
54
  secondExtent[2] === "%" ||
55
55
  secondExtent[2] === "em")
56
56
  {
57
- const toNum = Number(secondExtent[1]);
58
- if (secondExtent[2] === "%" && !isNaN(toNum) &&
59
- (toNum < 0 || toNum > 100))
60
- {
61
- element.style.width = "80%";
62
- } else {
63
- element.style.height = secondExtent[1] + secondExtent[2];
64
- }
57
+ element.style.height = secondExtent[1] + secondExtent[2];
65
58
  } else if (secondExtent[2] === "c") {
66
59
  addClassName(element, "proportional-style");
67
60
  element.setAttribute("data-proportional-height", secondExtent[1]);
@@ -53,15 +53,7 @@ export default function applyOrigin(
53
53
  secondOrigin[2] === "%" ||
54
54
  secondOrigin[2] === "em")
55
55
  {
56
- const toNum = Number(secondOrigin[1]);
57
- if (secondOrigin[2] === "%" && !isNaN(toNum) &&
58
- (toNum < 0 || toNum > 100))
59
- {
60
- element.style.bottom = "5%";
61
- element.style.left = "10%";
62
- } else {
63
- element.style.top = secondOrigin[1] + secondOrigin[2];
64
- }
56
+ element.style.top = secondOrigin[1] + secondOrigin[2];
65
57
  } else if (secondOrigin[2] === "c") {
66
58
  addClassName(element, "proportional-style");
67
59
  element.setAttribute("data-proportional-top", secondOrigin[1]);