rx-player 3.30.0-dev.2023022200 → 3.30.0

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 (39) hide show
  1. package/.eslintrc.js +8 -0
  2. package/CHANGELOG.md +4 -1
  3. package/VERSION +1 -1
  4. package/dist/_esm5.processed/compat/browser_detection.d.ts +23 -12
  5. package/dist/_esm5.processed/compat/browser_detection.js +80 -38
  6. package/dist/_esm5.processed/compat/can_reuse_media_keys.js +2 -2
  7. package/dist/_esm5.processed/config.d.ts +2 -0
  8. package/dist/_esm5.processed/core/api/debug/buffer_size_graph.js +0 -1
  9. package/dist/_esm5.processed/core/api/public_api.js +3 -3
  10. package/dist/_esm5.processed/core/decrypt/__tests__/__global__/utils.d.ts +27 -8
  11. package/dist/_esm5.processed/core/decrypt/__tests__/__global__/utils.js +28 -7
  12. package/dist/_esm5.processed/core/decrypt/find_key_system.js +33 -24
  13. package/dist/_esm5.processed/core/decrypt/session_events_listener.js +27 -13
  14. package/dist/_esm5.processed/core/fetchers/utils/schedule_request.js +13 -4
  15. package/dist/_esm5.processed/core/init/utils/media_duration_updater.js +1 -1
  16. package/dist/_esm5.processed/core/init/utils/rebuffering_controller.js +1 -1
  17. package/dist/_esm5.processed/core/stream/adaptation/adaptation_stream.js +15 -5
  18. package/dist/_esm5.processed/default_config.d.ts +16 -0
  19. package/dist/_esm5.processed/default_config.js +19 -0
  20. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +5 -2
  21. package/dist/_esm5.processed/transports/dash/add_segment_integrity_checks_to_loader.js +5 -2
  22. package/dist/rx-player.js +162 -72
  23. package/dist/rx-player.min.js +1 -1
  24. package/package.json +2 -1
  25. package/sonar-project.properties +1 -1
  26. package/src/compat/browser_detection.ts +99 -52
  27. package/src/compat/can_reuse_media_keys.ts +5 -2
  28. package/src/core/api/debug/buffer_size_graph.ts +0 -1
  29. package/src/core/api/public_api.ts +3 -3
  30. package/src/core/decrypt/__tests__/__global__/utils.ts +61 -40
  31. package/src/core/decrypt/find_key_system.ts +36 -35
  32. package/src/core/decrypt/session_events_listener.ts +28 -15
  33. package/src/core/fetchers/utils/schedule_request.ts +14 -4
  34. package/src/core/init/utils/media_duration_updater.ts +1 -1
  35. package/src/core/init/utils/rebuffering_controller.ts +1 -1
  36. package/src/core/stream/adaptation/adaptation_stream.ts +18 -8
  37. package/src/default_config.ts +30 -9
  38. package/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +4 -1
  39. package/src/transports/dash/add_segment_integrity_checks_to_loader.ts +5 -2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.30.0-dev.2023022200",
4
+ "version": "3.30.0",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -97,6 +97,7 @@
97
97
  "core-js": "3.28.0",
98
98
  "esbuild": "0.17.10",
99
99
  "eslint": "8.34.0",
100
+ "eslint-plugin-ban": "1.6.0",
100
101
  "eslint-plugin-import": "2.27.5",
101
102
  "eslint-plugin-jsdoc": "40.0.0",
102
103
  "eslint-plugin-react": "7.32.2",
@@ -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.30.0-dev.2023022200
4
+ sonar.projectVersion=3.30.0
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
@@ -24,71 +24,118 @@ interface IIE11Document extends Document {
24
24
  documentMode? : unknown;
25
25
  }
26
26
 
