rx-player 3.32.2-dev.2023110700 → 3.33.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 (46) hide show
  1. package/CHANGELOG.md +11 -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/mpd-parser.wasm +0 -0
  23. package/dist/rx-player.js +171 -31
  24. package/dist/rx-player.min.js +1 -1
  25. package/package.json +1 -1
  26. package/sonar-project.properties +1 -1
  27. package/src/core/api/option_utils.ts +8 -0
  28. package/src/core/api/public_api.ts +15 -3
  29. package/src/core/segment_buffers/implementations/text/html/__tests__/utils.test.ts +15 -0
  30. package/src/core/segment_buffers/implementations/text/html/text_track_cues_store.ts +57 -6
  31. package/src/core/segment_buffers/implementations/text/html/utils.ts +19 -2
  32. package/src/manifest/representation.ts +19 -6
  33. package/src/parsers/manifest/dash/common/__tests__/convert_supplemental_codecs.test.ts +37 -0
  34. package/src/parsers/manifest/dash/common/__tests__/flatten_overlapping_period.test.ts +20 -0
  35. package/src/parsers/manifest/dash/common/convert_supplemental_codecs.ts +32 -0
  36. package/src/parsers/manifest/dash/common/flatten_overlapping_periods.ts +5 -0
  37. package/src/parsers/manifest/dash/common/parse_representations.ts +30 -17
  38. package/src/parsers/manifest/dash/js-parser/node_parsers/AdaptationSet.ts +4 -0
  39. package/src/parsers/manifest/dash/js-parser/node_parsers/Representation.ts +4 -0
  40. package/src/parsers/manifest/dash/node_parser_types.ts +2 -0
  41. package/src/parsers/manifest/dash/wasm-parser/rs/events.rs +2 -0
  42. package/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs +2 -0
  43. package/src/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.ts +4 -0
  44. package/src/parsers/manifest/dash/wasm-parser/ts/generators/Representation.ts +4 -0
  45. package/src/parsers/manifest/dash/wasm-parser/ts/types.ts +2 -0
  46. package/src/parsers/manifest/types.ts +2 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.32.2-dev.2023110700",
4
+ "version": "3.33.0-dev.2023111400",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -1,7 +1,7 @@
1
1
  sonar.projectKey=rx-player
2
2
  sonar.organization=rx-player
3
3
  sonar.projectName=rx-player
