UNPKG

shaka-player

Version:
268 lines (243 loc) 7.61 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.transmuxer.TransmuxerWorker'); goog.require('shaka.log'); goog.require('shaka.transmuxer.TransmuxerEngine'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Error'); /** * @summary Web Worker entry point for offloading transmux operations. * * This class manages transmuxer instances inside a Web Worker and * communicates with the main thread via postMessage. * * Message protocol (main -> worker): * {cmd: 'init', id: number, mimeType: string} * {cmd: 'transmux', id: number, reqId: number, data: ArrayBuffer, * streamProps: Object, refProps: Object, duration: number, * contentType: string} * {cmd: 'destroy', id: number} * * Response protocol (worker -> main): * {cmd: 'transmuxed', id: number, reqId: number, output: Object, * streamMutations: Object} * {cmd: 'error', id: number, reqId: number, error: Object} * {cmd: 'destroyed', id: number} * * @export */ shaka.transmuxer.TransmuxerWorker = class { constructor() { /** * @private {!Map<number, !shaka.extern.Transmuxer>} */ this.transmuxers_ = new Map(); } /** * Starts listening for messages. Call this from the worker global scope. * @export */ start() { self.addEventListener('message', (event) => { this.onMessage_(/** @type {!MessageEvent} */(event)); }); } /** * Handles incoming messages from the main thread. * @param {!MessageEvent} event * @private */ onMessage_(event) { const msg = event.data; switch (msg['cmd']) { case 'init': this.onInit_(msg); break; case 'transmux': this.onTransmux_(msg); break; case 'destroy': this.onDestroy_(msg); break; } } /** * Creates a transmuxer instance. * @param {!Object} msg * @private */ onInit_(msg) { const id = msg['id']; const mimeType = msg['mimeType']; // Look up the transmuxer plugin directly without calling isSupported(). // The main thread already validated support; re-checking here would fail // because MediaSource in Workers may not report the same type support as // the main thread. const plugin = shaka.transmuxer.TransmuxerEngine.findTransmuxerPlugin(mimeType); if (plugin) { this.transmuxers_.set(id, plugin()); } else { // Only log here; the subsequent onTransmux_ call will post an error // with the correct reqId so the proxy can route it to the caller. shaka.log.warning('TransmuxerWorker: no plugin found for', mimeType); } } /** * Runs a transmux operation and posts back the result. * @param {!Object} msg * @private */ async onTransmux_(msg) { const id = msg['id']; const reqId = msg['reqId']; const transmuxer = this.transmuxers_.get(id); if (!transmuxer) { self.postMessage({ 'cmd': 'error', 'id': id, 'reqId': reqId, 'error': { 'severity': shaka.util.Error.Severity.CRITICAL, 'category': shaka.util.Error.Category.MEDIA, 'code': shaka.util.Error.Code.TRANSMUXING_FAILED, 'data': ['No transmuxer initialized for id ' + id], }, }); return; } const streamProps = msg['streamProps']; const refProps = msg['refProps']; const stream = { 'id': streamProps['id'], 'codecs': streamProps['codecs'], 'channelsCount': streamProps['channelsCount'], 'audioSamplingRate': streamProps['audioSamplingRate'], 'height': streamProps['height'], 'width': streamProps['width'], 'language': streamProps['language'], }; const reference = refProps ? { 'discontinuitySequence': refProps['discontinuitySequence'], 'startTime': refProps['startTime'], 'endTime': refProps['endTime'], 'getUris': () => refProps['uris'], } : null; const data = shaka.util.BufferUtils.toUint8( /** @type {!ArrayBuffer} */(msg['data'])); const duration = msg['duration']; const contentType = msg['contentType']; try { const result = await transmuxer.transmux( data, /** @type {shaka.extern.Stream} */(stream), /** @type {?} */(reference), duration, contentType); // Compute mutations: which stream properties changed. const streamMutations = {}; const mutatedKeys = [ 'audioSamplingRate', 'channelsCount', 'height', 'width', ]; for (const key of mutatedKeys) { if (stream[key] !== streamProps[key]) { streamMutations[key] = stream[key]; } } // Convert typed array views to ArrayBuffer before posting. Only // ArrayBuffer (not views) can be transferred zero-copy via postMessage. const BufferUtils = shaka.util.BufferUtils; if (ArrayBuffer.isView(result)) { const buf = BufferUtils.toArrayBuffer( /** @type {!Uint8Array} */(result)); self.postMessage({ 'cmd': 'transmuxed', 'id': id, 'reqId': reqId, 'output': {'type': 'raw', 'data': buf}, 'streamMutations': streamMutations, }, [buf]); } else { const output = /** @type {!shaka.extern.TransmuxerOutput} */(result); const dataBuf = BufferUtils.toArrayBuffer(output.data); const initBuf = output.init ? BufferUtils.toArrayBuffer(output.init) : null; const transfers = [dataBuf]; const response = { 'cmd': 'transmuxed', 'id': id, 'reqId': reqId, 'output': { 'type': 'segments', 'data': dataBuf, 'init': initBuf, }, 'streamMutations': streamMutations, }; if (initBuf) { transfers.push(initBuf); } self.postMessage(response, transfers); } } catch (e) { self.postMessage({ 'cmd': 'error', 'id': id, 'reqId': reqId, 'error': shaka.transmuxer.TransmuxerWorker.errorToObject_(e), }); } } /** * Converts a caught error into a plain serializable object for postMessage. * @param {*} e * @return {!Object} * @private */ static errorToObject_(e) { if (e instanceof shaka.util.Error) { return { 'severity': e.severity, 'category': e.category, 'code': e.code, 'data': e.data, }; } return { 'severity': shaka.util.Error.Severity.CRITICAL, 'category': shaka.util.Error.Category.MEDIA, 'code': shaka.util.Error.Code.TRANSMUXING_FAILED, 'data': [e instanceof Error ? e.message : 'Unknown error'], }; } /** * Destroys a transmuxer instance. * @param {!Object} msg * @private */ onDestroy_(msg) { const id = msg['id']; const transmuxer = this.transmuxers_.get(id); if (transmuxer) { transmuxer.destroy(); this.transmuxers_.delete(id); } self.postMessage({'cmd': 'destroyed', 'id': id}); } }; /** * Boots the worker if running in a Worker global scope. * This is called at load time so the worker is ready immediately. */ shaka.transmuxer.TransmuxerWorker.boot = () => { if (typeof DedicatedWorkerGlobalScope !== 'undefined' && self instanceof DedicatedWorkerGlobalScope) { const worker = new shaka.transmuxer.TransmuxerWorker(); worker.start(); } }; // Auto-boot when loaded in a worker context. shaka.transmuxer.TransmuxerWorker.boot();