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.
- package/CHANGELOG.md +11 -1
- package/VERSION +1 -1
- package/dist/_esm5.processed/core/api/option_utils.d.ts +2 -0
- package/dist/_esm5.processed/core/api/option_utils.js +6 -0
- package/dist/_esm5.processed/core/api/public_api.d.ts +2 -1
- package/dist/_esm5.processed/core/api/public_api.js +14 -4
- package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/text_track_cues_store.js +54 -6
- package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/utils.d.ts +2 -1
- package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/utils.js +19 -2
- package/dist/_esm5.processed/manifest/representation.js +18 -5
- package/dist/_esm5.processed/parsers/manifest/dash/common/convert_supplemental_codecs.d.ts +17 -0
- package/dist/_esm5.processed/parsers/manifest/dash/common/convert_supplemental_codecs.js +26 -0
- package/dist/_esm5.processed/parsers/manifest/dash/common/flatten_overlapping_periods.js +5 -0
- package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representations.js +29 -17
- package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/AdaptationSet.js +3 -0
- package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/Representation.js +3 -0
- package/dist/_esm5.processed/parsers/manifest/dash/node_parser_types.d.ts +2 -0
- package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.js +4 -0
- package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/Representation.js +4 -0
- package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/types.d.ts +2 -1
- package/dist/_esm5.processed/parsers/manifest/types.d.ts +1 -0
- package/dist/mpd-parser.wasm +0 -0
- package/dist/rx-player.js +171 -31
- package/dist/rx-player.min.js +1 -1
- package/package.json +1 -1
- package/sonar-project.properties +1 -1
- package/src/core/api/option_utils.ts +8 -0
- package/src/core/api/public_api.ts +15 -3
- package/src/core/segment_buffers/implementations/text/html/__tests__/utils.test.ts +15 -0
- package/src/core/segment_buffers/implementations/text/html/text_track_cues_store.ts +57 -6
- package/src/core/segment_buffers/implementations/text/html/utils.ts +19 -2
- package/src/manifest/representation.ts +19 -6
- package/src/parsers/manifest/dash/common/__tests__/convert_supplemental_codecs.test.ts +37 -0
- package/src/parsers/manifest/dash/common/__tests__/flatten_overlapping_period.test.ts +20 -0
- package/src/parsers/manifest/dash/common/convert_supplemental_codecs.ts +32 -0
- package/src/parsers/manifest/dash/common/flatten_overlapping_periods.ts +5 -0
- package/src/parsers/manifest/dash/common/parse_representations.ts +30 -17
- package/src/parsers/manifest/dash/js-parser/node_parsers/AdaptationSet.ts +4 -0
- package/src/parsers/manifest/dash/js-parser/node_parsers/Representation.ts +4 -0
- package/src/parsers/manifest/dash/node_parser_types.ts +2 -0
- package/src/parsers/manifest/dash/wasm-parser/rs/events.rs +2 -0
- package/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs +2 -0
- package/src/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.ts +4 -0
- package/src/parsers/manifest/dash/wasm-parser/ts/generators/Representation.ts +4 -0
- package/src/parsers/manifest/dash/wasm-parser/ts/types.ts +2 -0
- package/src/parsers/manifest/types.ts +2 -0
package/package.json
CHANGED
package/sonar-project.properties
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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(
|
|
64
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
124
|
+
let representationID = representation.attributes.id !== undefined ?
|
|
124
125
|
representation.attributes.id :
|
|
125
126
|
(String(representation.attributes.bitrate) +
|
|
126
|
-
(representation.attributes.height
|
|
127
|
+
(representation.attributes.height !== undefined ?
|
|
127
128
|
(`-${representation.attributes.height}`) :
|
|
128
129
|
"") +
|
|
129
|
-
(representation.attributes.width
|
|
130
|
+
(representation.attributes.width !== undefined ?
|
|
130
131
|
(`-${representation.attributes.width}`) :
|
|
131
132
|
"") +
|
|
132
|
-
(representation.attributes.mimeType
|
|
133
|
+
(representation.attributes.mimeType !== undefined ?
|
|
133
134
|
(`-${representation.attributes.mimeType}`) :
|
|
134
135
|
"") +
|
|
135
|
-
(representation.attributes.codecs
|
|
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
|
|
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
|
|
208
|
+
if (representation.attributes.codecs !== undefined) {
|
|
208
209
|
codecs = representation.attributes.codecs;
|
|
209
|
-
} else if (adaptation.attributes.codecs
|
|
210
|
+
} else if (adaptation.attributes.codecs !== undefined) {
|
|
210
211
|
codecs = adaptation.attributes.codecs;
|
|
211
212
|
}
|
|
212
|
-
if (codecs
|
|
213
|
+
if (codecs !== undefined) {
|
|
213
214
|
codecs = codecs === "mp4a.40.02" ? "mp4a.40.2" : codecs;
|
|
214
215
|
parsedRepresentation.codecs = codecs;
|
|
215
216
|
}
|
|
216
|
-
|
|
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
|
|
232
|
+
} else if (adaptation.attributes.frameRate !== undefined) {
|
|
220
233
|
parsedRepresentation.frameRate =
|
|
221
234
|
adaptation.attributes.frameRate;
|
|
222
235
|
}
|
|
223
|
-
if (representation.attributes.height
|
|
236
|
+
if (representation.attributes.height !== undefined) {
|
|
224
237
|
parsedRepresentation.height =
|
|
225
238
|
representation.attributes.height;
|
|
226
|
-
} else if (adaptation.attributes.height
|
|
239
|
+
} else if (adaptation.attributes.height !== undefined) {
|
|
227
240
|
parsedRepresentation.height =
|
|
228
241
|
adaptation.attributes.height;
|
|
229
242
|
}
|
|
230
|
-
if (representation.attributes.mimeType
|
|
243
|
+
if (representation.attributes.mimeType !== undefined) {
|
|
231
244
|
parsedRepresentation.mimeType =
|
|
232
245
|
representation.attributes.mimeType;
|
|
233
|
-
} else if (adaptation.attributes.mimeType
|
|
246
|
+
} else if (adaptation.attributes.mimeType !== undefined) {
|
|
234
247
|
parsedRepresentation.mimeType =
|
|
235
248
|
adaptation.attributes.mimeType;
|
|
236
249
|
}
|
|
237
|
-
if (representation.attributes.width
|
|
250
|
+
if (representation.attributes.width !== undefined) {
|
|
238
251
|
parsedRepresentation.width =
|
|
239
252
|
representation.attributes.width;
|
|
240
|
-
} else if (adaptation.attributes.width
|
|
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;
|
|
@@ -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;
|
|
@@ -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. */
|