rx-player
Version:
Canal+ HTML5 Video Player
162 lines (152 loc) • 5.86 kB
text/typescript
/**
* 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 log from "../../log";
import { getPsshSystemID } from "../../parsers/containers/isobmff";
import areArraysOfNumbersEqual from "../../utils/are_arrays_of_numbers_equal";
import { be4toi } from "../../utils/byte_parsing";
import isNullOrUndefined from "../../utils/is_null_or_undefined";
import type { IMediaEncryptedEvent } from "../browser_compatibility_types";
import { PSSH_TO_INTEGER } from "./constants";
/** Data recuperated from parsing the payload of an `encrypted` event. */
export interface IEncryptedEventData {
/**
* Initialization data type.
* String describing the format of the initialization data sent through this
* event.
* https://www.w3.org/TR/eme-initdata-registry/
*
* `undefined` if not known.
*/
type: string | undefined;
/** Every initialization data for that type. */
values: Array<{
/**
* Hex encoded system id, which identifies the key system.
* https://dashif.org/identifiers/content_protection/
*
* If `undefined`, we don't know the system id for that initialization data.
* In that case, the initialization data might even be a concatenation of
* the initialization data from multiple system ids.
*/
systemId: string | undefined;
/**
* The initialization data itself for that type and systemId.
* For example, with ISOBMFF "cenc" initialization data, this will be the
* whole PSSH box.
*/
data: Uint8Array;
}>;
forceSessionRecreation?: boolean | undefined;
}
/**
* Take in input initialization data from an encrypted event and generate the
* corresponding array of initialization data values from it.
*
* At the moment, this function only handles initialization data which have the
* "cenc" initialization data type.
* It will just return a single value with an `undefined` `systemId` for all
* other types of data.
* @param {Uint8Array} initData - Raw initialization data
* @returns {Array.<Object>}
*/
function getInitializationDataValues(
initData: Uint8Array,
): Array<{ systemId: string | undefined; data: Uint8Array }> {
const result: Array<{ systemId: string | undefined; data: Uint8Array }> = [];
let offset = 0;
while (offset < initData.length) {
if (
initData.length < offset + 8 ||
be4toi(initData, offset + 4) !== PSSH_TO_INTEGER
) {
log.warn("DRM", "Unrecognized initialization data. Use as is.");
return [{ systemId: undefined, data: initData }];
}
const len = be4toi(new Uint8Array(initData), offset);
if (offset + len > initData.length) {
log.warn("DRM", "Unrecognized initialization data. Use as is.");
return [{ systemId: undefined, data: initData }];
}
const currentPSSH = initData.subarray(offset, offset + len);
const systemId = getPsshSystemID(currentPSSH, 8);
const currentItem = { systemId, data: currentPSSH };
if (isPSSHAlreadyEncountered(result, currentItem)) {
// As we observed on some browsers (IE and Edge), the initialization data on
// some segments have sometimes duplicated PSSH when sent through an encrypted
// event (but not when the corresponding segment has been pushed to the
// SourceBuffer).
// We prefer filtering them out, to avoid further issues.
log.warn("DRM", "Duplicated PSSH found in initialization data, removing it.");
} else {
result.push(currentItem);
}
offset += len;
}
if (offset !== initData.length) {
log.warn("DRM", "Unrecognized initialization data. Use as is.");
return [{ systemId: undefined, data: initData }];
}
return result;
}
/**
* Returns `true` if the given PSSH has already been stored in the
* `encounteredPSSHs` cache given.
* Returns `false` otherwise.
* @param {Array.<Object>} encounteredPSSHs
* @param {Uint8Array} pssh
* @returns {boolean}
*/
function isPSSHAlreadyEncountered(
encounteredPSSHs: Array<{ systemId: string | undefined; data: Uint8Array }>,
pssh: { systemId: string | undefined; data: Uint8Array },
): boolean {
for (let i = 0; i < encounteredPSSHs.length; i++) {
const item = encounteredPSSHs[i];
if (
pssh.systemId === undefined ||
item.systemId === undefined ||
pssh.systemId === item.systemId
) {
if (areArraysOfNumbersEqual(pssh.data, item.data)) {
return true;
}
}
}
return false;
}
/**
* Take out the two things we need on an encryptedEvent:
* - the initialization Data
* - the initialization Data type
*
* @param {MediaEncryptedEvent} encryptedEvent - Payload received with an
* "encrypted" event.
* @returns {Object} - Initialization data and Initialization data type.
* @throws {EncryptedMediaError} - Throws if no initialization data is
* encountered in the given event.
*/
export default function getInitData(
encryptedEvent: IMediaEncryptedEvent,
): IEncryptedEventData | null {
const { initData, initDataType, forceSessionRecreation } = encryptedEvent;
if (isNullOrUndefined(initData)) {
log.warn("DRM", "No init data found on media encrypted event.");
return null;
}
const initDataBytes = new Uint8Array(initData);
const values = getInitializationDataValues(initDataBytes);
return { type: initDataType, values, forceSessionRecreation };
}