rx-player 4.2.0-dev.2024091600 → 4.2.0-dev.2024100200
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 +20 -11
- package/VERSION +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/commonjs/compat/eme/close_session.js +2 -2
- package/dist/commonjs/compat/eme/load_session.js +2 -2
- package/dist/commonjs/compat/patch_webkit_source_buffer.d.ts.map +1 -1
- package/dist/commonjs/compat/patch_webkit_source_buffer.js +0 -3
- package/dist/commonjs/config.d.ts +7 -156
- package/dist/commonjs/config.d.ts.map +1 -1
- package/dist/commonjs/config.js +50 -3
- package/dist/commonjs/core/fetchers/manifest/manifest_fetcher.js +2 -2
- package/dist/commonjs/core/fetchers/segment/segment_fetcher.js +2 -2
- package/dist/commonjs/core/fetchers/utils/schedule_request.js +2 -2
- package/dist/commonjs/core/main/worker/worker_main.d.ts.map +1 -1
- package/dist/commonjs/core/main/worker/worker_main.js +7 -0
- package/dist/commonjs/core/segment_sinks/garbage_collector.js +2 -2
- package/dist/commonjs/core/segment_sinks/implementations/audio_video/audio_video_segment_buffer.js +2 -2
- package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_buffer.js +2 -2
- package/dist/commonjs/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
- package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +35 -9
- package/dist/commonjs/core/stream/adaptation/index.d.ts +1 -1
- package/dist/commonjs/core/stream/adaptation/index.d.ts.map +1 -1
- package/dist/commonjs/core/stream/adaptation/index.js +0 -15
- package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.js +2 -2
- package/dist/commonjs/core/stream/period/index.d.ts +1 -1
- package/dist/commonjs/core/stream/period/index.d.ts.map +1 -1
- package/dist/commonjs/core/stream/period/index.js +0 -15
- package/dist/commonjs/core/stream/period/period_stream.js +2 -2
- package/dist/commonjs/core/stream/representation/index.d.ts +1 -1
- package/dist/commonjs/core/stream/representation/index.d.ts.map +1 -1
- package/dist/commonjs/core/stream/representation/index.js +0 -15
- package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
- package/dist/commonjs/core/stream/representation/representation_stream.js +2 -0
- package/dist/commonjs/core/stream/representation/utils/append_segment_to_buffer.d.ts.map +1 -1
- package/dist/commonjs/core/stream/representation/utils/append_segment_to_buffer.js +16 -11
- package/dist/commonjs/core/stream/representation/utils/push_init_segment.js +2 -2
- package/dist/commonjs/core/stream/representation/utils/push_media_segment.js +2 -2
- package/dist/commonjs/default_config.js +4 -0
- package/dist/commonjs/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +2 -2
- package/dist/commonjs/experimental/tools/createMetaplaylist/get_duration_from_manifest.js +2 -2
- package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/commonjs/main_thread/api/public_api.js +19 -4
- package/dist/commonjs/main_thread/decrypt/attach_media_keys.js +2 -2
- package/dist/commonjs/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
- package/dist/commonjs/main_thread/decrypt/content_decryptor.js +4 -2
- package/dist/commonjs/main_thread/decrypt/create_or_load_session.js +2 -2
- package/dist/commonjs/main_thread/decrypt/create_session.js +2 -2
- package/dist/commonjs/main_thread/decrypt/dispose_decryption_resources.js +2 -2
- package/dist/commonjs/main_thread/decrypt/find_key_system.js +2 -2
- package/dist/commonjs/main_thread/decrypt/get_media_keys.d.ts.map +1 -1
- package/dist/commonjs/main_thread/decrypt/get_media_keys.js +3 -2
- package/dist/commonjs/main_thread/decrypt/init_media_keys.js +2 -2
- package/dist/commonjs/main_thread/decrypt/session_events_listener.js +2 -2
- package/dist/commonjs/main_thread/decrypt/set_server_certificate.js +2 -4
- package/dist/commonjs/main_thread/decrypt/utils/clean_old_loaded_sessions.js +2 -2
- package/dist/commonjs/main_thread/decrypt/utils/loaded_sessions_store.js +2 -2
- package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/directfile_content_initializer.js +14 -6
- package/dist/commonjs/main_thread/init/media_source_content_initializer.js +2 -2
- package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts +13 -0
- package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/multi_thread_content_initializer.js +96 -47
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts +1 -1
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +21 -5
- package/dist/commonjs/mse/main_media_source_interface.d.ts.map +1 -1
- package/dist/commonjs/mse/main_media_source_interface.js +21 -2
- package/dist/commonjs/multithread_types.d.ts +8 -1
- package/dist/commonjs/multithread_types.d.ts.map +1 -1
- package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.js +2 -2
- package/dist/commonjs/parsers/manifest/index.d.ts +1 -1
- package/dist/commonjs/parsers/manifest/index.d.ts.map +1 -1
- package/dist/commonjs/parsers/manifest/index.js +0 -15
- package/dist/commonjs/transports/dash/integrity_checks.js +2 -2
- package/dist/commonjs/transports/dash/load_chunked_segment_data.js +2 -2
- package/dist/commonjs/transports/dash/segment_loader.js +2 -2
- package/dist/commonjs/transports/dash/text_loader.js +2 -2
- package/dist/commonjs/transports/smooth/pipelines.d.ts.map +1 -1
- package/dist/commonjs/transports/smooth/pipelines.js +1 -0
- package/dist/commonjs/transports/smooth/segment_loader.js +2 -2
- package/dist/commonjs/transports/utils/parse_text_track.d.ts.map +1 -1
- package/dist/commonjs/transports/utils/parse_text_track.js +1 -0
- package/dist/commonjs/utils/request/fetch.js +2 -2
- package/dist/commonjs/utils/retry_promise_with_backoff.js +2 -2
- package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/es2017/compat/patch_webkit_source_buffer.d.ts.map +1 -1
- package/dist/es2017/compat/patch_webkit_source_buffer.js +0 -3
- package/dist/es2017/config.d.ts +7 -156
- package/dist/es2017/config.d.ts.map +1 -1
- package/dist/es2017/config.js +6 -1
- package/dist/es2017/core/main/worker/worker_main.d.ts.map +1 -1
- package/dist/es2017/core/main/worker/worker_main.js +7 -0
- package/dist/es2017/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
- package/dist/es2017/core/stream/adaptation/adaptation_stream.js +33 -7
- package/dist/es2017/core/stream/adaptation/index.d.ts +1 -1
- package/dist/es2017/core/stream/adaptation/index.d.ts.map +1 -1
- package/dist/es2017/core/stream/adaptation/index.js +0 -1
- package/dist/es2017/core/stream/period/index.d.ts +1 -1
- package/dist/es2017/core/stream/period/index.d.ts.map +1 -1
- package/dist/es2017/core/stream/period/index.js +0 -1
- package/dist/es2017/core/stream/representation/index.d.ts +1 -1
- package/dist/es2017/core/stream/representation/index.d.ts.map +1 -1
- package/dist/es2017/core/stream/representation/index.js +0 -1
- package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
- package/dist/es2017/core/stream/representation/representation_stream.js +2 -0
- package/dist/es2017/core/stream/representation/utils/append_segment_to_buffer.d.ts.map +1 -1
- package/dist/es2017/core/stream/representation/utils/append_segment_to_buffer.js +6 -2
- package/dist/es2017/default_config.js +4 -0
- package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
- package/dist/es2017/main_thread/api/public_api.js +17 -2
- package/dist/es2017/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
- package/dist/es2017/main_thread/decrypt/content_decryptor.js +2 -0
- package/dist/es2017/main_thread/decrypt/get_media_keys.d.ts.map +1 -1
- package/dist/es2017/main_thread/decrypt/get_media_keys.js +1 -0
- package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/directfile_content_initializer.js +14 -6
- package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts +13 -0
- package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/multi_thread_content_initializer.js +78 -41
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts +1 -1
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +19 -3
- package/dist/es2017/mse/main_media_source_interface.d.ts.map +1 -1
- package/dist/es2017/mse/main_media_source_interface.js +19 -0
- package/dist/es2017/multithread_types.d.ts +8 -1
- package/dist/es2017/multithread_types.d.ts.map +1 -1
- package/dist/es2017/parsers/manifest/index.d.ts +1 -1
- package/dist/es2017/parsers/manifest/index.d.ts.map +1 -1
- package/dist/es2017/parsers/manifest/index.js +1 -1
- package/dist/es2017/transports/smooth/pipelines.d.ts.map +1 -1
- package/dist/es2017/transports/smooth/pipelines.js +1 -0
- package/dist/es2017/transports/utils/parse_text_track.d.ts.map +1 -1
- package/dist/es2017/transports/utils/parse_text_track.js +1 -0
- package/dist/rx-player.js +101 -23
- package/dist/rx-player.min.js +14 -14
- package/dist/worker.js +6 -6
- package/package.json +38 -21
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/compat/patch_webkit_source_buffer.ts +0 -3
- package/src/config.ts +10 -2
- package/src/core/main/worker/worker_main.ts +8 -0
- package/src/core/stream/adaptation/adaptation_stream.ts +41 -8
- package/src/core/stream/adaptation/index.ts +1 -1
- package/src/core/stream/period/index.ts +1 -1
- package/src/core/stream/representation/index.ts +1 -1
- package/src/core/stream/representation/representation_stream.ts +11 -0
- package/src/core/stream/representation/utils/append_segment_to_buffer.ts +6 -2
- package/src/default_config.ts +21 -0
- package/src/main_thread/api/public_api.ts +19 -2
- package/src/main_thread/decrypt/content_decryptor.ts +2 -0
- package/src/main_thread/decrypt/get_media_keys.ts +1 -0
- package/src/main_thread/init/directfile_content_initializer.ts +20 -10
- package/src/main_thread/init/multi_thread_content_initializer.ts +94 -61
- package/src/main_thread/init/utils/initial_seek_and_play.ts +24 -5
- package/src/mse/main_media_source_interface.ts +20 -0
- package/src/multithread_types.ts +9 -0
- package/src/parsers/manifest/index.ts +1 -1
- package/src/transports/smooth/pipelines.ts +1 -0
- package/src/transports/utils/parse_text_track.ts +1 -0
|
@@ -85,10 +85,8 @@ export default function patchWebkitSourceBuffer(): void {
|
|
|
85
85
|
val: unknown,
|
|
86
86
|
) {
|
|
87
87
|
queueMicrotask(() => {
|
|
88
|
-
// @ts-expect-error: trigger is normally protected
|
|
89
88
|
this.trigger(eventName, val as Event);
|
|
90
89
|
this.updating = false;
|
|
91
|
-
// @ts-expect-error: trigger is normally protected
|
|
92
90
|
this.trigger("updateend", new Event("updateend"));
|
|
93
91
|
});
|
|
94
92
|
};
|
|
@@ -97,7 +95,6 @@ export default function patchWebkitSourceBuffer(): void {
|
|
|
97
95
|
if (this.updating) {
|
|
98
96
|
throw new Error("updating");
|
|
99
97
|
}
|
|
100
|
-
// @ts-expect-error: trigger is normally protected
|
|
101
98
|
this.trigger("updatestart", new Event("updatestart"));
|
|
102
99
|
this.updating = true;
|
|
103
100
|
try {
|
package/src/config.ts
CHANGED
|
@@ -9,13 +9,21 @@
|
|
|
9
9
|
import type { IDefaultConfig } from "./default_config";
|
|
10
10
|
import DEFAULT_CONFIG from "./default_config";
|
|
11
11
|
import deepMerge from "./utils/deep_merge";
|
|
12
|
+
import EventEmitter from "./utils/event_emitter";
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
interface IConfigHandlerEvents {
|
|
15
|
+
update: Partial<IDefaultConfig>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class ConfigHandler extends EventEmitter<IConfigHandlerEvents> {
|
|
19
|
+
public updated = false;
|
|
20
|
+
private _config = DEFAULT_CONFIG;
|
|
15
21
|
|
|
16
22
|
update(config: Partial<IDefaultConfig>) {
|
|
17
23
|
const newConfig = deepMerge(this._config, config);
|
|
18
24
|
this._config = newConfig;
|
|
25
|
+
this.updated = true;
|
|
26
|
+
this.trigger("update", config);
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
getCurrent(): IDefaultConfig {
|
|
@@ -80,6 +80,9 @@ export default function initializeWorkerMain() {
|
|
|
80
80
|
*/
|
|
81
81
|
let playbackObservationRef: SharedReference<IWorkerPlaybackObservation> | null = null;
|
|
82
82
|
|
|
83
|
+
onmessageerror = (_msg: MessageEvent) => {
|
|
84
|
+
log.error("MTCI: Error when receiving message from main thread.");
|
|
85
|
+
};
|
|
83
86
|
onmessage = function (e: MessageEvent<IMainThreadMessage>) {
|
|
84
87
|
log.debug("Worker: received message", e.data.type);
|
|
85
88
|
|
|
@@ -401,6 +404,11 @@ export default function initializeWorkerMain() {
|
|
|
401
404
|
break;
|
|
402
405
|
}
|
|
403
406
|
|
|
407
|
+
case MainThreadMessageType.ConfigUpdate: {
|
|
408
|
+
config.update(msg.value);
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
|
|
404
412
|
default:
|
|
405
413
|
assertUnreachable(msg);
|
|
406
414
|
}
|
|
@@ -376,15 +376,21 @@ export default function AdaptationStream(
|
|
|
376
376
|
representationStreamCallbacks: IRepresentationStreamCallbacks,
|
|
377
377
|
fnCancelSignal: CancellationSignal,
|
|
378
378
|
): void {
|
|
379
|
+
/** Set to `true` if we've encountered an error with this `RepresentationStream` */
|
|
380
|
+
let hasEncounteredError = false;
|
|
381
|
+
|
|
379
382
|
const bufferGoalCanceller = new TaskCanceller();
|
|
380
383
|
bufferGoalCanceller.linkToSignal(fnCancelSignal);
|
|
384
|
+
|
|
385
|
+
/** Actually built buffer size, in seconds. */
|
|
381
386
|
const bufferGoal = createMappedReference(
|
|
382
387
|
wantedBufferAhead,
|
|
383
388
|
(prev) => {
|
|
384
|
-
return
|
|
389
|
+
return getBufferGoal(representation, prev);
|
|
385
390
|
},
|
|
386
391
|
bufferGoalCanceller.signal,
|
|
387
392
|
);
|
|
393
|
+
|
|
388
394
|
const maxBufferSize =
|
|
389
395
|
adaptation.type === "video" ? maxVideoBufferSize : new SharedReference(Infinity);
|
|
390
396
|
log.info(
|
|
@@ -394,7 +400,18 @@ export default function AdaptationStream(
|
|
|
394
400
|
representation.bitrate,
|
|
395
401
|
);
|
|
396
402
|
const updatedCallbacks = objectAssign({}, representationStreamCallbacks, {
|
|
397
|
-
error(err:
|
|
403
|
+
error(err: Error) {
|
|
404
|
+
if (hasEncounteredError) {
|
|
405
|
+
// A RepresentationStream might trigger multiple Errors (for example
|
|
406
|
+
// multiple segments it tried to push at once led to errors).
|
|
407
|
+
// In that case, we'll only consider the first Error.
|
|
408
|
+
//
|
|
409
|
+
// That could mean that we're hiding legitimate issues but handling
|
|
410
|
+
// multiple of those errors at once is too hard a task for now.
|
|
411
|
+
log.warn("Stream: Ignoring RepresentationStream error", err);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
hasEncounteredError = true;
|
|
398
415
|
const formattedError = formatError(err, {
|
|
399
416
|
defaultCode: "NONE",
|
|
400
417
|
defaultReason: "Unknown `RepresentationStream` error",
|
|
@@ -402,14 +419,20 @@ export default function AdaptationStream(
|
|
|
402
419
|
if (formattedError.code !== "BUFFER_FULL_ERROR") {
|
|
403
420
|
representationStreamCallbacks.error(err);
|
|
404
421
|
} else {
|
|
422
|
+
log.warn(
|
|
423
|
+
"Stream: received BUFFER_FULL_ERROR",
|
|
424
|
+
adaptation.type,
|
|
425
|
+
representation.bitrate,
|
|
426
|
+
);
|
|
405
427
|
const wba = wantedBufferAhead.getValue();
|
|
406
428
|
const lastBufferGoalRatio = bufferGoalRatioMap.get(representation.id) ?? 1;
|
|
407
429
|
// 70%, 49%, 34.3%, 24%, 16.81%, 11.76%, 8.24% and 5.76%
|
|
408
430
|
const newBufferGoalRatio = lastBufferGoalRatio * 0.7;
|
|
409
|
-
if (newBufferGoalRatio <= 0.05 || wba * newBufferGoalRatio <= 2) {
|
|
410
|
-
throw formattedError;
|
|
411
|
-
}
|
|
412
431
|
bufferGoalRatioMap.set(representation.id, newBufferGoalRatio);
|
|
432
|
+
if (newBufferGoalRatio <= 0.05 || getBufferGoal(representation, wba) <= 2) {
|
|
433
|
+
representationStreamCallbacks.error(formattedError);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
413
436
|
|
|
414
437
|
// We wait 4 seconds to let the situation evolve by itself before
|
|
415
438
|
// retrying loading segments with a lower buffer goal
|
|
@@ -481,15 +504,25 @@ export default function AdaptationStream(
|
|
|
481
504
|
}
|
|
482
505
|
|
|
483
506
|
/**
|
|
484
|
-
*
|
|
507
|
+
* Returns how much media data should be pre-buffered for this
|
|
508
|
+
* `Representation`, according to the `wantedBufferAhead` setting and previous
|
|
509
|
+
* issues encountered with that `Representation`.
|
|
510
|
+
* @param {Object} representation - The `Representation` you want to buffer.
|
|
511
|
+
* @param {number} wba - The value of `wantedBufferAhead` set by the user.
|
|
485
512
|
* @returns {number}
|
|
486
513
|
*/
|
|
487
|
-
function
|
|
514
|
+
function getBufferGoal(representation: IRepresentation, wba: number): number {
|
|
488
515
|
const oldBufferGoalRatio = bufferGoalRatioMap.get(representation.id);
|
|
489
516
|
const bufferGoalRatio = oldBufferGoalRatio !== undefined ? oldBufferGoalRatio : 1;
|
|
490
517
|
if (oldBufferGoalRatio === undefined) {
|
|
491
518
|
bufferGoalRatioMap.set(representation.id, bufferGoalRatio);
|
|
492
519
|
}
|
|
493
|
-
|
|
520
|
+
if (bufferGoalRatio < 1 && wba === Infinity) {
|
|
521
|
+
// When `wba` is equal to `Infinity`, dividing it will still make it equal
|
|
522
|
+
// to `Infinity`. To make the `bufferGoalRatio` still have an effect, we
|
|
523
|
+
// just starts from a `wba` set to the high value of 5 minutes.
|
|
524
|
+
return 5 * 60 * 1000 * bufferGoalRatio;
|
|
525
|
+
}
|
|
526
|
+
return wba * bufferGoalRatio;
|
|
494
527
|
}
|
|
495
528
|
}
|
|
@@ -90,6 +90,11 @@ export default function RepresentationStream<TSegmentDataType>(
|
|
|
90
90
|
callbacks: IRepresentationStreamCallbacks,
|
|
91
91
|
parentCancelSignal: CancellationSignal,
|
|
92
92
|
): void {
|
|
93
|
+
log.debug(
|
|
94
|
+
"Stream: Creating RepresentationStream",
|
|
95
|
+
content.adaptation.type,
|
|
96
|
+
content.representation.bitrate,
|
|
97
|
+
);
|
|
93
98
|
const { period, adaptation, representation } = content;
|
|
94
99
|
const { bufferGoal, maxBufferSize, drmSystemId, fastSwitchThreshold } = options;
|
|
95
100
|
const bufferType = adaptation.type;
|
|
@@ -488,6 +493,12 @@ export default function RepresentationStream<TSegmentDataType>(
|
|
|
488
493
|
// We can thus ignore it, it is very unlikely to lead to true buffer issues.
|
|
489
494
|
return;
|
|
490
495
|
}
|
|
496
|
+
log.warn(
|
|
497
|
+
"Stream: Received fatal buffer error",
|
|
498
|
+
adaptation.type,
|
|
499
|
+
representation.bitrate,
|
|
500
|
+
err instanceof Error ? err : null,
|
|
501
|
+
);
|
|
491
502
|
globalCanceller.cancel();
|
|
492
503
|
callbacks.error(err);
|
|
493
504
|
}
|
|
@@ -72,8 +72,12 @@ export default async function appendSegmentToBuffer<T>(
|
|
|
72
72
|
log.warn("Stream: Running garbage collector");
|
|
73
73
|
const start = Math.max(currentPos - 5, 0);
|
|
74
74
|
const end = currentPos + bufferGoal.getValue() + 12;
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
if (start > 0) {
|
|
76
|
+
await segmentSink.removeBuffer(0, start);
|
|
77
|
+
}
|
|
78
|
+
if (end < Number.MAX_VALUE) {
|
|
79
|
+
await segmentSink.removeBuffer(end, Number.MAX_VALUE);
|
|
80
|
+
}
|
|
77
81
|
await sleep(200);
|
|
78
82
|
if (cancellationSignal.cancellationError !== null) {
|
|
79
83
|
throw cancellationSignal.cancellationError;
|
package/src/default_config.ts
CHANGED
|
@@ -1187,3 +1187,24 @@ const DEFAULT_CONFIG = {
|
|
|
1187
1187
|
|
|
1188
1188
|
export type IDefaultConfig = typeof DEFAULT_CONFIG;
|
|
1189
1189
|
export default DEFAULT_CONFIG;
|
|
1190
|
+
|
|
1191
|
+
// NOTE Because the config may have to be serialized and shared between several
|
|
1192
|
+
// environments, we check here that some strict type is respected:
|
|
1193
|
+
// - only a subset of types are authorized for now, just make it easier to
|
|
1194
|
+
// reason about.
|
|
1195
|
+
// - Needs to make sense in JSON: no `function`, no `Date`, no `undefined`...
|
|
1196
|
+
interface IGenericConfigData {
|
|
1197
|
+
[key: string]:
|
|
1198
|
+
| string
|
|
1199
|
+
| number
|
|
1200
|
+
| boolean
|
|
1201
|
+
| number[]
|
|
1202
|
+
| string[]
|
|
1203
|
+
| Partial<Record<string, string[]>>
|
|
1204
|
+
| IGenericConfigData;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function checkIsSerializable(_conf: IGenericConfigData): void {
|
|
1208
|
+
// noop
|
|
1209
|
+
}
|
|
1210
|
+
checkIsSerializable(DEFAULT_CONFIG);
|
|
@@ -32,6 +32,7 @@ import getStartDate from "../../compat/get_start_date";
|
|
|
32
32
|
import hasMseInWorker from "../../compat/has_mse_in_worker";
|
|
33
33
|
import hasWorkerApi from "../../compat/has_worker_api";
|
|
34
34
|
import isDebugModeEnabled from "../../compat/is_debug_mode_enabled";
|
|
35
|
+
import config from "../../config";
|
|
35
36
|
import type { ISegmentSinkMetrics } from "../../core/segment_sinks/segment_buffers_store";
|
|
36
37
|
import type {
|
|
37
38
|
IAdaptationChoice,
|
|
@@ -39,6 +40,7 @@ import type {
|
|
|
39
40
|
IABRThrottlers,
|
|
40
41
|
IBufferType,
|
|
41
42
|
} from "../../core/types";
|
|
43
|
+
import type { IDefaultConfig } from "../../default_config";
|
|
42
44
|
import type { IErrorCode, IErrorType } from "../../errors";
|
|
43
45
|
import { ErrorCodes, ErrorTypes, formatError, MediaError } from "../../errors";
|
|
44
46
|
import WorkerInitializationError from "../../errors/worker_initialization_error";
|
|
@@ -409,7 +411,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
409
411
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
|
|
410
412
|
videoElement.preload = "auto";
|
|
411
413
|
|
|
412
|
-
this.version = /* PLAYER_VERSION */ "4.2.0-dev.
|
|
414
|
+
this.version = /* PLAYER_VERSION */ "4.2.0-dev.2024100200";
|
|
413
415
|
this.log = log;
|
|
414
416
|
this.state = "STOPPED";
|
|
415
417
|
this.videoElement = videoElement;
|
|
@@ -573,6 +575,21 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
573
575
|
},
|
|
574
576
|
this._destroyCanceller.signal,
|
|
575
577
|
);
|
|
578
|
+
|
|
579
|
+
const sendConfigUpdates = (updates: Partial<IDefaultConfig>) => {
|
|
580
|
+
if (this._priv_worker === null) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
log.debug("---> Sending To Worker:", MainThreadMessageType.ConfigUpdate);
|
|
584
|
+
this._priv_worker.postMessage({
|
|
585
|
+
type: MainThreadMessageType.ConfigUpdate,
|
|
586
|
+
value: updates,
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
if (config.updated) {
|
|
590
|
+
sendConfigUpdates(config.getCurrent());
|
|
591
|
+
}
|
|
592
|
+
config.addEventListener("update", sendConfigUpdates, this._destroyCanceller.signal);
|
|
576
593
|
});
|
|
577
594
|
}
|
|
578
595
|
|
|
@@ -3313,7 +3330,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
3313
3330
|
}
|
|
3314
3331
|
}
|
|
3315
3332
|
}
|
|
3316
|
-
Player.version = /* PLAYER_VERSION */ "4.2.0-dev.
|
|
3333
|
+
Player.version = /* PLAYER_VERSION */ "4.2.0-dev.2024100200";
|
|
3317
3334
|
|
|
3318
3335
|
/** Every events sent by the RxPlayer's public API. */
|
|
3319
3336
|
interface IPublicAPIEvent {
|
|
@@ -208,6 +208,7 @@ export default class ContentDecryptor extends EventEmitter<IContentDecryptorEven
|
|
|
208
208
|
|
|
209
209
|
this.systemId = systemId;
|
|
210
210
|
if (this._stateData.state === ContentDecryptorState.Initializing) {
|
|
211
|
+
log.debug("DRM: Waiting for attachment.");
|
|
211
212
|
this._stateData = {
|
|
212
213
|
state: ContentDecryptorState.WaitingForAttachment,
|
|
213
214
|
isInitDataQueueLocked: true,
|
|
@@ -260,6 +261,7 @@ export default class ContentDecryptor extends EventEmitter<IContentDecryptorEven
|
|
|
260
261
|
const shouldDisableLock = options.disableMediaKeysAttachmentLock === true;
|
|
261
262
|
|
|
262
263
|
if (shouldDisableLock) {
|
|
264
|
+
log.debug("DRM: disabling MediaKeys attachment lock. Ready for content");
|
|
263
265
|
this._stateData = {
|
|
264
266
|
state: ContentDecryptorState.ReadyForContent,
|
|
265
267
|
isInitDataQueueLocked: true,
|
|
@@ -102,6 +102,7 @@ export default async function getMediaKeysInfos(
|
|
|
102
102
|
currentState !== null &&
|
|
103
103
|
evt.type === "reuse-media-key-system-access"
|
|
104
104
|
) {
|
|
105
|
+
log.debug("DRM: Reusing already created MediaKeys");
|
|
105
106
|
const { mediaKeys, loadedSessionsStore } = currentState;
|
|
106
107
|
|
|
107
108
|
// We might just rely on the currently attached MediaKeys instance.
|
|
@@ -254,7 +254,7 @@ export default class DirectFileContentInitializer extends ContentInitializer {
|
|
|
254
254
|
function getDirectFileInitialTime(
|
|
255
255
|
mediaElement: IMediaElement,
|
|
256
256
|
startAt?: IInitialTimeOptions,
|
|
257
|
-
): number {
|
|
257
|
+
): number | undefined {
|
|
258
258
|
if (isNullOrUndefined(startAt)) {
|
|
259
259
|
return 0;
|
|
260
260
|
}
|
|
@@ -268,28 +268,38 @@ function getDirectFileInitialTime(
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
const duration = mediaElement.duration;
|
|
271
|
-
|
|
272
271
|
if (typeof startAt.fromLastPosition === "number") {
|
|
273
|
-
if (isNullOrUndefined(duration)
|
|
274
|
-
|
|
275
|
-
"startAt.fromLastPosition set but no known duration, " + "beginning at 0.",
|
|
276
|
-
);
|
|
277
|
-
return 0;
|
|
272
|
+
if (!isNullOrUndefined(duration) && isFinite(duration)) {
|
|
273
|
+
return Math.max(0, duration + startAt.fromLastPosition);
|
|
278
274
|
}
|
|
279
|
-
|
|
275
|
+
|
|
276
|
+
if (mediaElement.seekable.length > 0) {
|
|
277
|
+
const lastSegmentEnd = mediaElement.seekable.end(mediaElement.seekable.length - 1);
|
|
278
|
+
if (isFinite(lastSegmentEnd)) {
|
|
279
|
+
return Math.max(0, lastSegmentEnd + startAt.fromLastPosition);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
log.warn(
|
|
283
|
+
"Init: startAt.fromLastPosition set but no known duration, " +
|
|
284
|
+
"it may be too soon to seek",
|
|
285
|
+
);
|
|
286
|
+
return undefined;
|
|
280
287
|
} else if (typeof startAt.fromLivePosition === "number") {
|
|
281
288
|
const livePosition =
|
|
282
289
|
mediaElement.seekable.length > 0 ? mediaElement.seekable.end(0) : duration;
|
|
283
290
|
if (isNullOrUndefined(livePosition)) {
|
|
284
291
|
log.warn(
|
|
285
|
-
"startAt.fromLivePosition set but no known live position, " +
|
|
292
|
+
"Init: startAt.fromLivePosition set but no known live position, " +
|
|
293
|
+
"beginning at 0.",
|
|
286
294
|
);
|
|
287
295
|
return 0;
|
|
288
296
|
}
|
|
289
297
|
return Math.max(0, livePosition + startAt.fromLivePosition);
|
|
290
298
|
} else if (!isNullOrUndefined(startAt.percentage)) {
|
|
291
299
|
if (isNullOrUndefined(duration) || !isFinite(duration)) {
|
|
292
|
-
log.warn(
|
|
300
|
+
log.warn(
|
|
301
|
+
"Init: startAt.percentage set but no known duration, " + "beginning at 0.",
|
|
302
|
+
);
|
|
293
303
|
return 0;
|
|
294
304
|
}
|
|
295
305
|
const { percentage } = startAt;
|
|
@@ -78,6 +78,20 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
78
78
|
/** Constructor settings associated to this `MultiThreadContentInitializer`. */
|
|
79
79
|
private _settings: IInitializeArguments;
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* The WebWorker may be sending messages as soon as we're preparing the
|
|
83
|
+
* content but the `MultiThreadContentInitializer` is only able to handle all of
|
|
84
|
+
* them only once `start`ed.
|
|
85
|
+
*
|
|
86
|
+
* As such `_queuedWorkerMessages` is set to an Array when `prepare` has been
|
|
87
|
+
* called but not `start` yet, and contains all worker messages that have to
|
|
88
|
+
* be processed when `start` is called.
|
|
89
|
+
*
|
|
90
|
+
* It is set to `null` when there's no need to rely on that queue (either not
|
|
91
|
+
* yet `prepare`d or already `start`ed).
|
|
92
|
+
*/
|
|
93
|
+
private _queuedWorkerMessages: MessageEvent[] | null;
|
|
94
|
+
|
|
81
95
|
/**
|
|
82
96
|
* Information relative to the current loaded content.
|
|
83
97
|
*
|
|
@@ -123,6 +137,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
123
137
|
lastMessageId: 0,
|
|
124
138
|
resolvers: {},
|
|
125
139
|
};
|
|
140
|
+
this._queuedWorkerMessages = null;
|
|
126
141
|
}
|
|
127
142
|
|
|
128
143
|
/**
|
|
@@ -176,6 +191,66 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
176
191
|
if (this._initCanceller.isUsed()) {
|
|
177
192
|
return;
|
|
178
193
|
}
|
|
194
|
+
this._queuedWorkerMessages = [];
|
|
195
|
+
log.debug("MTCI: addEventListener prepare buffering worker messages");
|
|
196
|
+
const onmessage = (evt: MessageEvent): void => {
|
|
197
|
+
const msgData = evt.data as unknown as IWorkerMessage;
|
|
198
|
+
const type = msgData.type;
|
|
199
|
+
switch (type) {
|
|
200
|
+
case WorkerMessageType.LogMessage: {
|
|
201
|
+
const formatted = msgData.value.logs.map((l) => {
|
|
202
|
+
switch (typeof l) {
|
|
203
|
+
case "string":
|
|
204
|
+
case "number":
|
|
205
|
+
case "boolean":
|
|
206
|
+
case "undefined":
|
|
207
|
+
return l;
|
|
208
|
+
case "object":
|
|
209
|
+
if (l === null) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return formatWorkerError(l);
|
|
213
|
+
default:
|
|
214
|
+
assertUnreachable(l);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
switch (msgData.value.logLevel) {
|
|
218
|
+
case "NONE":
|
|
219
|
+
break;
|
|
220
|
+
case "ERROR":
|
|
221
|
+
log.error(...formatted);
|
|
222
|
+
break;
|
|
223
|
+
case "WARNING":
|
|
224
|
+
log.warn(...formatted);
|
|
225
|
+
break;
|
|
226
|
+
case "INFO":
|
|
227
|
+
log.info(...formatted);
|
|
228
|
+
break;
|
|
229
|
+
case "DEBUG":
|
|
230
|
+
log.debug(...formatted);
|
|
231
|
+
break;
|
|
232
|
+
default:
|
|
233
|
+
assertUnreachable(msgData.value.logLevel);
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
default:
|
|
238
|
+
if (this._queuedWorkerMessages !== null) {
|
|
239
|
+
this._queuedWorkerMessages.push(evt);
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
this._settings.worker.addEventListener("message", onmessage);
|
|
245
|
+
const onmessageerror = (_msg: MessageEvent) => {
|
|
246
|
+
log.error("MTCI: Error when receiving message from worker.");
|
|
247
|
+
};
|
|
248
|
+
this._settings.worker.addEventListener("messageerror", onmessageerror);
|
|
249
|
+
this._initCanceller.signal.register(() => {
|
|
250
|
+
log.debug("MTCI: removeEventListener prepare for worker message");
|
|
251
|
+
this._settings.worker.removeEventListener("message", onmessage);
|
|
252
|
+
this._settings.worker.removeEventListener("messageerror", onmessageerror);
|
|
253
|
+
});
|
|
179
254
|
|
|
180
255
|
// Also bind all `SharedReference` objects:
|
|
181
256
|
|
|
@@ -1042,49 +1117,6 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
1042
1117
|
}
|
|
1043
1118
|
break;
|
|
1044
1119
|
|
|
1045
|
-
case WorkerMessageType.LogMessage: {
|
|
1046
|
-
const formatted = msgData.value.logs.map((l) => {
|
|
1047
|
-
switch (typeof l) {
|
|
1048
|
-
case "string":
|
|
1049
|
-
case "number":
|
|
1050
|
-
case "boolean":
|
|
1051
|
-
case "undefined":
|
|
1052
|
-
return l;
|
|
1053
|
-
case "object":
|
|
1054
|
-
if (l === null) {
|
|
1055
|
-
return null;
|
|
1056
|
-
}
|
|
1057
|
-
return formatWorkerError(l);
|
|
1058
|
-
default:
|
|
1059
|
-
assertUnreachable(l);
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
switch (msgData.value.logLevel) {
|
|
1063
|
-
case "NONE":
|
|
1064
|
-
break;
|
|
1065
|
-
case "ERROR":
|
|
1066
|
-
log.error(...formatted);
|
|
1067
|
-
break;
|
|
1068
|
-
case "WARNING":
|
|
1069
|
-
log.warn(...formatted);
|
|
1070
|
-
break;
|
|
1071
|
-
case "INFO":
|
|
1072
|
-
log.info(...formatted);
|
|
1073
|
-
break;
|
|
1074
|
-
case "DEBUG":
|
|
1075
|
-
log.debug(...formatted);
|
|
1076
|
-
break;
|
|
1077
|
-
default:
|
|
1078
|
-
assertUnreachable(msgData.value.logLevel);
|
|
1079
|
-
}
|
|
1080
|
-
break;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
case WorkerMessageType.InitSuccess:
|
|
1084
|
-
case WorkerMessageType.InitError:
|
|
1085
|
-
// Should already be handled by the API
|
|
1086
|
-
break;
|
|
1087
|
-
|
|
1088
1120
|
case WorkerMessageType.SegmentSinkStoreUpdate: {
|
|
1089
1121
|
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1090
1122
|
return;
|
|
@@ -1098,13 +1130,32 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
1098
1130
|
}
|
|
1099
1131
|
break;
|
|
1100
1132
|
}
|
|
1133
|
+
|
|
1134
|
+
case WorkerMessageType.InitSuccess:
|
|
1135
|
+
case WorkerMessageType.InitError:
|
|
1136
|
+
// Should already be handled by the API
|
|
1137
|
+
break;
|
|
1138
|
+
|
|
1139
|
+
case WorkerMessageType.LogMessage:
|
|
1140
|
+
// Already handled by prepare's handler
|
|
1141
|
+
break;
|
|
1101
1142
|
default:
|
|
1102
1143
|
assertUnreachable(msgData);
|
|
1103
1144
|
}
|
|
1104
1145
|
};
|
|
1105
1146
|
|
|
1147
|
+
log.debug("MTCI: addEventListener for worker message");
|
|
1148
|
+
if (this._queuedWorkerMessages !== null) {
|
|
1149
|
+
const bufferedMessages = this._queuedWorkerMessages.slice();
|
|
1150
|
+
log.debug("MTCI: Processing buffered messages", bufferedMessages.length);
|
|
1151
|
+
for (const message of bufferedMessages) {
|
|
1152
|
+
onmessage(message);
|
|
1153
|
+
}
|
|
1154
|
+
this._queuedWorkerMessages = null;
|
|
1155
|
+
}
|
|
1106
1156
|
this._settings.worker.addEventListener("message", onmessage);
|
|
1107
1157
|
this._initCanceller.signal.register(() => {
|
|
1158
|
+
log.debug("MTCI: removeEventListener for worker message");
|
|
1108
1159
|
this._settings.worker.removeEventListener("message", onmessage);
|
|
1109
1160
|
});
|
|
1110
1161
|
}
|
|
@@ -2006,24 +2057,6 @@ type IDecryptionInitializationState =
|
|
|
2006
2057
|
* `HTMLMediaElement` (such as linking a content / `MediaSource` to it).
|
|
2007
2058
|
*/
|
|
2008
2059
|
| { type: "uninitialized"; value: null }
|
|
2009
|
-
/**
|
|
2010
|
-
* The `MediaSource` or media url has to be linked to the `HTMLMediaElement`
|
|
2011
|
-
* before continuing.
|
|
2012
|
-
* Once it has been linked with success (e.g. the `MediaSource` has "opened"),
|
|
2013
|
-
* the `isMediaLinked` `SharedReference` should be set to `true`.
|
|
2014
|
-
*
|
|
2015
|
-
* In the `MediaSource` case, you should wait until the `"initialized"`
|
|
2016
|
-
* state before pushing segment.
|
|
2017
|
-
*
|
|
2018
|
-
* Note that the `"awaiting-media-link"` is an optional state. It can be
|
|
2019
|
-
* skipped to directly `"initialized"` instead.
|
|
2020
|
-
*/
|
|
2021
|
-
| {
|
|
2022
|
-
type: "awaiting-media-link";
|
|
2023
|
-
value: {
|
|
2024
|
-
isMediaLinked: SharedReference<boolean>;
|
|
2025
|
-
};
|
|
2026
|
-
}
|
|
2027
2060
|
/**
|
|
2028
2061
|
* The `MediaSource` or media url can be linked AND segments can be pushed to
|
|
2029
2062
|
* the `HTMLMediaElement` on which decryption capabilities were wanted.
|
|
@@ -71,7 +71,7 @@ export default function performInitialSeekAndPlay(
|
|
|
71
71
|
}: {
|
|
72
72
|
mediaElement: IMediaElement;
|
|
73
73
|
playbackObserver: IMediaElementPlaybackObserver;
|
|
74
|
-
startTime: number | (() => number);
|
|
74
|
+
startTime: number | (() => number | undefined);
|
|
75
75
|
mustAutoPlay: boolean;
|
|
76
76
|
isDirectfile: boolean;
|
|
77
77
|
onWarning: (err: IPlayerError) => void;
|
|
@@ -107,18 +107,37 @@ export default function performInitialSeekAndPlay(
|
|
|
107
107
|
if (!isDirectfile || typeof startTime === "number") {
|
|
108
108
|
const initiallySeekedTime =
|
|
109
109
|
typeof startTime === "number" ? startTime : startTime();
|
|
110
|
-
if (initiallySeekedTime !== 0) {
|
|
110
|
+
if (initiallySeekedTime !== 0 && initiallySeekedTime !== undefined) {
|
|
111
111
|
performInitialSeek(initiallySeekedTime);
|
|
112
112
|
}
|
|
113
113
|
waitForSeekable();
|
|
114
114
|
} else {
|
|
115
115
|
playbackObserver.listen(
|
|
116
116
|
(obs, stopListening) => {
|
|
117
|
+
const initiallySeekedTime =
|
|
118
|
+
typeof startTime === "number" ? startTime : startTime();
|
|
119
|
+
if (
|
|
120
|
+
initiallySeekedTime === undefined &&
|
|
121
|
+
obs.readyState < HTMLMediaElement.HAVE_CURRENT_DATA
|
|
122
|
+
) {
|
|
123
|
+
/**
|
|
124
|
+
* On browser, such as Safari, the HTMLMediaElement.duration
|
|
125
|
+
* and HTMLMediaElement.buffered may not be initialized at readyState 1, leading
|
|
126
|
+
* to cases where it can be equal to `Infinity`.
|
|
127
|
+
* If so, the range in which it is possible to seek is not yet known.
|
|
128
|
+
* To solve this, the seek should be done after readyState HAVE_CURRENT_DATA (2),
|
|
129
|
+
* at that time the previously mentioned attributes are correctly initialized and
|
|
130
|
+
* the range in which it is possible to seek is correctly known.
|
|
131
|
+
* If the initiallySeekedTime is still `undefined` when the readyState is >= 2,
|
|
132
|
+
* let assume that the initiallySeekedTime will never be known and continue
|
|
133
|
+
* the logic without seeking.
|
|
134
|
+
*/
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
117
137
|
if (obs.readyState >= 1) {
|
|
118
138
|
stopListening();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (initiallySeekedTime !== 0) {
|
|
139
|
+
|
|
140
|
+
if (initiallySeekedTime !== 0 && initiallySeekedTime !== undefined) {
|
|
122
141
|
if (canSeekDirectlyAfterLoadedMetadata) {
|
|
123
142
|
performInitialSeek(initiallySeekedTime);
|
|
124
143
|
} else {
|