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.InitSegmentReference');
goog.require('shaka.media.SegmentUtils');
goog.require('shaka.msf.LOCParser');
goog.require('shaka.msf.PackagingRegistry');


/**
 * 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-02.html
 *
 * @implements {shaka.extern.MsfPackaging}
 * @final
 */
shaka.msf.packaging.Loc = class {
  constructor() {
    /** @private {number} */
    this.frameDuration_ = 0;
  }

  /**
   * @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;

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

    // LOC has no initialization segment. A track that declares a timescale
    // still gets a reference for it, because downstream consumers read the
    // timescale off the initialization segment reference.
    let initSegmentReference = null;
    if (track.timescale) {
      initSegmentReference = new shaka.media.InitSegmentReference(
          () => [],
          /* startBytes= */ 0,
          /* endBytes= */ null,
          /* mediaQuality= */ null,
          track.timescale,
          initData);
    }

    return {basicInfo, initSegmentReference};
  }

  /**
   * @override
   */
  createSegmenter() {
    return new shaka.msf.packaging.LocSegmenter(
        new shaka.msf.LOCParser(this.frameDuration_));
  }
};


/**
 * @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());