27
- // true on IE11
28
- // false on Edge and other IEs/browsers.
29
- const isIE11 : boolean =
30
- !isNode &&
31
- typeof (window as IIE11WindowObject).MSInputMethodContext !== "undefined" &&
32
- typeof (document as IIE11Document).documentMode !== "undefined";
33
-
34
- // true for IE / Edge
35
- const isIEOrEdge : boolean = isNode ?
36
- false :
37
- navigator.appName === "Microsoft Internet Explorer" ||
38
- navigator.appName === "Netscape" &&
39
- /(Trident|Edge)\//.test(navigator.userAgent);
40
-
41
- const isEdgeChromium: boolean = !isNode &&
42
- navigator.userAgent.toLowerCase().indexOf("edg/") !== -1;
43
-
44
- const isFirefox : boolean = !isNode &&
45
- navigator.userAgent.toLowerCase().indexOf("firefox") !== -1;
46
-
47
- const isSamsungBrowser : boolean = !isNode &&
48
- /SamsungBrowser/.test(navigator.userAgent);
49
-
50
- const isTizen : boolean = !isNode &&
51
- /Tizen/.test(navigator.userAgent);
52
-
53
- const isWebOs : boolean = !isNode &&
54
- navigator.userAgent.indexOf("Web0S") >= 0;
55
-
56
- // Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/
57
- // Note: even that page doesn't correspond to what we've actually seen in the
58
- // wild
59
- const isWebOs2021 : boolean = isWebOs &&
60
- (
61
- /[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) ||
62
- /[Cc]hr[o0]me\/79/.test(navigator.userAgent)
63
- );
64
- const isWebOs2022 : boolean = isWebOs &&
65
- (
66
- /[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) ||
67
- /[Cc]hr[o0]me\/87/.test(navigator.userAgent)
68
- );
27
+ /** Edge Chromium, regardless of the device */
28
+ let isEdgeChromium = false;
69
29
 
70
- interface ISafariWindowObject extends Window {
71
- safari? : { pushNotification? : { toString() : string } };
72
- }
30
+ /** IE11, regardless of the device */
31
+ let isIE11 = false;
32
+
33
+ /** IE11 or Edge __Legacy__ (not Edge Chromium), regardless of the device */
34
+ let isIEOrEdge = false;
35
+
36
+ /** Firefox, regardless of the device */
37
+ let isFirefox = false;
73
38
 
74
39
  /** `true` on Safari on a PC platform (i.e. not iPhone / iPad etc.) */
75
- const isSafariDesktop : boolean =
76
- !isNode && (
40
+ let isSafariDesktop = false;
41
+
42
+ /** `true` on Safari on an iPhone, iPad & iPod platform */
43
+ let isSafariMobile = false;
44
+
45
+ /** Samsung's own browser application */
46
+ let isSamsungBrowser = false;
47
+
48
+ /** `true` on devices where Tizen is the OS (e.g. Samsung TVs). */
49
+ let isTizen = false;
50
+
51
+ /** `true` on devices where WebOS is the OS (e.g. LG TVs). */
52
+ let isWebOs = false;
53
+
54
+ /** `true` specifically for WebOS 2021 version. */
55
+ let isWebOs2021 = false;
56
+
57
+ /** `true` specifically for WebOS 2022 version. */
58
+ let isWebOs2022 = false;
59
+
60
+ /** `true` for Panasonic devices. */
61
+ let isPanasonic = false;
62
+
63
+ ((function findCurrentBrowser() : void {
64
+ if (isNode) {
65
+ return ;
66
+ }
67
+
68
+ // 1 - Find out browser between IE/Edge Legacy/Edge Chromium/Firefox/Safari
69
+
70
+ if (typeof (window as IIE11WindowObject).MSInputMethodContext !== "undefined" &&
71
+ typeof (document as IIE11Document).documentMode !== "undefined")
72
+ {
73
+ isIE11 = true;
74
+ isIEOrEdge = true;
75
+ } else if (
76
+ navigator.appName === "Microsoft Internet Explorer" ||
77
+ navigator.appName === "Netscape" &&
78
+ /(Trident|Edge)\//.test(navigator.userAgent)
79
+ ) {
80
+ isIEOrEdge = true;
81
+ } else if (navigator.userAgent.toLowerCase().indexOf("edg/") !== -1) {
82
+ isEdgeChromium = true;
83
+ } else if (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1) {
84
+ isFirefox = true;
85
+ } else if (typeof navigator.platform === "string" &&
86
+ /iPad|iPhone|iPod/.test(navigator.platform))
87
+ {
88
+ isSafariMobile = true;
89
+ } else if (
77
90
  Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") >= 0 ||
78
91
  (window as ISafariWindowObject).safari?.pushNotification?.toString() ===
79
92
  "[object SafariRemoteNotification]"
80
- );
93
+ ) {
94
+ isSafariDesktop = true;
95
+ }
81
96
 
82
- /** `true` on Safari on an iPhone, iPad & iPod platform */
83
- const isSafariMobile : boolean = !isNode &&
84
- typeof navigator.platform === "string" &&
85
- /iPad|iPhone|iPod/.test(navigator.platform);
97
+ // 2 - Find out specific device/platform information
98
+
99
+ // Samsung browser e.g. on Android
100
+ if (/SamsungBrowser/.test(navigator.userAgent)) {
101
+ isSamsungBrowser = true;
102
+ }
103
+
104
+ if (/Tizen/.test(navigator.userAgent)) {
105
+ isTizen = true;
106
+
107
+ // Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/
108
+ // Note: even that page doesn't correspond to what we've actually seen in the
109
+ // wild
110
+ } else if (/[Ww]eb[O0]S/.test(navigator.userAgent)) {
111
+ isWebOs = true;
112
+
113
+ if (
114
+ /[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) ||
115
+ /[Cc]hr[o0]me\/87/.test(navigator.userAgent)
116
+ ) {
117
+ isWebOs2022 = true;
118
+ } else if (
119
+ /[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) ||
120
+ /[Cc]hr[o0]me\/79/.test(navigator.userAgent)
121
+ ) {
122
+ isWebOs2021 = true;
123
+ }
124
+ } else if (/[Pp]anasonic/.test(navigator.userAgent)) {
125
+ isPanasonic = true;
126
+ }
127
+ })());
128
+
129
+ interface ISafariWindowObject extends Window {
130
+ safari? : { pushNotification? : { toString() : string } };
131
+ }
86
132
 
