Home Reference Source

src/demux/transmuxer-worker.ts

import Transmuxer, { isPromise } from '../demux/transmuxer';
import { Events } from '../events';
import { enableLogs } from '../utils/logger';
import { EventEmitter } from 'eventemitter3';
import type { RemuxedTrack, RemuxerResult } from '../types/remuxer';
import type { TransmuxerResult, ChunkMetadata } from '../types/transmuxer';

export default function TransmuxerWorker (self) {
  const observer = new EventEmitter();
  const forwardMessage = (ev, data) => {
    self.postMessage({ event: ev, data: data });
  };

  // forward events to main thread
  observer.on(Events.FRAG_DECRYPTED, forwardMessage);
  observer.on(Events.ERROR, forwardMessage);

  self.addEventListener('message', (ev) => {
    const data = ev.data;
    switch (data.cmd) {
    case 'init': {
      const config = JSON.parse(data.config);
      self.transmuxer = new Transmuxer(observer, data.typeSupported, config, data.vendor);
      enableLogs(config.debug);
      forwardMessage('init', null);
      break;
    }
    case 'configure': {
      self.transmuxer.configure(data.config, data.state);
      break;
    }
    case 'demux': {
      const transmuxResult: TransmuxerResult | Promise<TransmuxerResult> =
          self.transmuxer.push(data.data, data.decryptdata, data.chunkMeta);
      if (isPromise(transmuxResult)) {
        transmuxResult.then((data) => {
          emitTransmuxComplete(self, data);
        });
      } else {
        emitTransmuxComplete(self, transmuxResult);
      }
      break;
    }
    case 'flush': {
      const id = data.chunkMeta;
      const transmuxResult = self.transmuxer.flush(id);
      if (isPromise(transmuxResult)) {
        transmuxResult.then((results: Array<TransmuxerResult>) => {
          handleFlushResult(self, results as Array<TransmuxerResult>, id);
        });
      } else {
        handleFlushResult(self, transmuxResult as Array<TransmuxerResult>, id);
      }
      break;
    }
    default:
      break;
    }
  });
}

function emitTransmuxComplete (self: any, transmuxResult: TransmuxerResult) {
  if (isEmptyResult(transmuxResult.remuxResult)) {
    return;
  }
  const transferable: Array<ArrayBuffer> = [];
  const { audio, video } = transmuxResult.remuxResult;
  if (audio) {
    addToTransferable(transferable, audio);
  }
  if (video) {
    addToTransferable(transferable, video);
  }
  self.postMessage({ event: 'transmuxComplete', data: transmuxResult }, transferable);
}

// Converts data to a transferable object https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast)
// in order to minimize message passing overhead
function addToTransferable (transferable: Array<ArrayBuffer>, track: RemuxedTrack) {
  if (track.data1) {
    transferable.push(track.data1.buffer);
  }
  if (track.data2) {
    transferable.push(track.data2.buffer);
  }
}

function handleFlushResult (self: any, results: Array<TransmuxerResult>, chunkMeta: ChunkMetadata) {
  results.forEach(result => {
    emitTransmuxComplete(self, result);
  });
  self.postMessage({ event: 'flush', data: chunkMeta });
}

function isEmptyResult (remuxResult: RemuxerResult) {
  return !remuxResult.audio &&
    !remuxResult.video &&
    !remuxResult.text &&
    !remuxResult.id3 &&
    !remuxResult.initSegment;
}