rx-player 4.2.0-dev.2024100200 → 4.2.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 (77) hide show
  1. package/.vscode/settings.json +9 -0
  2. package/CHANGELOG.md +8 -1
  3. package/VERSION +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  5. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  6. package/dist/commonjs/core/segment_sinks/segment_buffers_store.d.ts.map +1 -1
  7. package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.d.ts.map +1 -1
  8. package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.js +85 -8
  9. package/dist/commonjs/main_thread/api/debug/modules/general_info.js +1 -1
  10. package/dist/commonjs/main_thread/api/public_api.js +2 -2
  11. package/dist/commonjs/main_thread/decrypt/create_or_load_session.d.ts.map +1 -1
  12. package/dist/commonjs/main_thread/decrypt/create_or_load_session.js +4 -1
  13. package/dist/commonjs/main_thread/decrypt/find_key_system.d.ts.map +1 -1
  14. package/dist/commonjs/main_thread/decrypt/find_key_system.js +3 -0
  15. package/dist/commonjs/main_thread/decrypt/get_media_keys.d.ts.map +1 -1
  16. package/dist/commonjs/main_thread/decrypt/get_media_keys.js +2 -1
  17. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts +11 -1
  18. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  19. package/dist/commonjs/main_thread/init/media_source_content_initializer.js +23 -11
  20. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts +2 -1
  21. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -1
  22. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.js +9 -5
  23. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  24. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +3 -7
  25. package/dist/commonjs/main_thread/text_displayer/native/native_text_displayer.d.ts +1 -0
  26. package/dist/commonjs/main_thread/text_displayer/native/native_text_displayer.d.ts.map +1 -1
  27. package/dist/commonjs/main_thread/text_displayer/native/native_text_displayer.js +19 -16
  28. package/dist/commonjs/public_types.d.ts +54 -2
  29. package/dist/commonjs/public_types.d.ts.map +1 -1
  30. package/dist/commonjs/utils/sync_or_async.d.ts.map +1 -1
  31. package/dist/commonjs/utils/sync_or_async.js +3 -1
  32. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  33. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  34. package/dist/es2017/core/segment_sinks/segment_buffers_store.d.ts.map +1 -1
  35. package/dist/es2017/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.d.ts.map +1 -1
  36. package/dist/es2017/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.js +36 -7
  37. package/dist/es2017/main_thread/api/debug/modules/general_info.js +1 -1
  38. package/dist/es2017/main_thread/api/public_api.js +2 -2
  39. package/dist/es2017/main_thread/decrypt/create_or_load_session.d.ts.map +1 -1
  40. package/dist/es2017/main_thread/decrypt/create_or_load_session.js +4 -1
  41. package/dist/es2017/main_thread/decrypt/find_key_system.d.ts.map +1 -1
  42. package/dist/es2017/main_thread/decrypt/find_key_system.js +3 -0
  43. package/dist/es2017/main_thread/decrypt/get_media_keys.d.ts.map +1 -1
  44. package/dist/es2017/main_thread/decrypt/get_media_keys.js +2 -1
  45. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts +11 -1
  46. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  47. package/dist/es2017/main_thread/init/media_source_content_initializer.js +23 -11
  48. package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts +2 -1
  49. package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -1
  50. package/dist/es2017/main_thread/init/multi_thread_content_initializer.js +9 -5
  51. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  52. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +3 -7
  53. package/dist/es2017/main_thread/text_displayer/native/native_text_displayer.d.ts +1 -0
  54. package/dist/es2017/main_thread/text_displayer/native/native_text_displayer.d.ts.map +1 -1
  55. package/dist/es2017/main_thread/text_displayer/native/native_text_displayer.js +19 -16
  56. package/dist/es2017/public_types.d.ts +54 -2
  57. package/dist/es2017/public_types.d.ts.map +1 -1
  58. package/dist/es2017/utils/sync_or_async.d.ts.map +1 -1
  59. package/dist/es2017/utils/sync_or_async.js +3 -1
  60. package/dist/mpd-parser.wasm +0 -0
  61. package/dist/rx-player.js +60 -31
  62. package/dist/rx-player.min.js +17 -17
  63. package/package.json +1 -1
  64. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  65. package/src/core/segment_sinks/segment_buffers_store.ts +6 -2
  66. package/src/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.ts +39 -7
  67. package/src/main_thread/api/debug/modules/general_info.ts +1 -1
  68. package/src/main_thread/api/public_api.ts +2 -2
  69. package/src/main_thread/decrypt/create_or_load_session.ts +6 -1
  70. package/src/main_thread/decrypt/find_key_system.ts +3 -0
  71. package/src/main_thread/decrypt/get_media_keys.ts +1 -0
  72. package/src/main_thread/init/media_source_content_initializer.ts +50 -17
  73. package/src/main_thread/init/multi_thread_content_initializer.ts +15 -7
  74. package/src/main_thread/init/utils/initial_seek_and_play.ts +3 -7
  75. package/src/main_thread/text_displayer/native/native_text_displayer.ts +22 -18
  76. package/src/public_types.ts +54 -3
  77. package/src/utils/sync_or_async.ts +5 -3
