Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply TypeScript to Fragment & LevelKey #2107

Merged
merged 12 commits into from Feb 5, 2019
4 changes: 2 additions & 2 deletions src/controller/audio-stream-controller.js
Expand Up @@ -12,7 +12,7 @@ import { ErrorTypes, ErrorDetails } from '../errors';
import { logger } from '../utils/logger';
import { findFragWithCC } from '../utils/discontinuities';
import { FragmentState } from './fragment-tracker';
import Fragment from '../loader/fragment';
import Fragment, { ElementaryStreamTypes } from '../loader/fragment';
import BaseStreamController, { State } from './base-stream-controller';
const { performance } = window;

Expand Down Expand Up @@ -615,7 +615,7 @@ class AudioStreamController extends BaseStreamController {
data.endDTS = data.startDTS + fragCurrent.duration;
}

fragCurrent.addElementaryStream(Fragment.ElementaryStreamTypes.AUDIO);
fragCurrent.addElementaryStream(ElementaryStreamTypes.AUDIO);

logger.log(`parsed ${data.type},PTS:[${data.startPTS.toFixed(3)},${data.endPTS.toFixed(3)}],DTS:[${data.startDTS.toFixed(3)}/${data.endDTS.toFixed(3)}],nb:${data.nb}`);
LevelHelper.updateFragPTSDTS(track.details, fragCurrent, data.startPTS, data.endPTS);
Expand Down
8 changes: 4 additions & 4 deletions src/controller/stream-controller.js
Expand Up @@ -7,7 +7,7 @@ import { BufferHelper } from '../utils/buffer-helper';
import Demuxer from '../demux/demuxer';
import Event from '../events';
import { FragmentState } from './fragment-tracker';
import Fragment from '../loader/fragment';
import Fragment, { ElementaryStreamTypes } from '../loader/fragment';
import PlaylistLoader from '../loader/playlist-loader';
import * as LevelHelper from './level-helper';
import TimeRanges from '../utils/time-ranges';
Expand Down Expand Up @@ -1000,11 +1000,11 @@ class StreamController extends BaseStreamController {
}

if (data.hasAudio === true) {
frag.addElementaryStream(Fragment.ElementaryStreamTypes.AUDIO);
frag.addElementaryStream(ElementaryStreamTypes.AUDIO);
}

if (data.hasVideo === true) {
frag.addElementaryStream(Fragment.ElementaryStreamTypes.VIDEO);
frag.addElementaryStream(ElementaryStreamTypes.VIDEO);
}

logger.log(`Parsed ${data.type},PTS:[${data.startPTS.toFixed(3)},${data.endPTS.toFixed(3)}],DTS:[${data.startDTS.toFixed(3)}/${data.endDTS.toFixed(3)}],nb:${data.nb},dropped:${data.dropped || 0}`);
Expand Down Expand Up @@ -1305,7 +1305,7 @@ class StreamController extends BaseStreamController {
const media = this.mediaBuffer ? this.mediaBuffer : this.media;
if (media) {
// filter fragments potentially evicted from buffer. this is to avoid memleak on live streams
this.fragmentTracker.detectEvictedFragments(Fragment.ElementaryStreamTypes.VIDEO, media.buffered);
this.fragmentTracker.detectEvictedFragments(ElementaryStreamTypes.VIDEO, media.buffered);
}
// move to IDLE once flush complete. this should trigger new fragment loading
this.state = State.IDLE;
Expand Down
150 changes: 0 additions & 150 deletions src/loader/fragment.js

This file was deleted.

168 changes: 168 additions & 0 deletions src/loader/fragment.ts
@@ -0,0 +1,168 @@

import { buildAbsoluteURL } from 'url-toolkit';
import { logger } from '../utils/logger';
import LevelKey from './level-key';

export enum ElementaryStreamTypes {
AUDIO = 'audio',
VIDEO = 'video',
}

export default class Fragment {
private _url: string | null = null;
private _byteRange: number[] | null = null;
private _decryptdata: LevelKey | null = null;

// Holds the types of data this fragment supports
private _elementaryStreams: Record<ElementaryStreamTypes, boolean> = {
[ElementaryStreamTypes.AUDIO]: false,
[ElementaryStreamTypes.VIDEO]: false
};

public rawProgramDateTime: string | null = null;
public programDateTime: number | null = null;
public tagList: Array<string[]> = [];

// TODO: Move at least baseurl to constructor.
// Currently we do a two-pass construction as use the Fragment class almost like a object for holding parsing state.
// It may make more sense to just use a POJO to keep state during the parsing phase.
// Have Fragment be the representation once we have a known state?
// Something to think on.

// relurl is the portion of the URL that comes from inside the playlist.
public relurl!: string;
// baseurl is the URL to the playlist
public baseurl!: string;
// EXTINF has to be present for a m3u8 to be considered valid
public duration!: number;
// sn notates the sequence number for a segment, and if set to a string can be 'initSegment'
public sn: number | 'initSegment' = 0;
// levelkey is the EXT-X-KEY that applies to this segment for decryption
// core difference from the private field _decryptdata is the lack of the initialized IV
// _decryptdata will set the IV for this segment based on the segment number in the fragment
public levelkey?: LevelKey;

// setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
setByteRange (value: string, previousFrag?: Fragment) {
const params = value.split('@', 2);
const byteRange: number[] = [];
if (params.length === 1) {
byteRange[0] = previousFrag ? previousFrag.byteRangeEndOffset : 0;
} else {
byteRange[0] = parseInt(params[1]);
}
byteRange[1] = parseInt(params[0]) + byteRange[0];
this._byteRange = byteRange;
}

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

return this._url;
}

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

get byteRange (): number[] {
if (!this._byteRange) {
return [];
}

return this._byteRange;
}

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

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

get decryptdata (): LevelKey | null {
if (!this.levelkey && !this._decryptdata) {
return null;
}

if (!this._decryptdata && this.levelkey) {
// TODO look for this warning for 'initSegment' sn getting used in decryption IV
if (typeof this.sn !== 'number') {
logger.warn(`undefined behaviour for sn="${this.sn}" in IV generation`);
itsjamie marked this conversation as resolved.
Show resolved Hide resolved
}
this._decryptdata = this.fragmentDecryptdataFromLevelkey(this.levelkey, this.sn as number);
itsjamie marked this conversation as resolved.
Show resolved Hide resolved
}

return this._decryptdata;
}

get endProgramDateTime () {
if (this.programDateTime === null) {
return null;
}

if (!Number.isFinite(this.programDateTime)) {
return null;
}

let duration = !Number.isFinite(this.duration) ? 0 : this.duration;

return this.programDateTime + (duration * 1000);
}

get encrypted () {
return !!((this.decryptdata && this.decryptdata.uri !== null) && (this.decryptdata.key === null));
}

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

/**
* @param {ElementaryStreamTypes} type
*/
hasElementaryStream (type: ElementaryStreamTypes) {
return this._elementaryStreams[type] === true;
tjenkinson marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Utility method for parseLevelPlaylist to create an initialization vector for a given segment
* @returns {Uint8Array}
*/
createInitializationVector (segmentNumber: number): Uint8Array {
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: LevelKey, segmentNumber: number): LevelKey {
let decryptdata = levelkey;

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

return decryptdata;
}
}
18 changes: 0 additions & 18 deletions src/loader/level-key.js

This file was deleted.