mjpeg-player: ts code to js code

This PR is for the hls.js files where the only modifications made are for typescript to javascript conversion, and updated import statements.
This commit is contained in:
Trek H 2020-01-24 19:50:48 +10:30
parent 1b7e817266
commit 2e297101a7
7 changed files with 352 additions and 321 deletions

View File

@ -1,13 +1,32 @@
/*
AUTHOR
Trek Hopton <trek@ausocean.org>
LICENSE
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see http://www.gnu.org/licenses.
For hls.js Copyright notice and license, see LICENSE file.
*/
/*
*
* All objects in the event handling chain should inherit from this class
*
*/
import { logger } from './utils/logger';
import { ErrorTypes, ErrorDetails } from './errors';
import Event from './events';
import Hls from './hls';
import Event from './events.js';
const FORBIDDEN_EVENT_NAMES = {
'hlsEventGeneric': true,
@ -16,11 +35,7 @@ const FORBIDDEN_EVENT_NAMES = {
};
class EventHandler {
hls: Hls;
handledEvents: any[];
useGenericHandler: boolean;
constructor (hls: Hls, ...events: any[]) {
constructor(hls, ...events) {
this.hls = hls;
this.onEvent = this.onEvent.bind(this);
this.handledEvents = events;
@ -29,20 +44,20 @@ class EventHandler {
this.registerListeners();
}
destroy () {
destroy() {
this.onHandlerDestroying();
this.unregisterListeners();
this.onHandlerDestroyed();
}
protected onHandlerDestroying () {}
protected onHandlerDestroyed () {}
onHandlerDestroying() { }
onHandlerDestroyed() { }
isEventHandler () {
isEventHandler() {
return typeof this.handledEvents === 'object' && this.handledEvents.length && typeof this.onEvent === 'function';
}
registerListeners () {
registerListeners() {
if (this.isEventHandler()) {
this.handledEvents.forEach(function (event) {
if (FORBIDDEN_EVENT_NAMES[event]) {
@ -54,7 +69,7 @@ class EventHandler {
}
}
unregisterListeners () {
unregisterListeners() {
if (this.isEventHandler()) {
this.handledEvents.forEach(function (event) {
this.hls.off(event, this.onEvent);
@ -65,12 +80,12 @@ class EventHandler {
/**
* arguments: event (string), data (any)
*/
onEvent (event: string, data: any) {
onEvent(event, data) {
this.onEventGeneric(event, data);
}
onEventGeneric (event: string, data: any) {
let eventToFunction = function (event: string, data: any) {
onEventGeneric(event, data) {
let eventToFunction = function (event, data) {
let funcName = 'on' + event.replace('hls', '');
if (typeof this[funcName] !== 'function') {
throw new Error(`Event ${event} has no generic handler in this ${this.constructor.name} class (tried ${funcName})`);
@ -81,7 +96,7 @@ class EventHandler {
try {
eventToFunction.call(this, event, data).call();
} catch (err) {
logger.error(`An internal error happened while handling event ${event}. Error message: "${err.message}". Here is a stacktrace:`, err);
console.error(`An internal error happened while handling event ${event}. Error message: "${err.message}". Here is a stacktrace:`, err);
this.hls.trigger(Event.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, fatal: false, event: event, err: err });
}
}

View File

@ -1,69 +1,90 @@
/*
AUTHOR
Trek Hopton <trek@ausocean.org>
import { buildAbsoluteURL } from 'url-toolkit';
import { logger } from '../utils/logger';
import LevelKey from './level-key';
import { PlaylistLevelType } from '../types/loader';
LICENSE
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
export enum ElementaryStreamTypes {
AUDIO = 'audio',
VIDEO = 'video',
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see http://www.gnu.org/licenses.
For hls.js Copyright notice and license, see LICENSE file.
*/
import URLToolkit from '../../url-toolkit/url-toolkit.js';
import LevelKey from './level-key.js';
export const ElementaryStreamTypes = {
AUDIO: 'audio',
VIDEO: 'video'
}
export default class Fragment {
private _url: string | null = null;
private _byteRange: number[] | null = null;
private _decryptdata: LevelKey | null = null;
constructor() {
this._url = null;
this._byteRange = null;
this._decryptdata = null;
// Holds the types of data this fragment supports
private _elementaryStreams: Record<ElementaryStreamTypes, boolean> = {
[ElementaryStreamTypes.AUDIO]: false,
[ElementaryStreamTypes.VIDEO]: false
};
// Holds the types of data this fragment supports
this._elementaryStreams = {
[ElementaryStreamTypes.AUDIO]: false,
[ElementaryStreamTypes.VIDEO]: false
};
// deltaPTS tracks the change in presentation timestamp between fragments
public deltaPTS: number = 0;
// deltaPTS tracks the change in presentation timestamp between fragments
this.deltaPTS = 0;
public rawProgramDateTime: string | null = null;
public programDateTime: number | null = null;
public title: string | null = null;
public tagList: Array<string[]> = [];
this.rawProgramDateTime = null;
this.programDateTime = null;
this.title = null;
this.tagList = [];
// 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.
// 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.
// Discontinuity Counter
public cc!: number;
// Discontinuity Counter
this.cc;
this.type;
// relurl is the portion of the URL that comes from inside the playlist.
this.relurl;
// baseurl is the URL to the playlist
this.baseurl;
// EXTINF has to be present for a m3u8 to be considered valid
this.duration;
// When this segment starts in the timeline
this.start;
// sn notates the sequence number for a segment, and if set to a string can be 'initSegment'
this.sn = 0;
public type!: PlaylistLevelType;
// 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;
// When this segment starts in the timeline
public start!: number;
// sn notates the sequence number for a segment, and if set to a string can be 'initSegment'
public sn: number | 'initSegment' = 0;
this.urlId = 0;
// level matches this fragment to a index playlist
this.level = 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
this.levelkey;
public urlId: number = 0;
// level matches this fragment to a index playlist
public level: number = 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;
// TODO(typescript-xhrloader)
public loader: any;
// TODO(typescript-xhrloader)
this.loader;
}
// setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
setByteRange (value: string, previousFrag?: Fragment) {
setByteRange(value, previousFrag) {
const params = value.split('@', 2);
const byteRange: number[] = [];
const byteRange = [];
if (params.length === 1) {
byteRange[0] = previousFrag ? previousFrag.byteRangeEndOffset : 0;
} else {
@ -73,19 +94,19 @@ export default class Fragment {
this._byteRange = byteRange;
}
get url () {
get url() {
if (!this._url && this.relurl) {
this._url = buildAbsoluteURL(this.baseurl, this.relurl, { alwaysNormalize: true });
this._url = URLToolkit.buildAbsoluteURL(this.baseurl, this.relurl, { alwaysNormalize: true });
}
return this._url;
}
set url (value) {
set url(value) {
this._url = value;
}
get byteRange (): number[] {
get byteRange() {
if (!this._byteRange) {
return [];
}
@ -96,15 +117,15 @@ export default class Fragment {
/**
* @type {number}
*/
get byteRangeStartOffset () {
get byteRangeStartOffset() {
return this.byteRange[0];
}
get byteRangeEndOffset () {
get byteRangeEndOffset() {
return this.byteRange[1];
}
get decryptdata (): LevelKey | null {
get decryptdata() {
if (!this.levelkey && !this._decryptdata) {
return null;
}
@ -116,7 +137,7 @@ export default class Fragment {
// If the segment was encrypted with AES-128
// It must have an IV defined. We cannot substitute the Segment Number in.
if (this.levelkey && this.levelkey.method === 'AES-128' && !this.levelkey.iv) {
logger.warn(`missing IV for initialization segment with method="${this.levelkey.method}" - compliance issue`);
console.warn(`missing IV for initialization segment with method="${this.levelkey.method}" - compliance issue`);
}
/*
@ -134,7 +155,7 @@ export default class Fragment {
return this._decryptdata;
}
get endProgramDateTime () {
get endProgramDateTime() {
if (this.programDateTime === null) {
return null;
}
@ -148,21 +169,21 @@ export default class Fragment {
return this.programDateTime + (duration * 1000);
}
get encrypted () {
get encrypted() {
return !!((this.decryptdata && this.decryptdata.uri !== null) && (this.decryptdata.key === null));
}
/**
* @param {ElementaryStreamTypes} type
*/
addElementaryStream (type: ElementaryStreamTypes) {
addElementaryStream(type) {
this._elementaryStreams[type] = true;
}
/**
* @param {ElementaryStreamTypes} type
*/
hasElementaryStream (type: ElementaryStreamTypes) {
hasElementaryStream(type) {
return this._elementaryStreams[type] === true;
}
@ -171,7 +192,7 @@ export default class Fragment {
* @param {number} segmentNumber - segment number to generate IV with
* @returns {Uint8Array}
*/
createInitializationVector (segmentNumber: number): Uint8Array {
createInitializationVector(segmentNumber) {
let uint8View = new Uint8Array(16);
for (let i = 12; i < 16; i++) {
@ -187,7 +208,7 @@ export default class Fragment {
* @param segmentNumber - the fragment's segment number
* @returns {LevelKey} - an object to be applied as a fragment's decryptdata
*/
setDecryptDataFromLevelKey (levelkey: LevelKey, segmentNumber: number): LevelKey {
setDecryptDataFromLevelKey(levelkey, segmentNumber) {
let decryptdata = levelkey;
if (levelkey && levelkey.method && levelkey.uri && !levelkey.iv) {

View File

@ -1,22 +1,45 @@
import { buildAbsoluteURL } from 'url-toolkit';
/*
AUTHOR
Trek Hopton <trek@ausocean.org>
LICENSE
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see http://www.gnu.org/licenses.
For hls.js Copyright notice and license, see LICENSE file.
*/
import URLToolkit from '../../url-toolkit/url-toolkit.js';
export default class LevelKey {
private _uri: string | null = null;
constructor(baseURI, relativeURI) {
this._uri = null;
public baseuri: string;
public reluri: string;
public method: string | null = null;
public key: Uint8Array | null = null;
public iv: Uint8Array | null = null;
this.baseuri;
this.reluri;
this.method = null;
this.key = null;
this.iv = null;
constructor (baseURI: string, relativeURI: string) {
this.baseuri = baseURI;
this.reluri = relativeURI;
}
get uri () {
get uri() {
if (!this._uri && this.reluri) {
this._uri = buildAbsoluteURL(this.baseuri, this.reluri, { alwaysNormalize: true });
this._uri = URLToolkit.buildAbsoluteURL(this.baseuri, this.reluri, { alwaysNormalize: true });
}
return this._uri;

View File

@ -1,14 +1,32 @@
import * as URLToolkit from 'url-toolkit';
/*
AUTHOR
Trek Hopton <trek@ausocean.org>
import Fragment from './fragment';
import Level from './level';
import LevelKey from './level-key';
LICENSE
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
import AttrList from '../utils/attr-list';
import { logger } from '../utils/logger';
import { isCodecType, CodecType } from '../utils/codecs';
import { MediaPlaylist, AudioGroup, MediaPlaylistType } from '../types/media-playlist';
import { PlaylistLevelType } from '../types/loader';
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see http://www.gnu.org/licenses.
For hls.js Copyright notice and license, see LICENSE file.
*/
import URLToolkit from '../../url-toolkit/url-toolkit.js';
import Fragment from './fragment.js';
import Level from './level.js';
import LevelKey from './level-key.js';
import AttrList from '../utils/attr-list.js';
import { isCodecType } from '../utils/codecs.js';
/**
* M3U8 parser
@ -32,7 +50,7 @@ const LEVEL_PLAYLIST_REGEX_SLOW = /(?:(?:#(EXTM3U))|(?:#EXT-X-(PLAYLIST-TYPE):(.
const MP4_REGEX_SUFFIX = /\.(mp4|m4s|m4v|m4a)$/i;
export default class M3U8Parser {
static findGroup (groups: Array<AudioGroup>, mediaGroupId: string): AudioGroup | undefined {
static findGroup(groups, mediaGroupId) {
for (let i = 0; i < groups.length; i++) {
const group = groups[i];
if (group.id === mediaGroupId) {
@ -41,7 +59,7 @@ export default class M3U8Parser {
}
}
static convertAVC1ToAVCOTI (codec) {
static convertAVC1ToAVCOTI(codec) {
let avcdata = codec.split('.');
let result;
if (avcdata.length > 2) {
@ -54,18 +72,18 @@ export default class M3U8Parser {
return result;
}
static resolve (url, baseUrl) {
static resolve(url, baseUrl) {
return URLToolkit.buildAbsoluteURL(baseUrl, url, { alwaysNormalize: true });
}
static parseMasterPlaylist (string: string, baseurl: string) {
static parseMasterPlaylist(string, baseurl) {
// TODO(typescript-level)
let levels: Array<any> = [];
let levels = [];
MASTER_PLAYLIST_REGEX.lastIndex = 0;
// TODO(typescript-level)
function setCodecs (codecs: Array<string>, level: any) {
['video', 'audio'].forEach((type: CodecType) => {
function setCodecs(codecs, level) {
['video', 'audio'].forEach((type) => {
const filtered = codecs.filter((codec) => isCodecType(codec, type));
if (filtered.length) {
const preferred = filtered.filter((codec) => {
@ -81,10 +99,10 @@ export default class M3U8Parser {
level.unknownCodecs = codecs;
}
let result: RegExpExecArray | null;
let result;
while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) {
// TODO(typescript-level)
const level: any = {};
const level = {};
const attrs = level.attrs = new AttrList(result[1]);
level.url = M3U8Parser.resolve(result[2], baseurl);
@ -108,15 +126,15 @@ export default class M3U8Parser {
return levels;
}
static parseMasterPlaylistMedia (string: string, baseurl: string, type: MediaPlaylistType, audioGroups: Array<AudioGroup> = []): Array<MediaPlaylist> {
let result: RegExpExecArray | null;
let medias: Array<MediaPlaylist> = [];
static parseMasterPlaylistMedia(string, baseurl, type, audioGroups = []) {
let result;
let medias = [];
let id = 0;
MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
const attrs = new AttrList(result[1]);
if (attrs.TYPE === type) {
const media: MediaPlaylist = {
const media = {
id: id++,
groupId: attrs['GROUP-ID'],
name: attrs.NAME || attrs.LANGUAGE,
@ -146,16 +164,16 @@ export default class M3U8Parser {
return medias;
}
static parseLevelPlaylist (string: string, baseurl: string, id: number, type: PlaylistLevelType, levelUrlId: number) {
static parseLevelPlaylist(string, baseurl, id, type, levelUrlId) {
let currentSN = 0;
let totalduration = 0;
let level = new Level(baseurl);
let discontinuityCounter = 0;
let prevFrag: Fragment | null = null;
let frag: Fragment | null = new Fragment();
let result: RegExpExecArray | RegExpMatchArray | null;
let i: number;
let levelkey: LevelKey | undefined;
let prevFrag = null;
let frag = new Fragment();
let result;
let i;
let levelkey;
let firstPdtIndex = null;
@ -168,7 +186,7 @@ export default class M3U8Parser {
// avoid sliced strings https://github.com/video-dev/hls.js/issues/939
const title = (' ' + result[2]).slice(1);
frag.title = title || null;
frag.tagList.push(title ? [ 'INF', duration, title ] : [ 'INF', duration ]);
frag.tagList.push(title ? ['INF', duration, title] : ['INF', duration]);
} else if (result[3]) { // url
if (Number.isFinite(frag.duration)) {
const sn = currentSN++;
@ -209,7 +227,7 @@ export default class M3U8Parser {
} else {
result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW);
if (!result) {
logger.warn('No matches on slow regex match for level playlist!');
console.warn('No matches on slow regex match for level playlist!');
continue;
}
for (i = 1; i < result.length; i++) {
@ -223,84 +241,84 @@ export default class M3U8Parser {
const value2 = (' ' + result[i + 2]).slice(1);
switch (result[i]) {
case '#':
frag.tagList.push(value2 ? [ value1, value2 ] : [ value1 ]);
break;
case 'PLAYLIST-TYPE':
level.type = value1.toUpperCase();
break;
case 'MEDIA-SEQUENCE':
currentSN = level.startSN = parseInt(value1);
break;
case 'TARGETDURATION':
level.targetduration = parseFloat(value1);
break;
case 'VERSION':
level.version = parseInt(value1);
break;
case 'EXTM3U':
break;
case 'ENDLIST':
level.live = false;
break;
case 'DIS':
discontinuityCounter++;
frag.tagList.push(['DIS']);
break;
case 'DISCONTINUITY-SEQ':
discontinuityCounter = parseInt(value1);
break;
case 'KEY': {
// https://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-3.4.4
const decryptparams = value1;
const keyAttrs = new AttrList(decryptparams);
const decryptmethod = keyAttrs.enumeratedString('METHOD');
const decrypturi = keyAttrs.URI;
const decryptiv = keyAttrs.hexadecimalInteger('IV');
case '#':
frag.tagList.push(value2 ? [value1, value2] : [value1]);
break;
case 'PLAYLIST-TYPE':
level.type = value1.toUpperCase();
break;
case 'MEDIA-SEQUENCE':
currentSN = level.startSN = parseInt(value1);
break;
case 'TARGETDURATION':
level.targetduration = parseFloat(value1);
break;
case 'VERSION':
level.version = parseInt(value1);
break;
case 'EXTM3U':
break;
case 'ENDLIST':
level.live = false;
break;
case 'DIS':
discontinuityCounter++;
frag.tagList.push(['DIS']);
break;
case 'DISCONTINUITY-SEQ':
discontinuityCounter = parseInt(value1);
break;
case 'KEY': {
// https://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-3.4.4
const decryptparams = value1;
const keyAttrs = new AttrList(decryptparams);
const decryptmethod = keyAttrs.enumeratedString('METHOD');
const decrypturi = keyAttrs.URI;
const decryptiv = keyAttrs.hexadecimalInteger('IV');
if (decryptmethod) {
levelkey = new LevelKey(baseurl, decrypturi);
if ((decrypturi) && (['AES-128', 'SAMPLE-AES', 'SAMPLE-AES-CENC'].indexOf(decryptmethod) >= 0)) {
levelkey.method = decryptmethod;
levelkey.key = null;
// Initialization Vector (IV)
levelkey.iv = decryptiv;
if (decryptmethod) {
levelkey = new LevelKey(baseurl, decrypturi);
if ((decrypturi) && (['AES-128', 'SAMPLE-AES', 'SAMPLE-AES-CENC'].indexOf(decryptmethod) >= 0)) {
levelkey.method = decryptmethod;
levelkey.key = null;
// Initialization Vector (IV)
levelkey.iv = decryptiv;
}
}
break;
}
break;
}
case 'START': {
const startAttrs = new AttrList(value1);
const startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET');
// TIME-OFFSET can be 0
if (Number.isFinite(startTimeOffset)) {
level.startTimeOffset = startTimeOffset;
case 'START': {
const startAttrs = new AttrList(value1);
const startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET');
// TIME-OFFSET can be 0
if (Number.isFinite(startTimeOffset)) {
level.startTimeOffset = startTimeOffset;
}
break;
}
break;
}
case 'MAP': {
const mapAttrs = new AttrList(value1);
frag.relurl = mapAttrs.URI;
if (mapAttrs.BYTERANGE) {
frag.setByteRange(mapAttrs.BYTERANGE);
case 'MAP': {
const mapAttrs = new AttrList(value1);
frag.relurl = mapAttrs.URI;
if (mapAttrs.BYTERANGE) {
frag.setByteRange(mapAttrs.BYTERANGE);
}
frag.baseurl = baseurl;
frag.level = id;
frag.type = type;
frag.sn = 'initSegment';
level.initSegment = frag;
frag = new Fragment();
frag.rawProgramDateTime = level.initSegment.rawProgramDateTime;
break;
}
frag.baseurl = baseurl;
frag.level = id;
frag.type = type;
frag.sn = 'initSegment';
level.initSegment = frag;
frag = new Fragment();
frag.rawProgramDateTime = level.initSegment.rawProgramDateTime;
break;
}
default:
logger.warn(`line parsed but not handled: ${result}`);
break;
default:
console.warn(`line parsed but not handled: ${result}`);
break;
}
}
}
frag = prevFrag;
// logger.log('found ' + level.fragments.length + ' fragments');
// console.log('found ' + level.fragments.length + ' fragments');
if (frag && !frag.relurl) {
level.fragments.pop();
totalduration -= frag.duration;
@ -316,7 +334,7 @@ export default class M3U8Parser {
// if the fragments are TS or MP4, except if we download them :/
// but this is to be able to handle SIDX.
if (level.fragments.every((frag) => MP4_REGEX_SUFFIX.test(frag.relurl))) {
logger.warn('MP4 fragments found but no init segment (probably no MAP, incomplete M3U8), trying to fetch SIDX');
console.warn('MP4 fragments found but no init segment (probably no MAP, incomplete M3U8), trying to fetch SIDX');
frag = new Fragment();
frag.relurl = level.fragments[0].relurl;
@ -347,7 +365,7 @@ export default class M3U8Parser {
}
}
function backfillProgramDateTimes (fragments, startIndex) {
function backfillProgramDateTimes(fragments, startIndex) {
let fragPrev = fragments[startIndex];
for (let i = startIndex - 1; i >= 0; i--) {
const frag = fragments[i];
@ -356,7 +374,7 @@ function backfillProgramDateTimes (fragments, startIndex) {
}
}
function assignProgramDateTime (frag, prevFrag) {
function assignProgramDateTime(frag, prevFrag) {
if (frag.rawProgramDateTime) {
frag.programDateTime = Date.parse(frag.rawProgramDateTime);
} else if (prevFrag && prevFrag.programDateTime) {

View File

@ -1,4 +1,27 @@
import { EventEmitter } from 'eventemitter3';
/*
AUTHOR
Trek Hopton <trek@ausocean.org>
LICENSE
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see http://www.gnu.org/licenses.
For hls.js Copyright notice and license, see LICENSE file.
*/
import EventEmitter from '../eventemitter3/index.js';
/**
* Simple adapter sub-class of Nodejs-like EventEmitter.
@ -9,7 +32,7 @@ export class Observer extends EventEmitter {
* in every call to a handler, which is the purpose of our `trigger` method
* extending the standard API.
*/
trigger (event: string, ...data: Array<any>): void {
trigger(event, ...data) {
this.emit(event, event, ...data);
}
}

View File

@ -1,132 +1,42 @@
import Level from '../loader/level';
/*
AUTHOR
Trek Hopton <trek@ausocean.org>
export interface LoaderContext {
// target URL
url: string
// loader response type (arraybuffer or default response type for playlist)
responseType: string
// start byte range offset
rangeStart?: number
// end byte range offset
rangeEnd?: number
// true if onProgress should report partial chunk of loaded content
progressData?: boolean
}
LICENSE
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
export interface LoaderConfiguration {
// Max number of load retries
maxRetry: number
// Timeout after which `onTimeOut` callback will be triggered
// (if loading is still not finished after that delay)
timeout: number
// Delay between an I/O error and following connection retry (ms).
// This to avoid spamming the server
retryDelay: number
// max connection retry delay (ms)
maxRetryDelay: number
}
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
export interface LoaderResponse {
url: string,
// TODO(jstackhouse): SharedArrayBuffer, es2017 extension to TS
data: string | ArrayBuffer
}
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
export interface LoaderStats {
// performance.now() just after load() has been called
trequest: number
// performance.now() of first received byte
tfirst: number
// performance.now() on load complete
tload: number
// performance.now() on parse completion
tparsed: number
// number of loaded bytes
loaded: number
// total number of bytes
total: number
}
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see http://www.gnu.org/licenses.
type LoaderOnSuccess < T extends LoaderContext > = (
response: LoaderResponse,
stats: LoaderStats,
context: T,
networkDetails: any
) => void;
type LoaderOnProgress < T extends LoaderContext > = (
stats: LoaderStats,
context: T,
data: string | ArrayBuffer,
networkDetails: any,
) => void;
type LoaderOnError < T extends LoaderContext > = (
error: {
// error status code
code: number,
// error description
text: string,
},
context: T,
networkDetails: any,
) => void;
type LoaderOnTimeout < T extends LoaderContext > = (
stats: LoaderStats,
context: T,
) => void;
export interface LoaderCallbacks<T extends LoaderContext>{
onSuccess: LoaderOnSuccess<T>,
onError: LoaderOnError<T>,
onTimeout: LoaderOnTimeout<T>,
onProgress?: LoaderOnProgress<T>,
}
export interface Loader<T extends LoaderContext> {
destroy(): void
abort(): void
load(
context: LoaderContext,
config: LoaderConfiguration,
callbacks: LoaderCallbacks<T>,
): void
context: T
}
For hls.js Copyright notice and license, see LICENSE file.
*/
/**
* `type` property values for this loaders' context object
* @enum
*
* @readonly
* @enum {string}
*/
export enum PlaylistContextType {
MANIFEST = 'manifest',
LEVEL = 'level',
AUDIO_TRACK = 'audioTrack',
SUBTITLE_TRACK= 'subtitleTrack'
export const PlaylistContextType = {
MANIFEST: 'manifest',
LEVEL: 'level',
AUDIO_TRACK: 'audioTrack',
SUBTITLE_TRACK: 'subtitleTrack'
}
/**
* @enum {string}
*/
export enum PlaylistLevelType {
MAIN = 'main',
AUDIO = 'audio',
SUBTITLE = 'subtitle'
}
export interface PlaylistLoaderContext extends LoaderContext {
loader?: Loader<PlaylistLoaderContext>
type: PlaylistContextType
// the level index to load
level: number | null
// TODO: what is id?
id: number | null
// defines if the loader is handling a sidx request for the playlist
isSidxRequest?: boolean
// internal reprsentation of a parsed m3u8 level playlist
levelDetails?: Level
export const PlaylistLevelType = {
MAIN: 'main',
AUDIO: 'audio',
SUBTITLE: 'subtitle'
}

View File

@ -1,3 +1,26 @@
/*
AUTHOR
Trek Hopton <trek@ausocean.org>
LICENSE
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see http://www.gnu.org/licenses.
For hls.js Copyright notice and license, see LICENSE file.
*/
// from http://mp4ra.org/codecs.html
const sampleEntryCodesISO = {
audio: {
@ -63,14 +86,12 @@ const sampleEntryCodesISO = {
}
};
export type CodecType = 'audio' | 'video';
function isCodecType (codec: string, type: CodecType): boolean {
function isCodecType(codec, type) {
const typeCodes = sampleEntryCodesISO[type];
return !!typeCodes && typeCodes[codec.slice(0, 4)] === true;
}
function isCodecSupportedInMp4 (codec: string, type: CodecType): boolean {
function isCodecSupportedInMp4(codec, type) {
return MediaSource.isTypeSupported(`${type || 'video'}/mp4;codecs="${codec}"`);
}