Home Reference Source

src/demux/mp4demuxer.ts

/**
 * MP4 demuxer
 */
import {
  Demuxer,
  DemuxerResult,
  DemuxedTrack,
  PassthroughVideoTrack,
  DemuxedAudioTrack,
  DemuxedUserdataTrack,
  DemuxedMetadataTrack,
} from '../types/demuxer';
import {
  findBox,
  segmentValidRange,
  appendUint8Array,
} from '../utils/mp4-tools';
import { dummyTrack } from './dummy-demuxed-track';
import type { HlsEventEmitter } from '../events';
import type { HlsConfig } from '../config';

class MP4Demuxer implements Demuxer {
  static readonly minProbeByteLength = 1024;
  private remainderData: Uint8Array | null = null;
  private config: HlsConfig;

  constructor(observer: HlsEventEmitter, config: HlsConfig) {
    this.config = config;
  }

  resetTimeStamp() {}

  resetInitSegment() {}

  resetContiguity(): void {}

  static probe(data) {
    // ensure we find a moof box in the first 16 kB
    return (
      findBox({ data: data, start: 0, end: Math.min(data.length, 16384) }, [
        'moof',
      ]).length > 0
    );
  }

  demux(data): DemuxerResult {
    // Load all data into the avc track. The CMAF remuxer will look for the data in the samples object; the rest of the fields do not matter
    let avcSamples = data;
    const avcTrack = dummyTrack() as PassthroughVideoTrack;
    if (this.config.progressive) {
      // Split the bytestream into two ranges: one encompassing all data up until the start of the last moof, and everything else.
      // This is done to guarantee that we're sending valid data to MSE - when demuxing progressively, we have no guarantee
      // that the fetch loader gives us flush moof+mdat pairs. If we push jagged data to MSE, it will throw an exception.
      if (this.remainderData) {
        avcSamples = appendUint8Array(this.remainderData, data);
      }
      const segmentedData = segmentValidRange(avcSamples);
      this.remainderData = segmentedData.remainder;
      avcTrack.samples = segmentedData.valid || new Uint8Array();
    } else {
      avcTrack.samples = avcSamples;
    }

    return {
      audioTrack: dummyTrack() as DemuxedAudioTrack,
      avcTrack,
      id3Track: dummyTrack() as DemuxedMetadataTrack,
      textTrack: dummyTrack() as DemuxedUserdataTrack,
    };
  }

  flush() {
    const avcTrack = dummyTrack() as PassthroughVideoTrack;
    avcTrack.samples = this.remainderData || new Uint8Array();
    this.remainderData = null;

    return {
      audioTrack: dummyTrack() as DemuxedAudioTrack,
      avcTrack,
      id3Track: dummyTrack() as DemuxedMetadataTrack,
      textTrack: dummyTrack() as DemuxedUserdataTrack,
    };
  }

  demuxSampleAes(
    data: Uint8Array,
    decryptData: Uint8Array,
    timeOffset: number
  ): Promise<DemuxerResult> {
    return Promise.reject(
      new Error('The MP4 demuxer does not support SAMPLE-AES decryption')
    );
  }

  destroy() {}
}

export default MP4Demuxer;