UNPKG

rx-player

Version:
218 lines (202 loc) 8.16 kB
/** * Copyright 2015 CANAL+ Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import type { IMediaKeySession } from "../../compat/browser_compatibility_types"; import log from "../../log"; import type { CancellationSignal } from "../../utils/task_canceller"; import type { IProcessedProtectionData, IMediaKeySessionStores } from "./types"; import { MediaKeySessionLoadingType } from "./types"; import isSessionUsable from "./utils/is_session_usable"; import type KeySessionRecord from "./utils/key_session_record"; import type LoadedSessionsStore from "./utils/loaded_sessions_store"; import type PersistentSessionsStore from "./utils/persistent_sessions_store"; /** * Create a new Session or load a persistent one on the given MediaKeys, * according to wanted settings and what is currently stored. * * If session creating fails, remove the oldest MediaKeySession loaded and * retry. * * /!\ This only creates new sessions. * It will fail if loadedSessionsStore already has a MediaKeySession with * the given initialization data. * @param {Object} stores * @param {Object} initData * @param {string} wantedSessionType * @param {Object} cancelSignal * @returns {Promise} */ export default function createSession( stores: IMediaKeySessionStores, initData: IProcessedProtectionData, wantedSessionType: MediaKeySessionType, cancelSignal: CancellationSignal, ): Promise<ICreateSessionEvent> { const { loadedSessionsStore, persistentSessionsStore } = stores; if (wantedSessionType === "temporary") { return createTemporarySession(loadedSessionsStore, initData); } else if (persistentSessionsStore === null) { log.warn( "DRM", "Cannot create persistent MediaKeySession, " + "PersistentSessionsStore not created.", ); return createTemporarySession(loadedSessionsStore, initData); } return createAndTryToRetrievePersistentSession( loadedSessionsStore, persistentSessionsStore, initData, cancelSignal, ); } /** * Create a new temporary MediaKeySession linked to the given initData and * initDataType. * @param {Object} loadedSessionsStore * @param {Object} initData * @returns {Promise} */ function createTemporarySession( loadedSessionsStore: LoadedSessionsStore, initData: IProcessedProtectionData, ): Promise<INewSessionCreatedEvent> { log.info("DRM", "Creating a new temporary session"); const entry = loadedSessionsStore.createSession(initData, "temporary"); return Promise.resolve({ type: MediaKeySessionLoadingType.Created, value: entry, }); } /** * Create a persistent MediaKeySession and try to load on it a previous * MediaKeySession linked to the same initialization data. * @param {Object} loadedSessionsStore * @param {Object} persistentSessionsStore * @param {Object} initData * @param {Object} cancelSignal * @returns {Promise} */ async function createAndTryToRetrievePersistentSession( loadedSessionsStore: LoadedSessionsStore, persistentSessionsStore: PersistentSessionsStore, initData: IProcessedProtectionData, cancelSignal: CancellationSignal, ): Promise<INewSessionCreatedEvent | IPersistentSessionRecoveryEvent> { if (cancelSignal.cancellationError !== null) { throw cancelSignal.cancellationError; } log.info("DRM", "Creating persistent MediaKeySession"); const entry = loadedSessionsStore.createSession(initData, "persistent-license"); const storedEntry = persistentSessionsStore.getAndReuse(initData); if (storedEntry === null) { return { type: MediaKeySessionLoadingType.Created, value: entry }; } try { const hasLoadedSession = await loadedSessionsStore.loadPersistentSession( entry.mediaKeySession, storedEntry.sessionId, ); if (!hasLoadedSession) { log.warn("DRM", "No data stored for the loaded session", { sessionId: storedEntry.sessionId, }); persistentSessionsStore.delete(storedEntry.sessionId); // The EME specification is kind of implicit about it but it seems from my // understanding (Paul B.) that a MediaKeySession on wich a `load` attempt // did not succeed due to the loaded session not being found by the // browser/CDM, should neither be used anymore nor closed. // Thus, we're creating another `"persistent-license"` `MediaKeySession` // in that specific case. loadedSessionsStore.removeSessionWithoutClosingIt(entry.mediaKeySession); const newEntry = loadedSessionsStore.createSession(initData, "persistent-license"); return { type: MediaKeySessionLoadingType.Created, value: newEntry }; } if (hasLoadedSession && isSessionUsable(entry.mediaKeySession)) { persistentSessionsStore.add(initData, initData.keyIds, entry.mediaKeySession); log.info("DRM", "Succeeded to load persistent session."); return { type: MediaKeySessionLoadingType.LoadedPersistentSession, value: entry, }; } // Unusable persistent session: recreate a new session from scratch. log.warn("DRM", "Previous persistent session not usable anymore."); return recreatePersistentSession(); } catch (err) { log.warn( "DRM", "Unable to load persistent session: " + (err instanceof Error ? err.toString() : "Unknown Error"), ); return recreatePersistentSession(); } /** * Helper function to close and restart the current persistent session * considered, and re-create it from scratch. * @returns {Promise.<Object>} */ async function recreatePersistentSession(): Promise<INewSessionCreatedEvent> { if (cancelSignal.cancellationError !== null) { throw cancelSignal.cancellationError; } log.info("DRM", "Removing previous persistent session."); const persistentEntry = persistentSessionsStore.get(initData); if (persistentEntry !== null) { persistentSessionsStore.delete(persistentEntry.sessionId); } try { await loadedSessionsStore.closeSession(entry.mediaKeySession); } catch (err) { // From reading the EME specification in details, it seems that a // `MediaKeySession`'s ability to be closed is tightly linked to its // possession of a "sanitized session ID" set as `sessionId`. // This is never clearly stated however and I'm (Paul B.) always afraid of // breaking compatibility when it comes to EME code. // So we still try to close the `MediaKeySession` in any case, only, if it // fails and it didn't had any `sessionId` set, we just ignore the error. // Note that trying to close the `MediaKeySession` might incur some delays // in those rare cases. if (entry.mediaKeySession.sessionId !== "") { throw err; } loadedSessionsStore.removeSessionWithoutClosingIt(entry.mediaKeySession); } if (cancelSignal.cancellationError !== null) { throw cancelSignal.cancellationError; } const newEntry = loadedSessionsStore.createSession(initData, "persistent-license"); return { type: MediaKeySessionLoadingType.Created, value: newEntry }; } } export interface INewSessionCreatedEvent { type: MediaKeySessionLoadingType.Created; value: { mediaKeySession: IMediaKeySession; sessionType: MediaKeySessionType; keySessionRecord: KeySessionRecord; }; } export interface IPersistentSessionRecoveryEvent { type: MediaKeySessionLoadingType.LoadedPersistentSession; value: { mediaKeySession: IMediaKeySession; sessionType: MediaKeySessionType; keySessionRecord: KeySessionRecord; }; } export type ICreateSessionEvent = | INewSessionCreatedEvent | IPersistentSessionRecoveryEvent;