rx-player 4.2.0-dev.2024092400 → 4.2.0-dev.2024101500
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/.vscode/settings.json +9 -0
- package/CHANGELOG.md +22 -3
- package/VERSION +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +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/core/main/worker/worker_main.d.ts.map +1 -1
- package/dist/commonjs/core/main/worker/worker_main.js +3 -0
- package/dist/commonjs/core/segment_sinks/segment_buffers_store.d.ts.map +1 -1
- package/dist/commonjs/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
- package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +15 -0
- 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/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.d.ts.map +1 -1
- package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.js +85 -8
- package/dist/commonjs/main_thread/api/debug/modules/general_info.js +1 -1
- package/dist/commonjs/main_thread/api/public_api.js +2 -2
- package/dist/commonjs/main_thread/decrypt/find_key_system.d.ts.map +1 -1
- package/dist/commonjs/main_thread/decrypt/find_key_system.js +3 -0
- 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.d.ts +11 -1
- package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/init/media_source_content_initializer.js +23 -11
- package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts +15 -1
- 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 +103 -50
- 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/main_thread/text_displayer/native/native_text_displayer.d.ts +1 -0
- package/dist/commonjs/main_thread/text_displayer/native/native_text_displayer.d.ts.map +1 -1
- package/dist/commonjs/main_thread/text_displayer/native/native_text_displayer.js +19 -16
- 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/transports/smooth/pipelines.d.ts.map +1 -1
- package/dist/commonjs/transports/smooth/pipelines.js +1 -0
- 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/sync_or_async.d.ts.map +1 -1
- package/dist/commonjs/utils/sync_or_async.js +3 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
- 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/core/main/worker/worker_main.d.ts.map +1 -1
- package/dist/es2017/core/main/worker/worker_main.js +3 -0
- package/dist/es2017/core/segment_sinks/segment_buffers_store.d.ts.map +1 -1
- package/dist/es2017/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
- package/dist/es2017/core/stream/adaptation/adaptation_stream.js +15 -0
- 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/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.d.ts.map +1 -1
- package/dist/es2017/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.js +36 -7
- package/dist/es2017/main_thread/api/debug/modules/general_info.js +1 -1
- package/dist/es2017/main_thread/api/public_api.js +2 -2
- package/dist/es2017/main_thread/decrypt/find_key_system.d.ts.map +1 -1
- package/dist/es2017/main_thread/decrypt/find_key_system.js +3 -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/media_source_content_initializer.d.ts +11 -1
- package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
- package/dist/es2017/main_thread/init/media_source_content_initializer.js +23 -11
- package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts +15 -1
- 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 +87 -46
- 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/main_thread/text_displayer/native/native_text_displayer.d.ts +1 -0
- package/dist/es2017/main_thread/text_displayer/native/native_text_displayer.d.ts.map +1 -1
- package/dist/es2017/main_thread/text_displayer/native/native_text_displayer.js +19 -16
- 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/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/es2017/utils/sync_or_async.d.ts.map +1 -1
- package/dist/es2017/utils/sync_or_async.js +3 -1
- package/dist/mpd-parser.wasm +0 -0
- package/dist/rx-player.js +104 -41
- package/dist/rx-player.min.js +17 -17
- package/dist/worker.js +5 -5
- package/package.json +19 -7
- package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/core/main/worker/worker_main.ts +3 -0
- package/src/core/segment_sinks/segment_buffers_store.ts +6 -2
- package/src/core/stream/adaptation/adaptation_stream.ts +23 -1
- package/src/core/stream/representation/representation_stream.ts +11 -0
- package/src/experimental/tools/mediaCapabilitiesProber/probers/DRMInfos.ts +39 -7
- package/src/main_thread/api/debug/modules/general_info.ts +1 -1
- package/src/main_thread/api/public_api.ts +2 -2
- package/src/main_thread/decrypt/find_key_system.ts +3 -0
- package/src/main_thread/init/directfile_content_initializer.ts +20 -10
- package/src/main_thread/init/media_source_content_initializer.ts +50 -17
- package/src/main_thread/init/multi_thread_content_initializer.ts +109 -50
- package/src/main_thread/init/utils/initial_seek_and_play.ts +24 -5
- package/src/main_thread/text_displayer/native/native_text_displayer.ts +22 -18
- package/src/mse/main_media_source_interface.ts +20 -0
- package/src/transports/smooth/pipelines.ts +1 -0
- package/src/transports/utils/parse_text_track.ts +1 -0
- package/src/utils/sync_or_async.ts +5 -3
|
@@ -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
|
|
|
@@ -103,7 +103,11 @@ export default class SegmentSinksStore {
|
|
|
103
103
|
* disabled. This means that the corresponding type (e.g. audio, video etc.)
|
|
104
104
|
* won't be needed when playing the current content.
|
|
105
105
|
*/
|
|
106
|
-
private _initializedSegmentSinks:
|
|
106
|
+
private _initializedSegmentSinks: {
|
|
107
|
+
audio?: AudioVideoSegmentSink | undefined | null;
|
|
108
|
+
video?: AudioVideoSegmentSink | undefined | null;
|
|
109
|
+
text?: TextSegmentSink | null;
|
|
110
|
+
};
|
|
107
111
|
|
|
108
112
|
/**
|
|
109
113
|
* Callbacks called after a SourceBuffer is either created or disabled.
|
|
@@ -308,7 +312,7 @@ export default class SegmentSinksStore {
|
|
|
308
312
|
return memorizedSegmentSink;
|
|
309
313
|
}
|
|
310
314
|
|
|
311
|
-
let segmentSink:
|
|
315
|
+
let segmentSink: TextSegmentSink;
|
|
312
316
|
if (bufferType === "text") {
|
|
313
317
|
log.info("SB: Creating a new text SegmentSink");
|
|
314
318
|
if (this._textInterface === null) {
|
|
@@ -376,8 +376,13 @@ 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) => {
|
|
@@ -385,6 +390,7 @@ export default function AdaptationStream(
|
|
|
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,6 +419,11 @@ 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%
|
|
@@ -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
|
}
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { canRelyOnRequestMediaKeySystemAccess } from "../../../../compat/can_rely_on_request_media_key_system_access";
|
|
17
18
|
import eme from "../../../../compat/eme";
|
|
19
|
+
import {
|
|
20
|
+
DUMMY_PLAY_READY_HEADER,
|
|
21
|
+
generatePlayReadyInitData,
|
|
22
|
+
} from "../../../../compat/generate_init_data";
|
|
18
23
|
import isNullOrUndefined from "../../../../utils/is_null_or_undefined";
|
|
19
24
|
import log from "../log";
|
|
20
25
|
import type { ICompatibleKeySystem, IMediaConfiguration } from "../types";
|
|
@@ -57,13 +62,40 @@ export default function probeDRMInfos(
|
|
|
57
62
|
|
|
58
63
|
return eme
|
|
59
64
|
.requestMediaKeySystemAccess(type, [configuration])
|
|
60
|
-
.then((keySystemAccess) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
.then(async (keySystemAccess) => {
|
|
66
|
+
if (!canRelyOnRequestMediaKeySystemAccess(type)) {
|
|
67
|
+
try {
|
|
68
|
+
const mediaKeys = await keySystemAccess.createMediaKeys();
|
|
69
|
+
const session = mediaKeys.createSession();
|
|
70
|
+
const initData = generatePlayReadyInitData(DUMMY_PLAY_READY_HEADER);
|
|
71
|
+
await session.generateRequest("cenc", initData);
|
|
72
|
+
session.close().catch(() => {
|
|
73
|
+
log.warn("DRM: Failed to close the dummy session");
|
|
74
|
+
});
|
|
75
|
+
result.compatibleConfiguration = keySystemAccess.getConfiguration();
|
|
76
|
+
const status: [ProberStatus, ICompatibleKeySystem?] = [
|
|
77
|
+
ProberStatus.Supported,
|
|
78
|
+
result,
|
|
79
|
+
];
|
|
80
|
+
return status;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
log.debug("DRM: KeySystemAccess was granted but it is not usable");
|
|
83
|
+
|
|
84
|
+
const statusError: [ProberStatus, ICompatibleKeySystem] = [
|
|
85
|
+
ProberStatus.NotSupported,
|
|
86
|
+
result,
|
|
87
|
+
];
|
|
88
|
+
return statusError;
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
result.compatibleConfiguration = keySystemAccess.getConfiguration();
|
|
92
|
+
|
|
93
|
+
const status: [ProberStatus, ICompatibleKeySystem?] = [
|
|
94
|
+
ProberStatus.Supported,
|
|
95
|
+
result,
|
|
96
|
+
];
|
|
97
|
+
return status;
|
|
98
|
+
}
|
|
67
99
|
})
|
|
68
100
|
.catch(() => {
|
|
69
101
|
return [ProberStatus.NotSupported, result];
|
|
@@ -65,7 +65,7 @@ export default function constructDebugGeneralInfo(
|
|
|
65
65
|
valuesLine1.push(["wo", "0"]);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const valuesLine2: Array<[string, string]> = [];
|
|
68
|
+
const valuesLine2: Array<[string, string]> = [["v", instance.version]];
|
|
69
69
|
const ks = instance.getKeySystemConfiguration();
|
|
70
70
|
if (ks !== null) {
|
|
71
71
|
valuesLine2.push(["ks", ks.keySystem]);
|
|
@@ -411,7 +411,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
411
411
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
|
|
412
412
|
videoElement.preload = "auto";
|
|
413
413
|
|
|
414
|
-
this.version = /* PLAYER_VERSION */ "4.2.0-dev.
|
|
414
|
+
this.version = /* PLAYER_VERSION */ "4.2.0-dev.2024101500";
|
|
415
415
|
this.log = log;
|
|
416
416
|
this.state = "STOPPED";
|
|
417
417
|
this.videoElement = videoElement;
|
|
@@ -3330,7 +3330,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
3330
3330
|
}
|
|
3331
3331
|
}
|
|
3332
3332
|
}
|
|
3333
|
-
Player.version = /* PLAYER_VERSION */ "4.2.0-dev.
|
|
3333
|
+
Player.version = /* PLAYER_VERSION */ "4.2.0-dev.2024101500";
|
|
3334
3334
|
|
|
3335
3335
|
/** Every events sent by the RxPlayer's public API. */
|
|
3336
3336
|
interface IPublicAPIEvent {
|
|
@@ -539,6 +539,9 @@ export async function testKeySystem(
|
|
|
539
539
|
const session = mediaKeys.createSession();
|
|
540
540
|
const initData = generatePlayReadyInitData(DUMMY_PLAY_READY_HEADER);
|
|
541
541
|
await session.generateRequest("cenc", initData);
|
|
542
|
+
session.close().catch(() => {
|
|
543
|
+
log.warn("DRM: Failed to close the dummy session");
|
|
544
|
+
});
|
|
542
545
|
} catch (err) {
|
|
543
546
|
log.debug("DRM: KeySystemAccess was granted but it is not usable");
|
|
544
547
|
throw err;
|
|
@@ -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;
|
|
@@ -92,7 +92,7 @@ import listenToMediaError from "./utils/throw_on_media_error";
|
|
|
92
92
|
*/
|
|
93
93
|
export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
94
94
|
/** Constructor settings associated to this `MediaSourceContentInitializer`. */
|
|
95
|
-
private
|
|
95
|
+
private _initSettings: IInitializeArguments;
|
|
96
96
|
/**
|
|
97
97
|
* `TaskCanceller` allowing to abort everything that the
|
|
98
98
|
* `MediaSourceContentInitializer` is doing.
|
|
@@ -145,7 +145,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
145
145
|
*/
|
|
146
146
|
constructor(settings: IInitializeArguments) {
|
|
147
147
|
super();
|
|
148
|
-
this.
|
|
148
|
+
this._initSettings = settings;
|
|
149
149
|
this._initCanceller = new TaskCanceller();
|
|
150
150
|
this._manifest = null;
|
|
151
151
|
this._decryptionCapabilities = { status: "uninitialized", value: null };
|
|
@@ -234,6 +234,10 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
234
234
|
this._initCanceller.cancel();
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Callback called when an error interrupting playback arised.
|
|
239
|
+
* @param {*} err
|
|
240
|
+
*/
|
|
237
241
|
private _onFatalError(err: unknown) {
|
|
238
242
|
if (this._initCanceller.isUsed()) {
|
|
239
243
|
return;
|
|
@@ -242,6 +246,12 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
242
246
|
this.trigger("error", err);
|
|
243
247
|
}
|
|
244
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Initialize decryption mechanisms if needed and begin creating and relying
|
|
251
|
+
* on the initial `MediaSourceInterface` for this content.
|
|
252
|
+
* @param {HTMLMediaElement|null} mediaElement
|
|
253
|
+
* @returns {Promise.<Object>}
|
|
254
|
+
*/
|
|
245
255
|
private _initializeMediaSourceAndDecryption(mediaElement: IMediaElement): Promise<{
|
|
246
256
|
mediaSource: MainMediaSourceInterface;
|
|
247
257
|
drmSystemId: string | undefined;
|
|
@@ -249,7 +259,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
249
259
|
}> {
|
|
250
260
|
const initCanceller = this._initCanceller;
|
|
251
261
|
return createCancellablePromise(initCanceller.signal, (resolve) => {
|
|
252
|
-
const { keySystems } = this.
|
|
262
|
+
const { keySystems } = this._initSettings;
|
|
253
263
|
|
|
254
264
|
/** Initialize decryption capabilities. */
|
|
255
265
|
const { statusRef: drmInitRef, contentDecryptor } = initializeContentDecryption(
|
|
@@ -383,7 +393,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
383
393
|
startAt,
|
|
384
394
|
textTrackOptions,
|
|
385
395
|
transport,
|
|
386
|
-
} = this.
|
|
396
|
+
} = this._initSettings;
|
|
387
397
|
const initCanceller = this._initCanceller;
|
|
388
398
|
assert(this._manifest !== null);
|
|
389
399
|
let manifest: IManifest;
|
|
@@ -526,7 +536,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
526
536
|
*/
|
|
527
537
|
private _startBufferingOnMediaSource(
|
|
528
538
|
args: IBufferingMediaSettings,
|
|
529
|
-
onReloadOrder:
|
|
539
|
+
onReloadOrder: IReloadMediaSourceCallback,
|
|
530
540
|
cancelSignal: CancellationSignal,
|
|
531
541
|
): void {
|
|
532
542
|
const {
|
|
@@ -553,18 +563,10 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
553
563
|
}
|
|
554
564
|
|
|
555
565
|
let textDisplayerInterface: ITextDisplayerInterface | null = null;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
this.
|
|
559
|
-
|
|
560
|
-
) {
|
|
561
|
-
textDisplayer = new features.htmlTextDisplayer(
|
|
562
|
-
mediaElement,
|
|
563
|
-
this._settings.textTrackOptions.textTrackElement,
|
|
564
|
-
);
|
|
565
|
-
} else if (features.nativeTextDisplayer !== null) {
|
|
566
|
-
textDisplayer = new features.nativeTextDisplayer(mediaElement);
|
|
567
|
-
}
|
|
566
|
+
const textDisplayer = createTextDisplayer(
|
|
567
|
+
mediaElement,
|
|
568
|
+
this._initSettings.textTrackOptions,
|
|
569
|
+
);
|
|
568
570
|
if (textDisplayer !== null) {
|
|
569
571
|
const sender = new MainThreadTextDisplayerInterface(textDisplayer);
|
|
570
572
|
textDisplayerInterface = sender;
|
|
@@ -1123,6 +1125,21 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
1123
1125
|
}
|
|
1124
1126
|
}
|
|
1125
1127
|
|
|
1128
|
+
function createTextDisplayer(
|
|
1129
|
+
mediaElement: IMediaElement,
|
|
1130
|
+
textTrackOptions: ITextDisplayerOptions,
|
|
1131
|
+
): ITextDisplayer | null {
|
|
1132
|
+
if (textTrackOptions.textTrackMode === "html" && features.htmlTextDisplayer !== null) {
|
|
1133
|
+
return new features.htmlTextDisplayer(
|
|
1134
|
+
mediaElement,
|
|
1135
|
+
textTrackOptions.textTrackElement,
|
|
1136
|
+
);
|
|
1137
|
+
} else if (features.nativeTextDisplayer !== null) {
|
|
1138
|
+
return new features.nativeTextDisplayer(mediaElement);
|
|
1139
|
+
}
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1126
1143
|
/** Arguments to give to the `InitializeOnMediaSource` function. */
|
|
1127
1144
|
export interface IInitializeArguments {
|
|
1128
1145
|
/** Options concerning the ABR logic. */
|
|
@@ -1324,3 +1341,19 @@ function blackListProtectionDataOnManifest(
|
|
|
1324
1341
|
return rep.decipherable;
|
|
1325
1342
|
});
|
|
1326
1343
|
}
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* Function to call when you want to "reload" the MediaSource: basically
|
|
1347
|
+
* restarting playback on a new MediaSource for the same content (it may
|
|
1348
|
+
* be for varied reasons, such as ensuring data buffers are empty, or
|
|
1349
|
+
* restarting after some kind of fatal error).
|
|
1350
|
+
* @param {Object} reloadOrder
|
|
1351
|
+
* @param {number} reloadOrder.position - Position in seconds at which we
|
|
1352
|
+
* should restart from when playback restarts.
|
|
1353
|
+
* @param {boolean} reloadOrder.autoPlay - If `true` we will directly play
|
|
1354
|
+
* once enough data is re-loaded.
|
|
1355
|
+
*/
|
|
1356
|
+
type IReloadMediaSourceCallback = (reloadOrder: {
|
|
1357
|
+
position: number;
|
|
1358
|
+
autoPlay: boolean;
|
|
1359
|
+
}) => void;
|
|
@@ -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
|
*
|
|
@@ -99,12 +113,13 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
99
113
|
private _currentMediaSourceCanceller: TaskCanceller;
|
|
100
114
|
|
|
101
115
|
/**
|
|
102
|
-
* Stores the resolvers and the current messageId that is sent to the web worker to
|
|
116
|
+
* Stores the resolvers and the current messageId that is sent to the web worker to
|
|
117
|
+
* receive segment sink metrics.
|
|
103
118
|
* The purpose of collecting metrics is for monitoring and debugging.
|
|
104
119
|
*/
|
|
105
120
|
private _segmentMetrics: {
|
|
106
121
|
lastMessageId: number;
|
|
107
|
-
resolvers:
|
|
122
|
+
resolvers: Map<number, (value: ISegmentSinkMetrics | undefined) => void>;
|
|
108
123
|
};
|
|
109
124
|
|
|
110
125
|
/**
|
|
@@ -121,8 +136,9 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
121
136
|
this._currentContentInfo = null;
|
|
122
137
|
this._segmentMetrics = {
|
|
123
138
|
lastMessageId: 0,
|
|
124
|
-
resolvers:
|
|
139
|
+
resolvers: new Map(),
|
|
125
140
|
};
|
|
141
|
+
this._queuedWorkerMessages = null;
|
|
126
142
|
}
|
|
127
143
|
|
|
128
144
|
/**
|
|
@@ -176,6 +192,66 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
176
192
|
if (this._initCanceller.isUsed()) {
|
|
177
193
|
return;
|
|
178
194
|
}
|
|
195
|
+
this._queuedWorkerMessages = [];
|
|
196
|
+
log.debug("MTCI: addEventListener prepare buffering worker messages");
|
|
197
|
+
const onmessage = (evt: MessageEvent): void => {
|
|
198
|
+
const msgData = evt.data as unknown as IWorkerMessage;
|
|
199
|
+
const type = msgData.type;
|
|
200
|
+
switch (type) {
|
|
201
|
+
case WorkerMessageType.LogMessage: {
|
|
202
|
+
const formatted = msgData.value.logs.map((l) => {
|
|
203
|
+
switch (typeof l) {
|
|
204
|
+
case "string":
|
|
205
|
+
case "number":
|
|
206
|
+
case "boolean":
|
|
207
|
+
case "undefined":
|
|
208
|
+
return l;
|
|
209
|
+
case "object":
|
|
210
|
+
if (l === null) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return formatWorkerError(l);
|
|
214
|
+
default:
|
|
215
|
+
assertUnreachable(l);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
switch (msgData.value.logLevel) {
|
|
219
|
+
case "NONE":
|
|
220
|
+
break;
|
|
221
|
+
case "ERROR":
|
|
222
|
+
log.error(...formatted);
|
|
223
|
+
break;
|
|
224
|
+
case "WARNING":
|
|
225
|
+
log.warn(...formatted);
|
|
226
|
+
break;
|
|
227
|
+
case "INFO":
|
|
228
|
+
log.info(...formatted);
|
|
229
|
+
break;
|
|
230
|
+
case "DEBUG":
|
|
231
|
+
log.debug(...formatted);
|
|
232
|
+
break;
|
|
233
|
+
default:
|
|
234
|
+
assertUnreachable(msgData.value.logLevel);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
default:
|
|
239
|
+
if (this._queuedWorkerMessages !== null) {
|
|
240
|
+
this._queuedWorkerMessages.push(evt);
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
this._settings.worker.addEventListener("message", onmessage);
|
|
246
|
+
const onmessageerror = (_msg: MessageEvent) => {
|
|
247
|
+
log.error("MTCI: Error when receiving message from worker.");
|
|
248
|
+
};
|
|
249
|
+
this._settings.worker.addEventListener("messageerror", onmessageerror);
|
|
250
|
+
this._initCanceller.signal.register(() => {
|
|
251
|
+
log.debug("MTCI: removeEventListener prepare for worker message");
|
|
252
|
+
this._settings.worker.removeEventListener("message", onmessage);
|
|
253
|
+
this._settings.worker.removeEventListener("messageerror", onmessageerror);
|
|
254
|
+
});
|
|
179
255
|
|
|
180
256
|
// Also bind all `SharedReference` objects:
|
|
181
257
|
|
|
@@ -1042,69 +1118,44 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
1042
1118
|
}
|
|
1043
1119
|
break;
|
|
1044
1120
|
|
|
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
1121
|
case WorkerMessageType.SegmentSinkStoreUpdate: {
|
|
1089
1122
|
if (this._currentContentInfo?.contentId !== msgData.contentId) {
|
|
1090
1123
|
return;
|
|
1091
1124
|
}
|
|
1092
|
-
const resolveFn = this._segmentMetrics.resolvers
|
|
1125
|
+
const resolveFn = this._segmentMetrics.resolvers.get(msgData.value.messageId);
|
|
1093
1126
|
if (resolveFn !== undefined) {
|
|
1094
1127
|
resolveFn(msgData.value.segmentSinkMetrics);
|
|
1095
|
-
delete this._segmentMetrics.resolvers[msgData.value.messageId];
|
|
1096
1128
|
} else {
|
|
1097
1129
|
log.error("MTCI: Failed to send segment sink store update");
|
|
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
|
}
|
|
@@ -1538,11 +1589,19 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
|
|
|
1538
1589
|
value: { messageId },
|
|
1539
1590
|
});
|
|
1540
1591
|
return new Promise((resolve, reject) => {
|
|
1541
|
-
this._segmentMetrics.resolvers[messageId] = resolve;
|
|
1542
1592
|
const rejectFn = (err: CancellationError) => {
|
|
1543
|
-
|
|
1593
|
+
cancelSignal.deregister(rejectFn);
|
|
1594
|
+
this._segmentMetrics.resolvers.delete(messageId);
|
|
1544
1595
|
return reject(err);
|
|
1545
1596
|
};
|
|
1597
|
+
this._segmentMetrics.resolvers.set(
|
|
1598
|
+
messageId,
|
|
1599
|
+
(value: ISegmentSinkMetrics | undefined) => {
|
|
1600
|
+
cancelSignal.deregister(rejectFn);
|
|
1601
|
+
this._segmentMetrics.resolvers.delete(messageId);
|
|
1602
|
+
resolve(value);
|
|
1603
|
+
},
|
|
1604
|
+
);
|
|
1546
1605
|
cancelSignal.register(rejectFn);
|
|
1547
1606
|
});
|
|
1548
1607
|
};
|
|
@@ -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 {
|