rx-player 3.27.0-dev.2022032100 → 3.27.0
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 +5 -2
- package/VERSION +1 -1
- package/dist/_esm5.processed/compat/eme/custom_media_keys/old_webkit_media_keys.js +15 -11
- package/dist/_esm5.processed/compat/eme/custom_media_keys/webkit_media_keys.js +22 -6
- package/dist/_esm5.processed/compat/eme/generate_key_request.d.ts +4 -6
- package/dist/_esm5.processed/compat/eme/generate_key_request.js +4 -6
- package/dist/_esm5.processed/compat/get_start_date.d.ts +30 -0
- package/dist/_esm5.processed/compat/get_start_date.js +44 -0
- package/dist/_esm5.processed/compat/index.d.ts +2 -1
- package/dist/_esm5.processed/compat/index.js +2 -1
- package/dist/_esm5.processed/config.d.ts +1 -5
- package/dist/_esm5.processed/core/api/public_api.js +25 -25
- package/dist/_esm5.processed/core/decrypt/content_decryptor.js +11 -3
- package/dist/_esm5.processed/core/decrypt/create_or_load_session.js +1 -1
- package/dist/_esm5.processed/core/decrypt/create_session.d.ts +3 -1
- package/dist/_esm5.processed/core/decrypt/create_session.js +15 -5
- package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.d.ts +94 -1
- package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.js +237 -96
- package/dist/_esm5.processed/core/segment_buffers/garbage_collector.js +4 -1
- package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +2 -1
- package/dist/_esm5.processed/core/stream/period/period_stream.js +9 -3
- package/dist/_esm5.processed/core/stream/representation/append_segment_to_buffer.js +4 -3
- package/dist/_esm5.processed/core/stream/representation/force_garbage_collection.js +3 -2
- package/dist/_esm5.processed/core/stream/representation/get_buffer_status.d.ts +2 -2
- package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +9 -3
- package/dist/_esm5.processed/core/stream/representation/get_needed_segments.d.ts +11 -1
- package/dist/_esm5.processed/core/stream/representation/get_needed_segments.js +27 -45
- package/dist/_esm5.processed/core/stream/representation/representation_stream.js +6 -4
- package/dist/_esm5.processed/default_config.d.ts +2 -35
- package/dist/_esm5.processed/default_config.js +2 -35
- package/dist/_esm5.processed/transports/dash/add_segment_integrity_checks_to_loader.js +39 -38
- package/dist/_esm5.processed/utils/reference.js +0 -2
- package/dist/_esm5.processed/utils/task_canceller.d.ts +8 -1
- package/dist/_esm5.processed/utils/task_canceller.js +9 -1
- package/dist/rx-player.js +927 -587
- package/dist/rx-player.min.js +1 -1
- package/package.json +1 -1
- package/sonar-project.properties +1 -1
- package/src/compat/eme/custom_media_keys/old_webkit_media_keys.ts +16 -12
- package/src/compat/eme/custom_media_keys/webkit_media_keys.ts +21 -8
- package/src/compat/eme/generate_key_request.ts +4 -6
- package/src/compat/get_start_date.ts +48 -0
- package/src/compat/index.ts +2 -0
- package/src/core/api/public_api.ts +23 -27
- package/src/core/decrypt/content_decryptor.ts +15 -4
- package/src/core/decrypt/create_or_load_session.ts +4 -1
- package/src/core/decrypt/create_session.ts +23 -9
- package/src/core/decrypt/utils/loaded_sessions_store.ts +254 -102
- package/src/core/segment_buffers/garbage_collector.ts +4 -0
- package/src/core/stream/orchestrator/stream_orchestrator.ts +2 -1
- package/src/core/stream/period/period_stream.ts +9 -4
- package/src/core/stream/representation/append_segment_to_buffer.ts +17 -13
- package/src/core/stream/representation/force_garbage_collection.ts +4 -1
- package/src/core/stream/representation/get_buffer_status.ts +21 -13
- package/src/core/stream/representation/get_needed_segments.ts +40 -55
- package/src/core/stream/representation/representation_stream.ts +6 -4
- package/src/default_config.ts +20 -57
- package/src/transports/dash/add_segment_integrity_checks_to_loader.ts +41 -44
- package/src/utils/reference.ts +0 -2
- package/src/utils/task_canceller.ts +16 -1
|
@@ -14,17 +14,13 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { Subscription } from "rxjs";
|
|
18
17
|
import {
|
|
19
18
|
closeSession,
|
|
19
|
+
generateKeyRequest,
|
|
20
20
|
ICustomMediaKeys,
|
|
21
21
|
ICustomMediaKeySession,
|
|
22
|
+
loadSession,
|
|
22
23
|
} from "../../../compat";
|
|
23
|
-
import {
|
|
24
|
-
onKeyMessage$,
|
|
25
|
-
onKeyStatusesChange$,
|
|
26
|
-
} from "../../../compat/event_listeners";
|
|
27
|
-
import config from "../../../config";
|
|
28
24
|
import log from "../../../log";
|
|
29
25
|
import isNullOrUndefined from "../../../utils/is_null_or_undefined";
|
|
30
26
|
import { IProcessedProtectionData } from "../types";
|
|
@@ -57,7 +53,7 @@ export default class LoadedSessionsStore {
|
|
|
57
53
|
|
|
58
54
|
/**
|
|
59
55
|
* Create a new MediaKeySession and store it in this store.
|
|
60
|
-
* @param {Object}
|
|
56
|
+
* @param {Object} initData
|
|
61
57
|
* @param {string} sessionType
|
|
62
58
|
* @returns {Object}
|
|
63
59
|
*/
|
|
@@ -67,7 +63,12 @@ export default class LoadedSessionsStore {
|
|
|
67
63
|
) : IStoredSessionEntry {
|
|
68
64
|
const keySessionRecord = new KeySessionRecord(initData);
|
|
69
65
|
const mediaKeySession = this._mediaKeys.createSession(sessionType);
|
|
70
|
-
const entry = { mediaKeySession,
|
|
66
|
+
const entry = { mediaKeySession,
|
|
67
|
+
sessionType,
|
|
68
|
+
keySessionRecord,
|
|
69
|
+
isGeneratingRequest: false,
|
|
70
|
+
isLoadingPersistentSession: false,
|
|
71
|
+
closingStatus: { type: "none" as const } };
|
|
71
72
|
if (!isNullOrUndefined(mediaKeySession.closed)) {
|
|
72
73
|
mediaKeySession.closed
|
|
73
74
|
.then(() => {
|
|
@@ -85,7 +86,7 @@ export default class LoadedSessionsStore {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
log.debug("DRM-LSS: Add MediaKeySession", entry.sessionType);
|
|
88
|
-
this._storage.push({
|
|
89
|
+
this._storage.push({ ...entry });
|
|
89
90
|
return entry;
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -109,14 +110,145 @@ export default class LoadedSessionsStore {
|
|
|
109
110
|
if (stored.keySessionRecord.isCompatibleWith(initializationData)) {
|
|
110
111
|
this._storage.splice(i, 1);
|
|
111
112
|
this._storage.push(stored);
|
|
112
|
-
return {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
return { ...stored };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get `LoadedSessionsStore`'s entry for a given MediaKeySession.
|
|
121
|
+
* Returns `null` if the given MediaKeySession is not stored in the
|
|
122
|
+
* `LoadedSessionsStore`.
|
|
123
|
+
* @param {MediaKeySession} mediaKeySession
|
|
124
|
+
* @returns {Object|null}
|
|
125
|
+
*/
|
|
126
|
+
public getEntryForSession(
|
|
127
|
+
mediaKeySession : MediaKeySession | ICustomMediaKeySession
|
|
128
|
+
) : IStoredSessionEntry | null {
|
|
129
|
+
for (let i = this._storage.length - 1; i >= 0; i--) {
|
|
130
|
+
const stored = this._storage[i];
|
|
131
|
+
if (stored.mediaKeySession === mediaKeySession) {
|
|
132
|
+
return { ...stored };
|
|
115
133
|
}
|
|
116
134
|
}
|
|
117
135
|
return null;
|
|
118
136
|
}
|
|
119
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Generate a license request on the given MediaKeySession, while indicating
|
|
140
|
+
* to the LoadedSessionsStore that a license-request is pending so
|
|
141
|
+
* session-closing orders are properly scheduled after it is done.
|
|
142
|
+
* @param {Object} mediaKeySession
|
|
143
|
+
* @param {string} initializationDataType - Initialization data type given
|
|
144
|
+
* e.g. by the "encrypted" event for the corresponding request.
|
|
145
|
+
* @param {Uint8Array} initializationData - Initialization data given e.g. by
|
|
146
|
+
* the "encrypted" event for the corresponding request.
|
|
147
|
+
* @returns {Promise}
|
|
148
|
+
*/
|
|
149
|
+
public async generateLicenseRequest(
|
|
150
|
+
mediaKeySession : MediaKeySession | ICustomMediaKeySession,
|
|
151
|
+
initializationDataType : string | undefined,
|
|
152
|
+
initializationData : Uint8Array
|
|
153
|
+
) : Promise<unknown> {
|
|
154
|
+
let entry : IStoredSessionEntry | undefined;
|
|
155
|
+
for (const stored of this._storage) {
|
|
156
|
+
if (stored.mediaKeySession === mediaKeySession) {
|
|
157
|
+
entry = stored;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (entry === undefined) {
|
|
162
|
+
log.error("DRM-LSS: generateRequest error. No MediaKeySession found with " +
|
|
163
|
+
"the given initData and initDataType");
|
|
164
|
+
return generateKeyRequest(mediaKeySession,
|
|
165
|
+
initializationDataType,
|
|
166
|
+
initializationData);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
entry.isGeneratingRequest = true;
|
|
170
|
+
|
|
171
|
+
// Note the `as string` is needed due to TypeScript not understanding that
|
|
172
|
+
// the `closingStatus` might change in the next checks
|
|
173
|
+
if (entry.closingStatus.type as string !== "none") {
|
|
174
|
+
throw new Error("The `MediaKeySession` is being closed.");
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await generateKeyRequest(mediaKeySession,
|
|
178
|
+
initializationDataType,
|
|
179
|
+
initializationData);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (entry === undefined) {
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
entry.isGeneratingRequest = false;
|
|
185
|
+
if (entry.closingStatus.type === "awaiting") {
|
|
186
|
+
entry.closingStatus.start();
|
|
187
|
+
}
|
|
188
|
+
throw err;
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
if (entry === undefined) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
entry.isGeneratingRequest = false;
|
|
195
|
+
if (entry.closingStatus.type === "awaiting") {
|
|
196
|
+
entry.closingStatus.start();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {Object} mediaKeySession
|
|
202
|
+
* @param {string} sessionId
|
|
203
|
+
* @returns {Promise}
|
|
204
|
+
*/
|
|
205
|
+
public async loadPersistentSession(
|
|
206
|
+
mediaKeySession : MediaKeySession | ICustomMediaKeySession,
|
|
207
|
+
sessionId : string
|
|
208
|
+
) : Promise<boolean> {
|
|
209
|
+
let entry : IStoredSessionEntry | undefined;
|
|
210
|
+
for (const stored of this._storage) {
|
|
211
|
+
if (stored.mediaKeySession === mediaKeySession) {
|
|
212
|
+
entry = stored;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (entry === undefined) {
|
|
217
|
+
log.error("DRM-LSS: loadPersistentSession error. No MediaKeySession found with " +
|
|
218
|
+
"the given initData and initDataType");
|
|
219
|
+
return loadSession(mediaKeySession, sessionId);
|
|
220
|
+
}
|
|
221
|
+
entry.isLoadingPersistentSession = true;
|
|
222
|
+
|
|
223
|
+
// Note the `as string` is needed due to TypeScript not understanding that
|
|
224
|
+
// the `closingStatus` might change in the next checks
|
|
225
|
+
if (entry.closingStatus.type as string !== "none") {
|
|
226
|
+
throw new Error("The `MediaKeySession` is being closed.");
|
|
227
|
+
}
|
|
228
|
+
let ret : boolean;
|
|
229
|
+
try {
|
|
230
|
+
ret = await loadSession(mediaKeySession, sessionId);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
if (entry === undefined) {
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
entry.isLoadingPersistentSession = false;
|
|
236
|
+
if (entry.closingStatus.type === "awaiting") {
|
|
237
|
+
entry.closingStatus.start();
|
|
238
|
+
}
|
|
239
|
+
throw err;
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
if (entry === undefined) {
|
|
243
|
+
return ret;
|
|
244
|
+
}
|
|
245
|
+
entry.isLoadingPersistentSession = false;
|
|
246
|
+
if (entry.closingStatus.type === "awaiting") {
|
|
247
|
+
entry.closingStatus.start();
|
|
248
|
+
}
|
|
249
|
+
return ret;
|
|
250
|
+
}
|
|
251
|
+
|
|
120
252
|
/**
|
|
121
253
|
* Close a MediaKeySession and remove its related stored information from the
|
|
122
254
|
* `LoadedSessionsStore`.
|
|
@@ -127,7 +259,7 @@ export default class LoadedSessionsStore {
|
|
|
127
259
|
public async closeSession(
|
|
128
260
|
mediaKeySession : MediaKeySession | ICustomMediaKeySession
|
|
129
261
|
) : Promise<boolean> {
|
|
130
|
-
let entry;
|
|
262
|
+
let entry : IStoredSessionEntry | undefined;
|
|
131
263
|
for (const stored of this._storage) {
|
|
132
264
|
if (stored.mediaKeySession === mediaKeySession) {
|
|
133
265
|
entry = stored;
|
|
@@ -139,8 +271,7 @@ export default class LoadedSessionsStore {
|
|
|
139
271
|
"the given initData and initDataType");
|
|
140
272
|
return Promise.resolve(false);
|
|
141
273
|
}
|
|
142
|
-
|
|
143
|
-
return Promise.resolve(true);
|
|
274
|
+
return this._closeEntry(entry);
|
|
144
275
|
}
|
|
145
276
|
|
|
146
277
|
/**
|
|
@@ -175,10 +306,17 @@ export default class LoadedSessionsStore {
|
|
|
175
306
|
this._storage = [];
|
|
176
307
|
|
|
177
308
|
const closingProms = allEntries
|
|
178
|
-
.map((entry) =>
|
|
309
|
+
.map((entry) => this._closeEntry(entry));
|
|
179
310
|
await Promise.all(closingProms);
|
|
180
311
|
}
|
|
181
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Get the index of a stored MediaKeySession entry based on its
|
|
315
|
+
* `KeySessionRecord`.
|
|
316
|
+
* Returns -1 if not found.
|
|
317
|
+
* @param {Object} record
|
|
318
|
+
* @returns {number}
|
|
319
|
+
*/
|
|
182
320
|
private getIndex(record : KeySessionRecord) : number {
|
|
183
321
|
for (let i = 0; i < this._storage.length; i++) {
|
|
184
322
|
const stored = this._storage[i];
|
|
@@ -188,6 +326,50 @@ export default class LoadedSessionsStore {
|
|
|
188
326
|
}
|
|
189
327
|
return -1;
|
|
190
328
|
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Prepare the closure of a `MediaKeySession` stored as an entry of the
|
|
332
|
+
* `LoadedSessionsStore`.
|
|
333
|
+
* Allows to postpone the closure action if another MediaKeySession action
|
|
334
|
+
* is already pending.
|
|
335
|
+
* @param {Object} entry
|
|
336
|
+
* @returns {Promise.<boolean>}
|
|
337
|
+
*/
|
|
338
|
+
private async _closeEntry(
|
|
339
|
+
entry : IStoredSessionEntry
|
|
340
|
+
) : Promise<boolean> {
|
|
341
|
+
const { mediaKeySession } = entry;
|
|
342
|
+
return new Promise((resolve, reject) => {
|
|
343
|
+
if (entry !== undefined &&
|
|
344
|
+
(
|
|
345
|
+
entry.isLoadingPersistentSession || entry.isGeneratingRequest
|
|
346
|
+
))
|
|
347
|
+
{
|
|
348
|
+
entry.closingStatus = { type: "awaiting",
|
|
349
|
+
start: tryClosingEntryAndResolve };
|
|
350
|
+
} else {
|
|
351
|
+
tryClosingEntryAndResolve();
|
|
352
|
+
}
|
|
353
|
+
function tryClosingEntryAndResolve() {
|
|
354
|
+
if (entry !== undefined) {
|
|
355
|
+
entry.closingStatus = { type: "pending" };
|
|
356
|
+
}
|
|
357
|
+
safelyCloseMediaKeySession(mediaKeySession)
|
|
358
|
+
.then(() => {
|
|
359
|
+
if (entry !== undefined) {
|
|
360
|
+
entry.closingStatus = { type: "done" };
|
|
361
|
+
}
|
|
362
|
+
resolve(true);
|
|
363
|
+
})
|
|
364
|
+
.catch((err) => {
|
|
365
|
+
if (entry !== undefined) {
|
|
366
|
+
entry.closingStatus = { type: "failed" };
|
|
367
|
+
}
|
|
368
|
+
reject(err);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
191
373
|
}
|
|
192
374
|
|
|
193
375
|
/** Information linked to a `MediaKeySession` created by the `LoadedSessionsStore`. */
|
|
@@ -216,102 +398,72 @@ export interface IStoredSessionEntry {
|
|
|
216
398
|
* which the MediaKeySession was created.
|
|
217
399
|
*/
|
|
218
400
|
sessionType : MediaKeySessionType;
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Set to `true` while a `generateRequest` call is pending.
|
|
404
|
+
* This information might be useful as it is one of the operation we have to
|
|
405
|
+
* wait for before closing a MediaKeySession.
|
|
406
|
+
*/
|
|
407
|
+
isGeneratingRequest : boolean;
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Set to `true` while a `load` call is pending.
|
|
411
|
+
* This information might be useful as it is one of the operation we have to
|
|
412
|
+
* wait for before closing a MediaKeySession.
|
|
413
|
+
*/
|
|
414
|
+
isLoadingPersistentSession : boolean;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The status of a potential `MediaKeySession`'s close request.
|
|
418
|
+
* Closing a MediaKeySession could be made complex as it normally cannot
|
|
419
|
+
* happen until `generateRequest` or `load` has been called.
|
|
420
|
+
*
|
|
421
|
+
* To avoid problems while still staying compatible to the most devices
|
|
422
|
+
* possible - which may have strange implementation of the specification -
|
|
423
|
+
* we're adding the `closingStatus` property allowing to perform multiple
|
|
424
|
+
* type of interaction while a close operation is either pending or is
|
|
425
|
+
* awaited.
|
|
426
|
+
*/
|
|
427
|
+
closingStatus :
|
|
428
|
+
/** Status when the MediaKeySession is currently being closed. */
|
|
429
|
+
{ type : "pending" } |
|
|
430
|
+
/** Status when the MediaKeySession has been closed. */
|
|
431
|
+
{ type : "done" } |
|
|
432
|
+
/** Status when the MediaKeySession failed to close. */
|
|
433
|
+
{ type : "failed" } |
|
|
434
|
+
/**
|
|
435
|
+
* Status when a close order has been received for this MediaKeySession
|
|
436
|
+
* while some sensitive operation (examples are `generateRequest` and `load`
|
|
437
|
+
* calls).
|
|
438
|
+
* The `LoadedSessionsStore` should call `start` once it has finished those
|
|
439
|
+
* operations.
|
|
440
|
+
*/
|
|
441
|
+
{
|
|
442
|
+
type : "awaiting";
|
|
443
|
+
start : () => void;
|
|
444
|
+
} |
|
|
445
|
+
/** Status when the MediaKeySession failed to close. */
|
|
446
|
+
{ type : "none" };
|
|
219
447
|
}
|
|
220
448
|
|
|
221
449
|
/**
|
|
222
|
-
* Close a MediaKeySession
|
|
223
|
-
* this action throws an error.
|
|
450
|
+
* Close a MediaKeySession and just log an error if it fails (while resolving).
|
|
224
451
|
* Emits then complete when done.
|
|
225
452
|
* @param {MediaKeySession} mediaKeySession
|
|
226
453
|
* @returns {Observable}
|
|
227
454
|
*/
|
|
228
|
-
function safelyCloseMediaKeySession(
|
|
455
|
+
async function safelyCloseMediaKeySession(
|
|
229
456
|
mediaKeySession : MediaKeySession | ICustomMediaKeySession
|
|
230
|
-
) : Promise<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
* events, whichever comes first.
|
|
238
|
-
* Emits then complete when done.
|
|
239
|
-
* @param {number} retryNb - The attempt number starting at 0.
|
|
240
|
-
* @returns {Observable}
|
|
241
|
-
*/
|
|
242
|
-
async function recursivelyTryToCloseMediaKeySession(
|
|
243
|
-
retryNb : number
|
|
244
|
-
) : Promise<unknown> {
|
|
245
|
-
log.debug("DRM: Trying to close a MediaKeySession",
|
|
246
|
-
mediaKeySession.sessionId,
|
|
247
|
-
retryNb);
|
|
248
|
-
try {
|
|
249
|
-
await closeSession(mediaKeySession);
|
|
250
|
-
log.debug("DRM: Succeeded to close MediaKeySession");
|
|
251
|
-
return undefined;
|
|
252
|
-
} catch (err : unknown) {
|
|
253
|
-
// Unitialized MediaKeySession may not close properly until their
|
|
254
|
-
// corresponding `generateRequest` or `load` call are handled by the
|
|
255
|
-
// browser.
|
|
256
|
-
// In that case the EME specification tells us that the browser is
|
|
257
|
-
// supposed to reject the `close` call with an InvalidStateError.
|
|
258
|
-
if (!(err instanceof Error) || err.name !== "InvalidStateError" ||
|
|
259
|
-
mediaKeySession.sessionId !== "")
|
|
260
|
-
{
|
|
261
|
-
return failToCloseSession(err);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const { EME_SESSION_CLOSING_MAX_RETRY,
|
|
265
|
-
EME_SESSION_CLOSING_INITIAL_DELAY,
|
|
266
|
-
EME_SESSION_CLOSING_MAX_DELAY } = config.getCurrent();
|
|
267
|
-
|
|
268
|
-
// We will retry either:
|
|
269
|
-
// - when an event indicates that the MediaKeySession is
|
|
270
|
-
// initialized (`callable` is the proper EME term here)
|
|
271
|
-
// - after a delay, raising exponentially
|
|
272
|
-
const nextRetryNb = retryNb + 1;
|
|
273
|
-
if (nextRetryNb > EME_SESSION_CLOSING_MAX_RETRY) {
|
|
274
|
-
return failToCloseSession(err);
|
|
275
|
-
}
|
|
276
|
-
const delay = Math.min(Math.pow(2, retryNb) * EME_SESSION_CLOSING_INITIAL_DELAY,
|
|
277
|
-
EME_SESSION_CLOSING_MAX_DELAY);
|
|
278
|
-
log.warn("DRM: attempt to close a mediaKeySession failed, " +
|
|
279
|
-
"scheduling retry...", delay);
|
|
280
|
-
|
|
281
|
-
let ksChangeSub : undefined | Subscription;
|
|
282
|
-
const ksChangeProm = new Promise((res) => {
|
|
283
|
-
ksChangeSub = onKeyStatusesChange$(mediaKeySession).subscribe(res);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
let ksMsgSub : undefined | Subscription;
|
|
287
|
-
const ksMsgProm = new Promise((res) => {
|
|
288
|
-
ksMsgSub = onKeyMessage$(mediaKeySession).subscribe(res);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
let sleepTimer : undefined | number;
|
|
292
|
-
const sleepProm = new Promise((res) => {
|
|
293
|
-
sleepTimer = window.setTimeout(res, delay);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
await Promise.race([ksChangeProm, ksMsgProm, sleepProm]);
|
|
297
|
-
ksChangeSub?.unsubscribe();
|
|
298
|
-
ksMsgSub?.unsubscribe();
|
|
299
|
-
clearTimeout(sleepTimer);
|
|
300
|
-
|
|
301
|
-
return recursivelyTryToCloseMediaKeySession(nextRetryNb);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Log error anouncing that we could not close the MediaKeySession and emits
|
|
307
|
-
* then complete through Observable.
|
|
308
|
-
* TODO Emit warning?
|
|
309
|
-
* @returns {Observable}
|
|
310
|
-
*/
|
|
311
|
-
function failToCloseSession(err : unknown) : Promise<null> {
|
|
457
|
+
) : Promise<void> {
|
|
458
|
+
log.debug("DRM: Trying to close a MediaKeySession", mediaKeySession.sessionId);
|
|
459
|
+
try {
|
|
460
|
+
await closeSession(mediaKeySession);
|
|
461
|
+
log.debug("DRM: Succeeded to close MediaKeySession");
|
|
462
|
+
return ;
|
|
463
|
+
} catch (err : unknown) {
|
|
312
464
|
log.error("DRM: Could not close MediaKeySession: " +
|
|
313
465
|
(err instanceof Error ? err.toString() :
|
|
314
466
|
"Unknown error"));
|
|
315
|
-
return
|
|
467
|
+
return ;
|
|
316
468
|
}
|
|
317
469
|
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ignoreElements,
|
|
23
23
|
mergeMap,
|
|
24
24
|
Observable,
|
|
25
|
+
of as observableOf,
|
|
25
26
|
} from "rxjs";
|
|
26
27
|
import log from "../../log";
|
|
27
28
|
import { getInnerAndOuterTimeRanges } from "../../utils/ranges";
|
|
@@ -152,6 +153,9 @@ function clearBuffer(
|
|
|
152
153
|
const clean$ = observableFrom(
|
|
153
154
|
cleanedupRanges.map((range) => {
|
|
154
155
|
log.debug("GC: cleaning range from SegmentBuffer", range);
|
|
156
|
+
if (range.start >= range.end) {
|
|
157
|
+
return observableOf(null);
|
|
158
|
+
}
|
|
155
159
|
return segmentBuffer.removeBuffer(range.start, range.end);
|
|
156
160
|
})
|
|
157
161
|
).pipe(concatAll(),
|
|
@@ -348,7 +348,8 @@ export default function StreamOrchestrator(
|
|
|
348
348
|
|
|
349
349
|
return observableConcat(
|
|
350
350
|
...rangesToClean.map(({ start, end }) =>
|
|
351
|
-
|
|
351
|
+
start >= end ? EMPTY :
|
|
352
|
+
segmentBuffer.removeBuffer(start, end).pipe(ignoreElements())),
|
|
352
353
|
playbackObserver.observe(true).pipe(
|
|
353
354
|
take(1),
|
|
354
355
|
mergeMap((observation) => {
|
|
@@ -170,10 +170,15 @@ export default function PeriodStream({
|
|
|
170
170
|
if (SegmentBuffersStore.isNative(bufferType)) {
|
|
171
171
|
return reloadAfterSwitch(period, bufferType, playbackObserver, 0);
|
|
172
172
|
}
|
|
173
|
-
|
|
174
|
-
.removeBuffer(period.start,
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
if (period.end === undefined) {
|
|
174
|
+
cleanBuffer$ = segmentBufferStatus.value.removeBuffer(period.start,
|
|
175
|
+
Infinity);
|
|
176
|
+
} else if (period.end <= period.start) {
|
|
177
|
+
cleanBuffer$ = observableOf(null);
|
|
178
|
+
} else {
|
|
179
|
+
cleanBuffer$ = segmentBufferStatus.value.removeBuffer(period.start,
|
|
180
|
+
period.end);
|
|
181
|
+
}
|
|
177
182
|
} else {
|
|
178
183
|
if (segmentBufferStatus.type === "uninitialized") {
|
|
179
184
|
segmentBuffersStore.disableSegmentBuffer(bufferType);
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
mergeMap,
|
|
25
25
|
ignoreElements,
|
|
26
26
|
Observable,
|
|
27
|
+
take,
|
|
27
28
|
} from "rxjs";
|
|
28
29
|
import { MediaError } from "../../../errors";
|
|
29
30
|
import { IReadOnlyPlaybackObserver } from "../../api";
|
|
@@ -60,19 +61,22 @@ export default function appendSegmentToBuffer<T>(
|
|
|
60
61
|
throw new MediaError("BUFFER_APPEND_ERROR", reason);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
return playbackObserver.observe(true).pipe(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
return playbackObserver.observe(true).pipe(
|
|
65
|
+
take(1),
|
|
66
|
+
mergeMap((observation) => {
|
|
67
|
+
const currentPos = observation.position + observation.wantedTimeOffset;
|
|
68
|
+
return observableConcat(
|
|
69
|
+
forceGarbageCollection(currentPos, segmentBuffer).pipe(ignoreElements()),
|
|
70
|
+
append$
|
|
71
|
+
).pipe(
|
|
72
|
+
catchError((forcedGCError : unknown) => {
|
|
73
|
+
const reason = forcedGCError instanceof Error ?
|
|
74
|
+
forcedGCError.toString() :
|
|
75
|
+
"Could not clean the buffer";
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
throw new MediaError("BUFFER_FULL_ERROR", reason);
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
}));
|
|
77
81
|
}));
|
|
78
82
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
defer as observableDefer,
|
|
20
20
|
from as observableFrom,
|
|
21
21
|
Observable,
|
|
22
|
+
of as observableOf,
|
|
22
23
|
} from "rxjs";
|
|
23
24
|
import config from "../../../config";
|
|
24
25
|
import log from "../../../log";
|
|
@@ -54,7 +55,9 @@ export default function forceGarbageCollection(
|
|
|
54
55
|
|
|
55
56
|
log.debug("Stream: GC cleaning", cleanedupRanges);
|
|
56
57
|
return observableFrom(
|
|
57
|
-
cleanedupRanges.map(({ start, end }) =>
|
|
58
|
+
cleanedupRanges.map(({ start, end }) =>
|
|
59
|
+
start >= end ? observableOf(null) :
|
|
60
|
+
bufferingQueue.removeBuffer(start, end))
|
|
58
61
|
).pipe(concatAll());
|
|
59
62
|
});
|
|
60
63
|
}
|
|
@@ -62,8 +62,8 @@ export interface IBufferStatus {
|
|
|
62
62
|
*/
|
|
63
63
|
shouldRefreshManifest : boolean;
|
|
64
64
|
/**
|
|
65
|
-
* If 'true', the buffer memory is saturated
|
|
66
|
-
*
|
|
65
|
+
* If 'true', the buffer memory is saturated, thus we may have issues loading
|
|
66
|
+
* new segments.
|
|
67
67
|
*/
|
|
68
68
|
isBufferFull: boolean;
|
|
69
69
|
}
|
|
@@ -123,16 +123,18 @@ export default function getBufferStatus(
|
|
|
123
123
|
const getBufferedHistory = segmentBuffer.getSegmentHistory.bind(segmentBuffer);
|
|
124
124
|
|
|
125
125
|
/** List of segments we will need to download. */
|
|
126
|
-
const {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
const { segmentsToLoad,
|
|
127
|
+
segmentsOnHold,
|
|
128
|
+
isBufferFull } = getNeededSegments({ content,
|
|
129
|
+
bufferedSegments,
|
|
130
|
+
currentPlaybackTime,
|
|
131
|
+
fastSwitchThreshold,
|
|
132
|
+
getBufferedHistory,
|
|
133
|
+
neededRange,
|
|
134
|
+
segmentsBeingPushed,
|
|
135
|
+
maxBufferSize });
|
|
136
|
+
|
|
137
|
+
const prioritizedNeededSegments: IQueuedSegment[] = segmentsToLoad.map((segment) => (
|
|
136
138
|
{
|
|
137
139
|
priority: getSegmentPriority(segment.time, wantedStartPosition),
|
|
138
140
|
segment,
|
|
@@ -148,7 +150,8 @@ export default function getBufferStatus(
|
|
|
148
150
|
const lastPosition = representation.index.getLastPosition();
|
|
149
151
|
if (!representation.index.isInitialized() ||
|
|
150
152
|
period.end === undefined ||
|
|
151
|
-
prioritizedNeededSegments.length > 0
|
|
153
|
+
prioritizedNeededSegments.length > 0 ||
|
|
154
|
+
segmentsOnHold.length > 0)
|
|
152
155
|
{
|
|
153
156
|
hasFinishedLoading = false;
|
|
154
157
|
} else {
|
|
@@ -192,6 +195,11 @@ export default function getBufferStatus(
|
|
|
192
195
|
if (segmentsBeingPushed.length > 0) {
|
|
193
196
|
nextSegmentStart = Math.min(...segmentsBeingPushed.map(info => info.segment.time));
|
|
194
197
|
}
|
|
198
|
+
if (segmentsOnHold.length > 0) {
|
|
199
|
+
nextSegmentStart = nextSegmentStart !== null ?
|
|
200
|
+
Math.min(nextSegmentStart, segmentsOnHold[0].time) :
|
|
201
|
+
segmentsOnHold[0].time;
|
|
202
|
+
}
|
|
195
203
|
if (prioritizedNeededSegments.length > 0) {
|
|
196
204
|
nextSegmentStart = nextSegmentStart !== null ?
|
|
197
205
|
Math.min(nextSegmentStart, prioritizedNeededSegments[0].segment.time) :
|