UNPKG

shaka-player

Version:
161 lines (148 loc) • 5.23 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.msf.Compression'); goog.require('shaka.msf.Utils'); goog.require('shaka.util.Error'); goog.require('shaka.util.Uint8ArrayUtils'); /** * Undoes the payload compression MSF signals with the MSF_COMPRESSION * property (draft-ietf-moq-msf, "Compression Signaling"). * * The property has one type, 0x78, and two places it can be carried, which a * publisher must not combine on one track: * * - as a Track Property, in SUBSCRIBE_OK or FETCH_OK, when every Object of * the track is compressed; * - as an Object Property, on each compressed Object, when only some are -- * a catalog whose complete document is compressed but whose delta updates * are not, say. * * It applies to the JSON tracks: the catalog, media timeline tracks and event * timeline tracks. * * @final */ shaka.msf.Compression = class { /** * The compression algorithm an Object's payload is in. The Track Property * wins when both are somehow present, since it covers every Object. * * @param {!shaka.extern.MsfObject} obj * @param {!shaka.extern.MsfCodec} codec The negotiated draft's primitive * codec, which the property blocks are encoded with. * @return {number} A shaka.msf.Compression.Algorithm value, or whatever * other value the publisher sent. */ static getAlgorithm(obj, codec) { const Compression = shaka.msf.Compression; for (const block of [obj.trackProperties, obj.extensions]) { if (!block || !block.byteLength) { continue; } const value = shaka.msf.Utils.parseProperties(block, codec).get( BigInt(Compression.PROPERTY_TYPE_)); if (typeof value === 'bigint') { return Number(value); } } return Compression.Algorithm.NONE; } /** * Returns an Object's payload with its compression undone. * * An uncompressed payload comes back as is and synchronously, so a caller * that has nothing waiting ahead of it can go on handling it in the same * turn. A compressed one comes back as a promise. * * @param {!shaka.extern.MsfObject} obj * @param {!shaka.extern.MsfCodec} codec * @return {!Uint8Array|!Promise<!Uint8Array>} * @throws {shaka.util.Error} MSF_UNSUPPORTED_COMPRESSION when the algorithm * is one this player does not know, or the browser cannot decompress it. * MSF forbids processing such a payload. */ static decode(obj, codec) { const Compression = shaka.msf.Compression; const algorithm = Compression.getAlgorithm(obj, codec); if (algorithm == Compression.Algorithm.NONE) { return obj.data; } if (algorithm == Compression.Algorithm.GZIP) { try { return Compression.gunzip_(obj.data); } catch (error) { // The browser cannot decompress GZIP: DecompressionStream is newer // than the rest of what MSF needs (Chromium 80, Firefox 113, Safari // 16.4). } } throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MANIFEST, shaka.util.Error.Code.MSF_UNSUPPORTED_COMPRESSION, algorithm); } /** * Not async on purpose: the stream is created before returning, so a browser * that cannot create it throws to the caller synchronously rather than * through the returned promise, which is left for a payload that fails to * decompress. * * @param {!Uint8Array} data * @return {!Promise<!Uint8Array>} * @private */ static gunzip_(data) { const stream = new DecompressionStream('gzip'); return shaka.msf.Compression.readAll_(stream, data); } /** * @param {!DecompressionStream} stream * @param {!Uint8Array} data * @return {!Promise<!Uint8Array>} * @private */ static async readAll_(stream, data) { // The writes are not awaited: the writable side only drains as the // readable side is read, so waiting for them first would never finish. A // failure on them fails the reads below too, which is where it surfaces. const writer = stream.writable.getWriter(); writer.write(data).catch(() => {}); writer.close().catch(() => {}); const reader = stream.readable.getReader(); /** @type {!Array<!Uint8Array>} */ const chunks = []; while (true) { // eslint-disable-next-line no-await-in-loop const {value, done} = await reader.read(); if (done) { break; } chunks.push(/** @type {!Uint8Array} */ (value)); } return shaka.util.Uint8ArrayUtils.concat(...chunks); } }; /** * The MSF_COMPRESSION values, from the "MSF Compression Algorithms" registry. * * @enum {number} */ shaka.msf.Compression.Algorithm = { NONE: 0, GZIP: 1, }; /** * The MSF_COMPRESSION Property Type, used both as a Track Property and as an * Object Property. Even, so its value is a bare var int. * * Declared as a number and widened with BigInt() at the lookup: a BigInt() * call here would run at load time and throw on platforms without BigInt * (Tizen 3), abandoning the whole test run. * * @private @const {number} */ shaka.msf.Compression.PROPERTY_TYPE_ = 0x78;