4
- sonar.projectVersion=3.32.2-dev.2023110700
4
+ sonar.projectVersion=3.33.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
@@ -397,6 +397,8 @@ function parseConstructorOptions(
397
397
  */
398
398
  function checkReloadOptions(options?: {
399
399
  reloadAt?: { position?: number; relative?: number };
400
+ keySystems?: IKeySystemOption[];
401
+ autoPlay?: boolean;
400
402
  }): void {
401
403
  if (options === null ||
402
404
  (typeof options !== "object" && options !== undefined)) {
@@ -414,6 +416,12 @@ function checkReloadOptions(options?: {
414
416
  options?.reloadAt?.relative !== undefined) {
415
417
  throw new Error("API: reload - Invalid 'reloadAt.relative' option format.");
416
418
  }
419
+ if (!Array.isArray(options?.keySystems) && options?.keySystems !== undefined) {
420
+ throw new Error("API: reload - Invalid 'keySystems' option format.");
421
+ }
422
+ if (options?.autoPlay !== undefined && typeof options.autoPlay !== "boolean") {
423
+ throw new Error("API: reload - Invalid 'autoPlay' option format.");
424
+ }
417
425
  }
418
426
 
419
427
  /**
@@ -59,6 +59,7 @@ import {
59
59
  IConstructorOptions,
60
60
  IDecipherabilityUpdateContent,
61
61
  IKeySystemConfigurationOutput,
62
+ IKeySystemOption,
62
63
  ILoadVideoOptions,
63
64
  IPeriod,
64
65
  IPlayerError,
@@ -379,7 +380,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
379
380
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
380
381
  videoElement.preload = "auto";
381
382
 
382
- this.version = /* PLAYER_VERSION */"3.32.2-dev.2023110700";
383
+ this.version = /* PLAYER_VERSION */"3.33.0-dev.2023111400";
383
384
  this.log = log;
384
385
  this.state = "STOPPED";
385
386
  this.videoElement = videoElement;
@@ -577,6 +578,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
577
578
  */
578
579
  reload(reloadOpts?: {
579
580
  reloadAt?: { position?: number; relative?: number };
581
+ keySystems?: IKeySystemOption[];
580
582
  autoPlay?: boolean;
581
583
  }): void {
582
584
  const { options,
@@ -609,6 +611,13 @@ class Player extends EventEmitter<IPublicAPIEvent> {
609
611
  autoPlay = !reloadInPause;
610
612
  }
611
613
 
614
+ let keySystems : IKeySystemOption[] | undefined;
615
+ if (reloadOpts?.keySystems !== undefined) {
616
+ keySystems = reloadOpts.keySystems;
617
+ } else if (this._priv_reloadingMetadata.options?.keySystems !== undefined) {
618
+ keySystems = this._priv_reloadingMetadata.options.keySystems;
619
+ }
620
+
612
621
  const newOptions = { ...options,
613
622
  initialManifest: manifest };
614
623
  if (startAt !== undefined) {
@@ -617,6 +626,9 @@ class Player extends EventEmitter<IPublicAPIEvent> {
617
626
  if (autoPlay !== undefined) {
618
627
  newOptions.autoPlay = autoPlay;
619
628
  }
629
+ if (keySystems !== undefined) {
630
+ newOptions.keySystems = keySystems;
631
+ }
620
632
  this._priv_initializeContentPlayback(newOptions);
621
633
  }
622
634
 
@@ -626,7 +638,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
626
638
  if (features.createDebugElement === null) {
627
639
  throw new Error("Feature `DEBUG_ELEMENT` not added to the RxPlayer");
628
640
  }
629
- const canceller = new TaskCanceller() ;
641
+ const canceller = new TaskCanceller();
630
642
  features.createDebugElement(element, this, canceller.signal);
631
643
  return {
632
644
  dispose() {
@@ -3103,7 +3115,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3103
3115
  return mediaElementTrackChoiceManager;
3104
3116
  }
3105
3117
  }
3106
- Player.version = /* PLAYER_VERSION */"3.32.2-dev.2023110700";
3118
+ Player.version = /* PLAYER_VERSION */"3.33.0-dev.2023111400";
3107
3119
 
3108
3120
  /** Every events sent by the RxPlayer's public API. */
3109
3121
  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
  /**
@@ -190,15 +190,28 @@ class Representation {
190
190
  }
191
191
 
192
192
  this.cdnMetadata = args.cdnMetadata;
193
-
194
193
  this.index = args.index;
194
+
195
195
  if (opts.type === "audio" || opts.type === "video") {
196
- const mimeTypeStr = this.getMimeTypeString();
197
- const isSupported = isCodecSupported(mimeTypeStr);
198
- if (!isSupported) {
199
- log.info("Unsupported Representation", mimeTypeStr, this.id, this.bitrate);
196
+ this.isSupported = false;
197
+ // Supplemental codecs are defined as backwards-compatible codecs enhancing
198
+ // the experience of a base layer codec
199
+ if (args.supplementalCodecs !== undefined) {
200
+ const supplementalCodecMimeTypeStr =
201
+ `${this.mimeType ?? ""};codecs="${args.supplementalCodecs}"`;
202
+ if (isCodecSupported(supplementalCodecMimeTypeStr)) {
203
+ this.codec = args.supplementalCodecs;
204
+ this.isSupported = true;
205
+ }
206
+ }
207
+ if (!this.isSupported) {
208
+ const mimeTypeStr = this.getMimeTypeString();
209
+ const isSupported = isCodecSupported(mimeTypeStr);
210
+ if (!isSupported) {
211
+ log.info("Unsupported Representation", mimeTypeStr, this.id, this.bitrate);
212
+ }
213
+ this.isSupported = isSupported;
200
214
  }
201
- this.isSupported = isSupported;
202
215
  } else {
203
216
  this.isSupported = true; // TODO for other types
204
217
  }
@@ -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
  }
@@ -296,6 +296,10 @@ function parseAdaptationSetAttributes(
296
296
  parsedAdaptation.codecs = attribute.value;
297
297
  break;
298
298
 
299
+ case "scte214:supplementalCodecs":
300
+ parsedAdaptation.supplementalCodecs = attribute.value;
301
+ break;
302
+
299
303
  case "codingDependency":
300
304
  parseValue(attribute.value, { asKey: "codingDependency",
301
305
  parser: parseBoolean,
@@ -184,6 +184,10 @@ function parseRepresentationAttributes(
184
184
  dashName: "qualityRanking" });
185
185
  break;
186
186
 
187
+ case "scte214:supplementalCodecs":
188
+ attributes.supplementalCodecs = attr.value;
189
+ break;
190
+
187
191
  case "segmentProfiles":
188
192
  attributes.segmentProfiles = attr.value;
189
193
  break;
@@ -232,6 +232,7 @@ export interface IAdaptationSetAttributes {
232
232
  segmentAlignment? : number|boolean;
233
233
  segmentProfiles? : string;
234
234
  subsegmentAlignment? : number|boolean;
235
+ supplementalCodecs?: string;
235
236
  width? : number;
236
237
  availabilityTimeComplete?: boolean;
237
238
  availabilityTimeOffset?: number;
@@ -271,6 +272,7 @@ export interface IRepresentationAttributes {
271
272
  profiles? : string;
272
273
  qualityRanking? : number;
273
274
  segmentProfiles? : string;
275
+ supplementalCodecs?: string;
274
276
  width? : number;
275
277
  availabilityTimeComplete?: boolean;
276
278
  availabilityTimeOffset?: number;
@@ -280,6 +280,8 @@ pub enum AttributeName {
280
280
 
281
281
  // SegmentTemplate
282
282
  EndNumber = 76, // f64
283
+
284
+ SupplementalCodecs = 77 // string
283
285
  }
284
286
 
285
287
  impl TagName {
@@ -87,6 +87,7 @@ pub fn report_adaptation_set_attrs(e: &quick_xml::events::BytesStart) {
87
87
  b"bitstreamSwitching" => BitstreamSwitching.try_report_as_bool(&attr),
88
88
  b"audioSamplingRate" => AudioSamplingRate.try_report_as_string(&attr),
89
89
  b"codecs" => Codecs.try_report_as_string(&attr),
90
+ b"scte214:supplementalCodecs" => SupplementalCodecs.try_report_as_string(&attr),
90
91
  b"profiles" => Profiles.try_report_as_string(&attr),
91
92
  b"segmentProfiles" => SegmentProfiles.try_report_as_string(&attr),
92
93
  b"mimeType" => MimeType.try_report_as_string(&attr),
@@ -116,6 +117,7 @@ pub fn report_representation_attrs(tag_bs: &quick_xml::events::BytesStart) {
116
117
  b"audioSamplingRate" => AudioSamplingRate.try_report_as_string(&attr),
117
118
  b"bandwidth" => Bitrate.try_report_as_u64(&attr),
118
119
  b"codecs" => Codecs.try_report_as_string(&attr),
120
+ b"scte214:supplementalCodecs" => SupplementalCodecs.try_report_as_string(&attr),
119
121
  b"codingDependency" => CodingDependency.try_report_as_bool(&attr),
120
122
  b"frameRate" => FrameRate.try_report_as_string(&attr),
121
123
  b"height" => Height.try_report_as_u64(&attr),
@@ -289,6 +289,10 @@ export function generateAdaptationSetAttrParser(
289
289
  adaptationAttrs.codecs =
290
290
  parseString(textDecoder, linearMemory.buffer, ptr, len);
291
291
  break;
292
+ case AttributeName.SupplementalCodecs:
293
+ adaptationAttrs.supplementalCodecs =
294
+ parseString(textDecoder, linearMemory.buffer, ptr, len);
295
+ break;
292
296
  case AttributeName.Profiles:
293
297
  adaptationAttrs.profiles =
294
298
  parseString(textDecoder, linearMemory.buffer, ptr, len);
@@ -165,6 +165,10 @@ export function generateRepresentationAttrParser(
165
165
  representationAttrs.codecs =
166
166
  parseString(textDecoder, linearMemory.buffer, ptr, len);
167
167
  break;
168
+ case AttributeName.SupplementalCodecs:
169
+ representationAttrs.supplementalCodecs =
170
+ parseString(textDecoder, linearMemory.buffer, ptr, len);
171
+ break;
168
172
  case AttributeName.CodingDependency:
169
173
  representationAttrs.codingDependency =
170
174
  new DataView(linearMemory.buffer).getUint8(0) === 0;
@@ -293,4 +293,6 @@ export const enum AttributeName {
293
293
 
294
294
  // SegmentTemplate
295
295
  EndNumber = 76, // f64
296
+
297
+ SupplementalCodecs = 77, // String
296
298
  }
@@ -160,6 +160,8 @@ export interface IParsedRepresentation {
160
160
  hdrInfo?: IHDRInformation | undefined;
161
161
  /** `true` if audio has Dolby Atmos. */
162
162
  isSpatialAudio?: boolean | undefined;
163
+
164
+ supplementalCodecs? : string | undefined;
163
165
  }
164
166
 
165
167
  /** Every possible types an Adaptation can have. */