rx-player 4.0.0-dev.2023110700 → 4.0.0-dev.2023111400

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 (102) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/core/api/option_utils.d.ts +2 -0
  4. package/dist/_esm5.processed/core/api/option_utils.js +6 -0
  5. package/dist/_esm5.processed/core/api/public_api.d.ts +2 -1
  6. package/dist/_esm5.processed/core/api/public_api.js +14 -4
  7. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/text_track_cues_store.js +54 -6
  8. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/utils.d.ts +2 -1
  9. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/utils.js +19 -2
  10. package/dist/_esm5.processed/manifest/representation.js +18 -5
  11. package/dist/_esm5.processed/parsers/manifest/dash/common/convert_supplemental_codecs.d.ts +17 -0
  12. package/dist/_esm5.processed/parsers/manifest/dash/common/convert_supplemental_codecs.js +26 -0
  13. package/dist/_esm5.processed/parsers/manifest/dash/common/flatten_overlapping_periods.js +5 -0
  14. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representations.js +29 -17
  15. package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/AdaptationSet.js +3 -0
  16. package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/Representation.js +3 -0
  17. package/dist/_esm5.processed/parsers/manifest/dash/node_parser_types.d.ts +2 -0
  18. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.js +4 -0
  19. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/Representation.js +4 -0
  20. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/types.d.ts +2 -1
  21. package/dist/_esm5.processed/parsers/manifest/types.d.ts +1 -0
  22. package/dist/commonjs/compat/patch_webkit_source_buffer.js +2 -2
  23. package/dist/commonjs/core/api/option_utils.d.ts +2 -0
  24. package/dist/commonjs/core/api/option_utils.js +6 -0
  25. package/dist/commonjs/core/api/public_api.d.ts +2 -1
  26. package/dist/commonjs/core/api/public_api.js +14 -4
  27. package/dist/commonjs/core/segment_buffers/implementations/text/html/text_track_cues_store.js +54 -6
  28. package/dist/commonjs/core/segment_buffers/implementations/text/html/utils.d.ts +2 -1
  29. package/dist/commonjs/core/segment_buffers/implementations/text/html/utils.js +19 -2
  30. package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +2 -2
  31. package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.js +3 -3
  32. package/dist/commonjs/core/stream/period/period_stream.js +2 -2
  33. package/dist/commonjs/manifest/representation.js +18 -5
  34. package/dist/commonjs/parsers/manifest/dash/common/convert_supplemental_codecs.d.ts +17 -0
  35. package/dist/commonjs/parsers/manifest/dash/common/convert_supplemental_codecs.js +30 -0
  36. package/dist/commonjs/parsers/manifest/dash/common/flatten_overlapping_periods.js +5 -0
  37. package/dist/commonjs/parsers/manifest/dash/common/parse_representations.js +29 -17
  38. package/dist/commonjs/parsers/manifest/dash/js-parser/node_parsers/AdaptationSet.js +3 -0
  39. package/dist/commonjs/parsers/manifest/dash/js-parser/node_parsers/Representation.js +3 -0
  40. package/dist/commonjs/parsers/manifest/dash/node_parser_types.d.ts +2 -0
  41. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.js +4 -0
  42. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/generators/Representation.js +4 -0
  43. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/types.d.ts +2 -1
  44. package/dist/commonjs/parsers/manifest/types.d.ts +1 -0
  45. package/dist/commonjs/utils/queue_microtask.d.ts +2 -0
  46. package/dist/commonjs/utils/queue_microtask.js +7 -0
  47. package/dist/es2017/compat/patch_webkit_source_buffer.js +2 -2
  48. package/dist/es2017/core/api/option_utils.d.ts +2 -0
  49. package/dist/es2017/core/api/option_utils.js +6 -0
  50. package/dist/es2017/core/api/public_api.d.ts +2 -1
  51. package/dist/es2017/core/api/public_api.js +13 -3
  52. package/dist/es2017/core/segment_buffers/implementations/text/html/text_track_cues_store.js +54 -6
  53. package/dist/es2017/core/segment_buffers/implementations/text/html/utils.d.ts +2 -1
  54. package/dist/es2017/core/segment_buffers/implementations/text/html/utils.js +18 -2
  55. package/dist/es2017/core/stream/adaptation/adaptation_stream.js +2 -2
  56. package/dist/es2017/core/stream/orchestrator/stream_orchestrator.js +3 -3
  57. package/dist/es2017/core/stream/period/period_stream.js +2 -2
  58. package/dist/es2017/manifest/representation.js +18 -5
  59. package/dist/es2017/parsers/manifest/dash/common/convert_supplemental_codecs.d.ts +17 -0
  60. package/dist/es2017/parsers/manifest/dash/common/convert_supplemental_codecs.js +26 -0
  61. package/dist/es2017/parsers/manifest/dash/common/flatten_overlapping_periods.js +5 -0
  62. package/dist/es2017/parsers/manifest/dash/common/parse_representations.js +29 -17
  63. package/dist/es2017/parsers/manifest/dash/js-parser/node_parsers/AdaptationSet.js +3 -0
  64. package/dist/es2017/parsers/manifest/dash/js-parser/node_parsers/Representation.js +3 -0
  65. package/dist/es2017/parsers/manifest/dash/node_parser_types.d.ts +2 -0
  66. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.js +4 -0
  67. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/generators/Representation.js +4 -0
  68. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/types.d.ts +2 -1
  69. package/dist/es2017/parsers/manifest/types.d.ts +1 -0
  70. package/dist/es2017/utils/queue_microtask.d.ts +2 -0
  71. package/dist/es2017/utils/queue_microtask.js +5 -0
  72. package/dist/mpd-parser.wasm +0 -0
  73. package/dist/rx-player.js +179 -118
  74. package/dist/rx-player.min.js +1 -1
  75. package/package.json +1 -4
  76. package/sonar-project.properties +1 -1
  77. package/src/compat/patch_webkit_source_buffer.ts +2 -2
  78. package/src/core/api/option_utils.ts +8 -0
  79. package/src/core/api/public_api.ts +15 -3
  80. package/src/core/segment_buffers/implementations/text/html/__tests__/utils.test.ts +15 -0
  81. package/src/core/segment_buffers/implementations/text/html/text_track_cues_store.ts +57 -6
  82. package/src/core/segment_buffers/implementations/text/html/utils.ts +19 -2
  83. package/src/core/stream/adaptation/adaptation_stream.ts +2 -2
  84. package/src/core/stream/orchestrator/stream_orchestrator.ts +3 -3
  85. package/src/core/stream/period/period_stream.ts +2 -2
  86. package/src/manifest/representation.ts +19 -6
  87. package/src/parsers/manifest/dash/common/__tests__/convert_supplemental_codecs.test.ts +37 -0
  88. package/src/parsers/manifest/dash/common/__tests__/flatten_overlapping_period.test.ts +20 -0
  89. package/src/parsers/manifest/dash/common/convert_supplemental_codecs.ts +32 -0
  90. package/src/parsers/manifest/dash/common/flatten_overlapping_periods.ts +5 -0
  91. package/src/parsers/manifest/dash/common/parse_representations.ts +30 -17
  92. package/src/parsers/manifest/dash/js-parser/node_parsers/AdaptationSet.ts +4 -0
  93. package/src/parsers/manifest/dash/js-parser/node_parsers/Representation.ts +4 -0
  94. package/src/parsers/manifest/dash/node_parser_types.ts +2 -0
  95. package/src/parsers/manifest/dash/wasm-parser/rs/events.rs +2 -0
  96. package/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs +2 -0
  97. package/src/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.ts +4 -0
  98. package/src/parsers/manifest/dash/wasm-parser/ts/generators/Representation.ts +4 -0
  99. package/src/parsers/manifest/dash/wasm-parser/ts/types.ts +2 -0
  100. package/src/parsers/manifest/types.ts +2 -0
  101. package/src/utils/queue_microtask.ts +7 -0
  102. package/src/typings/next-tick.d.ts +0 -23
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "4.0.0-dev.2023110700",
4
+ "version": "4.0.0-dev.2023111400",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/commonjs/index.js",
7
7
  "module": "./dist/es2017/index.js",
