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
@@ -13,8 +13,8 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import nextTick from "next-tick";
17
16
  import EventEmitter from "../utils/event_emitter";
17
+ import queueMicrotask from "../utils/queue_microtask";
18
18
  import globalScope from "./global_scope";
19
19
  import isNode from "./is_node";
20
20
  // TODO This is the last ugly side-effect here.
@@ -41,7 +41,7 @@ export default function patchWebkitSourceBuffer() {
41
41
  sourceBufferWebkitProto._listeners = [];
42
42
  sourceBufferWebkitProto._emitUpdate =
43
43
  function (eventName, val) {
44
- nextTick(() => {
44
+ queueMicrotask(() => {
45
45
  /* eslint-disable no-invalid-this */
46
46
  this.trigger(eventName, val);
47
47
  this.updating = false;
@@ -103,6 +103,8 @@ declare function checkReloadOptions(options?: {
103
103
  position?: number;
104
104
  relative?: number;
105
105
  };
106
+ keySystems?: IKeySystemOption[];
107
+ autoPlay?: boolean;
106
108
  }): void;
107
109
  /**
108
110
  * Parse options given to loadVideo and set default options as found
@@ -139,6 +139,12 @@ function checkReloadOptions(options) {
139
139
  ((_d = options === null || options === void 0 ? void 0 : options.reloadAt) === null || _d === void 0 ? void 0 : _d.relative) !== undefined) {
140
140
  throw new Error("API: reload - Invalid 'reloadAt.relative' option format.");
141
141
  }
142
+ if (!Array.isArray(options === null || options === void 0 ? void 0 : options.keySystems) && (options === null || options === void 0 ? void 0 : options.keySystems) !== undefined) {
143
+ throw new Error("API: reload - Invalid 'keySystems' option format.");
144
+ }
145
+ if ((options === null || options === void 0 ? void 0 : options.autoPlay) !== undefined && typeof options.autoPlay !== "boolean") {
146
+ throw new Error("API: reload - Invalid 'autoPlay' option format.");
147
+ }
142
148
  }
143
149
  /**
144
150
  * Parse options given to loadVideo and set default options as found
@@ -16,7 +16,7 @@
16
16
  import { IErrorCode, IErrorType } from "../../errors";
17
17
  import { IFeature } from "../../features";
18
18
  import Manifest, { Adaptation, Representation } from "../../manifest";
19
- import { IAudioRepresentation, IAudioTrack, IAudioTrackSetting, IAvailableAudioTrack, IAvailableTextTrack, IAvailableVideoTrack, IBrokenRepresentationsLockContext, IConstructorOptions, IKeySystemConfigurationOutput, ILoadVideoOptions, ILockedAudioRepresentationsSettings, ILockedVideoRepresentationsSettings, ITrackUpdateEventPayload, IRepresentationListUpdateContext, IPeriod, IPeriodChangeEvent, IPlayerError, IPlayerState, IPositionUpdate, IStreamEvent, ITextTrack, IVideoRepresentation, ITextTrackSetting, IVideoTrack, IVideoTrackSetting } from "../../public_types";
19
+ import { IAudioRepresentation, IAudioTrack, IAudioTrackSetting, IAvailableAudioTrack, IAvailableTextTrack, IAvailableVideoTrack, IBrokenRepresentationsLockContext, IConstructorOptions, IKeySystemConfigurationOutput, IKeySystemOption, ILoadVideoOptions, ILockedAudioRepresentationsSettings, ILockedVideoRepresentationsSettings, ITrackUpdateEventPayload, IRepresentationListUpdateContext, IPeriod, IPeriodChangeEvent, IPlayerError, IPlayerState, IPositionUpdate, IStreamEvent, ITextTrack, IVideoRepresentation, ITextTrackSetting, IVideoTrack, IVideoTrackSetting } from "../../public_types";
20
20
  import EventEmitter, { IListener } from "../../utils/event_emitter";
21
21
  import Logger from "../../utils/logger";
22
22
  import { IBufferedChunk, IBufferType } from "../segment_buffers";
@@ -159,6 +159,7 @@ declare class Player extends EventEmitter<IPublicAPIEvent> {
159
159
  position?: number;
160
160
  relative?: number;
161
161
  };
162
+ keySystems?: IKeySystemOption[];
162
163
  autoPlay?: boolean;
163
164
  }): void;
164
165
  createDebugElement(element: HTMLElement): {
@@ -91,7 +91,7 @@ class Player extends EventEmitter {
91
91
  // Workaround to support Firefox autoplay on FF 42.
92
92
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
93
93
  videoElement.preload = "auto";
94
- this.version = /* PLAYER_VERSION */ "4.0.0-dev.2023110700";
94
+ this.version = /* PLAYER_VERSION */ "4.0.0-dev.2023111400";
95
95
  this.log = log;
96
96
  this.state = "STOPPED";
97
97
  this.videoElement = videoElement;
@@ -186,7 +186,7 @@ class Player extends EventEmitter {
186
186
  * @param {Object} reloadOpts
187
187
  */
188
188
  reload(reloadOpts) {
189
- var _a, _b;
189
+ var _a, _b, _c;
190
190
  const { options, manifest, reloadPosition, reloadInPause } = this._priv_reloadingMetadata;
191
191
  if (options === undefined) {
192
192
  throw new Error("API: Can't reload without having previously loaded a content.");
@@ -214,6 +214,13 @@ class Player extends EventEmitter {
214
214
  else if (reloadInPause !== undefined) {
215
215
  autoPlay = !reloadInPause;
216
216
  }
217
+ let keySystems;
218
+ if ((reloadOpts === null || reloadOpts === void 0 ? void 0 : reloadOpts.keySystems) !== undefined) {
219
+ keySystems = reloadOpts.keySystems;
220
+ }
221
+ else if (((_c = this._priv_reloadingMetadata.options) === null || _c === void 0 ? void 0 : _c.keySystems) !== undefined) {
222
+ keySystems = this._priv_reloadingMetadata.options.keySystems;
223
+ }
217
224
  const newOptions = Object.assign(Object.assign({}, options), { initialManifest: manifest });
218
225
  if (startAt !== undefined) {
219
226
  newOptions.startAt = startAt;
@@ -221,6 +228,9 @@ class Player extends EventEmitter {
221
228
  if (autoPlay !== undefined) {
222
229
  newOptions.autoPlay = autoPlay;
223
230
  }
231
+ if (keySystems !== undefined) {
232
+ newOptions.keySystems = keySystems;
233
+ }
224
234
  this._priv_initializeContentPlayback(newOptions);
225
235
  }
226
236
  createDebugElement(element) {
@@ -2097,5 +2107,5 @@ class Player extends EventEmitter {
2097
2107
  }
2098
2108
  }
2099
2109
  }
2100
- Player.version = /* PLAYER_VERSION */ "4.0.0-dev.2023110700";
2110
+ Player.version = /* PLAYER_VERSION */ "4.0.0-dev.2023111400";
2101
2111
  export default Player;
@@ -15,6 +15,22 @@
15
15
  */
16
16
  import assert from "../../../../../utils/assert";
17
17
  import { areNearlyEqual, getCuesAfter, getCuesBefore, removeCuesInfosBetween, } from "./utils";
18
+ /**
19
+ * first or last IHTMLCue in a group can have a slighlty different start
20
+ * or end time than the start or end time of the ICuesGroup due to parsing
21
+ * approximation.
22
+ * DELTA_CUES_GROUP defines the tolerance level when comparing the start/end
23
+ * of a IHTMLCue to the start/end of a ICuesGroup.
24
+ * Having this value too high may lead to have unwanted subtitle displayed
25
+ * Having this value too low may lead to have subtitles not displayed
26
+ */
27
+ const DELTA_CUES_GROUP = 1e-3;
28
+ /**
29
+ * segment_duration / RELATIVE_DELTA_RATIO = relative_delta
30
+ *
31
+ * relative_delta is the tolerance to determine if two segements are the same
32
+ */
33
+ const RELATIVE_DELTA_RATIO = 5;
18
34
  /**
19
35
  * Manage the buffer of the HTMLTextSegmentBuffer.
20
36
  * Allows to add, remove and recuperate cues at given times.
@@ -57,6 +73,18 @@ export default class TextTrackCuesStore {
57
73
  ret.push(cues[j].element);
58
74
  }
59
75
  }
76
+ // first or last IHTMLCue in a group can have a slighlty different start
77
+ // or end time than the start or end time of the ICuesGroup due to parsing
78
+ // approximation.
79
+ // Add a tolerance of 1ms to fix this issue
80
+ if (ret.length === 0 && cues.length > 0) {
81
+ for (let j = 0; j < cues.length; j++) {
82
+ if (areNearlyEqual(time, cues[j].start, DELTA_CUES_GROUP)
83
+ || areNearlyEqual(time, cues[j].end, DELTA_CUES_GROUP)) {
84
+ ret.push(cues[j].element);
85
+ }
86
+ }
87
+ }
60
88
  return ret;
61
89
  }
62
90
  }
@@ -143,6 +171,11 @@ export default class TextTrackCuesStore {
143
171
  insert(cues, start, end) {
144
172
  const cuesBuffer = this._cuesBuffer;
145
173
  const cuesInfosToInsert = { start, end, cues };
174
+ // it's preferable to have a delta depending on the duration of the segment
175
+ // if the delta is one fifth of the length of the segment:
176
+ // a segment of [0, 2] is the "same" segment as [0, 2.1]
177
+ // but [0, 0.04] is not the "same" segement as [0,04, 0.08]
178
+ const relativeDelta = Math.abs(start - end) / RELATIVE_DELTA_RATIO;
146
179
  /**
147
180
  * Called when we found the index of the next cue relative to the cue we
148
181
  * want to insert (that is a cue starting after its start or at the same
@@ -154,7 +187,7 @@ export default class TextTrackCuesStore {
154
187
  function onIndexOfNextCueFound(indexOfNextCue) {
155
188
  const nextCue = cuesBuffer[indexOfNextCue];
156
189
  if (nextCue === undefined || // no cue
157
- areNearlyEqual(cuesInfosToInsert.end, nextCue.end)) // samey end
190
+ areNearlyEqual(cuesInfosToInsert.end, nextCue.end, relativeDelta)) // samey end
158
191
  {
159
192
  // ours: |AAAAA|
160
193
  // the current one: |BBBBB|
@@ -190,8 +223,8 @@ export default class TextTrackCuesStore {
190
223
  for (let cueIdx = 0; cueIdx < cuesBuffer.length; cueIdx++) {
191
224
  let cuesInfos = cuesBuffer[cueIdx];
192
225
  if (start < cuesInfos.end) {
193
- if (areNearlyEqual(start, cuesInfos.start)) {
194
- if (areNearlyEqual(end, cuesInfos.end)) {
226
+ if (areNearlyEqual(start, cuesInfos.start, relativeDelta)) {
227
+ if (areNearlyEqual(end, cuesInfos.end, relativeDelta)) {
195
228
  // exact same segment
196
229
  // ours: |AAAAA|
197
230
  // the current one: |BBBBB|
@@ -239,7 +272,7 @@ export default class TextTrackCuesStore {
239
272
  cuesBuffer.splice(cueIdx, 0, cuesInfosToInsert);
240
273
  return;
241
274
  }
242
- else if (areNearlyEqual(end, cuesInfos.start)) {
275
+ else if (areNearlyEqual(end, cuesInfos.start, relativeDelta)) {
243
276
  // our cue goes just before the current one:
244
277
  // ours: |AAAAAAA|
245
278
  // the current one: |BBBB|
@@ -251,7 +284,7 @@ export default class TextTrackCuesStore {
251
284
  cuesBuffer.splice(cueIdx, 0, cuesInfosToInsert);
252
285
  return;
253
286
  }
254
- else if (areNearlyEqual(end, cuesInfos.end)) {
287
+ else if (areNearlyEqual(end, cuesInfos.end, relativeDelta)) {
255
288
  // ours: |AAAAAAA|
256
289
  // the current one: |BBBB|
257
290
  // Result: |AAAAAAA|
@@ -279,7 +312,7 @@ export default class TextTrackCuesStore {
279
312
  return;
280
313
  }
281
314
  // else -> start > cuesInfos.start
282
- if (areNearlyEqual(cuesInfos.end, end)) {
315
+ if (areNearlyEqual(cuesInfos.end, end, relativeDelta)) {
283
316
  // ours: |AAAAAA|
284
317
  // the current one: |BBBBBBBB|
285
318
  // Result: |BBAAAAAA|
@@ -315,6 +348,21 @@ export default class TextTrackCuesStore {
315
348
  }
316
349
  }
317
350
  }
351
+ if (cuesBuffer.length) {
352
+ const lastCue = cuesBuffer[cuesBuffer.length - 1];
353
+ if (areNearlyEqual(lastCue.end, start, relativeDelta)) {
354
+ // Match the end of the previous cue to the start of the following one
355
+ // if they are close enough. If there is a small gap between two segments
356
+ // it can lead to having no subtitles for a short time, this is noticeable when
357
+ // two successive segments displays the same text, making it diseappear
358
+ // and reappear quickly, which gives the impression of blinking
359
+ //
360
+ // ours: |AAAAA|
361
+ // the current one: |BBBBB|...
362
+ // Result: |BBBBBBBAAAAA|
363
+ lastCue.end = start;
364
+ }
365
+ }
318
366
  // no cues group has the end after our current start.
319
367
  // These cues should be the last one
320
368
  cuesBuffer.push(cuesInfosToInsert);
@@ -18,9 +18,10 @@ import { ICuesGroup, IHTMLCue } from "./types";
18
18
  * @see MAX_DELTA_BUFFER_TIME
19
19
  * @param {Number} a
20
20
  * @param {Number} b
21
+ * @param {Number} delta
21
22
  * @returns {Boolean}
22
23
  */
23
- export declare function areNearlyEqual(a: number, b: number): boolean;
24
+ export declare function areNearlyEqual(a: number, b: number, delta?: number): boolean;
24
25
  /**
25
26
  * Get all cues which have data before the given time.
26
27
  * @param {Object} cues
@@ -44,6 +44,21 @@
44
44
  * Setting a value too high might lead to two segments targeting different times
45
45
  * to be wrongly believed to target the same time. In worst case scenarios, this
46
46
  * could lead to wanted text tracks being removed.
47
+ *
48
+ * When comparing 2 segments s1 and s2, you may want to take into account the duration
49
+ * of the segments:
50
+ * - if s1 is [0, 2] and s2 is [0, 2.1] s1 and s2 can be considered as nearly equal as
51
+ * there is a relative difference of: (2.1-2) / 2 = 5%;
52
+ * Formula: (end_s1 - end_s2) / duration_s2 = relative_difference
53
+ * - if s1 is [0, 0.04] and s2 is [0.04, 0.08] s1 and s2 may not considered as nearly
54
+ * equal as there is a relative difference of: (0.04-0.08) / 0.04 = 100%
55
+ *
56
+ * To compare relatively to the duration of a segment you can provide and additional
57
+ * parameter "delta" that remplace MAX_DELTA_BUFFER_TIME.
58
+ * If parameter "delta" is higher than MAX_DELTA_BUFFER_TIME, MAX_DELTA_BUFFER_TIME
59
+ * is used instead of delta. This ensure that segments are nearly equal when comparing
60
+ * relatively AND absolutely.
61
+ *
47
62
  * @type Number
48
63
  */
49
64
  const MAX_DELTA_BUFFER_TIME = 0.2;
@@ -51,10 +66,11 @@ const MAX_DELTA_BUFFER_TIME = 0.2;
51
66
  * @see MAX_DELTA_BUFFER_TIME
52
67
  * @param {Number} a
53
68
  * @param {Number} b
69
+ * @param {Number} delta
54
70
  * @returns {Boolean}
55
71
  */
56
- export function areNearlyEqual(a, b) {
57
- return Math.abs(a - b) <= MAX_DELTA_BUFFER_TIME;
72
+ export function areNearlyEqual(a, b, delta = MAX_DELTA_BUFFER_TIME) {
73
+ return Math.abs(a - b) <= Math.min(delta, MAX_DELTA_BUFFER_TIME);
58
74
  }
59
75
  /**
60
76
  * Get all cues which have data before the given time.
@@ -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";
@@ -6,6 +5,7 @@ import assertUnreachable from "../../../utils/assert_unreachable";
6
5
  import cancellableSleep from "../../../utils/cancellable_sleep";
7
6
  import noop from "../../../utils/noop";
8
7
  import objectAssign from "../../../utils/object_assign";
8
+ import queueMicrotask from "../../../utils/queue_microtask";
9
9
  import SharedReference, { createMappedReference, } from "../../../utils/reference";
10
10
  import TaskCanceller from "../../../utils/task_canceller";
11
11
  import RepresentationStream from "../representation";
@@ -133,7 +133,7 @@ export default function AdaptationStream({ playbackObserver, content, options, r
133
133
  // conditions where the inner logic would be called synchronously before
134
134
  // the next observation (which may reflect very different playback conditions)
135
135
  // is actually received.
136
- return nextTick(() => {
136
+ return queueMicrotask(() => {
137
137
  playbackObserver.listen(() => {
138
138
  if (fnCancelSignal.isCancelled()) {
139
139
  return;
@@ -13,10 +13,10 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import nextTick from "next-tick";
17
16
  import config from "../../../config";
18
17
  import { MediaError } from "../../../errors";
19
18
  import log from "../../../log";
19
+ import queueMicrotask from "../../../utils/queue_microtask";
20
20
  import { createMappedReference, } from "../../../utils/reference";
21
21
  import SortedList from "../../../utils/sorted_list";
22
22
  import TaskCanceller from "../../../utils/task_canceller";
@@ -265,7 +265,7 @@ export default function StreamOrchestrator(content, playbackObserver, representa
265
265
  // Schedule micro task before checking the last playback observation
266
266
  // to reduce the risk of race conditions where the next observation
267
267
  // was going to be emitted synchronously.
268
- nextTick(() => {
268
+ queueMicrotask(() => {
269
269
  var _a;
270
270
  if (orchestratorCancelSignal.isCancelled()) {
271
271
  return;
@@ -430,7 +430,7 @@ export default function StreamOrchestrator(content, playbackObserver, representa
430
430
  // conditions where the inner logic would be called synchronously before
431
431
  // the next observation (which may reflect very different playback
432
432
  // conditions) is actually received.
433
- return nextTick(() => {
433
+ return queueMicrotask(() => {
434
434
  if (innerCancelSignal.isCancelled()) {
435
435
  return;
436
436
  }
@@ -13,11 +13,11 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import nextTick from "next-tick";
17
16
  import config from "../../../config";
18
17
  import { formatError, MediaError, } from "../../../errors";
19
18
  import log from "../../../log";
20
19
  import objectAssign from "../../../utils/object_assign";
20
+ import queueMicrotask from "../../../utils/queue_microtask";
21
21
  import { getLeftSizeOfBufferedTimeRange } from "../../../utils/ranges";
22
22
  import SharedReference from "../../../utils/reference";
23
23
  import TaskCanceller, { CancellationError, } from "../../../utils/task_canceller";
@@ -253,7 +253,7 @@ export default function PeriodStream({ bufferType, content, garbageCollectors, p
253
253
  // is actually received.
254
254
  // It can happen when `askForMediaSourceReload` is called as a side-effect of
255
255
  // the same event that triggers the playback observation to be emitted.
256
- nextTick(() => {
256
+ queueMicrotask(() => {
257
257
  playbackObserver.listen(() => {
258
258
  if (cancelSignal.isCancelled()) {
259
259
  return;
@@ -27,6 +27,7 @@ class Representation {
27
27
  * @param {Object} args
28
28
  */
29
29
  constructor(args, opts) {
30
+ var _a;
30
31
  this.id = args.id;
31
32
  this.uniqueId = generateRepresentationUniqueId();
32
33
  this.bitrate = args.bitrate;
@@ -55,12 +56,24 @@ class Representation {
55
56
  this.cdnMetadata = args.cdnMetadata;
56
57
  this.index = args.index;
57
58
  if (opts.type === "audio" || opts.type === "video") {
58
- const mimeTypeStr = this.getMimeTypeString();
59
- const isSupported = isCodecSupported(mimeTypeStr);
60
- if (!isSupported) {
61
- log.info("Unsupported Representation", mimeTypeStr, this.id, this.bitrate);
59
+ this.isSupported = false;
60
+ // Supplemental codecs are defined as backwards-compatible codecs enhancing
61
+ // the experience of a base layer codec
62
+ if (args.supplementalCodecs !== undefined) {
63
+ const supplementalCodecMimeTypeStr = `${(_a = this.mimeType) !== null && _a !== void 0 ? _a : ""};codecs="${args.supplementalCodecs}"`;
64
+ if (isCodecSupported(supplementalCodecMimeTypeStr)) {
65
+ this.codec = args.supplementalCodecs;
66
+ this.isSupported = true;
67
+ }
68
+ }
69
+ if (!this.isSupported) {
70
+ const mimeTypeStr = this.getMimeTypeString();
71
+ const isSupported = isCodecSupported(mimeTypeStr);
72
+ if (!isSupported) {
73
+ log.info("Unsupported Representation", mimeTypeStr, this.id, this.bitrate);
74
+ }
75
+ this.isSupported = isSupported;
62
76
  }
63
- this.isSupported = isSupported;
64
77
  }
65
78
  else {
66
79
  this.isSupported = true; // TODO for other types
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Converts SCTE 214 supplemental codec string into RFC4281 codec string
3
+ *
4
+ * The returned value is a codec string respecting RFC6381
5
+ *
6
+ * SCTE 214 defines supplemental codecs as a whitespace-separated multiple list of
7
+ * codec strings
8
+ *
9
+ * RFC6381 defines codecs as a comma-separated list of codec strings.
10
+ *
11
+ * This two syntax differs and this parser is used to convert SCTE214
12
+ * to be compliant with what MSE APIs expect
13
+ *
14
+ * @param {string} val - The codec string to parse
15
+ * @returns { Array.<string | undefined | null>}
16
+ */
17
+ export declare function convertSupplementalCodecsToRFC6381(val: string): string;
@@ -0,0 +1,26 @@
1
+ import isNonEmptyString from "../../../../utils/is_non_empty_string";
2
+ const supplementalCodecSeparator = /[, ]+/g;
3
+ /**
4
+ * Converts SCTE 214 supplemental codec string into RFC4281 codec string
5
+ *
6
+ * The returned value is a codec string respecting RFC6381
7
+ *
8
+ * SCTE 214 defines supplemental codecs as a whitespace-separated multiple list of
9
+ * codec strings
10
+ *
11
+ * RFC6381 defines codecs as a comma-separated list of codec strings.
12
+ *
13
+ * This two syntax differs and this parser is used to convert SCTE214
14
+ * to be compliant with what MSE APIs expect
15
+ *
16
+ * @param {string} val - The codec string to parse
17
+ * @returns { Array.<string | undefined | null>}
18
+ */
19
+ export function convertSupplementalCodecsToRFC6381(val) {
20
+ if (isNonEmptyString(val)) {
21
+ return val
22
+ .trim()
23
+ .replace(supplementalCodecSeparator, ", ");
24
+ }
25
+ return "";
26
+ }
@@ -65,6 +65,11 @@ export default function flattenOverlappingPeriods(parsedPeriods) {
65
65
  // `lastFlattenedPeriod` has now a negative or `0` duration.
66
66
  // Remove it, consider the next Period in its place, and re-start the loop.
67
67
  flattenedPeriods.pop();
68
+ if (flattenedPeriods.length === 0) {
69
+ // There's no remaining Period to compare to `parsedPeriod`
70
+ break;
71
+ }
72
+ // Take the previous Period as reference and compare it now to `parsedPeriod`
68
73
  lastFlattenedPeriod = flattenedPeriods[flattenedPeriods.length - 1];
69
74
  }
70
75
  }
@@ -16,6 +16,7 @@
16
16
  import log from "../../../../log";
17
17
  import arrayFind from "../../../../utils/array_find";
18
18
  import objectAssign from "../../../../utils/object_assign";
19
+ import { convertSupplementalCodecsToRFC6381 } from "./convert_supplemental_codecs";
19
20
  import { getWEBMHDRInformation } from "./get_hdr_information";
20
21
  import parseRepresentationIndex from "./parse_representation_index";
21
22
  import resolveBaseURLs from "./resolve_base_urls";
@@ -80,19 +81,19 @@ export default function parseRepresentations(representationsIR, adaptation, cont
80
81
  const parsedRepresentations = [];
81
82
  for (const representation of representationsIR) {
82
83
  // Compute Representation ID
83
- let representationID = representation.attributes.id != null ?
84
+ let representationID = representation.attributes.id !== undefined ?
84
85
  representation.attributes.id :
85
86
  (String(representation.attributes.bitrate) +
86
- (representation.attributes.height != null ?
87
+ (representation.attributes.height !== undefined ?
87
88
  (`-${representation.attributes.height}`) :
88
89
  "") +
89
- (representation.attributes.width != null ?
90
+ (representation.attributes.width !== undefined ?
90
91
  (`-${representation.attributes.width}`) :
91
92
  "") +
92
- (representation.attributes.mimeType != null ?
93
+ (representation.attributes.mimeType !== undefined ?
93
94
  (`-${representation.attributes.mimeType}`) :
94
95
  "") +
95
- (representation.attributes.codecs != null ?
96
+ (representation.attributes.codecs !== undefined ?
96
97
  (`-${representation.attributes.codecs}`) :
97
98
  ""));
98
99
  // Avoid duplicate IDs
@@ -114,7 +115,7 @@ export default function parseRepresentations(representationsIR, adaptation, cont
114
115
  const representationIndex = parseRepresentationIndex(representation, reprIndexCtxt);
115
116
  // Find bitrate
116
117
  let representationBitrate;
117
- if (representation.attributes.bitrate == null) {
118
+ if (representation.attributes.bitrate === undefined) {
118
119
  log.warn("DASH: No usable bitrate found in the Representation.");
119
120
  representationBitrate = 0;
120
121
  }
@@ -141,45 +142,56 @@ export default function parseRepresentations(representationsIR, adaptation, cont
141
142
  }
142
143
  // Add optional attributes
143
144
  let codecs;
144
- if (representation.attributes.codecs != null) {
145
+ if (representation.attributes.codecs !== undefined) {
145
146
  codecs = representation.attributes.codecs;
146
147
  }
147
- else if (adaptation.attributes.codecs != null) {
148
+ else if (adaptation.attributes.codecs !== undefined) {
148
149
  codecs = adaptation.attributes.codecs;
149
150
  }
150
- if (codecs != null) {
151
+ if (codecs !== undefined) {
151
152
  codecs = codecs === "mp4a.40.02" ? "mp4a.40.2" : codecs;
152
153
  parsedRepresentation.codecs = codecs;
153
154
  }
154
- if (representation.attributes.frameRate != null) {
155
+ let supplementalCodecs;
156
+ if (representation.attributes.supplementalCodecs !== undefined) {
157
+ supplementalCodecs = representation.attributes.supplementalCodecs;
158
+ }
159
+ else if (adaptation.attributes.supplementalCodecs !== undefined) {
160
+ supplementalCodecs = adaptation.attributes.supplementalCodecs;
161
+ }
162
+ if (supplementalCodecs !== undefined) {
163
+ parsedRepresentation.supplementalCodecs =
164
+ convertSupplementalCodecsToRFC6381(supplementalCodecs);
165
+ }
166
+ if (representation.attributes.frameRate !== undefined) {
155
167
  parsedRepresentation.frameRate =
156
168
  representation.attributes.frameRate;
157
169
  }
158
- else if (adaptation.attributes.frameRate != null) {
170
+ else if (adaptation.attributes.frameRate !== undefined) {
159
171
  parsedRepresentation.frameRate =
160
172
  adaptation.attributes.frameRate;
161
173
  }
162
- if (representation.attributes.height != null) {
174
+ if (representation.attributes.height !== undefined) {
163
175
  parsedRepresentation.height =
164
176
  representation.attributes.height;
165
177
  }
166
- else if (adaptation.attributes.height != null) {
178
+ else if (adaptation.attributes.height !== undefined) {
167
179
  parsedRepresentation.height =
168
180
  adaptation.attributes.height;
169
181
  }
170
- if (representation.attributes.mimeType != null) {
182
+ if (representation.attributes.mimeType !== undefined) {
171
183
  parsedRepresentation.mimeType =
172
184
  representation.attributes.mimeType;
173
185
  }
174
- else if (adaptation.attributes.mimeType != null) {
186
+ else if (adaptation.attributes.mimeType !== undefined) {
175
187
  parsedRepresentation.mimeType =
176
188
  adaptation.attributes.mimeType;
177
189
  }
178
- if (representation.attributes.width != null) {
190
+ if (representation.attributes.width !== undefined) {
179
191
  parsedRepresentation.width =
180
192
  representation.attributes.width;
181
193
  }
182
- else if (adaptation.attributes.width != null) {
194
+ else if (adaptation.attributes.width !== undefined) {
183
195
  parsedRepresentation.width =
184
196
  adaptation.attributes.width;
185
197
  }
@@ -240,6 +240,9 @@ function parseAdaptationSetAttributes(root) {
240
240
  case "codecs":
241
241
  parsedAdaptation.codecs = attribute.value;
242
242
  break;
243
+ case "scte214:supplementalCodecs":
244
+ parsedAdaptation.supplementalCodecs = attribute.value;
245
+ break;
243
246
  case "codingDependency":
244
247
  parseValue(attribute.value, { asKey: "codingDependency",
245
248
  parser: parseBoolean,
@@ -149,6 +149,9 @@ function parseRepresentationAttributes(representationElement) {
149
149
  parser: parseMPDInteger,
150
150
  dashName: "qualityRanking" });
151
151
  break;
152
+ case "scte214:supplementalCodecs":
153
+ attributes.supplementalCodecs = attr.value;
154
+ break;
152
155
  case "segmentProfiles":
153
156
  attributes.segmentProfiles = attr.value;
154
157
  break;
@@ -217,6 +217,7 @@ export interface IAdaptationSetAttributes {
217
217
  segmentAlignment?: number | boolean;
218
218
  segmentProfiles?: string;
219
219
  subsegmentAlignment?: number | boolean;
220
+ supplementalCodecs?: string;
220
221
  width?: number;
221
222
  availabilityTimeComplete?: boolean;
222
223
  availabilityTimeOffset?: number;
@@ -249,6 +250,7 @@ export interface IRepresentationAttributes {
249
250
  profiles?: string;
250
251
  qualityRanking?: number;
251
252
  segmentProfiles?: string;
253
+ supplementalCodecs?: string;
252
254
  width?: number;
253
255
  availabilityTimeComplete?: boolean;
254
256
  availabilityTimeOffset?: number;
@@ -222,6 +222,10 @@ export function generateAdaptationSetAttrParser(adaptationAttrs, linearMemory) {
222
222
  adaptationAttrs.codecs =
223
223
  parseString(textDecoder, linearMemory.buffer, ptr, len);
224
224
  break;
225
+ case 77 /* AttributeName.SupplementalCodecs */:
226
+ adaptationAttrs.supplementalCodecs =
227
+ parseString(textDecoder, linearMemory.buffer, ptr, len);
228
+ break;
225
229
  case 2 /* AttributeName.Profiles */:
226
230
  adaptationAttrs.profiles =
227
231
  parseString(textDecoder, linearMemory.buffer, ptr, len);
@@ -122,6 +122,10 @@ export function generateRepresentationAttrParser(representationAttrs, linearMemo
122
122
  representationAttrs.codecs =
123
123
  parseString(textDecoder, linearMemory.buffer, ptr, len);
124
124
  break;
125
+ case 77 /* AttributeName.SupplementalCodecs */:
126
+ representationAttrs.supplementalCodecs =
127
+ parseString(textDecoder, linearMemory.buffer, ptr, len);
128
+ break;
125
129
  case 5 /* AttributeName.CodingDependency */:
126
130
  representationAttrs.codingDependency =
127
131
  new DataView(linearMemory.buffer).getUint8(0) === 0;
@@ -140,5 +140,6 @@ export declare const enum AttributeName {
140
140
  QueryBeforeStart = 73,
141
141
  ProxyServerUrl = 74,
142
142
  DefaultServiceLocation = 75,
143
- EndNumber = 76
143
+ EndNumber = 76,
144
+ SupplementalCodecs = 77
144
145
  }
@@ -154,6 +154,7 @@ export interface IParsedRepresentation {
154
154
  hdrInfo?: IHDRInformation | undefined;
155
155
  /** `true` if audio has Dolby Atmos. */
156
156
  isSpatialAudio?: boolean | undefined;
157
+ supplementalCodecs?: string | undefined;
157
158
  }
158
159
  /** Every possible types an Adaptation can have. */
159
160
  export type IParsedAdaptationType = "audio" | "video" | "text";