@@ -103,7 +103,11 @@ export default class SegmentSinksStore {
103
103
  * disabled. This means that the corresponding type (e.g. audio, video etc.)
104
104
  * won't be needed when playing the current content.
105
105
  */
106
- private _initializedSegmentSinks: Partial<Record<IBufferType, SegmentSink | null>>;
106
+ private _initializedSegmentSinks: {
107
+ audio?: AudioVideoSegmentSink | undefined | null;
108
+ video?: AudioVideoSegmentSink | undefined | null;
109
+ text?: TextSegmentSink | null;
110
+ };
107
111
 
108
112
  /**
109
113
  * Callbacks called after a SourceBuffer is either created or disabled.
@@ -308,7 +312,7 @@ export default class SegmentSinksStore {
308
312
  return memorizedSegmentSink;
309
313
  }
310
314
 
311
- let segmentSink: SegmentSink;
315
+ let segmentSink: TextSegmentSink;
312
316
  if (bufferType === "text") {
313
317
  log.info("SB: Creating a new text SegmentSink");
314
318
  if (this._textInterface === null) {
@@ -14,7 +14,12 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ import { canRelyOnRequestMediaKeySystemAccess } from "../../../../compat/can_rely_on_request_media_key_system_access";
17
18
  import eme from "../../../../compat/eme";
19
+ import {
20
+ DUMMY_PLAY_READY_HEADER,
21
+ generatePlayReadyInitData,
22
+ } from "../../../../compat/generate_init_data";
18
23
  import isNullOrUndefined from "../../../../utils/is_null_or_undefined";
19
24
  import log from "../log";
20
25
  import type { ICompatibleKeySystem, IMediaConfiguration } from "../types";
@@ -57,13 +62,40 @@ export default function probeDRMInfos(
57
62
 
58
63
  return eme
59
64
  .requestMediaKeySystemAccess(type, [configuration])
60
- .then((keySystemAccess) => {
61
- result.compatibleConfiguration = keySystemAccess.getConfiguration();
62
- const status: [ProberStatus, ICompatibleKeySystem?] = [
63
- ProberStatus.Supported,
64
- result,
65
- ];
66
- return status;
65
+ .then(async (keySystemAccess) => {
66
+ if (!canRelyOnRequestMediaKeySystemAccess(type)) {
67
+ try {
68
+ const mediaKeys = await keySystemAccess.createMediaKeys();
69
+ const session = mediaKeys.createSession();
70
+ const initData = generatePlayReadyInitData(DUMMY_PLAY_READY_HEADER);
71
+ await session.generateRequest("cenc", initData);
72
+ session.close().catch(() => {
73
+ log.warn("DRM: Failed to close the dummy session");
74
+ });
75
+ result.compatibleConfiguration = keySystemAccess.getConfiguration();
76
+ const status: [ProberStatus, ICompatibleKeySystem?] = [
77
+ ProberStatus.Supported,
78
+ result,
79
+ ];
80
+ return status;
81
+ } catch (err) {
82
+ log.debug("DRM: KeySystemAccess was granted but it is not usable");
83
+
84
+ const statusError: [ProberStatus, ICompatibleKeySystem] = [
85
+ ProberStatus.NotSupported,
86
+ result,
87
+ ];
88
+ return statusError;
89
+ }
90
+ } else {
91
+ result.compatibleConfiguration = keySystemAccess.getConfiguration();
92
+
93
+ const status: [ProberStatus, ICompatibleKeySystem?] = [
94
+ ProberStatus.Supported,
95
+ result,
96
+ ];
97
+ return status;
98
+ }
67
99
  })
68
100
  .catch(() => {
69
101
  return [ProberStatus.NotSupported, result];
@@ -65,7 +65,7 @@ export default function constructDebugGeneralInfo(
65
65
  valuesLine1.push(["wo", "0"]);
66
66
  }
67
67
 
68
- const valuesLine2: Array<[string, string]> = [];
68
+ const valuesLine2: Array<[string, string]> = [["v", instance.version]];
69
69
  const ks = instance.getKeySystemConfiguration();
70
70
  if (ks !== null) {
71
71
  valuesLine2.push(["ks", ks.keySystem]);
@@ -411,7 +411,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
411
411
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
412
412
  videoElement.preload = "auto";
413
413
 
414
- this.version = /* PLAYER_VERSION */ "4.2.0-dev.2024100200";
414
+ this.version = /* PLAYER_VERSION */ "4.2.0";
415
415
  this.log = log;
416
416
  this.state = "STOPPED";
417
417
  this.videoElement = videoElement;
@@ -3330,7 +3330,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3330
3330
  }