87
133
  export {
88
134
  isEdgeChromium,
89
135
  isIE11,
90
136
  isIEOrEdge,
91
137
  isFirefox,
138
+ isPanasonic,
92
139
  isSafariDesktop,
93
140
  isSafariMobile,
94
141
  isSamsungBrowser,
@@ -1,4 +1,7 @@
1
- import { isWebOs } from "./browser_detection";
1
+ import {
2
+ isPanasonic,
3
+ isWebOs,
4
+ } from "./browser_detection";
2
5
 
3
6
  /**
4
7
  * Returns `true` if a `MediaKeys` instance (the `Encrypted Media Extension`
@@ -13,5 +16,5 @@ import { isWebOs } from "./browser_detection";
13
16
  * @returns {boolean}
14
17
  */
15
18
  export default function canReuseMediaKeys() : boolean {
16
- return !isWebOs;
19
+ return !isWebOs && !isPanasonic;
17
20
  }
@@ -72,7 +72,6 @@ export default class BufferSizeGraph {
72
72
  const gridWidth = width / TIME_SAMPLES_MS;
73
73
 
74
74
  drawData();
75
- // drawGrid();
76
75
 
77
76
  /**
78
77
  * Get more appropriate maximum buffer size to put on top of the graph
@@ -364,7 +364,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
364
364
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
365
365
  videoElement.preload = "auto";
366
366
 
367
- this.version = /* PLAYER_VERSION */"3.30.0-dev.2023022200";
367
+ this.version = /* PLAYER_VERSION */"3.30.0";
368
368
  this.log = log;
369
369
  this.state = "STOPPED";
370
370
  this.videoElement = videoElement;
@@ -980,7 +980,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
980
980
  // Previous call could have performed all kind of side-effects, thus,
981
981
  // we re-check the current state associated to the RxPlayer
982
982
  if (this.state === PLAYER_STATES.ENDED && this._priv_stopAtEnd) {
983
- currentContentCanceller.cancel();
983
+ this.stop();
984
984
  }
985
985
  }, { emitCurrentValue: true, clearSignal: currentContentCanceller.signal });
986
986
 
@@ -2985,7 +2985,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2985
2985
  return mediaElementTrackChoiceManager;
