mirror of https://bitbucket.org/ausocean/av.git
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:
parent
1b7e817266
commit
2e297101a7
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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}"`);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue