Home Reference Source

src/types/level.ts

import LevelDetails from '../loader/level-details';
import AttrList from '../utils/attr-list';

export interface LevelParsed {
  attrs: LevelAttributes
  audioCodec?: string
  bitrate: number
  details?: LevelDetails
  height?: number
  id?: number
  level?: number
  name: string
  textCodec?: string
  unknownCodecs?: string[]
  url: string
  videoCodec?: string
  width?: number
}

export interface LevelAttributes extends AttrList {
  AUDIO?: string
  AUTOSELECT?: string
  'AVERAGE-BANDWIDTH'?: string
  BANDWIDTH?: string
  BYTERANGE?: string
  'CLOSED-CAPTIONS'?: string
  CODECS?: string
  DEFAULT?: string
  FORCED?: string
  'FRAME-RATE'?: string
  LANGUAGE?: string
  NAME?: string
  'PROGRAM-ID'?: string
  RESOLUTION?: string
  SUBTITLES?: string
  TYPE?: string
  URI?: string
}

export enum HlsSkip {
  No = '',
  Yes = 'YES',
  v2 = 'v2'
}

export function getSkipValue (details: LevelDetails, msn: number): HlsSkip {
  const { canSkipUntil, canSkipDateRanges, endSN } = details;
  const snChangeGoal = msn - endSN;
  if (canSkipUntil && snChangeGoal < canSkipUntil) {
    if (canSkipDateRanges) {
      return HlsSkip.v2;
    }
    return HlsSkip.Yes;
  }
  return HlsSkip.No;
}

export class HlsUrlParameters {
  msn: number;
  part?: number;
  skip?: HlsSkip;

  constructor (msn: number, part?: number, skip?: HlsSkip) {
    this.msn = msn;
    this.part = part;
    this.skip = skip;
  }

  addDirectives (uri: string): string | never {
    const url: URL = new self.URL(uri);
    const searchParams: URLSearchParams = url.searchParams;
    searchParams.set('_HLS_msn', this.msn.toString());
    if (this.part !== undefined) {
      searchParams.set('_HLS_part', this.part.toString());
    }
    if (this.skip) {
      searchParams.set('_HLS_skip', this.skip);
    }
    searchParams.sort();
    url.search = searchParams.toString();
    return url.toString();
  }
}

export class Level {
  public attrs: LevelAttributes;
  public audioCodec?: string;
  public audioGroupIds?: string[];
  public bitrate: number;
  public details?: LevelDetails;
  public fragmentError: boolean = false;
  public height: number;
  public id: number;
  public loadError: number = 0;
  public loaded?: { bytes: number, duration: number };
  public name: string | undefined;
  public realBitrate: number = 0;
  public textGroupIds?: string[];
  public url: string[];
  public videoCodec?: string;
  public width: number;
  public unknownCodecs: string[] | undefined;
  private _urlId: number = 0;

  constructor (data: LevelParsed) {
    this.url = [data.url];
    this.attrs = data.attrs;
    this.bitrate = data.bitrate;
    this.details = data.details;
    this.id = data.id || 0;
    this.name = data.name;
    this.width = data.width || 0;
    this.height = data.height || 0;
    this.audioCodec = data.audioCodec;
    this.videoCodec = data.videoCodec;
    this.unknownCodecs = data.unknownCodecs;
  }

  get maxBitrate (): number {
    return Math.max(this.realBitrate, this.bitrate);
  }

  get uri (): string {
    return this.url[this._urlId] || '';
  }

  get urlId (): number {
    return this._urlId;
  }

  set urlId (value: number) {
    const newValue = value % this.url.length;
    if (this._urlId !== newValue) {
      this.details = undefined;
      this._urlId = newValue;
    }
  }
}