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