Home Reference Source

src/loader/fragment.js


import URLToolkit from 'url-toolkit';

import LevelKey from './level-key';

export default class Fragment {
  constructor () {
    this._url = null;
    this._byteRange = null;
    this._decryptdata = null;
    this.tagList = [];

    // Holds the types of data this fragment supports
    this._elementaryStreams = {
      [Fragment.ElementaryStreamTypes.AUDIO]: false,
      [Fragment.ElementaryStreamTypes.VIDEO]: false
    };
  }

  /**
   * `type` property for this._elementaryStreams
   *
   * @enum
   */
  static get ElementaryStreamTypes () {
    return {
      AUDIO: 'audio',
      VIDEO: 'video'
    };
  }

  get url () {
    if (!this._url && this.relurl)
      this._url = URLToolkit.buildAbsoluteURL(this.baseurl, this.relurl, { alwaysNormalize: true });

    return this._url;
  }

  set url (value) {
    this._url = value;
  }

  get programDateTime () {
    if (!this._programDateTime && this.rawProgramDateTime)
      this._programDateTime = new Date(Date.parse(this.rawProgramDateTime));

    return this._programDateTime;
  }

  get byteRange () {
    if (!this._byteRange && !this.rawByteRange)
      return [];

    if (this._byteRange)
      return this._byteRange;

    let byteRange = [];
    if (this.rawByteRange) {
      const params = this.rawByteRange.split('@', 2);
      if (params.length === 1) {
        const lastByteRangeEndOffset = this.lastByteRangeEndOffset;
        byteRange[0] = lastByteRangeEndOffset || 0;
      } else {
        byteRange[0] = parseInt(params[1]);
      }
      byteRange[1] = parseInt(params[0]) + byteRange[0];
      this._byteRange = byteRange;
    }
    return byteRange;
  }

  /**
   * @type {number}
   */
  get byteRangeStartOffset () {
    return this.byteRange[0];
  }

  get byteRangeEndOffset () {
    return this.byteRange[1];
  }

  get decryptdata () {
    if (!this._decryptdata)
      this._decryptdata = this.fragmentDecryptdataFromLevelkey(this.levelkey, this.sn);

    return this._decryptdata;
  }

  /**
   * @param {ElementaryStreamType} type
   */
  addElementaryStream (type) {
    this._elementaryStreams[type] = true;
  }

  /**
   * @param {ElementaryStreamType} type
   */
  hasElementaryStream (type) {
    return this._elementaryStreams[type] === true;
  }

  /**
   * Utility method for parseLevelPlaylist to create an initialization vector for a given segment
   * @returns {Uint8Array}
   */
  createInitializationVector (segmentNumber) {
    let uint8View = new Uint8Array(16);

    for (let i = 12; i < 16; i++)
      uint8View[i] = (segmentNumber >> 8 * (15 - i)) & 0xff;

    return uint8View;
  }

  /**
   * Utility method for parseLevelPlaylist to get a fragment's decryption data from the currently parsed encryption key data
   * @param levelkey - a playlist's encryption info
   * @param segmentNumber - the fragment's segment number
   * @returns {*} - an object to be applied as a fragment's decryptdata
   */
  fragmentDecryptdataFromLevelkey (levelkey, segmentNumber) {
    let decryptdata = levelkey;

    if (levelkey && levelkey.method && levelkey.uri && !levelkey.iv) {
      decryptdata = new LevelKey();
      decryptdata.method = levelkey.method;
      decryptdata.baseuri = levelkey.baseuri;
      decryptdata.reluri = levelkey.reluri;
      decryptdata.iv = this.createInitializationVector(segmentNumber);
    }

    return decryptdata;
  }
}