-
Notifications
You must be signed in to change notification settings - Fork 136
DRM: Second attempt to manage a maxSessionCacheSize on contents going over the limit
#1590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| import type { BlacklistedSessionError } from "../session_events_listener"; | ||
| import type { MediaKeySessionLoadingType } from "../types"; | ||
| import type KeySessionRecord from "./key_session_record"; | ||
|
|
||
| /** | ||
| * Contains information about all key sessions loaded for the current | ||
| * content. | ||
| * This object is most notably used to check which keys are already obtained, | ||
| * thus avoiding to perform new unnecessary license requests and CDM | ||
| * interactions. | ||
| * | ||
| * It is important to create only one `ActiveSessionsStore` for a given | ||
| * `MediaKeys` to prevent conflicts. | ||
| * | ||
| * An `ActiveSessionsStore` instance can also be "marked" as full with the | ||
| * `markAsFull` method. | ||
| * "Marking as full" this way does not change your ability do add new session, | ||
| * but the `isFull` method will return `true` until at least a single session is | ||
| * removed from this `ActiveSessionsInfo`. | ||
| * This "full" flag allows to simplify the management of having too many | ||
| * simultaneous `MediaKeySession` on the current device, by storing in a single | ||
| * place whether this event has been encountered and whether it had chance to | ||
| * be resolved since. | ||
| * | ||
| * @class ActiveSessionsInfo | ||
| */ | ||
| export default class ActiveSessionsStore { | ||
| /** Metadata on each `MediaKeySession` stored here. */ | ||
| private _sessions: IActiveSessionInfo[]; | ||
|
|
||
| /** | ||
| * `true` after the `markAsFull` method has been called, until `removeSession` | ||
| * is called **and** led to a `MediaKeySession` has been removed. | ||
| * | ||
| * This boolean has no impact on the creation of new `MediaKeySession`, it is | ||
| * only here as a flag to indicate that a surplus of `MediaKeySession` | ||
| * linked to this `ActiveSessionsStore` has been detected and only resets to | ||
| * `false` when it has chances to be resolved (when a `MediaKeySession` has | ||
| * since been removed). | ||
| */ | ||
| private _isFull: boolean; | ||
|
|
||
| constructor() { | ||
| this._sessions = []; | ||
| this._isFull = false; | ||
| } | ||
|
|
||
| /** | ||
| * Set the `isFull` flag to true meaning that the `isFull` method will from | ||
| * now on return `true` until at least one `MediaKeySession` has been removed | ||
| * from this `ActiveSessionsStore` (through the `removeSession` method). | ||
| * | ||
| * This flag allows to store the information of whether too much | ||
| * `MediaKeySession` seems to be created right now. | ||
| */ | ||
| public markAsFull(): void { | ||
| this._isFull = true; | ||
| } | ||
|
|
||
| /** | ||
| * Add a new `MediaKeySession`, and its associated information, to the | ||
| * `ActiveSessionsStore`. | ||
| * @param {Object} sessionInfo | ||
| */ | ||
| public addSession(sessionInfo: IActiveSessionInfo) { | ||
| this._sessions.push(sessionInfo); | ||
| } | ||
|
|
||
| /** | ||
| * Returns all information in the `ActiveSessionsStore` by order of insertion. | ||
| * @returns {Array.<Object>} | ||
| */ | ||
| public getSessions(): IActiveSessionInfo[] { | ||
| return this._sessions; | ||
| } | ||
|
|
||
| /** | ||
| * Remove element with the corresponding `MediaKeySession` information from | ||
| * the `ActiveSessionsStore` if found. | ||
| * | ||
| * Returns `true` if the corresponding element has been found and removed, or | ||
| * `false` if it wasn't found. | ||
| * | ||
| * @param {Object} sessionInfo | ||
| * @returns {boolean} | ||
| */ | ||
| public removeSession(sessionInfo: IActiveSessionInfo): boolean { | ||
| const indexOf = this._sessions.indexOf(sessionInfo); | ||
| if (indexOf >= 0) { | ||
| this._sessions.splice(indexOf, 1); | ||
| this._isFull = false; | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * If `true`, we know that there's too much `MediaKeySession` currently | ||
| * created. | ||
| * | ||
| * @see `markAsFull` method. | ||
| * @returns {boolean} | ||
| */ | ||
| public isFull(): boolean { | ||
| return this._isFull; | ||
| } | ||
| } | ||
|
|
||
| /** Information linked to a session created by the `ContentDecryptor`. */ | ||
| export interface IActiveSessionInfo { | ||
| /** | ||
| * Record associated to the session. | ||
| * Most notably, it allows both to identify the session as well as to | ||
| * anounce and find out which key ids are already handled. | ||
| */ | ||
| record: KeySessionRecord; | ||
|
|
||
| /** Current keys' statuses linked that session. */ | ||
| keyStatuses: { | ||
| /** Key ids linked to keys that are "usable". */ | ||
| whitelisted: Uint8Array[]; | ||
| /** | ||
| * Key ids linked to keys that are not considered "usable". | ||
| * Content linked to those keys are not decipherable and may thus be | ||
| * fallbacked from. | ||
| */ | ||
| blacklisted: Uint8Array[]; | ||
| }; | ||
|
|
||
| /** Source of the MediaKeySession linked to that record. */ | ||
| source: MediaKeySessionLoadingType; | ||
|
|
||
| /** | ||
| * If different than `null`, all initialization data compatible with this | ||
| * processed initialization data has been blacklisted with this corresponding | ||
| * error. | ||
| */ | ||
| blacklistedSessionError: BlacklistedSessionError | null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ | |
| */ | ||
|
|
||
| import log from "../../../log"; | ||
| import arrayIncludes from "../../../utils/array_includes"; | ||
| import type KeySessionRecord from "./key_session_record"; | ||
| import type LoadedSessionsStore from "./loaded_sessions_store"; | ||
|
|
||
| /** | ||
|
|
@@ -28,6 +30,7 @@ import type LoadedSessionsStore from "./loaded_sessions_store"; | |
| */ | ||
| export default async function cleanOldLoadedSessions( | ||
| loadedSessionsStore: LoadedSessionsStore, | ||
| activeRecords: KeySessionRecord[], | ||
| limit: number, | ||
| ): Promise<void> { | ||
| if (limit < 0 || limit >= loadedSessionsStore.getLength()) { | ||
|
|
@@ -38,11 +41,36 @@ export default async function cleanOldLoadedSessions( | |
| length: loadedSessionsStore.getLength(), | ||
| }); | ||
| const proms: Array<Promise<unknown>> = []; | ||
| const entries = loadedSessionsStore.getAll().slice(); // clone | ||
| const toDelete = entries.length - limit; | ||
| for (let i = 0; i < toDelete; i++) { | ||
| const entry = entries[i]; | ||
| proms.push(loadedSessionsStore.closeSession(entry.mediaKeySession)); | ||
| const sessionsMetadata = loadedSessionsStore.getAll().slice(); // clone | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What the purpose of cloning here?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't seem necessary here. Here none of that appears to be necessary, do you think I should remove the slice call? |
||
| let toDelete = sessionsMetadata.length - limit; | ||
| for (let i = 0; toDelete > 0 && i < sessionsMetadata.length; i++) { | ||
| const metadata = sessionsMetadata[i]; | ||
| if (!arrayIncludes(activeRecords, metadata.keySessionRecord)) { | ||
| proms.push(loadedSessionsStore.closeSession(metadata.mediaKeySession)); | ||
| toDelete--; | ||
| } | ||
| } | ||
| if (toDelete > 0) { | ||
| return Promise.all(proms).then(() => { | ||
| return Promise.reject( | ||
| new NoSessionSpaceError("Could not remove all sessions: some are still active"), | ||
| ); | ||
| }); | ||
| } | ||
| await Promise.all(proms); | ||
| } | ||
|
|
||
| /** | ||
| * Error thrown when the MediaKeySession is blacklisted. | ||
| * Such MediaKeySession should not be re-used but other MediaKeySession for the | ||
| * same content can still be used. | ||
| * @class NoSessionSpaceError | ||
| * @extends Error | ||
| */ | ||
| export class NoSessionSpaceError extends Error { | ||
| constructor(message: string) { | ||
| super(message); | ||
| // @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class | ||
| Object.setPrototypeOf(this, NoSessionSpaceError.prototype); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be typed as Promise boolean ?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but here the return type was not important because we never rely on it. I typed it as
unknownso it is not an obstacle now or in future evolutions.