2986
2986
  }
2987
2987
  }
2988
- Player.version = /* PLAYER_VERSION */"3.30.0-dev.2023022200";
2988
+ Player.version = /* PLAYER_VERSION */"3.30.0";
2989
2989
 
2990
2990
  /** Every events sent by the RxPlayer's public API. */
2991
2991
  interface IPublicAPIEvent {
@@ -40,48 +40,68 @@ import {
40
40
  import { CancellationSignal } from "../../../../utils/task_canceller";
41
41
 
42
42
  /** Default MediaKeySystemAccess configuration used by the RxPlayer. */
43
- export const defaultKSConfig = [{
44
- audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
45
- { contentType: "audio/webm;codecs=opus" } ],
46
- distinctiveIdentifier: "optional" as const,
47
- initDataTypes: ["cenc"] as const,
48
- persistentState: "optional" as const,
49
- sessionTypes: ["temporary"] as const,
50
- videoCapabilities: [ { contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
51
- { contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
52
- { contentType: "video/webm;codecs=\"vp8\"" } ],
53
- }];
43
+ export const defaultKSConfig = [
44
+ {
45
+ audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
46
+ { contentType: "audio/webm;codecs=opus" } ],
47
+ distinctiveIdentifier: "optional" as const,
48
+ initDataTypes: ["cenc"] as const,
49
+ persistentState: "optional" as const,
50
+ sessionTypes: ["temporary"] as const,
51
+ videoCapabilities: [ { contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
52
+ { contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
53
+ { contentType: "video/webm;codecs=\"vp8\"" } ],
54
+ },
55
+ {
56
+ audioCapabilities: undefined,
57
+ distinctiveIdentifier: "optional" as const,
58
+ initDataTypes: ["cenc"] as const,
59
+ persistentState: "optional" as const,
60
+ sessionTypes: ["temporary"] as const,
61
+ videoCapabilities: undefined,
62
+ },
63
+ ];
54
64
 
55
65
  /**
56
66
  * Default "com.microsoft.playready.recommendation" MediaKeySystemAccess
57
67
  * configuration used by the RxPlayer.
58
68
  */
59
- export const defaultPRRecommendationKSConfig = [{
60
- audioCapabilities: [ { robustness: "3000",
61
- contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
62
- { robustness: "3000",
63
- contentType: "audio/webm;codecs=opus" },
64
- { robustness: "2000",
65
- contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
66
- { robustness: "2000",
67
- contentType: "audio/webm;codecs=opus" } ],
68
- distinctiveIdentifier: "optional" as const,
69
- initDataTypes: ["cenc"] as const,
70
- persistentState: "optional" as const,
71
- sessionTypes: ["temporary"] as const,
72
- videoCapabilities: [ { robustness: "3000",
73
- contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
74
- { robustness: "3000",
75
- contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
76
- { robustness: "3000",
77
- contentType: "video/webm;codecs=\"vp8\"" },
78
- { robustness: "2000",
79
- contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
80
- { robustness: "2000",
81
- contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
82
- { robustness: "2000",
83
- contentType: "video/webm;codecs=\"vp8\"" } ],
84
- }];
69
+ export const defaultPRRecommendationKSConfig = [
70
+ {
71
+ audioCapabilities: [ { robustness: "3000",
72
+ contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
73
+ { robustness: "3000",
74
+ contentType: "audio/webm;codecs=opus" },
75
+ { robustness: "2000",
76
+ contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
77
+ { robustness: "2000",
78
+ contentType: "audio/webm;codecs=opus" } ],
79
+ distinctiveIdentifier: "optional" as const,
80
+ initDataTypes: ["cenc"] as const,
81
+ persistentState: "optional" as const,
82
+ sessionTypes: ["temporary"] as const,
83
+ videoCapabilities: [ { robustness: "3000",
84
+ contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
85
+ { robustness: "3000",
86
+ contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
87
+ { robustness: "3000",
88
+ contentType: "video/webm;codecs=\"vp8\"" },
89
+ { robustness: "2000",
90
+ contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
91
+ { robustness: "2000",
92
+ contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
93
+ { robustness: "2000",
94
+ contentType: "video/webm;codecs=\"vp8\"" } ],
95
+ },
96
+ {
97
+ audioCapabilities: undefined,
98
+ distinctiveIdentifier: "optional" as const,
99
+ initDataTypes: ["cenc"] as const,
100
+ persistentState: "optional" as const,
101
+ sessionTypes: ["temporary"] as const,
102
+ videoCapabilities: undefined,
103
+ },
104
+ ];
85
105
 
86
106
  /** Default Widevine MediaKeySystemAccess configuration used by the RxPlayer. */
87
107
  export const defaultWidevineConfig = (() => {
@@ -104,9 +124,10 @@ export const defaultWidevineConfig = (() => {
104
124
  { contentType: "audio/webm;codecs=opus",
105
125
  robustness } ];
106
126
  });
107
- return defaultKSConfig.map(conf => {
108
- return { ...conf, audioCapabilities, videoCapabilities };
109
- });
127
+ return [
128
+ { ...defaultKSConfig[0], audioCapabilities, videoCapabilities },
129
+ defaultKSConfig[1],
130
+ ];
110
131
  })();
111
132
 
112
133
  /**
@@ -155,7 +155,9 @@ function buildKeySystemConfigurations(
155
155
  if (keySystem.distinctiveIdentifierRequired === true) {
156
156
  distinctiveIdentifier = "required";
157
157
  }
158
- const { EME_DEFAULT_WIDEVINE_ROBUSTNESSES,
158
+ const { EME_DEFAULT_AUDIO_CODECS,
159
+ EME_DEFAULT_VIDEO_CODECS,
160
+ EME_DEFAULT_WIDEVINE_ROBUSTNESSES,
159
161
  EME_DEFAULT_PLAYREADY_ROBUSTNESSES } = config.getCurrent();
160
162
 
161
163
  // Set robustness, in order of consideration:
@@ -206,42 +208,41 @@ function buildKeySystemConfigurations(
206
208
  // https://www.w3.org/TR/encrypted-media/#get-supported-configuration-and-consent
207
209
 
208
210
  const videoCapabilities: IMediaCapability[] =
209
- flatMap(videoRobustnesses, (robustness) =>
210
- ["video/mp4;codecs=\"avc1.4d401e\"",
211
- "video/mp4;codecs=\"avc1.42e01e\"",
212
- "video/webm;codecs=\"vp8\""].map(contentType => {
213
- return robustness !== undefined ? { contentType, robustness } :
214
- { contentType };
215
- }));
211
+ flatMap(videoRobustnesses, (robustness) => {
212
+ return EME_DEFAULT_VIDEO_CODECS.map(contentType => {
213
+ return robustness === undefined ? { contentType } :
214
+ { contentType, robustness };
215
+ });
216
+ });
216
217
 
217
218
  const audioCapabilities: IMediaCapability[] =
218
- flatMap(audioRobustnesses, (robustness) =>
219
- ["audio/mp4;codecs=\"mp4a.40.2\"",
220
- "audio/webm;codecs=opus"].map(contentType => {
221
- return robustness !== undefined ? { contentType, robustness } :
222
- { contentType };
223
- }));
224
-
225
- // TODO Re-test with a set contentType but an undefined robustness on the
226
- // STBs on which this problem was found.
227
- //
228
- // add another with no {audio,video}Capabilities for some legacy browsers.
229
- // As of today's spec, this should return NotSupported but the first
230
- // candidate configuration should be good, so we should have no downside
231
- // doing that.
232
- // initDataTypes: ["cenc"],
233
- // videoCapabilities: undefined,
234
- // audioCapabilities: undefined,
235
- // distinctiveIdentifier,
236
- // persistentState,
237
- // sessionTypes,
238
-
239
- return [{ initDataTypes: ["cenc"],
240
- videoCapabilities,
241
- audioCapabilities,
242
- distinctiveIdentifier,
243
- persistentState,
244
- sessionTypes }];
219
+ flatMap(audioRobustnesses, (robustness) => {
220
+ return EME_DEFAULT_AUDIO_CODECS.map(contentType => {
221
+ return robustness === undefined ? { contentType } :
222
+ { contentType, robustness };
223
+ });
224
+ });
225
+
226
+ const wantedMediaKeySystemConfiguration : MediaKeySystemConfiguration = {
227
+ initDataTypes: ["cenc"],
228
+ videoCapabilities,
229
+ audioCapabilities,
230
+ distinctiveIdentifier,
231
+ persistentState,
232
+ sessionTypes,
233
+ };
234
+
235
+ return [
236
+ wantedMediaKeySystemConfiguration,
237
+
238
+ // Some legacy implementations have issues with `audioCapabilities` and
239
+ // `videoCapabilities`, so we're including a supplementary
240
+ // `MediaKeySystemConfiguration` without those properties.
241
+ { ...wantedMediaKeySystemConfiguration,
242
+ audioCapabilities: undefined ,
243
+ videoCapabilities: undefined,
244
+ } as unknown as MediaKeySystemConfiguration,
245
+ ];
245
246
  }
246
247
 
247
248
  /**
@@ -210,23 +210,36 @@ export default function SessionEventsListener(
210
210
  ) : Promise<BufferSource | null> {
211
211
  let timeoutId : number | undefined;
212
212
  return new Promise<BufferSource | null>((res, rej) => {
213
- log.debug("DRM: Calling `getLicense`", messageType);
214
- const getLicense = keySystemOptions.getLicense(message, messageType);
215
- const getLicenseTimeout = isNullOrUndefined(getLicenseConfig.timeout) ?
216
- 10 * 1000 :
217
- getLicenseConfig.timeout;
213
+ try {
214
+ log.debug("DRM: Calling `getLicense`", messageType);
215
+ const getLicense = keySystemOptions.getLicense(message, messageType);
216
+ const getLicenseTimeout = isNullOrUndefined(getLicenseConfig.timeout) ?
217
+ 10 * 1000 :
218
+ getLicenseConfig.timeout;
218
219
 
219
- if (getLicenseTimeout >= 0) {
220
- timeoutId = setTimeout(() => {
221
- rej(new GetLicenseTimeoutError(
222
- `"getLicense" timeout exceeded (${getLicenseTimeout} ms)`
223
- ));
224
- }, getLicenseTimeout) as unknown as number;
220
+ if (getLicenseTimeout >= 0) {
221
+ timeoutId = setTimeout(() => {
222
+ rej(new GetLicenseTimeoutError(
223
+ `"getLicense" timeout exceeded (${getLicenseTimeout} ms)`
224
+ ));
225
+ }, getLicenseTimeout) as unknown as number;
226
+ }
227
+ Promise.resolve(getLicense)
228
+ .then(clearTimeoutAndResolve, clearTimeoutAndReject);
229
+ } catch (err) {
230
+ clearTimeoutAndReject(err);
231
+ }
232
+ function clearTimeoutAndResolve(data : BufferSource | null) {
233
+ if (timeoutId !== undefined) {
234
+ clearTimeout(timeoutId);
235
+ }
236
+ res(data);
225
237
  }
226
- Promise.resolve(getLicense).then(res, rej);
227
- }).finally(() => {
228
- if (timeoutId !== undefined) {
229
- clearTimeout(timeoutId);
238
+ function clearTimeoutAndReject(err : unknown) {
239
+ if (timeoutId !== undefined) {
240
+ clearTimeout(timeoutId);
241
+ }
242
+ rej(err);
230
243
  }
231
244
  });
232
245
  }
@@ -368,18 +368,28 @@ export async function scheduleRequestWithCdns<T>(
368
368
  throw cancellationSignal.cancellationError;
369
369
  }
370
370
  if (updatedPrioritaryCdn === undefined) {
371
- return rej(prevRequestError);
371
+ return cleanAndReject(prevRequestError);
372
372
  }
373
373
  if (updatedPrioritaryCdn !== nextWantedCdn) {
374
374
  canceller.cancel();
375
375
  waitPotentialBackoffAndRequest(updatedPrioritaryCdn, prevRequestError)
376
- .then(res, rej);
376
+ .then(cleanAndResolve, cleanAndReject);
377
377
  }
378
378
  }, canceller.signal);
379
379
 
380
380
  cancellableSleep(blockedFor, canceller.signal)
381
- .then(() => requestCdn(nextWantedCdn).then(res, rej), noop);
382
- }).finally(unlinkCanceller);
381
+ .then(() => requestCdn(nextWantedCdn)
382
+ .then(cleanAndResolve, cleanAndReject), noop);
383
+
384
+ function cleanAndResolve(response : T) {
385
+ unlinkCanceller();
386
+ res(response);
387
+ }
388
+ function cleanAndReject(err : unknown) {
389
+ unlinkCanceller();
390
+ rej(err);
391
+ }
392
+ });
383
393
  }
384
394
 
385
395
  /**
@@ -209,7 +209,7 @@ function setMediaSourceDuration(
209
209
  if (maxBufferedEnd < mediaSource.duration) {
210
210
  try {
211
211
  log.info("Init: Updating duration to what is currently buffered", maxBufferedEnd);
212
- mediaSource.duration = newDuration;
212
+ mediaSource.duration = maxBufferedEnd;
213
213
  } catch (err) {
214
214
  log.warn("Duration Updater: Can't update duration on the MediaSource.",
215
215
  err instanceof Error ? err : "");
@@ -123,7 +123,7 @@ export default class RebufferingController
123
123
  playbackRateUpdater.dispose();
124
124
  });
125
125
 
126
- let prevFreezingState : { attemptTimestamp : number } | null;
126
+ let prevFreezingState : { attemptTimestamp : number } | null = null;
127
127
 
128
128
  this._playbackObserver.listen((observation) => {
129
129
  const discontinuitiesStore = this._discontinuitiesStore;
@@ -19,6 +19,8 @@ import config from "../../../config";
19
19
  import { formatError } from "../../../errors";
20
20
  import log from "../../../log";
21
21
  import { Representation } from "../../../manifest";
22
+ import cancellableSleep from "../../../utils/cancellable_sleep";
23
+ import noop from "../../../utils/noop";
22
24
  import objectAssign from "../../../utils/object_assign";
23
25
  import {
24
26
  createMappedReference,
@@ -325,19 +327,27 @@ export default function AdaptationStream<T>(
325
327
  defaultCode: "NONE",
326
328
  defaultReason: "Unknown `RepresentationStream` error",
327
329
  });
328
- if (formattedError.code === "BUFFER_FULL_ERROR") {
330
+ if (formattedError.code !== "BUFFER_FULL_ERROR") {
331
+ representationStreamCallbacks.error(err);
332
+ } else {
329
333
  const wba = wantedBufferAhead.getValue();
330
334
  const lastBufferGoalRatio = bufferGoalRatioMap.get(representation.id) ?? 1;
331
- if (lastBufferGoalRatio <= 0.25 || wba * lastBufferGoalRatio <= 2) {
335
+ // 70%, 49%, 34.3%, 24%, 16.81%, 11.76%, 8.24% and 5.76%
336
+ const newBufferGoalRatio = lastBufferGoalRatio * 0.7;
337
+ if (newBufferGoalRatio <= 0.05 || wba * newBufferGoalRatio <= 2) {
332
338
  throw formattedError;
333
339
  }
334
- bufferGoalRatioMap.set(representation.id, lastBufferGoalRatio - 0.25);
335
- return createRepresentationStream(representation,
336
- terminateCurrentStream,
337
- fastSwitchThreshold,
338
- representationStreamCallbacks);
340
+ bufferGoalRatioMap.set(representation.id, newBufferGoalRatio);
341
+
342
+ // We wait 4 seconds to let the situation evolve by itself before
343
+ // retrying loading segments with a lower buffer goal
344
+ cancellableSleep(4000, adapStreamCanceller.signal).then(() => {
345
+ return createRepresentationStream(representation,
346
+ terminateCurrentStream,
347
+ fastSwitchThreshold,
348
+ representationStreamCallbacks);
349
+ }).catch(noop);
339
350
  }
340
- representationStreamCallbacks.error(err);
341
351
  },
342
352
  terminating() {
343
353
  terminatingRepStreamCanceller.cancel();