3331
3331
  }
3332
3332
  }
3333
- Player.version = /* PLAYER_VERSION */ "4.2.0-dev.2024100200";
3333
+ Player.version = /* PLAYER_VERSION */ "4.2.0";
3334
3334
 
3335
3335
  /** Every events sent by the RxPlayer's public API. */
3336
3336
  interface IPublicAPIEvent {
@@ -82,7 +82,12 @@ export default async function createOrLoadSession(
82
82
  }
83
83
  }
84
84
 
85
- await cleanOldLoadedSessions(loadedSessionsStore, maxSessionCacheSize);
85
+ await cleanOldLoadedSessions(
86
+ loadedSessionsStore,
87
+ // Account for the next session we will be creating
88
+ // Note that `maxSessionCacheSize < 0 has special semantic (no limit)`
89
+ maxSessionCacheSize <= 0 ? maxSessionCacheSize : maxSessionCacheSize - 1,
90
+ );
86
91
  if (cancelSignal.cancellationError !== null) {
87
92
  throw cancelSignal.cancellationError; // stop here if cancelled since
88
93
  }
@@ -539,6 +539,9 @@ export async function testKeySystem(
539
539
  const session = mediaKeys.createSession();
540
540
  const initData = generatePlayReadyInitData(DUMMY_PLAY_READY_HEADER);
541
541
  await session.generateRequest("cenc", initData);
542
+ session.close().catch(() => {
543
+ log.warn("DRM: Failed to close the dummy session");
544
+ });
542
545
  } catch (err) {
543
546
  log.debug("DRM: KeySystemAccess was granted but it is not usable");
544
547
  throw err;
@@ -98,6 +98,7 @@ export default async function getMediaKeysInfos(
98
98
  const persistentSessionsStore = createPersistentSessionsStorage(options);
99
99
 
100
100
  if (
101
+ evt.value.options.reuseMediaKeys !== false &&
101
102
  canReuseMediaKeys() &&
102
103
  currentState !== null &&
103
104
  evt.type === "reuse-media-key-system-access"
@@ -92,7 +92,7 @@ import listenToMediaError from "./utils/throw_on_media_error";
92
92
  */
93
93
  export default class MediaSourceContentInitializer extends ContentInitializer {
94
94
  /** Constructor settings associated to this `MediaSourceContentInitializer`. */
95
- private _settings: IInitializeArguments;
95
+ private _initSettings: IInitializeArguments;
96
96
  /**
97
97
  * `TaskCanceller` allowing to abort everything that the
98
98
  * `MediaSourceContentInitializer` is doing.
@@ -145,7 +145,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
145
145
  */
146
146
  constructor(settings: IInitializeArguments) {
147
147
  super();
148
- this._settings = settings;
148
+ this._initSettings = settings;
149
149
  this._initCanceller = new TaskCanceller();
150
150
  this._manifest = null;
151
151
  this._decryptionCapabilities = { status: "uninitialized", value: null };
@@ -234,6 +234,10 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
234
234
  this._initCanceller.cancel();
235
235
  }
236
236
 
237
+ /**
238
+ * Callback called when an error interrupting playback arised.
239
+ * @param {*} err
240
+ */
237
241
  private _onFatalError(err: unknown) {
238
242
  if (this._initCanceller.isUsed()) {
239
243
  return;
@@ -242,6 +246,12 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
242
246
  this.trigger("error", err);
243
247
  }
244
248
 
249
+ /**
250
+ * Initialize decryption mechanisms if needed and begin creating and relying
251
+ * on the initial `MediaSourceInterface` for this content.
252
+ * @param {HTMLMediaElement|null} mediaElement
253
+ * @returns {Promise.<Object>}
254
+ */
245
255
  private _initializeMediaSourceAndDecryption(mediaElement: IMediaElement): Promise<{
246
256
  mediaSource: MainMediaSourceInterface;
247
257
  drmSystemId: string | undefined;
@@ -249,7 +259,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
249
259
  }> {
250
260
  const initCanceller = this._initCanceller;
251
261
  return createCancellablePromise(initCanceller.signal, (resolve) => {
252
- const { keySystems } = this._settings;
262
+ const { keySystems } = this._initSettings;
253
263
 
254
264
  /** Initialize decryption capabilities. */
255
265
  const { statusRef: drmInitRef, contentDecryptor } = initializeContentDecryption(
@@ -383,7 +393,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
383
393
  startAt,
384
394
  textTrackOptions,
385
395
  transport,
386
- } = this._settings;
396
+ } = this._initSettings;
387
397
  const initCanceller = this._initCanceller;
388
398
  assert(this._manifest !== null);
389
399
  let manifest: IManifest;
@@ -526,7 +536,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
526
536
  */
527
537
  private _startBufferingOnMediaSource(
528
538
  args: IBufferingMediaSettings,
529
- onReloadOrder: (reloadOrder: { position: number; autoPlay: boolean }) => void,
539
+ onReloadOrder: IReloadMediaSourceCallback,
530
540
  cancelSignal: CancellationSignal,
531
541
  ): void {
532
542
  const {
@@ -553,18 +563,10 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
553
563
  }
554
564
 
555
565
  let textDisplayerInterface: ITextDisplayerInterface | null = null;
556
- let textDisplayer: ITextDisplayer | null = null;
557
- if (
558
- this._settings.textTrackOptions.textTrackMode === "html" &&
559
- features.htmlTextDisplayer !== null
560
- ) {
561
- textDisplayer = new features.htmlTextDisplayer(
562
- mediaElement,
563
- this._settings.textTrackOptions.textTrackElement,
564
- );
565
- } else if (features.nativeTextDisplayer !== null) {
566
- textDisplayer = new features.nativeTextDisplayer(mediaElement);
567
- }
566
+ const textDisplayer = createTextDisplayer(
567
+ mediaElement,
568
+ this._initSettings.textTrackOptions,
569
+ );
568
570
  if (textDisplayer !== null) {
569
571
  const sender = new MainThreadTextDisplayerInterface(textDisplayer);
570
572
  textDisplayerInterface = sender;
@@ -1123,6 +1125,21 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
1123
1125
  }
1124
1126
  }
1125
1127
 
1128
+ function createTextDisplayer(
1129
+ mediaElement: IMediaElement,
1130
+ textTrackOptions: ITextDisplayerOptions,
1131
+ ): ITextDisplayer | null {
1132
+ if (textTrackOptions.textTrackMode === "html" && features.htmlTextDisplayer !== null) {
1133
+ return new features.htmlTextDisplayer(
1134
+ mediaElement,
1135
+ textTrackOptions.textTrackElement,
1136
+ );
1137
+ } else if (features.nativeTextDisplayer !== null) {
1138
+ return new features.nativeTextDisplayer(mediaElement);
1139
+ }
1140
+ return null;
1141
+ }
1142
+
1126
1143
  /** Arguments to give to the `InitializeOnMediaSource` function. */
1127
1144
  export interface IInitializeArguments {
1128
1145
  /** Options concerning the ABR logic. */
@@ -1324,3 +1341,19 @@ function blackListProtectionDataOnManifest(
1324
1341
  return rep.decipherable;
1325
1342
  });
1326
1343
  }
1344
+
1345
+ /**
1346
+ * Function to call when you want to "reload" the MediaSource: basically
1347
+ * restarting playback on a new MediaSource for the same content (it may
1348
+ * be for varied reasons, such as ensuring data buffers are empty, or
1349
+ * restarting after some kind of fatal error).
1350
+ * @param {Object} reloadOrder
1351
+ * @param {number} reloadOrder.position - Position in seconds at which we
1352
+ * should restart from when playback restarts.
1353
+ * @param {boolean} reloadOrder.autoPlay - If `true` we will directly play
1354
+ * once enough data is re-loaded.
1355
+ */
1356
+ type IReloadMediaSourceCallback = (reloadOrder: {
1357
+ position: number;
1358
+ autoPlay: boolean;
1359
+ }) => void;
@@ -113,12 +113,13 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
113
113
  private _currentMediaSourceCanceller: TaskCanceller;
114
114
 
115
115
  /**
116
- * Stores the resolvers and the current messageId that is sent to the web worker to receive segment sink metrics.
116
+ * Stores the resolvers and the current messageId that is sent to the web worker to
117
+ * receive segment sink metrics.
117
118
  * The purpose of collecting metrics is for monitoring and debugging.
118
119
  */
119
120
  private _segmentMetrics: {
120
121
  lastMessageId: number;
121
- resolvers: Record<number, (value: ISegmentSinkMetrics | undefined) => void>;
122
+ resolvers: Map<number, (value: ISegmentSinkMetrics | undefined) => void>;
122
123
  };
123
124
 
124
125
  /**
@@ -135,7 +136,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
135
136
  this._currentContentInfo = null;
136
137
  this._segmentMetrics = {
137
138
  lastMessageId: 0,
138
- resolvers: {},
139
+ resolvers: new Map(),
139
140
  };
140
141
  this._queuedWorkerMessages = null;
141
142
  }
@@ -1121,10 +1122,9 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
1121
1122
  if (this._currentContentInfo?.contentId !== msgData.contentId) {
1122
1123
  return;
1123
1124
  }
1124
- const resolveFn = this._segmentMetrics.resolvers[msgData.value.messageId];
1125
+ const resolveFn = this._segmentMetrics.resolvers.get(msgData.value.messageId);
1125
1126
  if (resolveFn !== undefined) {
1126
1127
  resolveFn(msgData.value.segmentSinkMetrics);
1127
- delete this._segmentMetrics.resolvers[msgData.value.messageId];
1128
1128
  } else {
1129
1129
  log.error("MTCI: Failed to send segment sink store update");
1130
1130
  }
@@ -1589,11 +1589,19 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
1589
1589
  value: { messageId },
1590
1590
  });
1591
1591
  return new Promise((resolve, reject) => {
1592
- this._segmentMetrics.resolvers[messageId] = resolve;
1593
1592
  const rejectFn = (err: CancellationError) => {
1594
- delete this._segmentMetrics.resolvers[messageId];
1593
+ cancelSignal.deregister(rejectFn);
1594
+ this._segmentMetrics.resolvers.delete(messageId);
1595
1595
  return reject(err);
1596
1596
  };
1597
+ this._segmentMetrics.resolvers.set(
1598
+ messageId,
1599
+ (value: ISegmentSinkMetrics | undefined) => {
1600
+ cancelSignal.deregister(rejectFn);
1601
+ this._segmentMetrics.resolvers.delete(messageId);
1602
+ resolve(value);
1603
+ },
1604
+ );
1597
1605
  cancelSignal.register(rejectFn);
1598
1606
  });
1599
1607
  };
@@ -121,13 +121,9 @@ export default function performInitialSeekAndPlay(
121
121
  obs.readyState < HTMLMediaElement.HAVE_CURRENT_DATA
122
122
  ) {
123
123
  /**
124
- * On browser, such as Safari, the HTMLMediaElement.duration
125
- * and HTMLMediaElement.buffered may not be initialized at readyState 1, leading
126
- * to cases where it can be equal to `Infinity`.
127
- * If so, the range in which it is possible to seek is not yet known.
128
- * To solve this, the seek should be done after readyState HAVE_CURRENT_DATA (2),
129
- * at that time the previously mentioned attributes are correctly initialized and
130
- * the range in which it is possible to seek is correctly known.
124
+ * The starting position may not be known yet.
125
+ * Postpone the seek to a moment where the starting position should be known,
126
+ * assumely it's when readyState is greater or equal to HAVE_CURRENT_DATA (2).
131
127
  * If the initiallySeekedTime is still `undefined` when the readyState is >= 2,
132
128
  * let assume that the initiallySeekedTime will never be known and continue
133
129
  * the logic without seeking.
@@ -158,24 +158,7 @@ export default class NativeTextDisplayer implements ITextDisplayer {
158
158
  public reset(): void {
159
159
  log.debug("NTD: Aborting NativeTextDisplayer");
160
160
  this._removeData(0, Infinity);
161
- const { _trackElement, _videoElement } = this;
162
-
163
- if (_trackElement !== undefined && _videoElement.hasChildNodes()) {
164
- try {
165
- _videoElement.removeChild(_trackElement);
166
- } catch (_e) {
167
- log.warn("NTD: Can't remove track element from the video");
168
- }
169
- }
170
-
171
- // Ugly trick to work-around browser bugs by refreshing its mode
172
- const oldMode = this._track.mode;
173
- this._track.mode = "disabled";
174
- this._track.mode = oldMode;
175
-
176
- if (this._trackElement !== undefined) {
177
- this._trackElement.innerHTML = "";
178
- }
161
+ this._clearTrackElement();
179
162
  }
180
163
 
181
164
  public stop(): void {
@@ -213,6 +196,27 @@ export default class NativeTextDisplayer implements ITextDisplayer {
213
196
  }
214
197
  this._buffered.remove(start, end);
215
198
  }
199
+
200
+ private _clearTrackElement(): void {
201
+ const { _trackElement, _videoElement } = this;
202
+
203
+ if (_trackElement !== undefined && _videoElement.hasChildNodes()) {
204
+ try {
205
+ _videoElement.removeChild(_trackElement);
206
+ } catch (_e) {
207
+ log.warn("NTD: Can't remove track element from the video");
208
+ }
209
+ }
210
+
211
+ // Ugly trick to work-around browser bugs by refreshing its mode
212
+ const oldMode = this._track.mode;
213
+ this._track.mode = "disabled";
214
+ this._track.mode = oldMode;
215
+
216
+ if (this._trackElement !== undefined) {
217
+ this._trackElement.innerHTML = "";
218
+ }
219
+ }
216
220
  }
217
221
 
218
222
  /** Data of chunks that should be pushed to the NativeTextDisplayer. */
@@ -559,11 +559,62 @@ export interface IKeySystemOption {
559
559
  */
560
560
  distinctiveIdentifier?: MediaKeysRequirement | undefined;
561
561
  /**
562
- * If true, all open MediaKeySession (used to decrypt the content) will be
563
- * closed when the current playback stops.
562
+ * If true, all open `MediaKeySession` (JavaScript Objects linked to the keys
563
+ * used to decrypt the content) will be closed when the current playback
564
+ * stops.
565
+ *
566
+ * By default, we keep `MediaKeySession` from previous contents (up to
567
+ * `maxSessionCacheSize` `MediaKeySession`) to speed-up playback and avoid
568
+ * round-trips to the license server if the user ever decide to go back to
569
+ * those contents or to other contents relying to the same keys.
570
+ *
571
+ * However we found that some devices poorly handle that optimization.
572
+ *
573
+ * Note that if setting that property to `true` fixes your issue, it may be
574
+ * that it's just the `maxSessionCacheSize` which is for now too high.
575
+ *
576
+ * However if your problem doesn't disappear after setting
577
+ * `closeSessionsOnStop` to `true`, you may try `reuseMediaKeys` next.
564
578
  */
565
579
  closeSessionsOnStop?: boolean;
566
-
580
+ /**
581
+ * If set to `true` or if not set, we might rely on the previous `MediaKeys`
582
+ * if a compatible one is already set on the media element, allowing to
583
+ * potentially speed-up content playback.
584
+ *
585
+ * If set to `false`, we will create a new `MediaKeys` instance (a
586
+ * JavaScript object needed to decrypt contents) if needed for that content.
587
+ *
588
+ * We noticed that reusing a previous MediaKeys had led to errors on a few
589
+ * devices. For example some smart TVs had shown errors after playing several
590
+ * encrypted contents, errors which disappeared if we renewed the
591
+ * `MediaKeys` for each content.
592
+ *
593
+ * We should already be able to detect most of those cases in the RxPlayer
594
+ * logic. However, it is still possible that we don't know yet of a device
595
+ * which also has problem with that optimization.
596
+ *
597
+ * If you have issues appearing only after playing multiple encrypted
598
+ * contents:
599
+ *
600
+ * - First, try setting the `closeSessionsOnStop` option which is less
601
+ * destructive.
602
+ *
603
+ * If it fixes your issue, it may be that it's just the number of
604
+ * `MediaKeySession` cached by the RxPlayer that is here too high.
605
+ *
606
+ * In that case you can instead update the `maxSessionCacheSize` option
607
+ * to still profit from a `MediaKeySession` cache (which avoid making
608
+ * license requests for already-played contents).
609
+ *
610
+ * If that second option doesn't seem to have an effect, you can just set
611
+ * `closeSessionsOnStop`.
612
+ *
613
+ * - If none of the precedent work-arounds work however, you can try setting
614
+ * `reuseMediaKeys` to `false`. If it fixes your problem, please open an
615
+ * RxPlayer issue so we can add your device to our list.
616
+ */
617
+ reuseMediaKeys?: boolean | undefined;
567
618
  singleLicensePer?: "content" | "periods" | "init-data";
568
619
  /**
569
620
  * Maximum number of `MediaKeySession` that should be created on the same
@@ -78,12 +78,14 @@ const SyncOrAsync = {
78
78
  * @returns {Object}
79
79
  */
80
80
  createAsync<T>(val: Promise<T>): ISyncOrAsyncValue<T> {
81
- let ret = null;
82
- val.then((resolved) => {
81
+ let ret: T | null = null;
82
+ val.then((resolved: T) => {
83
83
  ret = resolved;
84
84
  }, noop);
85
85
  return {
86
- syncValue: ret,
86
+ get syncValue(): T | null {
87
+ return ret;
88
+ },
87
89
  getValueAsAsync() {
88
90
  return val;
89
91
  },