@@ -167,9 +167,6 @@
167
167
  "type": "git",
168
168
  "url": "git://github.com/canalplus/rx-player.git"
169
169
  },
170
- "dependencies": {
171
- "next-tick": "1.1.0"
172
- },
173
170
  "devDependencies": {
174
171
  "@babel/core": "7.23.0",
175
172
  "@babel/plugin-transform-runtime": "7.22.15",
@@ -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=4.0.0-dev.2023110700
4
+ sonar.projectVersion=4.0.0-dev.2023111400
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
@@ -14,8 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import nextTick from "next-tick";
18
17
  import EventEmitter from "../utils/event_emitter";
18
+ import queueMicrotask from "../utils/queue_microtask";
19
19
  import globalScope from "./global_scope";
20
20
  import isNode from "./is_node";
21
21
 
@@ -57,7 +57,7 @@ export default function patchWebkitSourceBuffer() : void {
57
57
 
58
58
  sourceBufferWebkitProto._emitUpdate =
59
59
  function(eventName : string, val : unknown) {
60
- nextTick(() => {
60
+ queueMicrotask(() => {
61
61
  /* eslint-disable no-invalid-this */
62
62
  this.trigger(eventName, val);
63
63
  this.updating = false;
@@ -226,6 +226,8 @@ function parseConstructorOptions(
226
226
  */
227
227
  function checkReloadOptions(options?: {
228
228
  reloadAt?: { position?: number; relative?: number };
229
+ keySystems?: IKeySystemOption[];
230
+ autoPlay?: boolean;
229
231
  }): void {
230
232
  if (options === null ||
231
233
  (typeof options !== "object" && options !== undefined)) {
@@ -243,6 +245,12 @@ function checkReloadOptions(options?: {
243
245
  options?.reloadAt?.relative !== undefined) {
244
246
  throw new Error("API: reload - Invalid 'reloadAt.relative' option format.");
245
247
  }
248
+ if (!Array.isArray(options?.keySystems) && options?.keySystems !== undefined) {
249
+ throw new Error("API: reload - Invalid 'keySystems' option format.");
250
+ }
251
+ if (options?.autoPlay !== undefined && typeof options.autoPlay !== "boolean") {
252
+ throw new Error("API: reload - Invalid 'autoPlay' option format.");
253
+ }
246
254
  }
247
255
 
248
256
  /**
@@ -56,6 +56,7 @@ import {
56
56
  IBrokenRepresentationsLockContext,
57
57
  IConstructorOptions,
58
58
  IKeySystemConfigurationOutput,
59
+ IKeySystemOption,
59
60
  ILoadVideoOptions,
60
61
  ILockedAudioRepresentationsSettings,
61
62
  ILockedVideoRepresentationsSettings,
@@ -345,7 +346,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
345
346
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
346
347
  videoElement.preload = "auto";
347
348
 
348
- this.version = /* PLAYER_VERSION */"4.0.0-dev.2023110700";
349
+ this.version = /* PLAYER_VERSION */"4.0.0-dev.2023111400";
349
350
  this.log = log;
350
351
  this.state = "STOPPED";
351
352
  this.videoElement = videoElement;
@@ -471,6 +472,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
471
472
  */
472
473
  reload(reloadOpts?: {
473
474
  reloadAt?: { position?: number; relative?: number };
475
+ keySystems?: IKeySystemOption[];
474
476
  autoPlay?: boolean;
475
477
  }): void {
476
478
  const { options,
@@ -503,6 +505,13 @@ class Player extends EventEmitter<IPublicAPIEvent> {
503
505
  autoPlay = !reloadInPause;
504
506
  }
505
507
 
508
+ let keySystems : IKeySystemOption[] | undefined;
509
+ if (reloadOpts?.keySystems !== undefined) {
510
+ keySystems = reloadOpts.keySystems;
511
+ } else if (this._priv_reloadingMetadata.options?.keySystems !== undefined) {
512
+ keySystems = this._priv_reloadingMetadata.options.keySystems;
513
+ }
514
+
506
515
  const newOptions = { ...options,
507
516
  initialManifest: manifest };
508
517
  if (startAt !== undefined) {
@@ -511,6 +520,9 @@ class Player extends EventEmitter<IPublicAPIEvent> {
511
520
  if (autoPlay !== undefined) {
512
521
  newOptions.autoPlay = autoPlay;
513
522
  }
523
+ if (keySystems !== undefined) {
524
+ newOptions.keySystems = keySystems;
525
+ }
514
526
  this._priv_initializeContentPlayback(newOptions);
515
527
  }
516
528
 
@@ -520,7 +532,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
520
532
  if (features.createDebugElement === null) {
521
533
  throw new Error("Feature `DEBUG_ELEMENT` not added to the RxPlayer");
522
534
  }
523
- const canceller = new TaskCanceller() ;
535
+ const canceller = new TaskCanceller();
524
536
  features.createDebugElement(element, this, canceller.signal);
525
537
  return {
526
538
  dispose() {
@@ -2772,7 +2784,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2772
2784
  }
2773
2785
  }
2774
2786
  }
2775
- Player.version = /* PLAYER_VERSION */"4.0.0-dev.2023110700";
2787
+ Player.version = /* PLAYER_VERSION */"4.0.0-dev.2023111400";
2776
2788
 
2777
2789
  /** Every events sent by the RxPlayer's public API. */
2778
2790
  interface IPublicAPIEvent {
@@ -261,6 +261,21 @@ describe("HTML Text buffer utils - areNearlyEqual", () => {
261
261
  it("should return true if input number are equals", () => {
262
262
  expect(areNearlyEqual(5, 5)).toBe(true);
263
263
  });
264
+ it(
265
+ "should return false if input number are not nearly equals with delta parameter",
266
+ () => {
267
+ expect(areNearlyEqual(5, 5.1, 0.02)).toBe(false);
268
+ });
269
+ it(
270
+ "should return true if input number are nearly equals with delta parameter",
271
+ () => {
272
+ expect(areNearlyEqual(5, 5.01, 0.02)).toBe(true);
273
+ });
274
+ it(
275
+ "should return true if input number are equals with delta parameter",
276
+ () => {
277
+ expect(areNearlyEqual(5, 5, 0.02)).toBe(true);
278
+ });
264
279
  });
265
280
 
266
281
  describe("HTML Text buffer utils - removeCuesInfosBetween", () => {
@@ -26,6 +26,23 @@ import {
26
26
  removeCuesInfosBetween,
27
27
  } from "./utils";
28
28
 
29
+ /**
30
+ * first or last IHTMLCue in a group can have a slighlty different start
31
+ * or end time than the start or end time of the ICuesGroup due to parsing
32
+ * approximation.
33
+ * DELTA_CUES_GROUP defines the tolerance level when comparing the start/end
34
+ * of a IHTMLCue to the start/end of a ICuesGroup.
35
+ * Having this value too high may lead to have unwanted subtitle displayed
36
+ * Having this value too low may lead to have subtitles not displayed
37
+ */
38
+ const DELTA_CUES_GROUP = 1e-3;
39
+
40
+ /**
41
+ * segment_duration / RELATIVE_DELTA_RATIO = relative_delta
42
+ *
43
+ * relative_delta is the tolerance to determine if two segements are the same
44
+ */
45
+ const RELATIVE_DELTA_RATIO = 5;
29
46
  /**
30
47
  * Manage the buffer of the HTMLTextSegmentBuffer.
31
48
  * Allows to add, remove and recuperate cues at given times.
@@ -72,6 +89,19 @@ export default class TextTrackCuesStore {
72
89
  ret.push(cues[j].element);
73
90
  }
74
91
  }
92
+ // first or last IHTMLCue in a group can have a slighlty different start
93
+ // or end time than the start or end time of the ICuesGroup due to parsing
94
+ // approximation.
95
+ // Add a tolerance of 1ms to fix this issue
96
+ if (ret.length === 0 && cues.length > 0) {
97
+ for (let j = 0; j < cues.length; j++) {
98
+ if (areNearlyEqual(time, cues[j].start, DELTA_CUES_GROUP)
99
+ || areNearlyEqual(time, cues[j].end, DELTA_CUES_GROUP)
100
+ ) {
101
+ ret.push(cues[j].element);
102
+ }
103
+ }
104
+ }
75
105
  return ret;
76
106
  }
77
107
  }
@@ -163,6 +193,11 @@ export default class TextTrackCuesStore {
163
193
  insert(cues : IHTMLCue[], start : number, end : number) : void {
164
194
  const cuesBuffer = this._cuesBuffer;
165
195
  const cuesInfosToInsert = { start, end, cues };
196
+ // it's preferable to have a delta depending on the duration of the segment
197
+ // if the delta is one fifth of the length of the segment:
198
+ // a segment of [0, 2] is the "same" segment as [0, 2.1]
199
+ // but [0, 0.04] is not the "same" segement as [0,04, 0.08]
200
+ const relativeDelta = Math.abs(start - end) / RELATIVE_DELTA_RATIO;
166
201
 
167
202
  /**
168
203
  * Called when we found the index of the next cue relative to the cue we
@@ -175,7 +210,7 @@ export default class TextTrackCuesStore {
175
210
  function onIndexOfNextCueFound(indexOfNextCue : number) : void {
176
211
  const nextCue = cuesBuffer[indexOfNextCue];
177
212
  if (nextCue === undefined || // no cue
178
- areNearlyEqual(cuesInfosToInsert.end, nextCue.end)) // samey end
213
+ areNearlyEqual(cuesInfosToInsert.end, nextCue.end, relativeDelta)) // samey end
179
214
  {
180
215
  // ours: |AAAAA|
181
216
  // the current one: |BBBBB|
@@ -210,8 +245,8 @@ export default class TextTrackCuesStore {
210
245
  for (let cueIdx = 0; cueIdx < cuesBuffer.length; cueIdx++) {
211
246
  let cuesInfos = cuesBuffer[cueIdx];
212
247
  if (start < cuesInfos.end) {
213
- if (areNearlyEqual(start, cuesInfos.start)) {
214
- if (areNearlyEqual(end, cuesInfos.end)) {
248
+ if (areNearlyEqual(start, cuesInfos.start, relativeDelta)) {
249
+ if (areNearlyEqual(end, cuesInfos.end, relativeDelta)) {
215
250
  // exact same segment
216
251
  // ours: |AAAAA|
217
252
  // the current one: |BBBBB|
@@ -257,7 +292,7 @@ export default class TextTrackCuesStore {
257
292
  // - add ours before the current one
258
293
  cuesBuffer.splice(cueIdx, 0, cuesInfosToInsert);
259
294
  return;
260
- } else if (areNearlyEqual(end, cuesInfos.start)) {
295
+ } else if (areNearlyEqual(end, cuesInfos.start, relativeDelta)) {
261
296
  // our cue goes just before the current one:
262
297
  // ours: |AAAAAAA|
263
298
  // the current one: |BBBB|
@@ -268,7 +303,7 @@ export default class TextTrackCuesStore {
268
303
  cuesInfos.start = end;
269
304
  cuesBuffer.splice(cueIdx, 0, cuesInfosToInsert);
270
305
  return;
271
- } else if (areNearlyEqual(end, cuesInfos.end)) {
306
+ } else if (areNearlyEqual(end, cuesInfos.end, relativeDelta)) {
272
307
  // ours: |AAAAAAA|
273
308
  // the current one: |BBBB|
274
309
  // Result: |AAAAAAA|
@@ -297,7 +332,7 @@ export default class TextTrackCuesStore {
297
332
  }
298
333
  // else -> start > cuesInfos.start
299
334
 
300
- if (areNearlyEqual(cuesInfos.end, end)) {
335
+ if (areNearlyEqual(cuesInfos.end, end, relativeDelta)) {
301
336
  // ours: |AAAAAA|
302
337
  // the current one: |BBBBBBBB|
303
338
  // Result: |BBAAAAAA|
@@ -333,6 +368,22 @@ export default class TextTrackCuesStore {
333
368
  }
334
369
  }
335
370
  }
371
+
372
+ if (cuesBuffer.length) {
373
+ const lastCue = cuesBuffer[cuesBuffer.length - 1];
374
+ if (areNearlyEqual(lastCue.end, start, relativeDelta)) {
375
+ // Match the end of the previous cue to the start of the following one
376
+ // if they are close enough. If there is a small gap between two segments
377
+ // it can lead to having no subtitles for a short time, this is noticeable when
378
+ // two successive segments displays the same text, making it diseappear
379
+ // and reappear quickly, which gives the impression of blinking
380
+ //
381
+ // ours: |AAAAA|
382
+ // the current one: |BBBBB|...
383
+ // Result: |BBBBBBBAAAAA|
384
+ lastCue.end = start;
385
+ }
386
+ }
336
387
  // no cues group has the end after our current start.
337
388
  // These cues should be the last one
338
389
  cuesBuffer.push(cuesInfosToInsert);
@@ -50,6 +50,21 @@ import {
50
50
  * Setting a value too high might lead to two segments targeting different times
51
51
  * to be wrongly believed to target the same time. In worst case scenarios, this
52
52
  * could lead to wanted text tracks being removed.
53
+ *
54
+ * When comparing 2 segments s1 and s2, you may want to take into account the duration
55
+ * of the segments:
56
+ * - if s1 is [0, 2] and s2 is [0, 2.1] s1 and s2 can be considered as nearly equal as
57
+ * there is a relative difference of: (2.1-2) / 2 = 5%;
58
+ * Formula: (end_s1 - end_s2) / duration_s2 = relative_difference
59
+ * - if s1 is [0, 0.04] and s2 is [0.04, 0.08] s1 and s2 may not considered as nearly
60
+ * equal as there is a relative difference of: (0.04-0.08) / 0.04 = 100%
61
+ *
62
+ * To compare relatively to the duration of a segment you can provide and additional
63
+ * parameter "delta" that remplace MAX_DELTA_BUFFER_TIME.
64
+ * If parameter "delta" is higher than MAX_DELTA_BUFFER_TIME, MAX_DELTA_BUFFER_TIME
65
+ * is used instead of delta. This ensure that segments are nearly equal when comparing
66
+ * relatively AND absolutely.
67
+ *
53
68
  * @type Number
54
69
  */
55
70
  const MAX_DELTA_BUFFER_TIME = 0.2;
@@ -58,10 +73,12 @@ const MAX_DELTA_BUFFER_TIME = 0.2;
58
73
  * @see MAX_DELTA_BUFFER_TIME
59
74
  * @param {Number} a
60
75
  * @param {Number} b
76
+ * @param {Number} delta
61
77
  * @returns {Boolean}
62
78
  */
63
- export function areNearlyEqual(a : number, b : number) : boolean {
64
- return Math.abs(a - b) <= MAX_DELTA_BUFFER_TIME;
79
+ export function areNearlyEqual(
80
+ a : number, b : number, delta: number = MAX_DELTA_BUFFER_TIME) : boolean {
81
+ return Math.abs(a - b) <= Math.min(delta, MAX_DELTA_BUFFER_TIME);
65
82
  }
66
83
 
67
84
  /**
@@ -1,4 +1,3 @@
1
- import nextTick from "next-tick";
2
1
  import config from "../../../config";
3
2
  import { formatError } from "../../../errors";
4
3
  import log from "../../../log";
@@ -7,6 +6,7 @@ import assertUnreachable from "../../../utils/assert_unreachable";
7
6
  import cancellableSleep from "../../../utils/cancellable_sleep";
8
7
  import noop from "../../../utils/noop";
9
8
  import objectAssign from "../../../utils/object_assign";
9
+ import queueMicrotask from "../../../utils/queue_microtask";
10
10
  import SharedReference, {
11
11
  createMappedReference,
12
12
  IReadOnlySharedReference,
@@ -192,7 +192,7 @@ export default function AdaptationStream(
192
192
  // conditions where the inner logic would be called synchronously before
193
193
  // the next observation (which may reflect very different playback conditions)
194
194
  // is actually received.
195
- return nextTick(() => {
195
+ return queueMicrotask(() => {
196
196
  playbackObserver.listen(() => {
197
197
  if (fnCancelSignal.isCancelled()) {
198
198
  return;
@@ -14,7 +14,6 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import nextTick from "next-tick";
18
17
  import config from "../../../config";
19
18
  import { MediaError } from "../../../errors";
20
19
  import log from "../../../log";
@@ -22,6 +21,7 @@ import Manifest, {
22
21
  IDecipherabilityUpdateElement,
23
22
  Period,
24
23
  } from "../../../manifest";
24
+ import queueMicrotask from "../../../utils/queue_microtask";
25
25
  import {
26
26
  createMappedReference,
27
27
  IReadOnlySharedReference,
@@ -351,7 +351,7 @@ export default function StreamOrchestrator(
351
351
  // Schedule micro task before checking the last playback observation
352
352
  // to reduce the risk of race conditions where the next observation
353
353
  // was going to be emitted synchronously.
354
- nextTick(() => {
354
+ queueMicrotask(() => {
355
355
  if (orchestratorCancelSignal.isCancelled()) {
356
356
  return ;
357
357
  }
@@ -554,7 +554,7 @@ export default function StreamOrchestrator(
554
554
  // conditions where the inner logic would be called synchronously before
555
555
  // the next observation (which may reflect very different playback
556
556
  // conditions) is actually received.
557
- return nextTick(() => {
557
+ return queueMicrotask(() => {
558
558
  if (innerCancelSignal.isCancelled()) {
559
559
  return;
560
560
  }
@@ -14,7 +14,6 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import nextTick from "next-tick";
18
17
  import config from "../../../config";
19
18
  import {
20
19
  formatError,
@@ -26,6 +25,7 @@ import {
26
25
  Period,
27
26
  } from "../../../manifest";
28
27
  import objectAssign from "../../../utils/object_assign";
28
+ import queueMicrotask from "../../../utils/queue_microtask";
29
29
  import { getLeftSizeOfBufferedTimeRange } from "../../../utils/ranges";
30
30
  import SharedReference, {
31
31
  IReadOnlySharedReference,
@@ -362,7 +362,7 @@ export default function PeriodStream(
362
362
  // is actually received.
363
363
  // It can happen when `askForMediaSourceReload` is called as a side-effect of
364
364
  // the same event that triggers the playback observation to be emitted.
365
- nextTick(() => {
365
+ queueMicrotask(() => {
366
366
  playbackObserver.listen(() => {
367
367
  if (cancelSignal.isCancelled()) {
368
368
  return;
@@ -188,15 +188,28 @@ class Representation {
188
188
  }
189
189
 
190
190
  this.cdnMetadata = args.cdnMetadata;
191
-
192
191
  this.index = args.index;
192
+
193
193
  if (opts.type === "audio" || opts.type === "video") {
194
- const mimeTypeStr = this.getMimeTypeString();
195
- const isSupported = isCodecSupported(mimeTypeStr);
196
- if (!isSupported) {
197
- log.info("Unsupported Representation", mimeTypeStr, this.id, this.bitrate);
194
+ this.isSupported = false;
195
+ // Supplemental codecs are defined as backwards-compatible codecs enhancing
196
+ // the experience of a base layer codec
197
+ if (args.supplementalCodecs !== undefined) {
198
+ const supplementalCodecMimeTypeStr =
199
+ `${this.mimeType ?? ""};codecs="${args.supplementalCodecs}"`;
200
+ if (isCodecSupported(supplementalCodecMimeTypeStr)) {
201
+ this.codec = args.supplementalCodecs;
202
+ this.isSupported = true;
203
+ }
204
+ }
205
+ if (!this.isSupported) {
206
+ const mimeTypeStr = this.getMimeTypeString();
207
+ const isSupported = isCodecSupported(mimeTypeStr);
208
+ if (!isSupported) {
209
+ log.info("Unsupported Representation", mimeTypeStr, this.id, this.bitrate);
210
+ }
211
+ this.isSupported = isSupported;
198
212
  }
199
- this.isSupported = isSupported;
200
213
  } else {
201
214
  this.isSupported = true; // TODO for other types
202
215
  }
@@ -0,0 +1,37 @@
1
+ import { convertSupplementalCodecsToRFC6381 } from "../convert_supplemental_codecs";
2
+
3
+ describe("parseSupplementalCodec", () => {
4
+ it("should return the codec unchanged if there is only one codec", () => {
5
+ expect(convertSupplementalCodecsToRFC6381("avc1.4d400d"))
6
+ .toEqual("avc1.4d400d");
7
+ });
8
+ it("should trim starting and ending whitespace", () => {
9
+ expect(
10
+ convertSupplementalCodecsToRFC6381(" avc1.4d400d "))
11
+ .toEqual("avc1.4d400d");
12
+ });
13
+ it("should return comma-separated list if input is whitespace-separated", () => {
14
+ expect(
15
+ convertSupplementalCodecsToRFC6381("avc1.4d400d avc1.4d4015"))
16
+ .toEqual("avc1.4d400d, avc1.4d4015");
17
+ });
18
+ it("should return comma-separated value if input is already comma-separated", () => {
19
+ expect(
20
+ convertSupplementalCodecsToRFC6381("avc1.4d400d, avc1.4d4015"))
21
+ .toEqual("avc1.4d400d, avc1.4d4015");
22
+ });
23
+
24
+ it("should return comma-separated value if input as missplaced whitespace", () => {
25
+ expect(
26
+ convertSupplementalCodecsToRFC6381("avc1.4d400d , avc1.4d4015 "))
27
+ .toEqual("avc1.4d400d, avc1.4d4015");
28
+ });
29
+
30
+ it(`should return comma-separated value if input is mix of comma and
31
+ whitespace separated list`
32
+ , () => {
33
+ expect(
34
+ convertSupplementalCodecsToRFC6381("avc1.4d400d avc1.4d4015, avc1.4d401f"))
35
+ .toEqual("avc1.4d400d, avc1.4d4015, avc1.4d401f");
36
+ });
37
+ });
@@ -139,4 +139,24 @@ describe("flattenOverlappingPeriods", function() {
139
139
  expect(mockLog).toHaveBeenCalledTimes(99);
140
140
  mockLog.mockRestore();
141
141
  });
142
+
143
+ // [ Period 1 ][ Period 2 ] ------> [ Period 3 ]
144
+ // [ Period 3 ]
145
+ it("should handle when a Period overlaps all previous periods", () => {
146
+ const mockLog = jest.spyOn(log, "warn").mockImplementation(jest.fn());
147
+
148
+ const periods = [
149
+ { id: "1", start: 40, duration: 20, adaptations: {} },
150
+ { id: "2", start: 60, duration: 20, adaptations: {} },
151
+ { id: "3", start: 20, duration: 100, adaptations: {} },
152
+ ];
153
+
154
+ const flattenPeriods = flattenOverlappingPeriods(periods);
155
+ expect(flattenPeriods.length).toBe(1);
156
+ expect(flattenPeriods[0].start).toBe(20);
157
+ expect(flattenPeriods[0].duration).toBe(100);
158
+ expect(flattenPeriods[0].id).toBe("3");
159
+ expect(mockLog).toHaveBeenCalledTimes(2);
160
+ mockLog.mockRestore();
161
+ });
142
162
  });
@@ -0,0 +1,32 @@
1
+ import isNonEmptyString from "../../../../utils/is_non_empty_string";
2
+
3
+ const supplementalCodecSeparator = /[, ]+/g;
4
+ /**
5
+ * Converts SCTE 214 supplemental codec string into RFC4281 codec string
6
+ *
7
+ * The returned value is a codec string respecting RFC6381
8
+ *
9
+ * SCTE 214 defines supplemental codecs as a whitespace-separated multiple list of
10
+ * codec strings
11
+ *
12
+ * RFC6381 defines codecs as a comma-separated list of codec strings.
13
+ *
14
+ * This two syntax differs and this parser is used to convert SCTE214
15
+ * to be compliant with what MSE APIs expect
16
+ *
17
+ * @param {string} val - The codec string to parse
18
+ * @returns { Array.<string | undefined | null>}
19
+ */
20
+ export function convertSupplementalCodecsToRFC6381(
21
+ val: string
22
+ ) : string {
23
+
24
+ if (isNonEmptyString(val)) {
25
+ return val
26
+ .trim()
27
+ .replace(supplementalCodecSeparator, ", ");
28
+ }
29
+ return "";
30
+ }
31
+
32
+
@@ -73,6 +73,11 @@ export default function flattenOverlappingPeriods(
73
73
  // `lastFlattenedPeriod` has now a negative or `0` duration.
74
74
  // Remove it, consider the next Period in its place, and re-start the loop.
75
75
  flattenedPeriods.pop();
76
+ if (flattenedPeriods.length === 0) {
77
+ // There's no remaining Period to compare to `parsedPeriod`
78
+ break;
79
+ }
80
+ // Take the previous Period as reference and compare it now to `parsedPeriod`
76
81
  lastFlattenedPeriod = flattenedPeriods[flattenedPeriods.length - 1];
77
82
  }
78
83
  }
@@ -29,6 +29,7 @@ import {
29
29
  IScheme,
30
30
  IContentProtectionIntermediateRepresentation,
31
31
  } from "../node_parser_types";
32
+ import { convertSupplementalCodecsToRFC6381 } from "./convert_supplemental_codecs";
32
33
  import { getWEBMHDRInformation } from "./get_hdr_information";
33
34
  import parseRepresentationIndex, {
34
35
  IRepresentationIndexContext,
@@ -120,19 +121,19 @@ export default function parseRepresentations(
120
121
  const parsedRepresentations : IParsedRepresentation[] = [];
121
122
  for (const representation of representationsIR) {
122
123
  // Compute Representation ID
123
- let representationID = representation.attributes.id != null ?
124
+ let representationID = representation.attributes.id !== undefined ?
124
125
  representation.attributes.id :
125
126
  (String(representation.attributes.bitrate) +
126
- (representation.attributes.height != null ?
127
+ (representation.attributes.height !== undefined ?
127
128
  (`-${representation.attributes.height}`) :
128
129
  "") +
129
- (representation.attributes.width != null ?
130
+ (representation.attributes.width !== undefined ?
130
131
  (`-${representation.attributes.width}`) :
131
132
  "") +
132
- (representation.attributes.mimeType != null ?
133
+ (representation.attributes.mimeType !== undefined ?
133
134
  (`-${representation.attributes.mimeType}`) :
134
135
  "") +
135
- (representation.attributes.codecs != null ?
136
+ (representation.attributes.codecs !== undefined ?
136
137
  (`-${representation.attributes.codecs}`) :
137
138
  ""));
138
139
 
@@ -167,7 +168,7 @@ export default function parseRepresentations(
167
168
 
168
169
  // Find bitrate
169
170
  let representationBitrate : number;
170
- if (representation.attributes.bitrate == null) {
171
+ if (representation.attributes.bitrate === undefined) {
171
172
  log.warn("DASH: No usable bitrate found in the Representation.");
172
173
  representationBitrate = 0;
173
174
  } else {
@@ -204,40 +205,52 @@ export default function parseRepresentations(
204
205
 
205
206
  // Add optional attributes
206
207
  let codecs : string|undefined;
207
- if (representation.attributes.codecs != null) {
208
+ if (representation.attributes.codecs !== undefined) {
208
209
  codecs = representation.attributes.codecs;
209
- } else if (adaptation.attributes.codecs != null) {
210
+ } else if (adaptation.attributes.codecs !== undefined) {
210
211
  codecs = adaptation.attributes.codecs;
211
212
  }
212
- if (codecs != null) {
213
+ if (codecs !== undefined) {
213
214
  codecs = codecs === "mp4a.40.02" ? "mp4a.40.2" : codecs;
214
215
  parsedRepresentation.codecs = codecs;
215
216
  }
216
- if (representation.attributes.frameRate != null) {
217
+
218
+ let supplementalCodecs: string | undefined;
219
+ if (representation.attributes.supplementalCodecs !== undefined) {
220
+ supplementalCodecs = representation.attributes.supplementalCodecs;
221
+ } else if (adaptation.attributes.supplementalCodecs !== undefined) {
222
+ supplementalCodecs = adaptation.attributes.supplementalCodecs;
223
+ }
224
+ if (supplementalCodecs !== undefined) {
225
+ parsedRepresentation.supplementalCodecs =
226
+ convertSupplementalCodecsToRFC6381(supplementalCodecs);
227
+ }
228
+
229
+ if (representation.attributes.frameRate !== undefined) {
217
230
  parsedRepresentation.frameRate =
218
231
  representation.attributes.frameRate;
219
- } else if (adaptation.attributes.frameRate != null) {
232
+ } else if (adaptation.attributes.frameRate !== undefined) {
220
233
  parsedRepresentation.frameRate =
221
234
  adaptation.attributes.frameRate;
222
235
  }
223
- if (representation.attributes.height != null) {
236
+ if (representation.attributes.height !== undefined) {
224
237
  parsedRepresentation.height =
225
238
  representation.attributes.height;
226
- } else if (adaptation.attributes.height != null) {
239
+ } else if (adaptation.attributes.height !== undefined) {
227
240
  parsedRepresentation.height =
228
241
  adaptation.attributes.height;
229
242
  }
230
- if (representation.attributes.mimeType != null) {
243
+ if (representation.attributes.mimeType !== undefined) {
231
244
  parsedRepresentation.mimeType =
232
245
  representation.attributes.mimeType;
233
- } else if (adaptation.attributes.mimeType != null) {
246
+ } else if (adaptation.attributes.mimeType !== undefined) {
234
247
  parsedRepresentation.mimeType =
235
248
  adaptation.attributes.mimeType;
236
249
  }
237
- if (representation.attributes.width != null) {
250
+ if (representation.attributes.width !== undefined) {
238
251
  parsedRepresentation.width =
239
252
  representation.attributes.width;
240
- } else if (adaptation.attributes.width != null) {
253
+ } else if (adaptation.attributes.width !== undefined) {
241
254
  parsedRepresentation.width =
242
255
  adaptation.attributes.width;
243
256
  }
@@ -300,6 +300,10 @@ function parseAdaptationSetAttributes(
300
300
  parsedAdaptation.codecs = attribute.value;
301
301
  break;
302
302
 
303
+ case "scte214:supplementalCodecs":
304
+ parsedAdaptation.supplementalCodecs = attribute.value;
305
+ break;
306
+
303
307
  case "codingDependency":
304
308
  parseValue(attribute.value, { asKey: "codingDependency",
305
309
  parser: parseBoolean,