rx-player 4.2.0-dev.2024092400 → 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 +10 -3
- 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/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/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/main_thread/api/public_api.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/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 +94 -45
- 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/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/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/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/main_thread/api/public_api.js +2 -2
- 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/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 +53 -14
- package/dist/rx-player.min.js +8 -8
- package/dist/worker.js +5 -5
- package/package.json +19 -7
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/core/main/worker/worker_main.ts +3 -0
- package/src/core/stream/adaptation/adaptation_stream.ts +23 -1
- package/src/core/stream/representation/representation_stream.ts +11 -0
- package/src/main_thread/api/public_api.ts +2 -2
- package/src/main_thread/init/directfile_content_initializer.ts +20 -10
- package/src/main_thread/init/multi_thread_content_initializer.ts +94 -43
- 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/transports/smooth/pipelines.ts +1 -0
- package/src/transports/utils/parse_text_track.ts +1 -0
|
@@ -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
|
|
|
@@ -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
|
}
|
|
@@ -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.2024100200";
|
|
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.2024100200";
|
|
3334
3334
|
|
|
3335
3335
|
/** Every events sent by the RxPlayer's public API. */
|
|
3336
3336
|
interface IPublicAPIEvent {
|
|
@@ -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
|
}
|
|
@@ -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 {
|
|
@@ -464,6 +464,20 @@ export class MainSourceBufferInterface implements ISourceBufferInterface {
|
|
|
464
464
|
op.reject(error);
|
|
465
465
|
});
|
|
466
466
|
this._currentOperations = [];
|
|
467
|
+
|
|
468
|
+
// A synchronous error probably will not lead to updateend event, so we need to
|
|
469
|
+
// go to next queue element manually
|
|
470
|
+
//
|
|
471
|
+
// FIXME: This here is needed to ensure that we're not left with a
|
|
472
|
+
// dangling queue of operations.
|
|
473
|
+
// However it can potentially be counter-productive if e.g. the `appendBuffer`
|
|
474
|
+
// error was due to a full buffer and if there are pushing operations awaiting in
|
|
475
|
+
// the queue.
|
|
476
|
+
//
|
|
477
|
+
// A better solution might just be to reject all push operations right away here?
|
|
478
|
+
// Only for a `QuotaExceededError` (to check MSE)?
|
|
479
|
+
// However this is too disruptive for what is now a hotfix
|
|
480
|
+
this._performNextOperation();
|
|
467
481
|
}
|
|
468
482
|
} else {
|
|
469
483
|
// TODO merge contiguous removes?
|
|
@@ -482,7 +496,13 @@ export class MainSourceBufferInterface implements ISourceBufferInterface {
|
|
|
482
496
|
false,
|
|
483
497
|
);
|
|
484
498
|
nextElem.reject(error);
|
|
499
|
+
this._currentOperations.forEach((op) => {
|
|
500
|
+
op.reject(error);
|
|
501
|
+
});
|
|
485
502
|
this._currentOperations = [];
|
|
503
|
+
// A synchronous error probably will not lead to updateend event, so we need to
|
|
504
|
+
// go to next queue element manually
|
|
505
|
+
this._performNextOperation();
|
|
486
506
|
}
|
|
487
507
|
}
|
|
488
508
|
}
|
|
@@ -350,6 +350,7 @@ export default function (transportOptions: ITransportOptions): ITransportPipelin
|
|
|
350
350
|
if (
|
|
351
351
|
mimeType === "application/ttml+xml+mp4" ||
|
|
352
352
|
lcCodec === "stpp" ||
|
|
353
|
+
lcCodec === "stpp.ttml" ||
|
|
353
354
|
lcCodec === "stpp.ttml.im1t"
|
|
354
355
|
) {
|
|
355
356
|
_sdType = "ttml";
|
|
@@ -42,6 +42,7 @@ export function getISOBMFFTextTrackFormat(codecs: string | undefined): "ttml" |
|
|
|
42
42
|
}
|
|
43
43
|
switch (codecs.toLowerCase()) {
|
|
44
44
|
case "stpp": // stpp === TTML in MP4
|
|
45
|
+
case "stpp.ttml":
|
|
45
46
|
case "stpp.ttml.im1t":
|
|
46
47
|
return "ttml";
|
|
47
48
|
case "wvtt": // wvtt === WebVTT in MP4
|