Source: lib/msf/packaging/loc.js

/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shaka.msf.packaging.Loc');

goog.require('shaka.log');
goog.require('shaka.media.SegmentUtils');
goog.require('shaka.msf.LOCParser');
goog.require('shaka.msf.PackagingRegistry');
goog.require('shaka.msf.QuicVarIntCodec');
goog.require('shaka.util.MimeUtils');


/**
 * Packaging for Low Overhead Container tracks, where every MoQT object is one
 * frame of a raw elementary bitstream.
 *
 * A frame carries no container, so nothing about the media can be read out of
 * the objects themselves: the codec comes from the catalog, and timing is a
 * fixed frame duration derived from the catalog's framerate or samplerate,
 * with the frame's own timestamp used only to place it.
 *
 * @see https://www.ietf.org/archive/id/draft-ietf-moq-loc-04.html
 *
 * @implements {shaka.extern.MsfPackaging}
 * @final
 */
shaka.msf.packaging.Loc = class {
  constructor() {
    /** @private {number} */
    this.frameDuration_ = 0;

    /** @private {string} */
    this.normalizedCodec_ = '';
  }

  /**
   * @override
   */
  describeTrack(track, initData) {
    if (!track.codec) {
      shaka.log.info('Skipping LOC track with no codec', track);
      return null;
    }

    // For LOC tracks compute the fixed frame duration from MSF catalog fields
    // so that timing never depends on the optional LOC Timestamp property.
    const frameDuration = shaka.msf.LOCParser.frameDurationFromTrack(track);
    if (frameDuration === null) {
      shaka.log.warning(`LOC track "${track.name}" has no usable frame ` +
          'duration; skipping.');
      return null;
    }
    this.frameDuration_ = frameDuration;
    this.normalizedCodec_ =
        shaka.util.MimeUtils.getNormalizedCodec(track.codec);

    const basicInfo = shaka.media.SegmentUtils.getBasicInfoFromMimeType(
        `moq/loc; codecs="${track.codec}"`);

    // LOC has no initialization segment: an object is a raw elementary
    // bitstream frame, and LocTransmuxer builds the MP4 initialization
    // segment itself from the parsed parameter sets.
    //
    // A reference used to be synthesised for any track declaring a timescale.
    // MediaSourceEngine then appended it, and an initialization-segment
    // append passes reference=null into transmux(), which every LOC stream
    // handler dereferences for its baseMediaDecodeTime:
    //
    //   TypeError: Cannot read properties of null (reading 'startTime')
    //
    // That aborted every append, and surfaced only as "failed fetch and
    // append: code=undefined" — the code is undefined because a raw
    // TypeError is not a shaka.util.Error — with the player stalled at
    // HAVE_NOTHING. The timescale it carried is read only by
    // MediaSourceEngine's prft parsing, which cannot apply to a track that
    // has no MP4 boxes on the wire.
    return {basicInfo, initSegmentReference: null};
  }

  /**
   * @override
   */
  createSegmenter(codec) {
    // A caller with no session -- a test, or a packaging driven directly --
    // gets the encoding every draft up to 16 used.
    return new shaka.msf.packaging.LocSegmenter(
        new shaka.msf.LOCParser(
            codec || new shaka.msf.QuicVarIntCodec(),
            this.frameDuration_, this.normalizedCodec_));
  }
};


/**
 * @implements {shaka.extern.MsfSegmenter}
 * @final
 */
shaka.msf.packaging.LocSegmenter = class {
  /**
   * @param {!shaka.msf.LOCParser} parser
   */
  constructor(parser) {
    /** @private {!shaka.msf.LOCParser} */
    this.parser_ = parser;
  }

  /**
   * @override
   */
  push(obj) {
    if (!obj.data.byteLength) {
      return [];
    }

    const result = this.parser_.parse(obj);
    // The payload is empty once the private properties prefix is stripped when
    // the object carried properties but no bitstream.
    if (!result.duration || !result.payload.byteLength) {
      return [];
    }

    return [{
      startTime: result.startTime,
      duration: result.duration,
      data: result.payload,
      timestampOffset: 0,
      discontinuitySequence: -1,
    }];
  }
};


shaka.msf.PackagingRegistry.registerPackaging(
    'loc', () => new shaka.msf.packaging.Loc());