Source: lib/msf/drafts/draft20/messages.js

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

goog.provide('shaka.msf.draft20.MessageWriter');

goog.require('shaka.msf.draft18.MessageTypeId');
goog.require('shaka.msf.draft18.MessageWriter');


/**
 * Serializes draft-20 control messages.
 *
 * Draft-20 is draft-18 with one message body changed, so this extends the
 * draft-18 writer and overrides marshalFetch() alone. Everything else --
 * the var int type and 16-bit length framing, SETUP, SUBSCRIBE, the parameter
 * and namespace encodings -- is byte for byte what draft-18 writes.
 *
 * The message type IDs this player sends and reads are unchanged too, so there
 * is no draft-20 copy of the enum. Draft-20 does rename PUBLISH_BLOCKED to
 * PUBLISH_SKIPPED and reserve PUBLISH_OK (0x1E), but this player sends
 * neither, so the draft-18 names are the ones in use and renaming them here
 * would leave two enums to keep in step for no gain.
 *
 * Draft-22 changed the LOCATION_FILTER value alone, so it extends this class
 * and overrides locationFilterValue() and createNestedWriter().
 */
shaka.msf.draft20.MessageWriter = class
  extends shaka.msf.draft18.MessageWriter {
  /**
   * Draft-20 removed the Fetch Type field along with the Joining variants, and
   * moved the range out of the message and into the LOCATION_FILTER parameter,
   * leaving a body shaped exactly like SUBSCRIBE.
   *
   * The range asked for is the same one draft-18 puts in the message, so it
   * goes out as a filter rather than being dropped: an absent LOCATION_FILTER
   * means an unfiltered fetch of the whole track (draft-20 section 10.2.9),
   * which is a different request.
   *
   * @override
   */
  marshalFetch(msg) {
    const range = shaka.msf.draft18.MessageWriter.fetchRange(msg);
    if (range.end.group < range.start.group) {
      // Delta encoding cannot express it, and the range is invalid in every
      // draft of this family anyway.
      throw new Error(
          `FETCH end group ${range.end.group} precedes start group ` +
          `${range.start.group}`);
    }

    const params = (msg.params || []).concat([{
      type: BigInt(shaka.msf.draft18.MessageWriter.Parameter.LOCATION_FILTER),
      // With all four fields present the filter is absolute, and EndGroupDelta
      // is measured from StartGroup rather than from the Largest Object. A
      // shorter field list would be read as a filter relative to the live edge
      // instead (draft-20 section 5.1.2).
      value: this.locationFilterValue([
        range.start.group,
        range.start.object,
        range.end.group - range.start.group,
        range.end.object,
      ]),
    }]);

    return this.marshal(shaka.msf.draft18.MessageTypeId.FETCH, () => {
      this.writeVarInt(msg.requestId);
      this.writeNamespace(msg.namespace);
      this.writeString(msg.trackName);
      this.writeParameters(params);
    });
  }

  /**
   * Draft-20 removed the Joining FETCH. A subscriber joins at the current
   * Group with FILL_PARAMETERS instead: its LOCATION_FILTER, evaluated like a
   * FETCH's, selects the Groups to fill, and StartGroup alone is relative to
   * the Next Group, so 1 is the current one (draft-20 section 5.1.6). The
   * publisher delivers them on a fill fetch stream whose FETCH_HEADER carries
   * the SUBSCRIBE's Request ID.
   *
   * The value is a Parameter list of its own, counted and delta encoded like
   * the one around it.
   *
   * @override
   */
  fillCurrentGroupParam() {
    const inner = this.createNestedWriter();
    inner.writeParameters([{
      type: BigInt(shaka.msf.draft18.MessageWriter.Parameter.LOCATION_FILTER),
      value: this.locationFilterValue([BigInt(1)]),
    }]);
    return {
      type: BigInt(shaka.msf.draft18.MessageWriter.Parameter.FILL_PARAMETERS),
      value: inner.getBytes(),
    };
  }

  /**
   * Draft-20 has no Joining FETCH; fillCurrentGroupParam() is what joins.
   *
   * @override
   */
  marshalJoiningFetch(msg) {
    throw new Error('Draft-20 has no Joining FETCH');
  }

  /**
   * Draft-20 replaced the Filter Type with a field count: the number of var
   * ints in the LOCATION_FILTER value is what says which fields are present,
   * and two of them are a Start Group and a Start Object, absolute and
   * open-ended (draft-20 section 9.20.10). That is the same request draft-18
   * spells as AbsoluteStart, so only the encoding changes here.
   *
   * The one reading that is not absolute is two zeroed fields, which mean the
   * Next Object instead; callers ask for {0, 0} by sending no filter at all,
   * so it cannot arrive here.
   *
   * @override
   */
  locationFilterParam(startLocation) {
    return {
      type: BigInt(shaka.msf.draft18.MessageWriter.Parameter.LOCATION_FILTER),
      value: this.locationFilterValue([
        startLocation.group,
        startLocation.object,
      ]),
    };
  }

  /**
   * The value of a LOCATION_FILTER holding these fields. In draft-20 that is
   * the fields alone: the parameter's length says how many there are.
   *
   * @param {!Array<bigint>} fields
   * @return {!Uint8Array}
   * @protected
   */
  locationFilterValue(fields) {
    return this.encodeVarIntField(fields);
  }

  /**
   * A writer of this draft for a parameter list nested inside another, as
   * FILL_PARAMETERS carries.
   *
   * @return {!shaka.msf.draft20.MessageWriter}
   * @protected
   */
  createNestedWriter() {
    return new shaka.msf.draft20.MessageWriter(this.getCodec());
  }
};