mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
get api libs from bower
This commit is contained in:
parent
def418714f
commit
f36e664503
97 changed files with 16860 additions and 197 deletions
84
dashboard-ui/bower_components/hls.js/src/controller/abr-controller.js
vendored
Normal file
84
dashboard-ui/bower_components/hls.js/src/controller/abr-controller.js
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* simple ABR Controller
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
|
||||
class AbrController {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
this.lastfetchlevel = 0;
|
||||
this._autoLevelCapping = -1;
|
||||
this._nextAutoLevel = -1;
|
||||
this.onflp = this.onFragmentLoadProgress.bind(this);
|
||||
hls.on(Event.FRAG_LOAD_PROGRESS, this.onflp);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.hls.off(Event.FRAG_LOAD_PROGRESS, this.onflp);
|
||||
}
|
||||
|
||||
onFragmentLoadProgress(event, data) {
|
||||
var stats = data.stats;
|
||||
if (stats.aborted === undefined) {
|
||||
this.lastfetchduration = (performance.now() - stats.trequest) / 1000;
|
||||
this.lastfetchlevel = data.frag.level;
|
||||
this.lastbw = (stats.loaded * 8) / this.lastfetchduration;
|
||||
//console.log(`fetchDuration:${this.lastfetchduration},bw:${(this.lastbw/1000).toFixed(0)}/${stats.aborted}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the capping/max level value that could be used by automatic level selection algorithm **/
|
||||
get autoLevelCapping() {
|
||||
return this._autoLevelCapping;
|
||||
}
|
||||
|
||||
/** set the capping/max level value that could be used by automatic level selection algorithm **/
|
||||
set autoLevelCapping(newLevel) {
|
||||
this._autoLevelCapping = newLevel;
|
||||
}
|
||||
|
||||
get nextAutoLevel() {
|
||||
var lastbw = this.lastbw, hls = this.hls,adjustedbw, i, maxAutoLevel;
|
||||
if (this._autoLevelCapping === -1) {
|
||||
maxAutoLevel = hls.levels.length - 1;
|
||||
} else {
|
||||
maxAutoLevel = this._autoLevelCapping;
|
||||
}
|
||||
|
||||
if (this._nextAutoLevel !== -1) {
|
||||
var nextLevel = Math.min(this._nextAutoLevel,maxAutoLevel);
|
||||
if (nextLevel === this.lastfetchlevel) {
|
||||
this._nextAutoLevel = -1;
|
||||
} else {
|
||||
return nextLevel;
|
||||
}
|
||||
}
|
||||
|
||||
// follow algorithm captured from stagefright :
|
||||
// https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp
|
||||
// Pick the highest bandwidth stream below or equal to estimated bandwidth.
|
||||
for (i = 0; i <= maxAutoLevel; i++) {
|
||||
// consider only 80% of the available bandwidth, but if we are switching up,
|
||||
// be even more conservative (70%) to avoid overestimating and immediately
|
||||
// switching back.
|
||||
if (i <= this.lastfetchlevel) {
|
||||
adjustedbw = 0.8 * lastbw;
|
||||
} else {
|
||||
adjustedbw = 0.7 * lastbw;
|
||||
}
|
||||
if (adjustedbw < hls.levels[i].bitrate) {
|
||||
return Math.max(0, i - 1);
|
||||
}
|
||||
}
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
set nextAutoLevel(nextLevel) {
|
||||
this._nextAutoLevel = nextLevel;
|
||||
}
|
||||
}
|
||||
|
||||
export default AbrController;
|
||||
|
49
dashboard-ui/bower_components/hls.js/src/controller/fps-controller.js
vendored
Normal file
49
dashboard-ui/bower_components/hls.js/src/controller/fps-controller.js
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* FPS Controller
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
import {logger} from '../utils/logger';
|
||||
|
||||
class FPSController {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
this.timer = setInterval(this.checkFPS, hls.config.fpsDroppedMonitoringPeriod);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
}
|
||||
|
||||
checkFPS() {
|
||||
var v = this.hls.video;
|
||||
if (v) {
|
||||
var decodedFrames = v.webkitDecodedFrameCount, droppedFrames = v.webkitDroppedFrameCount, currentTime = new Date();
|
||||
if (decodedFrames) {
|
||||
if (this.lastTime) {
|
||||
var currentPeriod = currentTime - this.lastTime;
|
||||
var currentDropped = droppedFrames - this.lastDroppedFrames;
|
||||
var currentDecoded = decodedFrames - this.lastDecodedFrames;
|
||||
var decodedFPS = 1000 * currentDecoded / currentPeriod;
|
||||
var droppedFPS = 1000 * currentDropped / currentPeriod;
|
||||
if (droppedFPS > 0) {
|
||||
logger.log(`checkFPS : droppedFPS/decodedFPS:${droppedFPS.toFixed(1)}/${decodedFPS.toFixed(1)}`);
|
||||
if (currentDropped > this.hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
||||
logger.warn('drop FPS ratio greater than max allowed value');
|
||||
this.hls.trigger(Event.FPS_DROP, {currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.lastTime = currentTime;
|
||||
this.lastDroppedFrames = droppedFrames;
|
||||
this.lastDecodedFrames = decodedFrames;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FPSController;
|
||||
|
254
dashboard-ui/bower_components/hls.js/src/controller/level-controller.js
vendored
Normal file
254
dashboard-ui/bower_components/hls.js/src/controller/level-controller.js
vendored
Normal file
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* Level Controller
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
import {logger} from '../utils/logger';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
|
||||
class LevelController {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
this.onml = this.onManifestLoaded.bind(this);
|
||||
this.onll = this.onLevelLoaded.bind(this);
|
||||
this.onerr = this.onError.bind(this);
|
||||
this.ontick = this.tick.bind(this);
|
||||
hls.on(Event.MANIFEST_LOADED, this.onml);
|
||||
hls.on(Event.LEVEL_LOADED, this.onll);
|
||||
hls.on(Event.ERROR, this.onerr);
|
||||
this._manualLevel = this._autoLevelCapping = -1;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
var hls = this.hls;
|
||||
hls.off(Event.MANIFEST_LOADED, this.onml);
|
||||
hls.off(Event.LEVEL_LOADED, this.onll);
|
||||
hls.off(Event.ERROR, this.onerr);
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
this._manualLevel = -1;
|
||||
}
|
||||
|
||||
onManifestLoaded(event, data) {
|
||||
var levels0 = [], levels = [], bitrateStart, i, bitrateSet = {}, videoCodecFound = false, audioCodecFound = false;
|
||||
|
||||
// regroup redundant level together
|
||||
data.levels.forEach(level => {
|
||||
if(level.videoCodec) {
|
||||
videoCodecFound = true;
|
||||
}
|
||||
if(level.audioCodec) {
|
||||
audioCodecFound = true;
|
||||
}
|
||||
var redundantLevelId = bitrateSet[level.bitrate];
|
||||
if (redundantLevelId === undefined) {
|
||||
bitrateSet[level.bitrate] = levels0.length;
|
||||
level.url = [level.url];
|
||||
level.urlId = 0;
|
||||
levels0.push(level);
|
||||
} else {
|
||||
levels0[redundantLevelId].url.push(level.url);
|
||||
}
|
||||
});
|
||||
|
||||
// remove audio-only level if we also have levels with audio+video codecs signalled
|
||||
if(videoCodecFound && audioCodecFound) {
|
||||
levels0.forEach(level => {
|
||||
if(level.videoCodec) {
|
||||
levels.push(level);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
levels = levels0;
|
||||
}
|
||||
|
||||
// only keep level with supported audio/video codecs
|
||||
levels0 = levels0.filter(function(level) {
|
||||
var checkSupported = function(codec) { return MediaSource.isTypeSupported(`video/mp4;codecs=${codec}`);};
|
||||
var audioCodec = level.audioCodec, videoCodec = level.videoCodec;
|
||||
|
||||
return ((audioCodec && checkSupported(audioCodec)) || !audioCodec) &&
|
||||
((videoCodec && checkSupported(videoCodec)) || !videoCodec);
|
||||
|
||||
});
|
||||
|
||||
// start bitrate is the first bitrate of the manifest
|
||||
bitrateStart = levels[0].bitrate;
|
||||
// sort level on bitrate
|
||||
levels.sort(function (a, b) {
|
||||
return a.bitrate - b.bitrate;
|
||||
});
|
||||
this._levels = levels;
|
||||
// find index of first level in sorted levels
|
||||
for (i = 0; i < levels.length; i++) {
|
||||
if (levels[i].bitrate === bitrateStart) {
|
||||
this._firstLevel = i;
|
||||
logger.log(`manifest loaded,${levels.length} level(s) found, first bitrate:${bitrateStart}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.hls.trigger(Event.MANIFEST_PARSED, {levels: this._levels, firstLevel: this._firstLevel, stats: data.stats});
|
||||
return;
|
||||
}
|
||||
|
||||
get levels() {
|
||||
return this._levels;
|
||||
}
|
||||
|
||||
get level() {
|
||||
return this._level;
|
||||
}
|
||||
|
||||
set level(newLevel) {
|
||||
if (this._level !== newLevel || this._levels[newLevel].details === undefined) {
|
||||
this.setLevelInternal(newLevel);
|
||||
}
|
||||
}
|
||||
|
||||
setLevelInternal(newLevel) {
|
||||
// check if level idx is valid
|
||||
if (newLevel >= 0 && newLevel < this._levels.length) {
|
||||
// stopping live reloading timer if any
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
this._level = newLevel;
|
||||
logger.log(`switching to level ${newLevel}`);
|
||||
this.hls.trigger(Event.LEVEL_SWITCH, {level: newLevel});
|
||||
var level = this._levels[newLevel];
|
||||
// check if we need to load playlist for this level
|
||||
if (level.details === undefined || level.details.live === true) {
|
||||
// level not retrieved yet, or live playlist we need to (re)load it
|
||||
logger.log(`(re)loading playlist for level ${newLevel}`);
|
||||
var urlId = level.urlId;
|
||||
this.hls.trigger(Event.LEVEL_LOADING, {url: level.url[urlId], level: newLevel, id: urlId});
|
||||
}
|
||||
} else {
|
||||
// invalid level id given, trigger error
|
||||
this.hls.trigger(Event.ERROR, {type : ErrorTypes.OTHER_ERROR, details: ErrorDetails.LEVEL_SWITCH_ERROR, level: newLevel, fatal: false, reason: 'invalid level idx'});
|
||||
}
|
||||
}
|
||||
|
||||
get manualLevel() {
|
||||
return this._manualLevel;
|
||||
}
|
||||
|
||||
set manualLevel(newLevel) {
|
||||
this._manualLevel = newLevel;
|
||||
if (newLevel !== -1) {
|
||||
this.level = newLevel;
|
||||
}
|
||||
}
|
||||
|
||||
get firstLevel() {
|
||||
return this._firstLevel;
|
||||
}
|
||||
|
||||
set firstLevel(newLevel) {
|
||||
this._firstLevel = newLevel;
|
||||
}
|
||||
|
||||
get startLevel() {
|
||||
if (this._startLevel === undefined) {
|
||||
return this._firstLevel;
|
||||
} else {
|
||||
return this._startLevel;
|
||||
}
|
||||
}
|
||||
|
||||
set startLevel(newLevel) {
|
||||
this._startLevel = newLevel;
|
||||
}
|
||||
|
||||
onError(event, data) {
|
||||
if(data.fatal) {
|
||||
return;
|
||||
}
|
||||
|
||||
var details = data.details, hls = this.hls, levelId, level;
|
||||
// try to recover not fatal errors
|
||||
switch(details) {
|
||||
case ErrorDetails.FRAG_LOAD_ERROR:
|
||||
case ErrorDetails.FRAG_LOAD_TIMEOUT:
|
||||
case ErrorDetails.FRAG_LOOP_LOADING_ERROR:
|
||||
case ErrorDetails.KEY_LOAD_ERROR:
|
||||
case ErrorDetails.KEY_LOAD_TIMEOUT:
|
||||
levelId = data.frag.level;
|
||||
break;
|
||||
case ErrorDetails.LEVEL_LOAD_ERROR:
|
||||
case ErrorDetails.LEVEL_LOAD_TIMEOUT:
|
||||
levelId = data.level;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* try to switch to a redundant stream if any available.
|
||||
* if no redundant stream available, emergency switch down (if in auto mode and current level not 0)
|
||||
* otherwise, we cannot recover this network error ....
|
||||
*/
|
||||
if (levelId !== undefined) {
|
||||
level = this._levels[levelId];
|
||||
if (level.urlId < (level.url.length - 1)) {
|
||||
level.urlId++;
|
||||
level.details = undefined;
|
||||
logger.warn(`level controller,${details} for level ${levelId}: switching to redundant stream id ${level.urlId}`);
|
||||
} else {
|
||||
// we could try to recover if in auto mode and current level not lowest level (0)
|
||||
let recoverable = ((this._manualLevel === -1) && levelId);
|
||||
if (recoverable) {
|
||||
logger.warn(`level controller,${details}: emergency switch-down for next fragment`);
|
||||
hls.abrController.nextAutoLevel = 0;
|
||||
} else if(level && level.details && level.details.live) {
|
||||
logger.warn(`level controller,${details} on live stream, discard`);
|
||||
} else {
|
||||
logger.error(`cannot recover ${details} error`);
|
||||
this._level = undefined;
|
||||
// stopping live reloading timer if any
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
// redispatch same error but with fatal set to true
|
||||
data.fatal = true;
|
||||
hls.trigger(event, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLevelLoaded(event, data) {
|
||||
// check if current playlist is a live playlist
|
||||
if (data.details.live && !this.timer) {
|
||||
// if live playlist we will have to reload it periodically
|
||||
// set reload period to playlist target duration
|
||||
this.timer = setInterval(this.ontick, 1000 * data.details.targetduration);
|
||||
}
|
||||
if (!data.details.live && this.timer) {
|
||||
// playlist is not live and timer is armed : stopping it
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
tick() {
|
||||
var levelId = this._level;
|
||||
if (levelId !== undefined) {
|
||||
var level = this._levels[levelId], urlId = level.urlId;
|
||||
this.hls.trigger(Event.LEVEL_LOADING, {url: level.url[urlId], level: levelId, id: urlId});
|
||||
}
|
||||
}
|
||||
|
||||
nextLoadLevel() {
|
||||
if (this._manualLevel !== -1) {
|
||||
return this._manualLevel;
|
||||
} else {
|
||||
return this.hls.abrController.nextAutoLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LevelController;
|
||||
|
1216
dashboard-ui/bower_components/hls.js/src/controller/mse-media-controller.js
vendored
Normal file
1216
dashboard-ui/bower_components/hls.js/src/controller/mse-media-controller.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
205
dashboard-ui/bower_components/hls.js/src/crypt/aes.js
vendored
Normal file
205
dashboard-ui/bower_components/hls.js/src/crypt/aes.js
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
*
|
||||
* This file contains an adaptation of the AES decryption algorithm
|
||||
* from the Standford Javascript Cryptography Library. That work is
|
||||
* covered by the following copyright and permissions notice:
|
||||
*
|
||||
* Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation
|
||||
* are those of the authors and should not be interpreted as representing
|
||||
* official policies, either expressed or implied, of the authors.
|
||||
*/
|
||||
class AES {
|
||||
|
||||
/**
|
||||
* Schedule out an AES key for both encryption and decryption. This
|
||||
* is a low-level class. Use a cipher mode to do bulk encryption.
|
||||
*
|
||||
* @constructor
|
||||
* @param key {Array} The key as an array of 4, 6 or 8 words.
|
||||
*/
|
||||
constructor(key) {
|
||||
/**
|
||||
* The expanded S-box and inverse S-box tables. These will be computed
|
||||
* on the client so that we don't have to send them down the wire.
|
||||
*
|
||||
* There are two tables, _tables[0] is for encryption and
|
||||
* _tables[1] is for decryption.
|
||||
*
|
||||
* The first 4 sub-tables are the expanded S-box with MixColumns. The
|
||||
* last (_tables[01][4]) is the S-box itself.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
this._tables = [[[],[],[],[],[]],[[],[],[],[],[]]];
|
||||
|
||||
this._precompute();
|
||||
|
||||
var i, j, tmp,
|
||||
encKey, decKey,
|
||||
sbox = this._tables[0][4], decTable = this._tables[1],
|
||||
keyLen = key.length, rcon = 1;
|
||||
|
||||
if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
|
||||
throw new Error('Invalid aes key size=' + keyLen);
|
||||
}
|
||||
|
||||
encKey = key.slice(0);
|
||||
decKey = [];
|
||||
this._key = [encKey, decKey];
|
||||
|
||||
// schedule encryption keys
|
||||
for (i = keyLen; i < 4 * keyLen + 28; i++) {
|
||||
tmp = encKey[i-1];
|
||||
|
||||
// apply sbox
|
||||
if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) {
|
||||
tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255];
|
||||
|
||||
// shift rows and add rcon
|
||||
if (i%keyLen === 0) {
|
||||
tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24;
|
||||
rcon = rcon<<1 ^ (rcon>>7)*283;
|
||||
}
|
||||
}
|
||||
|
||||
encKey[i] = encKey[i-keyLen] ^ tmp;
|
||||
}
|
||||
|
||||
// schedule decryption keys
|
||||
for (j = 0; i; j++, i--) {
|
||||
tmp = encKey[j&3 ? i : i - 4];
|
||||
if (i<=4 || j<4) {
|
||||
decKey[j] = tmp;
|
||||
} else {
|
||||
decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^
|
||||
decTable[1][sbox[tmp>>16 & 255]] ^
|
||||
decTable[2][sbox[tmp>>8 & 255]] ^
|
||||
decTable[3][sbox[tmp & 255]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the S-box tables.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_precompute() {
|
||||
var encTable = this._tables[0], decTable = this._tables[1],
|
||||
sbox = encTable[4], sboxInv = decTable[4],
|
||||
i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec;
|
||||
|
||||
// Compute double and third tables
|
||||
for (i = 0; i < 256; i++) {
|
||||
th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i;
|
||||
}
|
||||
|
||||
for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
|
||||
// Compute sbox
|
||||
s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4;
|
||||
s = s>>8 ^ s&255 ^ 99;
|
||||
sbox[x] = s;
|
||||
sboxInv[s] = x;
|
||||
|
||||
// Compute MixColumns
|
||||
x8 = d[x4 = d[x2 = d[x]]];
|
||||
tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100;
|
||||
tEnc = d[s]*0x101 ^ s*0x1010100;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8;
|
||||
decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8;
|
||||
}
|
||||
}
|
||||
|
||||
// Compactify. Considerable speedup on Firefox.
|
||||
for (i = 0; i < 5; i++) {
|
||||
encTable[i] = encTable[i].slice(0);
|
||||
decTable[i] = decTable[i].slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt 16 bytes, specified as four 32-bit words.
|
||||
* @param encrypted0 {number} the first word to decrypt
|
||||
* @param encrypted1 {number} the second word to decrypt
|
||||
* @param encrypted2 {number} the third word to decrypt
|
||||
* @param encrypted3 {number} the fourth word to decrypt
|
||||
* @param out {Int32Array} the array to write the decrypted words
|
||||
* into
|
||||
* @param offset {number} the offset into the output array to start
|
||||
* writing results
|
||||
* @return {Array} The plaintext.
|
||||
*/
|
||||
decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
|
||||
var key = this._key[1],
|
||||
// state variables a,b,c,d are loaded with pre-whitened data
|
||||
a = encrypted0 ^ key[0],
|
||||
b = encrypted3 ^ key[1],
|
||||
c = encrypted2 ^ key[2],
|
||||
d = encrypted1 ^ key[3],
|
||||
a2, b2, c2,
|
||||
|
||||
nInnerRounds = key.length / 4 - 2, // key.length === 2 ?
|
||||
i,
|
||||
kIndex = 4,
|
||||
table = this._tables[1],
|
||||
|
||||
// load up the tables
|
||||
table0 = table[0],
|
||||
table1 = table[1],
|
||||
table2 = table[2],
|
||||
table3 = table[3],
|
||||
sbox = table[4];
|
||||
|
||||
// Inner rounds. Cribbed from OpenSSL.
|
||||
for (i = 0; i < nInnerRounds; i++) {
|
||||
a2 = table0[a>>>24] ^ table1[b>>16 & 255] ^ table2[c>>8 & 255] ^ table3[d & 255] ^ key[kIndex];
|
||||
b2 = table0[b>>>24] ^ table1[c>>16 & 255] ^ table2[d>>8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
|
||||
c2 = table0[c>>>24] ^ table1[d>>16 & 255] ^ table2[a>>8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
|
||||
d = table0[d>>>24] ^ table1[a>>16 & 255] ^ table2[b>>8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
|
||||
kIndex += 4;
|
||||
a=a2; b=b2; c=c2;
|
||||
}
|
||||
|
||||
// Last round.
|
||||
for (i = 0; i < 4; i++) {
|
||||
out[(3 & -i) + offset] =
|
||||
sbox[a>>>24 ]<<24 ^
|
||||
sbox[b>>16 & 255]<<16 ^
|
||||
sbox[c>>8 & 255]<<8 ^
|
||||
sbox[d & 255] ^
|
||||
key[kIndex++];
|
||||
a2=a; a=b; b=c; c=d; d=a2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AES;
|
167
dashboard-ui/bower_components/hls.js/src/crypt/aes128-decrypter.js
vendored
Normal file
167
dashboard-ui/bower_components/hls.js/src/crypt/aes128-decrypter.js
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
*
|
||||
* This file contains an adaptation of the AES decryption algorithm
|
||||
* from the Standford Javascript Cryptography Library. That work is
|
||||
* covered by the following copyright and permissions notice:
|
||||
*
|
||||
* Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation
|
||||
* are those of the authors and should not be interpreted as representing
|
||||
* official policies, either expressed or implied, of the authors.
|
||||
*/
|
||||
|
||||
import AES from './aes';
|
||||
|
||||
class AES128Decrypter {
|
||||
|
||||
constructor(key, initVector) {
|
||||
this.key = key;
|
||||
this.iv = initVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert network-order (big-endian) bytes into their little-endian
|
||||
* representation.
|
||||
*/
|
||||
ntoh(word) {
|
||||
return (word << 24) |
|
||||
((word & 0xff00) << 8) |
|
||||
((word & 0xff0000) >> 8) |
|
||||
(word >>> 24);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
|
||||
* @param encrypted {Uint8Array} the encrypted bytes
|
||||
* @param key {Uint32Array} the bytes of the decryption key
|
||||
* @param initVector {Uint32Array} the initialization vector (IV) to
|
||||
* use for the first round of CBC.
|
||||
* @return {Uint8Array} the decrypted bytes
|
||||
*
|
||||
* @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
|
||||
* @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
|
||||
* @see https://tools.ietf.org/html/rfc2315
|
||||
*/
|
||||
doDecrypt(encrypted, key, initVector) {
|
||||
var
|
||||
// word-level access to the encrypted bytes
|
||||
encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2),
|
||||
|
||||
decipher = new AES(Array.prototype.slice.call(key)),
|
||||
|
||||
// byte and word-level access for the decrypted output
|
||||
decrypted = new Uint8Array(encrypted.byteLength),
|
||||
decrypted32 = new Int32Array(decrypted.buffer),
|
||||
|
||||
// temporary variables for working with the IV, encrypted, and
|
||||
// decrypted data
|
||||
init0, init1, init2, init3,
|
||||
encrypted0, encrypted1, encrypted2, encrypted3,
|
||||
|
||||
// iteration variable
|
||||
wordIx;
|
||||
|
||||
// pull out the words of the IV to ensure we don't modify the
|
||||
// passed-in reference and easier access
|
||||
init0 = ~~initVector[0];
|
||||
init1 = ~~initVector[1];
|
||||
init2 = ~~initVector[2];
|
||||
init3 = ~~initVector[3];
|
||||
|
||||
// decrypt four word sequences, applying cipher-block chaining (CBC)
|
||||
// to each decrypted block
|
||||
for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
|
||||
// convert big-endian (network order) words into little-endian
|
||||
// (javascript order)
|
||||
encrypted0 = ~~this.ntoh(encrypted32[wordIx]);
|
||||
encrypted1 = ~~this.ntoh(encrypted32[wordIx + 1]);
|
||||
encrypted2 = ~~this.ntoh(encrypted32[wordIx + 2]);
|
||||
encrypted3 = ~~this.ntoh(encrypted32[wordIx + 3]);
|
||||
|
||||
// decrypt the block
|
||||
decipher.decrypt(encrypted0,
|
||||
encrypted1,
|
||||
encrypted2,
|
||||
encrypted3,
|
||||
decrypted32,
|
||||
wordIx);
|
||||
|
||||
// XOR with the IV, and restore network byte-order to obtain the
|
||||
// plaintext
|
||||
decrypted32[wordIx] = this.ntoh(decrypted32[wordIx] ^ init0);
|
||||
decrypted32[wordIx + 1] = this.ntoh(decrypted32[wordIx + 1] ^ init1);
|
||||
decrypted32[wordIx + 2] = this.ntoh(decrypted32[wordIx + 2] ^ init2);
|
||||
decrypted32[wordIx + 3] = this.ntoh(decrypted32[wordIx + 3] ^ init3);
|
||||
|
||||
// setup the IV for the next round
|
||||
init0 = encrypted0;
|
||||
init1 = encrypted1;
|
||||
init2 = encrypted2;
|
||||
init3 = encrypted3;
|
||||
}
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
localDecript(encrypted, key, initVector, decrypted) {
|
||||
var bytes = this.doDecrypt(encrypted,
|
||||
key,
|
||||
initVector);
|
||||
decrypted.set(bytes, encrypted.byteOffset);
|
||||
}
|
||||
|
||||
decrypt(encrypted) {
|
||||
var
|
||||
step = 4 * 8000,
|
||||
//encrypted32 = new Int32Array(encrypted.buffer),
|
||||
encrypted32 = new Int32Array(encrypted),
|
||||
decrypted = new Uint8Array(encrypted.byteLength),
|
||||
i = 0;
|
||||
|
||||
// split up the encryption job and do the individual chunks asynchronously
|
||||
var key = this.key;
|
||||
var initVector = this.iv;
|
||||
this.localDecript(encrypted32.subarray(i, i + step), key, initVector, decrypted);
|
||||
|
||||
for (i = step; i < encrypted32.length; i += step) {
|
||||
initVector = new Uint32Array([
|
||||
this.ntoh(encrypted32[i - 4]),
|
||||
this.ntoh(encrypted32[i - 3]),
|
||||
this.ntoh(encrypted32[i - 2]),
|
||||
this.ntoh(encrypted32[i - 1])
|
||||
]);
|
||||
this.localDecript(encrypted32.subarray(i, i + step), key, initVector, decrypted);
|
||||
}
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
export default AES128Decrypter;
|
86
dashboard-ui/bower_components/hls.js/src/crypt/decrypter.js
vendored
Normal file
86
dashboard-ui/bower_components/hls.js/src/crypt/decrypter.js
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* AES128 decryption.
|
||||
*/
|
||||
|
||||
import AES128Decrypter from './aes128-decrypter';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
import {logger} from '../utils/logger';
|
||||
|
||||
class Decrypter {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
try {
|
||||
const browserCrypto = window ? window.crypto : crypto;
|
||||
this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
|
||||
this.disableWebCrypto = !this.subtle;
|
||||
} catch (e) {
|
||||
this.disableWebCrypto = true;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
}
|
||||
|
||||
decrypt(data, key, iv, callback) {
|
||||
if (this.disableWebCrypto && this.hls.config.enableSoftwareAES) {
|
||||
this.decryptBySoftware(data, key, iv, callback);
|
||||
} else {
|
||||
this.decryptByWebCrypto(data, key, iv, callback);
|
||||
}
|
||||
}
|
||||
|
||||
decryptByWebCrypto(data, key, iv, callback) {
|
||||
logger.log('decrypting by WebCrypto API');
|
||||
|
||||
this.subtle.importKey('raw', key, { name : 'AES-CBC', length : 128 }, false, ['decrypt']).
|
||||
then((importedKey) => {
|
||||
this.subtle.decrypt({ name : 'AES-CBC', iv : iv.buffer }, importedKey, data).
|
||||
then(callback).
|
||||
catch ((err) => {
|
||||
this.onWebCryptoError(err, data, key, iv, callback);
|
||||
});
|
||||
}).
|
||||
catch ((err) => {
|
||||
this.onWebCryptoError(err, data, key, iv, callback);
|
||||
});
|
||||
}
|
||||
|
||||
decryptBySoftware(data, key8, iv8, callback) {
|
||||
logger.log('decrypting by JavaScript Implementation');
|
||||
|
||||
var view = new DataView(key8.buffer);
|
||||
var key = new Uint32Array([
|
||||
view.getUint32(0),
|
||||
view.getUint32(4),
|
||||
view.getUint32(8),
|
||||
view.getUint32(12)
|
||||
]);
|
||||
|
||||
view = new DataView(iv8.buffer);
|
||||
var iv = new Uint32Array([
|
||||
view.getUint32(0),
|
||||
view.getUint32(4),
|
||||
view.getUint32(8),
|
||||
view.getUint32(12)
|
||||
]);
|
||||
|
||||
var decrypter = new AES128Decrypter(key, iv);
|
||||
callback(decrypter.decrypt(data).buffer);
|
||||
}
|
||||
|
||||
onWebCryptoError(err, data, key, iv, callback) {
|
||||
if (this.hls.config.enableSoftwareAES) {
|
||||
logger.log('disabling to use WebCrypto API');
|
||||
this.disableWebCrypto = true;
|
||||
this.decryptBySoftware(data, key, iv, callback);
|
||||
}
|
||||
else {
|
||||
logger.error(`decrypting error : ${err.message}`);
|
||||
this.hls.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details : ErrorDetails.FRAG_DECRYPT_ERROR, fatal : true, reason : err.message});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Decrypter;
|
208
dashboard-ui/bower_components/hls.js/src/demux/aacdemuxer.js
vendored
Normal file
208
dashboard-ui/bower_components/hls.js/src/demux/aacdemuxer.js
vendored
Normal file
|
@ -0,0 +1,208 @@
|
|||
/**
|
||||
* AAC demuxer
|
||||
*/
|
||||
import {logger} from '../utils/logger';
|
||||
import ID3 from '../demux/id3';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
|
||||
class AACDemuxer {
|
||||
|
||||
constructor(observer,remuxerClass) {
|
||||
this.observer = observer;
|
||||
this.remuxerClass = remuxerClass;
|
||||
this.remuxer = new this.remuxerClass(observer);
|
||||
this._aacTrack = {type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};
|
||||
}
|
||||
|
||||
static probe(data) {
|
||||
// check if data contains ID3 timestamp and ADTS sync worc
|
||||
var id3 = new ID3(data), adtsStartOffset,len;
|
||||
if(id3.hasTimeStamp) {
|
||||
// look for ADTS header (0xFFFx)
|
||||
for (adtsStartOffset = id3.length, len = data.length; adtsStartOffset < len - 1; adtsStartOffset++) {
|
||||
if ((data[adtsStartOffset] === 0xff) && (data[adtsStartOffset+1] & 0xf0) === 0xf0) {
|
||||
//logger.log('ADTS sync word found !');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// feed incoming data to the front of the parsing pipeline
|
||||
push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {
|
||||
var id3 = new ID3(data), adtsStartOffset,len, track = this._aacTrack, pts = id3.timeStamp, config, nbSamples,adtsFrameSize,adtsHeaderLen,stamp,aacSample;
|
||||
// look for ADTS header (0xFFFx)
|
||||
for (adtsStartOffset = id3.length, len = data.length; adtsStartOffset < len - 1; adtsStartOffset++) {
|
||||
if ((data[adtsStartOffset] === 0xff) && (data[adtsStartOffset+1] & 0xf0) === 0xf0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!track.audiosamplerate) {
|
||||
config = this._ADTStoAudioConfig(data, adtsStartOffset, audioCodec);
|
||||
track.config = config.config;
|
||||
track.audiosamplerate = config.samplerate;
|
||||
track.channelCount = config.channelCount;
|
||||
track.codec = config.codec;
|
||||
track.timescale = this.remuxer.timescale;
|
||||
track.duration = this.remuxer.timescale * duration;
|
||||
logger.log(`parsed codec:${track.codec},rate:${config.samplerate},nb channel:${config.channelCount}`);
|
||||
}
|
||||
nbSamples = 0;
|
||||
while ((adtsStartOffset + 5) < len) {
|
||||
// retrieve frame size
|
||||
adtsFrameSize = ((data[adtsStartOffset + 3] & 0x03) << 11);
|
||||
// byte 4
|
||||
adtsFrameSize |= (data[adtsStartOffset + 4] << 3);
|
||||
// byte 5
|
||||
adtsFrameSize |= ((data[adtsStartOffset + 5] & 0xE0) >>> 5);
|
||||
adtsHeaderLen = (!!(data[adtsStartOffset + 1] & 0x01) ? 7 : 9);
|
||||
adtsFrameSize -= adtsHeaderLen;
|
||||
stamp = Math.round(90*pts + nbSamples * 1024 * 90000 / track.audiosamplerate);
|
||||
//stamp = pes.pts;
|
||||
//console.log('AAC frame, offset/length/pts:' + (adtsStartOffset+7) + '/' + adtsFrameSize + '/' + stamp.toFixed(0));
|
||||
if ((adtsFrameSize > 0) && ((adtsStartOffset + adtsHeaderLen + adtsFrameSize) <= len)) {
|
||||
aacSample = {unit: data.subarray(adtsStartOffset + adtsHeaderLen, adtsStartOffset + adtsHeaderLen + adtsFrameSize), pts: stamp, dts: stamp};
|
||||
track.samples.push(aacSample);
|
||||
track.len += adtsFrameSize;
|
||||
adtsStartOffset += adtsFrameSize + adtsHeaderLen;
|
||||
nbSamples++;
|
||||
// look for ADTS header (0xFFFx)
|
||||
for ( ; adtsStartOffset < (len - 1); adtsStartOffset++) {
|
||||
if ((data[adtsStartOffset] === 0xff) && ((data[adtsStartOffset + 1] & 0xf0) === 0xf0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.remuxer.remux(this._aacTrack,{samples : []}, {samples : []}, timeOffset);
|
||||
}
|
||||
|
||||
_ADTStoAudioConfig(data, offset, audioCodec) {
|
||||
var adtsObjectType, // :int
|
||||
adtsSampleingIndex, // :int
|
||||
adtsExtensionSampleingIndex, // :int
|
||||
adtsChanelConfig, // :int
|
||||
config,
|
||||
userAgent = navigator.userAgent.toLowerCase(),
|
||||
adtsSampleingRates = [
|
||||
96000, 88200,
|
||||
64000, 48000,
|
||||
44100, 32000,
|
||||
24000, 22050,
|
||||
16000, 12000,
|
||||
11025, 8000,
|
||||
7350];
|
||||
// byte 2
|
||||
adtsObjectType = ((data[offset + 2] & 0xC0) >>> 6) + 1;
|
||||
adtsSampleingIndex = ((data[offset + 2] & 0x3C) >>> 2);
|
||||
if(adtsSampleingIndex > adtsSampleingRates.length-1) {
|
||||
this.observer.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: `invalid ADTS sampling index:${adtsSampleingIndex}`});
|
||||
return;
|
||||
}
|
||||
adtsChanelConfig = ((data[offset + 2] & 0x01) << 2);
|
||||
// byte 3
|
||||
adtsChanelConfig |= ((data[offset + 3] & 0xC0) >>> 6);
|
||||
logger.log(`manifest codec:${audioCodec},ADTS data:type:${adtsObjectType},sampleingIndex:${adtsSampleingIndex}[${adtsSampleingRates[adtsSampleingIndex]}Hz],channelConfig:${adtsChanelConfig}`);
|
||||
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
||||
if (userAgent.indexOf('firefox') !== -1) {
|
||||
if (adtsSampleingIndex >= 6) {
|
||||
adtsObjectType = 5;
|
||||
config = new Array(4);
|
||||
// HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
|
||||
// there is a factor 2 between frame sample rate and output sample rate
|
||||
// multiply frequency by 2 (see table below, equivalent to substract 3)
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex - 3;
|
||||
} else {
|
||||
adtsObjectType = 2;
|
||||
config = new Array(2);
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex;
|
||||
}
|
||||
// Android : always use AAC
|
||||
} else if (userAgent.indexOf('android') !== -1) {
|
||||
adtsObjectType = 2;
|
||||
config = new Array(2);
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex;
|
||||
} else {
|
||||
/* for other browsers (chrome ...)
|
||||
always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...)
|
||||
*/
|
||||
adtsObjectType = 5;
|
||||
config = new Array(4);
|
||||
// if (manifest codec is HE-AAC) OR (manifest codec not specified AND frequency less than 24kHz)
|
||||
if ((audioCodec && audioCodec.indexOf('mp4a.40.5') !== -1) || (!audioCodec && adtsSampleingIndex >= 6)) {
|
||||
// HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
|
||||
// there is a factor 2 between frame sample rate and output sample rate
|
||||
// multiply frequency by 2 (see table below, equivalent to substract 3)
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex - 3;
|
||||
} else {
|
||||
// if (manifest codec is AAC) AND (frequency less than 24kHz OR nb channel is 1)
|
||||
if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSampleingIndex >= 6 || adtsChanelConfig === 1)) {
|
||||
adtsObjectType = 2;
|
||||
config = new Array(2);
|
||||
}
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex;
|
||||
}
|
||||
}
|
||||
/* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
|
||||
ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig()
|
||||
Audio Profile / Audio Object Type
|
||||
0: Null
|
||||
1: AAC Main
|
||||
2: AAC LC (Low Complexity)
|
||||
3: AAC SSR (Scalable Sample Rate)
|
||||
4: AAC LTP (Long Term Prediction)
|
||||
5: SBR (Spectral Band Replication)
|
||||
6: AAC Scalable
|
||||
sampling freq
|
||||
0: 96000 Hz
|
||||
1: 88200 Hz
|
||||
2: 64000 Hz
|
||||
3: 48000 Hz
|
||||
4: 44100 Hz
|
||||
5: 32000 Hz
|
||||
6: 24000 Hz
|
||||
7: 22050 Hz
|
||||
8: 16000 Hz
|
||||
9: 12000 Hz
|
||||
10: 11025 Hz
|
||||
11: 8000 Hz
|
||||
12: 7350 Hz
|
||||
13: Reserved
|
||||
14: Reserved
|
||||
15: frequency is written explictly
|
||||
Channel Configurations
|
||||
These are the channel configurations:
|
||||
0: Defined in AOT Specifc Config
|
||||
1: 1 channel: front-center
|
||||
2: 2 channels: front-left, front-right
|
||||
*/
|
||||
// audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
|
||||
config[0] = adtsObjectType << 3;
|
||||
// samplingFrequencyIndex
|
||||
config[0] |= (adtsSampleingIndex & 0x0E) >> 1;
|
||||
config[1] |= (adtsSampleingIndex & 0x01) << 7;
|
||||
// channelConfiguration
|
||||
config[1] |= adtsChanelConfig << 3;
|
||||
if (adtsObjectType === 5) {
|
||||
// adtsExtensionSampleingIndex
|
||||
config[1] |= (adtsExtensionSampleingIndex & 0x0E) >> 1;
|
||||
config[2] = (adtsExtensionSampleingIndex & 0x01) << 7;
|
||||
// adtsObjectType (force to 2, chrome is checking that object type is less than 5 ???
|
||||
// https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc
|
||||
config[2] |= 2 << 2;
|
||||
config[3] = 0;
|
||||
}
|
||||
return {config: config, samplerate: adtsSampleingRates[adtsSampleingIndex], channelCount: adtsChanelConfig, codec: ('mp4a.40.' + adtsObjectType)};
|
||||
}
|
||||
|
||||
destroy() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AACDemuxer;
|
41
dashboard-ui/bower_components/hls.js/src/demux/demuxer-inline.js
vendored
Normal file
41
dashboard-ui/bower_components/hls.js/src/demux/demuxer-inline.js
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
/* inline demuxer.
|
||||
* probe fragments and instantiate appropriate demuxer depending on content type (TSDemuxer, AACDemuxer, ...)
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
import AACDemuxer from '../demux/aacdemuxer';
|
||||
import TSDemuxer from '../demux/tsdemuxer';
|
||||
|
||||
class DemuxerInline {
|
||||
|
||||
constructor(hls,remuxer) {
|
||||
this.hls = hls;
|
||||
this.remuxer = remuxer;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
var demuxer = this.demuxer;
|
||||
if (demuxer) {
|
||||
demuxer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {
|
||||
var demuxer = this.demuxer;
|
||||
if (!demuxer) {
|
||||
// probe for content type
|
||||
if (TSDemuxer.probe(data)) {
|
||||
demuxer = this.demuxer = new TSDemuxer(this.hls,this.remuxer);
|
||||
} else if(AACDemuxer.probe(data)) {
|
||||
demuxer = this.demuxer = new AACDemuxer(this.hls,this.remuxer);
|
||||
} else {
|
||||
this.hls.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: 'no demux matching with content found'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
demuxer.push(data,audioCodec,videoCodec,timeOffset,cc,level,sn,duration);
|
||||
}
|
||||
}
|
||||
|
||||
export default DemuxerInline;
|
78
dashboard-ui/bower_components/hls.js/src/demux/demuxer-worker.js
vendored
Normal file
78
dashboard-ui/bower_components/hls.js/src/demux/demuxer-worker.js
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
/* demuxer web worker.
|
||||
* - listen to worker message, and trigger DemuxerInline upon reception of Fragments.
|
||||
* - provides MP4 Boxes back to main thread using [transferable objects](https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast) in order to minimize message passing overhead.
|
||||
*/
|
||||
|
||||
import DemuxerInline from '../demux/demuxer-inline';
|
||||
import Event from '../events';
|
||||
import EventEmitter from 'events';
|
||||
import MP4Remuxer from '../remux/mp4-remuxer';
|
||||
|
||||
var DemuxerWorker = function (self) {
|
||||
// observer setup
|
||||
var observer = new EventEmitter();
|
||||
observer.trigger = function trigger (event, ...data) {
|
||||
observer.emit(event, event, ...data);
|
||||
};
|
||||
|
||||
observer.off = function off (event, ...data) {
|
||||
observer.removeListener(event, ...data);
|
||||
};
|
||||
self.addEventListener('message', function (ev) {
|
||||
//console.log('demuxer cmd:' + ev.data.cmd);
|
||||
switch (ev.data.cmd) {
|
||||
case 'init':
|
||||
self.demuxer = new DemuxerInline(observer,MP4Remuxer);
|
||||
break;
|
||||
case 'demux':
|
||||
var data = ev.data;
|
||||
self.demuxer.push(new Uint8Array(data.data), data.audioCodec, data.videoCodec, data.timeOffset, data.cc, data.level, data.sn, data.duration);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// listen to events triggered by TS Demuxer
|
||||
observer.on(Event.FRAG_PARSING_INIT_SEGMENT, function(ev, data) {
|
||||
var objData = {event: ev};
|
||||
var objTransferable = [];
|
||||
if (data.audioCodec) {
|
||||
objData.audioCodec = data.audioCodec;
|
||||
objData.audioMoov = data.audioMoov.buffer;
|
||||
objData.audioChannelCount = data.audioChannelCount;
|
||||
objTransferable.push(objData.audioMoov);
|
||||
}
|
||||
if (data.videoCodec) {
|
||||
objData.videoCodec = data.videoCodec;
|
||||
objData.videoMoov = data.videoMoov.buffer;
|
||||
objData.videoWidth = data.videoWidth;
|
||||
objData.videoHeight = data.videoHeight;
|
||||
objTransferable.push(objData.videoMoov);
|
||||
}
|
||||
// pass moov as transferable object (no copy)
|
||||
self.postMessage(objData,objTransferable);
|
||||
});
|
||||
|
||||
observer.on(Event.FRAG_PARSING_DATA, function(ev, data) {
|
||||
var objData = {event: ev, type: data.type, startPTS: data.startPTS, endPTS: data.endPTS, startDTS: data.startDTS, endDTS: data.endDTS, moof: data.moof.buffer, mdat: data.mdat.buffer, nb: data.nb};
|
||||
// pass moof/mdat data as transferable object (no copy)
|
||||
self.postMessage(objData, [objData.moof, objData.mdat]);
|
||||
});
|
||||
|
||||
observer.on(Event.FRAG_PARSED, function(event) {
|
||||
self.postMessage({event: event});
|
||||
});
|
||||
|
||||
observer.on(Event.ERROR, function(event, data) {
|
||||
self.postMessage({event: event, data: data});
|
||||
});
|
||||
|
||||
observer.on(Event.FRAG_PARSING_METADATA, function(event, data) {
|
||||
var objData = {event: event, samples: data.samples};
|
||||
self.postMessage(objData);
|
||||
});
|
||||
};
|
||||
|
||||
export default DemuxerWorker;
|
||||
|
112
dashboard-ui/bower_components/hls.js/src/demux/demuxer.js
vendored
Normal file
112
dashboard-ui/bower_components/hls.js/src/demux/demuxer.js
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
import Event from '../events';
|
||||
import DemuxerInline from '../demux/demuxer-inline';
|
||||
import DemuxerWorker from '../demux/demuxer-worker';
|
||||
import {logger} from '../utils/logger';
|
||||
import MP4Remuxer from '../remux/mp4-remuxer';
|
||||
import Decrypter from '../crypt/decrypter';
|
||||
|
||||
class Demuxer {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
if (hls.config.enableWorker && (typeof(Worker) !== 'undefined')) {
|
||||
logger.log('demuxing in webworker');
|
||||
try {
|
||||
var work = require('webworkify');
|
||||
this.w = work(DemuxerWorker);
|
||||
this.onwmsg = this.onWorkerMessage.bind(this);
|
||||
this.w.addEventListener('message', this.onwmsg);
|
||||
this.w.postMessage({cmd: 'init'});
|
||||
} catch(err) {
|
||||
logger.error('error while initializing DemuxerWorker, fallback on DemuxerInline');
|
||||
this.demuxer = new DemuxerInline(hls,MP4Remuxer);
|
||||
}
|
||||
} else {
|
||||
this.demuxer = new DemuxerInline(hls,MP4Remuxer);
|
||||
}
|
||||
this.demuxInitialized = true;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.w) {
|
||||
this.w.removeEventListener('message', this.onwmsg);
|
||||
this.w.terminate();
|
||||
this.w = null;
|
||||
} else {
|
||||
this.demuxer.destroy();
|
||||
this.demuxer = null;
|
||||
}
|
||||
if (this.decrypter) {
|
||||
this.decrypter.destroy();
|
||||
this.decrypter = null;
|
||||
}
|
||||
}
|
||||
|
||||
pushDecrypted(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {
|
||||
if (this.w) {
|
||||
// post fragment payload as transferable objects (no copy)
|
||||
this.w.postMessage({cmd: 'demux', data: data, audioCodec: audioCodec, videoCodec: videoCodec, timeOffset: timeOffset, cc: cc, level: level, sn : sn, duration: duration}, [data]);
|
||||
} else {
|
||||
this.demuxer.push(new Uint8Array(data), audioCodec, videoCodec, timeOffset, cc, level, sn, duration);
|
||||
}
|
||||
}
|
||||
|
||||
push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration, decryptdata) {
|
||||
if ((data.byteLength > 0) && (decryptdata != null) && (decryptdata.key != null) && (decryptdata.method === 'AES-128')) {
|
||||
if (this.decrypter == null) {
|
||||
this.decrypter = new Decrypter(this.hls);
|
||||
}
|
||||
|
||||
var localthis = this;
|
||||
this.decrypter.decrypt(data, decryptdata.key, decryptdata.iv, function(decryptedData){
|
||||
localthis.pushDecrypted(decryptedData, audioCodec, videoCodec, timeOffset, cc, level, sn, duration);
|
||||
});
|
||||
} else {
|
||||
this.pushDecrypted(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration);
|
||||
}
|
||||
}
|
||||
|
||||
onWorkerMessage(ev) {
|
||||
//console.log('onWorkerMessage:' + ev.data.event);
|
||||
switch(ev.data.event) {
|
||||
case Event.FRAG_PARSING_INIT_SEGMENT:
|
||||
var obj = {};
|
||||
if (ev.data.audioMoov) {
|
||||
obj.audioMoov = new Uint8Array(ev.data.audioMoov);
|
||||
obj.audioCodec = ev.data.audioCodec;
|
||||
obj.audioChannelCount = ev.data.audioChannelCount;
|
||||
}
|
||||
if (ev.data.videoMoov) {
|
||||
obj.videoMoov = new Uint8Array(ev.data.videoMoov);
|
||||
obj.videoCodec = ev.data.videoCodec;
|
||||
obj.videoWidth = ev.data.videoWidth;
|
||||
obj.videoHeight = ev.data.videoHeight;
|
||||
}
|
||||
this.hls.trigger(Event.FRAG_PARSING_INIT_SEGMENT, obj);
|
||||
break;
|
||||
case Event.FRAG_PARSING_DATA:
|
||||
this.hls.trigger(Event.FRAG_PARSING_DATA,{
|
||||
moof: new Uint8Array(ev.data.moof),
|
||||
mdat: new Uint8Array(ev.data.mdat),
|
||||
startPTS: ev.data.startPTS,
|
||||
endPTS: ev.data.endPTS,
|
||||
startDTS: ev.data.startDTS,
|
||||
endDTS: ev.data.endDTS,
|
||||
type: ev.data.type,
|
||||
nb: ev.data.nb
|
||||
});
|
||||
break;
|
||||
case Event.FRAG_PARSING_METADATA:
|
||||
this.hls.trigger(Event.FRAG_PARSING_METADATA, {
|
||||
samples: ev.data.samples
|
||||
});
|
||||
break;
|
||||
default:
|
||||
this.hls.trigger(ev.data.event, ev.data.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Demuxer;
|
||||
|
280
dashboard-ui/bower_components/hls.js/src/demux/exp-golomb.js
vendored
Normal file
280
dashboard-ui/bower_components/hls.js/src/demux/exp-golomb.js
vendored
Normal file
|
@ -0,0 +1,280 @@
|
|||
/**
|
||||
* Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
|
||||
*/
|
||||
|
||||
import {logger} from '../utils/logger';
|
||||
|
||||
class ExpGolomb {
|
||||
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
// the number of bytes left to examine in this.data
|
||||
this.bytesAvailable = this.data.byteLength;
|
||||
// the current word being examined
|
||||
this.word = 0; // :uint
|
||||
// the number of bits left to examine in the current word
|
||||
this.bitsAvailable = 0; // :uint
|
||||
}
|
||||
|
||||
// ():void
|
||||
loadWord() {
|
||||
var
|
||||
position = this.data.byteLength - this.bytesAvailable,
|
||||
workingBytes = new Uint8Array(4),
|
||||
availableBytes = Math.min(4, this.bytesAvailable);
|
||||
if (availableBytes === 0) {
|
||||
throw new Error('no bytes available');
|
||||
}
|
||||
workingBytes.set(this.data.subarray(position, position + availableBytes));
|
||||
this.word = new DataView(workingBytes.buffer).getUint32(0);
|
||||
// track the amount of this.data that has been processed
|
||||
this.bitsAvailable = availableBytes * 8;
|
||||
this.bytesAvailable -= availableBytes;
|
||||
}
|
||||
|
||||
// (count:int):void
|
||||
skipBits(count) {
|
||||
var skipBytes; // :int
|
||||
if (this.bitsAvailable > count) {
|
||||
this.word <<= count;
|
||||
this.bitsAvailable -= count;
|
||||
} else {
|
||||
count -= this.bitsAvailable;
|
||||
skipBytes = count >> 3;
|
||||
count -= (skipBytes >> 3);
|
||||
this.bytesAvailable -= skipBytes;
|
||||
this.loadWord();
|
||||
this.word <<= count;
|
||||
this.bitsAvailable -= count;
|
||||
}
|
||||
}
|
||||
|
||||
// (size:int):uint
|
||||
readBits(size) {
|
||||
var
|
||||
bits = Math.min(this.bitsAvailable, size), // :uint
|
||||
valu = this.word >>> (32 - bits); // :uint
|
||||
if (size > 32) {
|
||||
logger.error('Cannot read more than 32 bits at a time');
|
||||
}
|
||||
this.bitsAvailable -= bits;
|
||||
if (this.bitsAvailable > 0) {
|
||||
this.word <<= bits;
|
||||
} else if (this.bytesAvailable > 0) {
|
||||
this.loadWord();
|
||||
}
|
||||
bits = size - bits;
|
||||
if (bits > 0) {
|
||||
return valu << bits | this.readBits(bits);
|
||||
} else {
|
||||
return valu;
|
||||
}
|
||||
}
|
||||
|
||||
// ():uint
|
||||
skipLZ() {
|
||||
var leadingZeroCount; // :uint
|
||||
for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) {
|
||||
if (0 !== (this.word & (0x80000000 >>> leadingZeroCount))) {
|
||||
// the first bit of working word is 1
|
||||
this.word <<= leadingZeroCount;
|
||||
this.bitsAvailable -= leadingZeroCount;
|
||||
return leadingZeroCount;
|
||||
}
|
||||
}
|
||||
// we exhausted word and still have not found a 1
|
||||
this.loadWord();
|
||||
return leadingZeroCount + this.skipLZ();
|
||||
}
|
||||
|
||||
// ():void
|
||||
skipUEG() {
|
||||
this.skipBits(1 + this.skipLZ());
|
||||
}
|
||||
|
||||
// ():void
|
||||
skipEG() {
|
||||
this.skipBits(1 + this.skipLZ());
|
||||
}
|
||||
|
||||
// ():uint
|
||||
readUEG() {
|
||||
var clz = this.skipLZ(); // :uint
|
||||
return this.readBits(clz + 1) - 1;
|
||||
}
|
||||
|
||||
// ():int
|
||||
readEG() {
|
||||
var valu = this.readUEG(); // :int
|
||||
if (0x01 & valu) {
|
||||
// the number is odd if the low order bit is set
|
||||
return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
|
||||
} else {
|
||||
return -1 * (valu >>> 1); // divide by two then make it negative
|
||||
}
|
||||
}
|
||||
|
||||
// Some convenience functions
|
||||
// :Boolean
|
||||
readBoolean() {
|
||||
return 1 === this.readBits(1);
|
||||
}
|
||||
|
||||
// ():int
|
||||
readUByte() {
|
||||
return this.readBits(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
||||
* list is optionally transmitted as part of a sequence parameter
|
||||
* set and is not relevant to transmuxing.
|
||||
* @param count {number} the number of entries in this scaling list
|
||||
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
||||
*/
|
||||
skipScalingList(count) {
|
||||
var
|
||||
lastScale = 8,
|
||||
nextScale = 8,
|
||||
j,
|
||||
deltaScale;
|
||||
for (j = 0; j < count; j++) {
|
||||
if (nextScale !== 0) {
|
||||
deltaScale = this.readEG();
|
||||
nextScale = (lastScale + deltaScale + 256) % 256;
|
||||
}
|
||||
lastScale = (nextScale === 0) ? lastScale : nextScale;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a sequence parameter set and return some interesting video
|
||||
* properties. A sequence parameter set is the H264 metadata that
|
||||
* describes the properties of upcoming video frames.
|
||||
* @param data {Uint8Array} the bytes of a sequence parameter set
|
||||
* @return {object} an object with configuration parsed from the
|
||||
* sequence parameter set, including the dimensions of the
|
||||
* associated video frames.
|
||||
*/
|
||||
readSPS() {
|
||||
var
|
||||
frameCropLeftOffset = 0,
|
||||
frameCropRightOffset = 0,
|
||||
frameCropTopOffset = 0,
|
||||
frameCropBottomOffset = 0,
|
||||
sarScale = 1,
|
||||
profileIdc,profileCompat,levelIdc,
|
||||
numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1,
|
||||
picHeightInMapUnitsMinus1,
|
||||
frameMbsOnlyFlag,
|
||||
scalingListCount,
|
||||
i;
|
||||
this.readUByte();
|
||||
profileIdc = this.readUByte(); // profile_idc
|
||||
profileCompat = this.readBits(5); // constraint_set[0-4]_flag, u(5)
|
||||
this.skipBits(3); // reserved_zero_3bits u(3),
|
||||
levelIdc = this.readUByte(); //level_idc u(8)
|
||||
this.skipUEG(); // seq_parameter_set_id
|
||||
// some profiles have more optional data we don't need
|
||||
if (profileIdc === 100 ||
|
||||
profileIdc === 110 ||
|
||||
profileIdc === 122 ||
|
||||
profileIdc === 144) {
|
||||
var chromaFormatIdc = this.readUEG();
|
||||
if (chromaFormatIdc === 3) {
|
||||
this.skipBits(1); // separate_colour_plane_flag
|
||||
}
|
||||
this.skipUEG(); // bit_depth_luma_minus8
|
||||
this.skipUEG(); // bit_depth_chroma_minus8
|
||||
this.skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
||||
if (this.readBoolean()) { // seq_scaling_matrix_present_flag
|
||||
scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
|
||||
for (i = 0; i < scalingListCount; i++) {
|
||||
if (this.readBoolean()) { // seq_scaling_list_present_flag[ i ]
|
||||
if (i < 6) {
|
||||
this.skipScalingList(16);
|
||||
} else {
|
||||
this.skipScalingList(64);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.skipUEG(); // log2_max_frame_num_minus4
|
||||
var picOrderCntType = this.readUEG();
|
||||
if (picOrderCntType === 0) {
|
||||
this.readUEG(); //log2_max_pic_order_cnt_lsb_minus4
|
||||
} else if (picOrderCntType === 1) {
|
||||
this.skipBits(1); // delta_pic_order_always_zero_flag
|
||||
this.skipEG(); // offset_for_non_ref_pic
|
||||
this.skipEG(); // offset_for_top_to_bottom_field
|
||||
numRefFramesInPicOrderCntCycle = this.readUEG();
|
||||
for(i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
||||
this.skipEG(); // offset_for_ref_frame[ i ]
|
||||
}
|
||||
}
|
||||
this.skipUEG(); // max_num_ref_frames
|
||||
this.skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
||||
picWidthInMbsMinus1 = this.readUEG();
|
||||
picHeightInMapUnitsMinus1 = this.readUEG();
|
||||
frameMbsOnlyFlag = this.readBits(1);
|
||||
if (frameMbsOnlyFlag === 0) {
|
||||
this.skipBits(1); // mb_adaptive_frame_field_flag
|
||||
}
|
||||
this.skipBits(1); // direct_8x8_inference_flag
|
||||
if (this.readBoolean()) { // frame_cropping_flag
|
||||
frameCropLeftOffset = this.readUEG();
|
||||
frameCropRightOffset = this.readUEG();
|
||||
frameCropTopOffset = this.readUEG();
|
||||
frameCropBottomOffset = this.readUEG();
|
||||
}
|
||||
if (this.readBoolean()) {
|
||||
// vui_parameters_present_flag
|
||||
if (this.readBoolean()) {
|
||||
// aspect_ratio_info_present_flag
|
||||
let sarRatio;
|
||||
const aspectRatioIdc = this.readUByte();
|
||||
switch (aspectRatioIdc) {
|
||||
//case 1: sarRatio = [1,1]; break;
|
||||
case 2: sarRatio = [12,11]; break;
|
||||
case 3: sarRatio = [10,11]; break;
|
||||
case 4: sarRatio = [16,11]; break;
|
||||
case 5: sarRatio = [40,33]; break;
|
||||
case 6: sarRatio = [24,11]; break;
|
||||
case 7: sarRatio = [20,11]; break;
|
||||
case 8: sarRatio = [32,11]; break;
|
||||
case 9: sarRatio = [80,33]; break;
|
||||
case 10: sarRatio = [18,11]; break;
|
||||
case 11: sarRatio = [15,11]; break;
|
||||
case 12: sarRatio = [64,33]; break;
|
||||
case 13: sarRatio = [160,99]; break;
|
||||
case 14: sarRatio = [4,3]; break;
|
||||
case 15: sarRatio = [3,2]; break;
|
||||
case 16: sarRatio = [2,1]; break;
|
||||
case 255: {
|
||||
sarRatio = [this.readUByte() << 8 | this.readUByte(), this.readUByte() << 8 | this.readUByte()];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sarRatio) {
|
||||
sarScale = sarRatio[0] / sarRatio[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
width: (((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale,
|
||||
height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset))
|
||||
};
|
||||
}
|
||||
|
||||
readSliceType() {
|
||||
// skip NALu type
|
||||
this.readUByte();
|
||||
// discard first_mb_in_slice
|
||||
this.readUEG();
|
||||
// return slice_type
|
||||
return this.readUEG();
|
||||
}
|
||||
}
|
||||
|
||||
export default ExpGolomb;
|
123
dashboard-ui/bower_components/hls.js/src/demux/id3.js
vendored
Normal file
123
dashboard-ui/bower_components/hls.js/src/demux/id3.js
vendored
Normal file
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* ID3 parser
|
||||
*/
|
||||
import {logger} from '../utils/logger';
|
||||
//import Hex from '../utils/hex';
|
||||
|
||||
class ID3 {
|
||||
|
||||
constructor(data) {
|
||||
this._hasTimeStamp = false;
|
||||
var offset = 0, byte1,byte2,byte3,byte4,tagSize,endPos,header,len;
|
||||
do {
|
||||
header = this.readUTF(data,offset,3);
|
||||
offset+=3;
|
||||
// first check for ID3 header
|
||||
if (header === 'ID3') {
|
||||
// skip 24 bits
|
||||
offset += 3;
|
||||
// retrieve tag(s) length
|
||||
byte1 = data[offset++] & 0x7f;
|
||||
byte2 = data[offset++] & 0x7f;
|
||||
byte3 = data[offset++] & 0x7f;
|
||||
byte4 = data[offset++] & 0x7f;
|
||||
tagSize = (byte1 << 21) + (byte2 << 14) + (byte3 << 7) + byte4;
|
||||
endPos = offset + tagSize;
|
||||
//logger.log(`ID3 tag found, size/end: ${tagSize}/${endPos}`);
|
||||
|
||||
// read ID3 tags
|
||||
this._parseID3Frames(data, offset,endPos);
|
||||
offset = endPos;
|
||||
} else if (header === '3DI') {
|
||||
// http://id3.org/id3v2.4.0-structure chapter 3.4. ID3v2 footer
|
||||
offset += 7;
|
||||
logger.log(`3DI footer found, end: ${offset}`);
|
||||
} else {
|
||||
offset -= 3;
|
||||
len = offset;
|
||||
if (len) {
|
||||
//logger.log(`ID3 len: ${len}`);
|
||||
if (!this.hasTimeStamp) {
|
||||
logger.warn('ID3 tag found, but no timestamp');
|
||||
}
|
||||
this._length = len;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
readUTF(data,start,len) {
|
||||
|
||||
var result = '',offset = start, end = start + len;
|
||||
do {
|
||||
result += String.fromCharCode(data[offset++]);
|
||||
} while(offset < end);
|
||||
return result;
|
||||
}
|
||||
|
||||
_parseID3Frames(data,offset,endPos) {
|
||||
var tagId,tagLen,tagStart,tagFlags,timestamp;
|
||||
while(offset + 8 <= endPos) {
|
||||
tagId = this.readUTF(data,offset,4);
|
||||
offset +=4;
|
||||
|
||||
tagLen = data[offset++] << 24 +
|
||||
data[offset++] << 16 +
|
||||
data[offset++] << 8 +
|
||||
data[offset++];
|
||||
|
||||
tagFlags = data[offset++] << 8 +
|
||||
data[offset++];
|
||||
|
||||
tagStart = offset;
|
||||
//logger.log("ID3 tag id:" + tagId);
|
||||
switch(tagId) {
|
||||
case 'PRIV':
|
||||
//logger.log('parse frame:' + Hex.hexDump(data.subarray(offset,endPos)));
|
||||
// owner should be "com.apple.streaming.transportStreamTimestamp"
|
||||
if (this.readUTF(data,offset,44) === 'com.apple.streaming.transportStreamTimestamp') {
|
||||
offset+=44;
|
||||
// smelling even better ! we found the right descriptor
|
||||
// skip null character (string end) + 3 first bytes
|
||||
offset+= 4;
|
||||
|
||||
// timestamp is 33 bit expressed as a big-endian eight-octet number, with the upper 31 bits set to zero.
|
||||
var pts33Bit = data[offset++] & 0x1;
|
||||
this._hasTimeStamp = true;
|
||||
|
||||
timestamp = ((data[offset++] << 23) +
|
||||
(data[offset++] << 15) +
|
||||
(data[offset++] << 7) +
|
||||
data[offset++]) /45;
|
||||
|
||||
if (pts33Bit) {
|
||||
timestamp += 47721858.84; // 2^32 / 90
|
||||
}
|
||||
timestamp = Math.round(timestamp);
|
||||
logger.trace(`ID3 timestamp found: ${timestamp}`);
|
||||
this._timeStamp = timestamp;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get hasTimeStamp() {
|
||||
return this._hasTimeStamp;
|
||||
}
|
||||
|
||||
get timeStamp() {
|
||||
return this._timeStamp;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ID3;
|
||||
|
665
dashboard-ui/bower_components/hls.js/src/demux/tsdemuxer.js
vendored
Normal file
665
dashboard-ui/bower_components/hls.js/src/demux/tsdemuxer.js
vendored
Normal file
|
@ -0,0 +1,665 @@
|
|||
/**
|
||||
* highly optimized TS demuxer:
|
||||
* parse PAT, PMT
|
||||
* extract PES packet from audio and video PIDs
|
||||
* extract AVC/H264 NAL units and AAC/ADTS samples from PES packet
|
||||
* trigger the remuxer upon parsing completion
|
||||
* it also tries to workaround as best as it can audio codec switch (HE-AAC to AAC and vice versa), without having to restart the MediaSource.
|
||||
* it also controls the remuxing process :
|
||||
* upon discontinuity or level switch detection, it will also notifies the remuxer so that it can reset its state.
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
import ExpGolomb from './exp-golomb';
|
||||
// import Hex from '../utils/hex';
|
||||
import {logger} from '../utils/logger';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
|
||||
class TSDemuxer {
|
||||
|
||||
constructor(observer,remuxerClass) {
|
||||
this.observer = observer;
|
||||
this.remuxerClass = remuxerClass;
|
||||
this.lastCC = 0;
|
||||
this.PES_TIMESCALE = 90000;
|
||||
this.remuxer = new this.remuxerClass(observer);
|
||||
}
|
||||
|
||||
static probe(data) {
|
||||
// a TS fragment should contain at least 3 TS packets, a PAT, a PMT, and one PID, each starting with 0x47
|
||||
if (data.length >= 3*188 && data[0] === 0x47 && data[188] === 0x47 && data[2*188] === 0x47) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switchLevel() {
|
||||
this.pmtParsed = false;
|
||||
this._pmtId = -1;
|
||||
this._avcTrack = {type: 'video', id :-1, sequenceNumber: 0, samples : [], len : 0, nbNalu : 0};
|
||||
this._aacTrack = {type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};
|
||||
this._id3Track = {type: 'id3', id :-1, sequenceNumber: 0, samples : [], len : 0};
|
||||
this.remuxer.switchLevel();
|
||||
}
|
||||
|
||||
insertDiscontinuity() {
|
||||
this.switchLevel();
|
||||
this.remuxer.insertDiscontinuity();
|
||||
}
|
||||
|
||||
// feed incoming data to the front of the parsing pipeline
|
||||
push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {
|
||||
var avcData, aacData, id3Data,
|
||||
start, len = data.length, stt, pid, atf, offset;
|
||||
this.audioCodec = audioCodec;
|
||||
this.videoCodec = videoCodec;
|
||||
this.timeOffset = timeOffset;
|
||||
this._duration = duration;
|
||||
this.contiguous = false;
|
||||
if (cc !== this.lastCC) {
|
||||
logger.log('discontinuity detected');
|
||||
this.insertDiscontinuity();
|
||||
this.lastCC = cc;
|
||||
} else if (level !== this.lastLevel) {
|
||||
logger.log('level switch detected');
|
||||
this.switchLevel();
|
||||
this.lastLevel = level;
|
||||
} else if (sn === (this.lastSN+1)) {
|
||||
this.contiguous = true;
|
||||
}
|
||||
this.lastSN = sn;
|
||||
|
||||
if(!this.contiguous) {
|
||||
// flush any partial content
|
||||
this.aacOverFlow = null;
|
||||
}
|
||||
|
||||
var pmtParsed = this.pmtParsed,
|
||||
avcId = this._avcTrack.id,
|
||||
aacId = this._aacTrack.id,
|
||||
id3Id = this._id3Track.id;
|
||||
// loop through TS packets
|
||||
for (start = 0; start < len; start += 188) {
|
||||
if (data[start] === 0x47) {
|
||||
stt = !!(data[start + 1] & 0x40);
|
||||
// pid is a 13-bit field starting at the last bit of TS[1]
|
||||
pid = ((data[start + 1] & 0x1f) << 8) + data[start + 2];
|
||||
atf = (data[start + 3] & 0x30) >> 4;
|
||||
// if an adaption field is present, its length is specified by the fifth byte of the TS packet header.
|
||||
if (atf > 1) {
|
||||
offset = start + 5 + data[start + 4];
|
||||
// continue if there is only adaptation field
|
||||
if (offset === (start + 188)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
offset = start + 4;
|
||||
}
|
||||
if (pmtParsed) {
|
||||
if (pid === avcId) {
|
||||
if (stt) {
|
||||
if (avcData) {
|
||||
this._parseAVCPES(this._parsePES(avcData));
|
||||
}
|
||||
avcData = {data: [], size: 0};
|
||||
}
|
||||
if (avcData) {
|
||||
avcData.data.push(data.subarray(offset, start + 188));
|
||||
avcData.size += start + 188 - offset;
|
||||
}
|
||||
} else if (pid === aacId) {
|
||||
if (stt) {
|
||||
if (aacData) {
|
||||
this._parseAACPES(this._parsePES(aacData));
|
||||
}
|
||||
aacData = {data: [], size: 0};
|
||||
}
|
||||
if (aacData) {
|
||||
aacData.data.push(data.subarray(offset, start + 188));
|
||||
aacData.size += start + 188 - offset;
|
||||
}
|
||||
} else if (pid === id3Id) {
|
||||
if (stt) {
|
||||
if (id3Data) {
|
||||
this._parseID3PES(this._parsePES(id3Data));
|
||||
}
|
||||
id3Data = {data: [], size: 0};
|
||||
}
|
||||
if (id3Data) {
|
||||
id3Data.data.push(data.subarray(offset, start + 188));
|
||||
id3Data.size += start + 188 - offset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (stt) {
|
||||
offset += data[offset] + 1;
|
||||
}
|
||||
if (pid === 0) {
|
||||
this._parsePAT(data, offset);
|
||||
} else if (pid === this._pmtId) {
|
||||
this._parsePMT(data, offset);
|
||||
pmtParsed = this.pmtParsed = true;
|
||||
avcId = this._avcTrack.id;
|
||||
aacId = this._aacTrack.id;
|
||||
id3Id = this._id3Track.id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.observer.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, reason: 'TS packet did not start with 0x47'});
|
||||
}
|
||||
}
|
||||
// parse last PES packet
|
||||
if (avcData) {
|
||||
this._parseAVCPES(this._parsePES(avcData));
|
||||
}
|
||||
if (aacData) {
|
||||
this._parseAACPES(this._parsePES(aacData));
|
||||
}
|
||||
if (id3Data) {
|
||||
this._parseID3PES(this._parsePES(id3Data));
|
||||
}
|
||||
this.remux();
|
||||
}
|
||||
|
||||
remux() {
|
||||
this.remuxer.remux(this._aacTrack,this._avcTrack, this._id3Track, this.timeOffset, this.contiguous);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.switchLevel();
|
||||
this._initPTS = this._initDTS = undefined;
|
||||
this._duration = 0;
|
||||
}
|
||||
|
||||
_parsePAT(data, offset) {
|
||||
// skip the PSI header and parse the first PMT entry
|
||||
this._pmtId = (data[offset + 10] & 0x1F) << 8 | data[offset + 11];
|
||||
//logger.log('PMT PID:' + this._pmtId);
|
||||
}
|
||||
|
||||
_parsePMT(data, offset) {
|
||||
var sectionLength, tableEnd, programInfoLength, pid;
|
||||
sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2];
|
||||
tableEnd = offset + 3 + sectionLength - 4;
|
||||
// to determine where the table is, we have to figure out how
|
||||
// long the program info descriptors are
|
||||
programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11];
|
||||
// advance the offset to the first entry in the mapping table
|
||||
offset += 12 + programInfoLength;
|
||||
while (offset < tableEnd) {
|
||||
pid = (data[offset + 1] & 0x1F) << 8 | data[offset + 2];
|
||||
switch(data[offset]) {
|
||||
// ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)
|
||||
case 0x0f:
|
||||
//logger.log('AAC PID:' + pid);
|
||||
this._aacTrack.id = pid;
|
||||
break;
|
||||
// Packetized metadata (ID3)
|
||||
case 0x15:
|
||||
//logger.log('ID3 PID:' + pid);
|
||||
this._id3Track.id = pid;
|
||||
break;
|
||||
// ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)
|
||||
case 0x1b:
|
||||
//logger.log('AVC PID:' + pid);
|
||||
this._avcTrack.id = pid;
|
||||
break;
|
||||
default:
|
||||
logger.log('unkown stream type:' + data[offset]);
|
||||
break;
|
||||
}
|
||||
// move to the next table entry
|
||||
// skip past the elementary stream descriptors, if present
|
||||
offset += ((data[offset + 3] & 0x0F) << 8 | data[offset + 4]) + 5;
|
||||
}
|
||||
}
|
||||
|
||||
_parsePES(stream) {
|
||||
var i = 0, frag, pesFlags, pesPrefix, pesLen, pesHdrLen, pesData, pesPts, pesDts, payloadStartOffset;
|
||||
//retrieve PTS/DTS from first fragment
|
||||
frag = stream.data[0];
|
||||
pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2];
|
||||
if (pesPrefix === 1) {
|
||||
pesLen = (frag[4] << 8) + frag[5];
|
||||
pesFlags = frag[7];
|
||||
if (pesFlags & 0xC0) {
|
||||
/* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
|
||||
as PTS / DTS is 33 bit we cannot use bitwise operator in JS,
|
||||
as Bitwise operators treat their operands as a sequence of 32 bits */
|
||||
pesPts = (frag[9] & 0x0E) * 536870912 +// 1 << 29
|
||||
(frag[10] & 0xFF) * 4194304 +// 1 << 22
|
||||
(frag[11] & 0xFE) * 16384 +// 1 << 14
|
||||
(frag[12] & 0xFF) * 128 +// 1 << 7
|
||||
(frag[13] & 0xFE) / 2;
|
||||
// check if greater than 2^32 -1
|
||||
if (pesPts > 4294967295) {
|
||||
// decrement 2^33
|
||||
pesPts -= 8589934592;
|
||||
}
|
||||
if (pesFlags & 0x40) {
|
||||
pesDts = (frag[14] & 0x0E ) * 536870912 +// 1 << 29
|
||||
(frag[15] & 0xFF ) * 4194304 +// 1 << 22
|
||||
(frag[16] & 0xFE ) * 16384 +// 1 << 14
|
||||
(frag[17] & 0xFF ) * 128 +// 1 << 7
|
||||
(frag[18] & 0xFE ) / 2;
|
||||
// check if greater than 2^32 -1
|
||||
if (pesDts > 4294967295) {
|
||||
// decrement 2^33
|
||||
pesDts -= 8589934592;
|
||||
}
|
||||
} else {
|
||||
pesDts = pesPts;
|
||||
}
|
||||
}
|
||||
pesHdrLen = frag[8];
|
||||
payloadStartOffset = pesHdrLen + 9;
|
||||
// trim PES header
|
||||
stream.data[0] = stream.data[0].subarray(payloadStartOffset);
|
||||
stream.size -= payloadStartOffset;
|
||||
//reassemble PES packet
|
||||
pesData = new Uint8Array(stream.size);
|
||||
// reassemble the packet
|
||||
while (stream.data.length) {
|
||||
frag = stream.data.shift();
|
||||
pesData.set(frag, i);
|
||||
i += frag.byteLength;
|
||||
}
|
||||
return {data: pesData, pts: pesPts, dts: pesDts, len: pesLen};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_parseAVCPES(pes) {
|
||||
var track = this._avcTrack,
|
||||
samples = track.samples,
|
||||
units = this._parseAVCNALu(pes.data),
|
||||
units2 = [],
|
||||
debug = false,
|
||||
key = false,
|
||||
length = 0,
|
||||
avcSample,
|
||||
push;
|
||||
// no NALu found
|
||||
if (units.length === 0 && samples.length > 0) {
|
||||
// append pes.data to previous NAL unit
|
||||
var lastavcSample = samples[samples.length - 1];
|
||||
var lastUnit = lastavcSample.units.units[lastavcSample.units.units.length - 1];
|
||||
var tmp = new Uint8Array(lastUnit.data.byteLength + pes.data.byteLength);
|
||||
tmp.set(lastUnit.data, 0);
|
||||
tmp.set(pes.data, lastUnit.data.byteLength);
|
||||
lastUnit.data = tmp;
|
||||
lastavcSample.units.length += pes.data.byteLength;
|
||||
track.len += pes.data.byteLength;
|
||||
}
|
||||
//free pes.data to save up some memory
|
||||
pes.data = null;
|
||||
var debugString = '';
|
||||
units.forEach(unit => {
|
||||
switch(unit.type) {
|
||||
//NDR
|
||||
case 1:
|
||||
push = true;
|
||||
if(debug) {
|
||||
debugString += 'NDR ';
|
||||
}
|
||||
break;
|
||||
//IDR
|
||||
case 5:
|
||||
push = true;
|
||||
if(debug) {
|
||||
debugString += 'IDR ';
|
||||
}
|
||||
key = true;
|
||||
break;
|
||||
case 6:
|
||||
push = true;
|
||||
if(debug) {
|
||||
debugString += 'SEI ';
|
||||
}
|
||||
break;
|
||||
//SPS
|
||||
case 7:
|
||||
push = true;
|
||||
if(debug) {
|
||||
debugString += 'SPS ';
|
||||
}
|
||||
if(!track.sps) {
|
||||
var expGolombDecoder = new ExpGolomb(unit.data);
|
||||
var config = expGolombDecoder.readSPS();
|
||||
track.width = config.width;
|
||||
track.height = config.height;
|
||||
track.sps = [unit.data];
|
||||
track.timescale = this.remuxer.timescale;
|
||||
track.duration = this.remuxer.timescale * this._duration;
|
||||
var codecarray = unit.data.subarray(1, 4);
|
||||
var codecstring = 'avc1.';
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var h = codecarray[i].toString(16);
|
||||
if (h.length < 2) {
|
||||
h = '0' + h;
|
||||
}
|
||||
codecstring += h;
|
||||
}
|
||||
track.codec = codecstring;
|
||||
}
|
||||
break;
|
||||
//PPS
|
||||
case 8:
|
||||
push = true;
|
||||
if(debug) {
|
||||
debugString += 'PPS ';
|
||||
}
|
||||
if (!track.pps) {
|
||||
track.pps = [unit.data];
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
push = true;
|
||||
if(debug) {
|
||||
debugString += 'AUD ';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
push = false;
|
||||
debugString += 'unknown NAL ' + unit.type + ' ';
|
||||
break;
|
||||
}
|
||||
if(push) {
|
||||
units2.push(unit);
|
||||
length+=unit.data.byteLength;
|
||||
}
|
||||
});
|
||||
if(debug || debugString.length) {
|
||||
logger.log(debugString);
|
||||
}
|
||||
//build sample from PES
|
||||
// Annex B to MP4 conversion to be done
|
||||
if (units2.length) {
|
||||
// only push AVC sample if keyframe already found. browsers expect a keyframe at first to start decoding
|
||||
if (key === true || track.sps ) {
|
||||
avcSample = {units: { units : units2, length : length}, pts: pes.pts, dts: pes.dts, key: key};
|
||||
samples.push(avcSample);
|
||||
track.len += length;
|
||||
track.nbNalu += units2.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_parseAVCNALu(array) {
|
||||
var i = 0, len = array.byteLength, value, overflow, state = 0;
|
||||
var units = [], unit, unitType, lastUnitStart, lastUnitType;
|
||||
//logger.log('PES:' + Hex.hexDump(array));
|
||||
while (i < len) {
|
||||
value = array[i++];
|
||||
// finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (value === 0) {
|
||||
state = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if( value === 0) {
|
||||
state = 2;
|
||||
} else {
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
if( value === 0) {
|
||||
state = 3;
|
||||
} else if (value === 1) {
|
||||
unitType = array[i] & 0x1f;
|
||||
//logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
||||
if (lastUnitStart) {
|
||||
unit = {data: array.subarray(lastUnitStart, i - state - 1), type: lastUnitType};
|
||||
//logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
||||
units.push(unit);
|
||||
} else {
|
||||
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
||||
overflow = i - state - 1;
|
||||
if (overflow) {
|
||||
//logger.log('first NALU found with overflow:' + overflow);
|
||||
if (this._avcTrack.samples.length) {
|
||||
var lastavcSample = this._avcTrack.samples[this._avcTrack.samples.length - 1];
|
||||
var lastUnit = lastavcSample.units.units[lastavcSample.units.units.length - 1];
|
||||
var tmp = new Uint8Array(lastUnit.data.byteLength + overflow);
|
||||
tmp.set(lastUnit.data, 0);
|
||||
tmp.set(array.subarray(0, overflow), lastUnit.data.byteLength);
|
||||
lastUnit.data = tmp;
|
||||
lastavcSample.units.length += overflow;
|
||||
this._avcTrack.len += overflow;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastUnitStart = i;
|
||||
lastUnitType = unitType;
|
||||
if (unitType === 1 || unitType === 5) {
|
||||
// OPTI !!! if IDR/NDR unit, consider it is last NALu
|
||||
i = len;
|
||||
}
|
||||
state = 0;
|
||||
} else {
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastUnitStart) {
|
||||
unit = {data: array.subarray(lastUnitStart, len), type: lastUnitType};
|
||||
units.push(unit);
|
||||
//logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
_parseAACPES(pes) {
|
||||
var track = this._aacTrack, aacSample, data = pes.data, config, adtsFrameSize, adtsStartOffset, adtsHeaderLen, stamp, nbSamples, len;
|
||||
if (this.aacOverFlow) {
|
||||
var tmp = new Uint8Array(this.aacOverFlow.byteLength + data.byteLength);
|
||||
tmp.set(this.aacOverFlow, 0);
|
||||
tmp.set(data, this.aacOverFlow.byteLength);
|
||||
data = tmp;
|
||||
}
|
||||
// look for ADTS header (0xFFFx)
|
||||
for (adtsStartOffset = 0, len = data.length; adtsStartOffset < len - 1; adtsStartOffset++) {
|
||||
if ((data[adtsStartOffset] === 0xff) && (data[adtsStartOffset+1] & 0xf0) === 0xf0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if ADTS header does not start straight from the beginning of the PES payload, raise an error
|
||||
if (adtsStartOffset) {
|
||||
var reason, fatal;
|
||||
if (adtsStartOffset < len - 1) {
|
||||
reason = `AAC PES did not start with ADTS header,offset:${adtsStartOffset}`;
|
||||
fatal = false;
|
||||
} else {
|
||||
reason = 'no ADTS header found in AAC PES';
|
||||
fatal = true;
|
||||
}
|
||||
this.observer.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: fatal, reason: reason});
|
||||
if (fatal) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!track.audiosamplerate) {
|
||||
config = this._ADTStoAudioConfig(data, adtsStartOffset, this.audioCodec);
|
||||
track.config = config.config;
|
||||
track.audiosamplerate = config.samplerate;
|
||||
track.channelCount = config.channelCount;
|
||||
track.codec = config.codec;
|
||||
track.timescale = this.remuxer.timescale;
|
||||
track.duration = this.remuxer.timescale * this._duration;
|
||||
logger.log(`parsed codec:${track.codec},rate:${config.samplerate},nb channel:${config.channelCount}`);
|
||||
}
|
||||
nbSamples = 0;
|
||||
while ((adtsStartOffset + 5) < len) {
|
||||
// retrieve frame size
|
||||
adtsFrameSize = ((data[adtsStartOffset + 3] & 0x03) << 11);
|
||||
// byte 4
|
||||
adtsFrameSize |= (data[adtsStartOffset + 4] << 3);
|
||||
// byte 5
|
||||
adtsFrameSize |= ((data[adtsStartOffset + 5] & 0xE0) >>> 5);
|
||||
adtsHeaderLen = (!!(data[adtsStartOffset + 1] & 0x01) ? 7 : 9);
|
||||
adtsFrameSize -= adtsHeaderLen;
|
||||
stamp = Math.round(pes.pts + nbSamples * 1024 * this.PES_TIMESCALE / track.audiosamplerate);
|
||||
//stamp = pes.pts;
|
||||
//console.log('AAC frame, offset/length/pts:' + (adtsStartOffset+7) + '/' + adtsFrameSize + '/' + stamp.toFixed(0));
|
||||
if ((adtsFrameSize > 0) && ((adtsStartOffset + adtsHeaderLen + adtsFrameSize) <= len)) {
|
||||
aacSample = {unit: data.subarray(adtsStartOffset + adtsHeaderLen, adtsStartOffset + adtsHeaderLen + adtsFrameSize), pts: stamp, dts: stamp};
|
||||
this._aacTrack.samples.push(aacSample);
|
||||
this._aacTrack.len += adtsFrameSize;
|
||||
adtsStartOffset += adtsFrameSize + adtsHeaderLen;
|
||||
nbSamples++;
|
||||
// look for ADTS header (0xFFFx)
|
||||
for ( ; adtsStartOffset < (len - 1); adtsStartOffset++) {
|
||||
if ((data[adtsStartOffset] === 0xff) && ((data[adtsStartOffset + 1] & 0xf0) === 0xf0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (adtsStartOffset < len) {
|
||||
this.aacOverFlow = data.subarray(adtsStartOffset, len);
|
||||
} else {
|
||||
this.aacOverFlow = null;
|
||||
}
|
||||
}
|
||||
|
||||
_ADTStoAudioConfig(data, offset, audioCodec) {
|
||||
var adtsObjectType, // :int
|
||||
adtsSampleingIndex, // :int
|
||||
adtsExtensionSampleingIndex, // :int
|
||||
adtsChanelConfig, // :int
|
||||
config,
|
||||
userAgent = navigator.userAgent.toLowerCase(),
|
||||
adtsSampleingRates = [
|
||||
96000, 88200,
|
||||
64000, 48000,
|
||||
44100, 32000,
|
||||
24000, 22050,
|
||||
16000, 12000,
|
||||
11025, 8000,
|
||||
7350];
|
||||
// byte 2
|
||||
adtsObjectType = ((data[offset + 2] & 0xC0) >>> 6) + 1;
|
||||
adtsSampleingIndex = ((data[offset + 2] & 0x3C) >>> 2);
|
||||
if(adtsSampleingIndex > adtsSampleingRates.length-1) {
|
||||
this.observer.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: `invalid ADTS sampling index:${adtsSampleingIndex}`});
|
||||
return;
|
||||
}
|
||||
adtsChanelConfig = ((data[offset + 2] & 0x01) << 2);
|
||||
// byte 3
|
||||
adtsChanelConfig |= ((data[offset + 3] & 0xC0) >>> 6);
|
||||
logger.log(`manifest codec:${audioCodec},ADTS data:type:${adtsObjectType},sampleingIndex:${adtsSampleingIndex}[${adtsSampleingRates[adtsSampleingIndex]}Hz],channelConfig:${adtsChanelConfig}`);
|
||||
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
||||
if (userAgent.indexOf('firefox') !== -1) {
|
||||
if (adtsSampleingIndex >= 6) {
|
||||
adtsObjectType = 5;
|
||||
config = new Array(4);
|
||||
// HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
|
||||
// there is a factor 2 between frame sample rate and output sample rate
|
||||
// multiply frequency by 2 (see table below, equivalent to substract 3)
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex - 3;
|
||||
} else {
|
||||
adtsObjectType = 2;
|
||||
config = new Array(2);
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex;
|
||||
}
|
||||
// Android : always use AAC
|
||||
} else if (userAgent.indexOf('android') !== -1) {
|
||||
adtsObjectType = 2;
|
||||
config = new Array(2);
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex;
|
||||
} else {
|
||||
/* for other browsers (chrome ...)
|
||||
always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...)
|
||||
*/
|
||||
adtsObjectType = 5;
|
||||
config = new Array(4);
|
||||
// if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz)
|
||||
if ((audioCodec && ((audioCodec.indexOf('mp4a.40.29') !== -1) ||
|
||||
(audioCodec.indexOf('mp4a.40.5') !== -1))) ||
|
||||
(!audioCodec && adtsSampleingIndex >= 6)) {
|
||||
// HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
|
||||
// there is a factor 2 between frame sample rate and output sample rate
|
||||
// multiply frequency by 2 (see table below, equivalent to substract 3)
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex - 3;
|
||||
} else {
|
||||
// if (manifest codec is AAC) AND (frequency less than 24kHz OR nb channel is 1) OR (manifest codec not specified and mono audio)
|
||||
// Chrome fails to play back with AAC LC mono when initialized with HE-AAC. This is not a problem with stereo.
|
||||
if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSampleingIndex >= 6 || adtsChanelConfig === 1) ||
|
||||
(!audioCodec && adtsChanelConfig === 1)) {
|
||||
adtsObjectType = 2;
|
||||
config = new Array(2);
|
||||
}
|
||||
adtsExtensionSampleingIndex = adtsSampleingIndex;
|
||||
}
|
||||
}
|
||||
/* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
|
||||
ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig()
|
||||
Audio Profile / Audio Object Type
|
||||
0: Null
|
||||
1: AAC Main
|
||||
2: AAC LC (Low Complexity)
|
||||
3: AAC SSR (Scalable Sample Rate)
|
||||
4: AAC LTP (Long Term Prediction)
|
||||
5: SBR (Spectral Band Replication)
|
||||
6: AAC Scalable
|
||||
sampling freq
|
||||
0: 96000 Hz
|
||||
1: 88200 Hz
|
||||
2: 64000 Hz
|
||||
3: 48000 Hz
|
||||
4: 44100 Hz
|
||||
5: 32000 Hz
|
||||
6: 24000 Hz
|
||||
7: 22050 Hz
|
||||
8: 16000 Hz
|
||||
9: 12000 Hz
|
||||
10: 11025 Hz
|
||||
11: 8000 Hz
|
||||
12: 7350 Hz
|
||||
13: Reserved
|
||||
14: Reserved
|
||||
15: frequency is written explictly
|
||||
Channel Configurations
|
||||
These are the channel configurations:
|
||||
0: Defined in AOT Specifc Config
|
||||
1: 1 channel: front-center
|
||||
2: 2 channels: front-left, front-right
|
||||
*/
|
||||
// audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
|
||||
config[0] = adtsObjectType << 3;
|
||||
// samplingFrequencyIndex
|
||||
config[0] |= (adtsSampleingIndex & 0x0E) >> 1;
|
||||
config[1] |= (adtsSampleingIndex & 0x01) << 7;
|
||||
// channelConfiguration
|
||||
config[1] |= adtsChanelConfig << 3;
|
||||
if (adtsObjectType === 5) {
|
||||
// adtsExtensionSampleingIndex
|
||||
config[1] |= (adtsExtensionSampleingIndex & 0x0E) >> 1;
|
||||
config[2] = (adtsExtensionSampleingIndex & 0x01) << 7;
|
||||
// adtsObjectType (force to 2, chrome is checking that object type is less than 5 ???
|
||||
// https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc
|
||||
config[2] |= 2 << 2;
|
||||
config[3] = 0;
|
||||
}
|
||||
return {config: config, samplerate: adtsSampleingRates[adtsSampleingIndex], channelCount: adtsChanelConfig, codec: ('mp4a.40.' + adtsObjectType)};
|
||||
}
|
||||
|
||||
_parseID3PES(pes) {
|
||||
this._id3Track.samples.push(pes);
|
||||
}
|
||||
}
|
||||
|
||||
export default TSDemuxer;
|
||||
|
41
dashboard-ui/bower_components/hls.js/src/errors.js
vendored
Normal file
41
dashboard-ui/bower_components/hls.js/src/errors.js
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
export const ErrorTypes = {
|
||||
// Identifier for a network error (loading error / timeout ...)
|
||||
NETWORK_ERROR: 'hlsNetworkError',
|
||||
// Identifier for a media Error (video/parsing/mediasource error)
|
||||
MEDIA_ERROR: 'hlsMediaError',
|
||||
// Identifier for all other errors
|
||||
OTHER_ERROR: 'hlsOtherError'
|
||||
};
|
||||
|
||||
export const ErrorDetails = {
|
||||
// Identifier for a manifest load error - data: { url : faulty URL, response : XHR response}
|
||||
MANIFEST_LOAD_ERROR: 'manifestLoadError',
|
||||
// Identifier for a manifest load timeout - data: { url : faulty URL, response : XHR response}
|
||||
MANIFEST_LOAD_TIMEOUT: 'manifestLoadTimeOut',
|
||||
// Identifier for a manifest parsing error - data: { url : faulty URL, reason : error reason}
|
||||
MANIFEST_PARSING_ERROR: 'manifestParsingError',
|
||||
// Identifier for playlist load error - data: { url : faulty URL, response : XHR response}
|
||||
LEVEL_LOAD_ERROR: 'levelLoadError',
|
||||
// Identifier for playlist load timeout - data: { url : faulty URL, response : XHR response}
|
||||
LEVEL_LOAD_TIMEOUT: 'levelLoadTimeOut',
|
||||
// Identifier for a level switch error - data: { level : faulty level Id, event : error description}
|
||||
LEVEL_SWITCH_ERROR: 'levelSwitchError',
|
||||
// Identifier for fragment load error - data: { frag : fragment object, response : XHR response}
|
||||
FRAG_LOAD_ERROR: 'fragLoadError',
|
||||
// Identifier for fragment loop loading error - data: { frag : fragment object}
|
||||
FRAG_LOOP_LOADING_ERROR: 'fragLoopLoadingError',
|
||||
// Identifier for fragment load timeout error - data: { frag : fragment object}
|
||||
FRAG_LOAD_TIMEOUT: 'fragLoadTimeOut',
|
||||
// Identifier for a fragment decryption error event - data: parsing error description
|
||||
FRAG_DECRYPT_ERROR: 'fragDecryptError',
|
||||
// Identifier for a fragment parsing error event - data: parsing error description
|
||||
FRAG_PARSING_ERROR: 'fragParsingError',
|
||||
// Identifier for decrypt key load error - data: { frag : fragment object, response : XHR response}
|
||||
KEY_LOAD_ERROR: 'keyLoadError',
|
||||
// Identifier for decrypt key load timeout error - data: { frag : fragment object}
|
||||
KEY_LOAD_TIMEOUT: 'keyLoadTimeOut',
|
||||
// Identifier for a buffer append error - data: append error description
|
||||
BUFFER_APPEND_ERROR: 'bufferAppendError',
|
||||
// Identifier for a buffer appending error event - data: appending error description
|
||||
BUFFER_APPENDING_ERROR: 'bufferAppendingError'
|
||||
};
|
56
dashboard-ui/bower_components/hls.js/src/events.js
vendored
Normal file
56
dashboard-ui/bower_components/hls.js/src/events.js
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
export default {
|
||||
// fired before MediaSource is attaching to media element - data: { media }
|
||||
MEDIA_ATTACHING: 'hlsMediaAttaching',
|
||||
// fired when MediaSource has been succesfully attached to media element - data: { }
|
||||
MEDIA_ATTACHED: 'hlsMediaAttached',
|
||||
// fired before detaching MediaSource from media element - data: { }
|
||||
MEDIA_DETACHING: 'hlsMediaDetaching',
|
||||
// fired when MediaSource has been detached from media element - data: { }
|
||||
MEDIA_DETACHED: 'hlsMediaDetached',
|
||||
// fired to signal that a manifest loading starts - data: { url : manifestURL}
|
||||
MANIFEST_LOADING: 'hlsManifestLoading',
|
||||
// fired after manifest has been loaded - data: { levels : [available quality levels] , url : manifestURL, stats : { trequest, tfirst, tload, mtime}}
|
||||
MANIFEST_LOADED: 'hlsManifestLoaded',
|
||||
// fired after manifest has been parsed - data: { levels : [available quality levels] , firstLevel : index of first quality level appearing in Manifest}
|
||||
MANIFEST_PARSED: 'hlsManifestParsed',
|
||||
// fired when a level playlist loading starts - data: { url : level URL level : id of level being loaded}
|
||||
LEVEL_LOADING: 'hlsLevelLoading',
|
||||
// fired when a level playlist loading finishes - data: { details : levelDetails object, level : id of loaded level, stats : { trequest, tfirst, tload, mtime} }
|
||||
LEVEL_LOADED: 'hlsLevelLoaded',
|
||||
// fired when a level's details have been updated based on previous details, after it has been loaded. - data: { details : levelDetails object, level : id of updated level }
|
||||
LEVEL_UPDATED: 'hlsLevelUpdated',
|
||||
// fired when a level's PTS information has been updated after parsing a fragment - data: { details : levelDetails object, level : id of updated level, drift: PTS drift observed when parsing last fragment }
|
||||
LEVEL_PTS_UPDATED: 'hlsPTSUpdated',
|
||||
// fired when a level switch is requested - data: { level : id of new level }
|
||||
LEVEL_SWITCH: 'hlsLevelSwitch',
|
||||
// fired when a fragment loading starts - data: { frag : fragment object}
|
||||
FRAG_LOADING: 'hlsFragLoading',
|
||||
// fired when a fragment loading is progressing - data: { frag : fragment object, { trequest, tfirst, loaded}}
|
||||
FRAG_LOAD_PROGRESS: 'hlsFragLoadProgress',
|
||||
// Identifier for fragment load aborting for emergency switch down - data: {frag : fragment object}
|
||||
FRAG_LOAD_EMERGENCY_ABORTED: 'hlsFragLoadEmergencyAborted',
|
||||
// fired when a fragment loading is completed - data: { frag : fragment object, payload : fragment payload, stats : { trequest, tfirst, tload, length}}
|
||||
FRAG_LOADED: 'hlsFragLoaded',
|
||||
// fired when Init Segment has been extracted from fragment - data: { moov : moov MP4 box, codecs : codecs found while parsing fragment}
|
||||
FRAG_PARSING_INIT_SEGMENT: 'hlsFragParsingInitSegment',
|
||||
// fired when parsing id3 is completed - data: { samples : [ id3 samples pes ] }
|
||||
FRAG_PARSING_METADATA: 'hlsFraParsingMetadata',
|
||||
// fired when moof/mdat have been extracted from fragment - data: { moof : moof MP4 box, mdat : mdat MP4 box}
|
||||
FRAG_PARSING_DATA: 'hlsFragParsingData',
|
||||
// fired when fragment parsing is completed - data: undefined
|
||||
FRAG_PARSED: 'hlsFragParsed',
|
||||
// fired when fragment remuxed MP4 boxes have all been appended into SourceBuffer - data: { frag : fragment object, stats : { trequest, tfirst, tload, tparsed, tbuffered, length} }
|
||||
FRAG_BUFFERED: 'hlsFragBuffered',
|
||||
// fired when fragment matching with current media position is changing - data : { frag : fragment object }
|
||||
FRAG_CHANGED: 'hlsFragChanged',
|
||||
// Identifier for a FPS drop event - data: {curentDropped, currentDecoded, totalDroppedFrames}
|
||||
FPS_DROP: 'hlsFPSDrop',
|
||||
// Identifier for an error event - data: { type : error type, details : error details, fatal : if true, hls.js cannot/will not try to recover, if false, hls.js will try to recover,other error specific data}
|
||||
ERROR: 'hlsError',
|
||||
// fired when hls.js instance starts destroying. Different from MEDIA_DETACHED as one could want to detach and reattach a media to the instance of hls.js to handle mid-rolls for example
|
||||
DESTROYING: 'hlsDestroying',
|
||||
// fired when a decrypt key loading starts - data: { frag : fragment object}
|
||||
KEY_LOADING: 'hlsKeyLoading',
|
||||
// fired when a decrypt key loading is completed - data: { frag : fragment object, payload : key payload, stats : { trequest, tfirst, tload, length}}
|
||||
KEY_LOADED: 'hlsKeyLoaded',
|
||||
};
|
121
dashboard-ui/bower_components/hls.js/src/helper/level-helper.js
vendored
Normal file
121
dashboard-ui/bower_components/hls.js/src/helper/level-helper.js
vendored
Normal file
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* Level Helper class, providing methods dealing with playlist sliding and drift
|
||||
*/
|
||||
|
||||
import {logger} from '../utils/logger';
|
||||
|
||||
class LevelHelper {
|
||||
|
||||
static mergeDetails(oldDetails,newDetails) {
|
||||
var start = Math.max(oldDetails.startSN,newDetails.startSN)-newDetails.startSN,
|
||||
end = Math.min(oldDetails.endSN,newDetails.endSN)-newDetails.startSN,
|
||||
delta = newDetails.startSN - oldDetails.startSN,
|
||||
oldfragments = oldDetails.fragments,
|
||||
newfragments = newDetails.fragments,
|
||||
ccOffset =0,
|
||||
PTSFrag;
|
||||
|
||||
// check if old/new playlists have fragments in common
|
||||
if ( end < start) {
|
||||
newDetails.PTSKnown = false;
|
||||
return;
|
||||
}
|
||||
// loop through overlapping SN and update startPTS , cc, and duration if any found
|
||||
for(var i = start ; i <= end ; i++) {
|
||||
var oldFrag = oldfragments[delta+i],
|
||||
newFrag = newfragments[i];
|
||||
ccOffset = oldFrag.cc - newFrag.cc;
|
||||
if (!isNaN(oldFrag.startPTS)) {
|
||||
newFrag.start = newFrag.startPTS = oldFrag.startPTS;
|
||||
newFrag.endPTS = oldFrag.endPTS;
|
||||
newFrag.duration = oldFrag.duration;
|
||||
PTSFrag = newFrag;
|
||||
}
|
||||
}
|
||||
|
||||
if(ccOffset) {
|
||||
logger.log(`discontinuity sliding from playlist, take drift into account`);
|
||||
for(i = 0 ; i < newfragments.length ; i++) {
|
||||
newfragments[i].cc += ccOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// if at least one fragment contains PTS info, recompute PTS information for all fragments
|
||||
if(PTSFrag) {
|
||||
LevelHelper.updateFragPTS(newDetails,PTSFrag.sn,PTSFrag.startPTS,PTSFrag.endPTS);
|
||||
} else {
|
||||
// adjust start by sliding offset
|
||||
var sliding = oldfragments[delta].start;
|
||||
for(i = 0 ; i < newfragments.length ; i++) {
|
||||
newfragments[i].start += sliding;
|
||||
}
|
||||
}
|
||||
// if we are here, it means we have fragments overlapping between
|
||||
// old and new level. reliable PTS info is thus relying on old level
|
||||
newDetails.PTSKnown = oldDetails.PTSKnown;
|
||||
return;
|
||||
}
|
||||
|
||||
static updateFragPTS(details,sn,startPTS,endPTS) {
|
||||
var fragIdx, fragments, frag, i;
|
||||
// exit if sn out of range
|
||||
if (sn < details.startSN || sn > details.endSN) {
|
||||
return 0;
|
||||
}
|
||||
fragIdx = sn - details.startSN;
|
||||
fragments = details.fragments;
|
||||
frag = fragments[fragIdx];
|
||||
if(!isNaN(frag.startPTS)) {
|
||||
startPTS = Math.max(startPTS,frag.startPTS);
|
||||
endPTS = Math.min(endPTS, frag.endPTS);
|
||||
}
|
||||
|
||||
var drift = startPTS - frag.start;
|
||||
|
||||
frag.start = frag.startPTS = startPTS;
|
||||
frag.endPTS = endPTS;
|
||||
frag.duration = endPTS - startPTS;
|
||||
// adjust fragment PTS/duration from seqnum-1 to frag 0
|
||||
for(i = fragIdx ; i > 0 ; i--) {
|
||||
LevelHelper.updatePTS(fragments,i,i-1);
|
||||
}
|
||||
|
||||
// adjust fragment PTS/duration from seqnum to last frag
|
||||
for(i = fragIdx ; i < fragments.length - 1 ; i++) {
|
||||
LevelHelper.updatePTS(fragments,i,i+1);
|
||||
}
|
||||
details.PTSKnown = true;
|
||||
//logger.log(` frag start/end:${startPTS.toFixed(3)}/${endPTS.toFixed(3)}`);
|
||||
|
||||
return drift;
|
||||
}
|
||||
|
||||
static updatePTS(fragments,fromIdx, toIdx) {
|
||||
var fragFrom = fragments[fromIdx],fragTo = fragments[toIdx], fragToPTS = fragTo.startPTS;
|
||||
// if we know startPTS[toIdx]
|
||||
if(!isNaN(fragToPTS)) {
|
||||
// update fragment duration.
|
||||
// it helps to fix drifts between playlist reported duration and fragment real duration
|
||||
if (toIdx > fromIdx) {
|
||||
fragFrom.duration = fragToPTS-fragFrom.start;
|
||||
if(fragFrom.duration < 0) {
|
||||
logger.error(`negative duration computed for ${fragFrom}, there should be some duration drift between playlist and fragment!`);
|
||||
}
|
||||
} else {
|
||||
fragTo.duration = fragFrom.start - fragToPTS;
|
||||
if(fragTo.duration < 0) {
|
||||
logger.error(`negative duration computed for ${fragTo}, there should be some duration drift between playlist and fragment!`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we dont know startPTS[toIdx]
|
||||
if (toIdx > fromIdx) {
|
||||
fragTo.start = fragFrom.start + fragFrom.duration;
|
||||
} else {
|
||||
fragTo.start = fragFrom.start - fragTo.duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LevelHelper;
|
248
dashboard-ui/bower_components/hls.js/src/hls.js
vendored
Normal file
248
dashboard-ui/bower_components/hls.js/src/hls.js
vendored
Normal file
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* HLS interface
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import Event from './events';
|
||||
import {ErrorTypes, ErrorDetails} from './errors';
|
||||
import PlaylistLoader from './loader/playlist-loader';
|
||||
import FragmentLoader from './loader/fragment-loader';
|
||||
import AbrController from './controller/abr-controller';
|
||||
import MSEMediaController from './controller/mse-media-controller';
|
||||
import LevelController from './controller/level-controller';
|
||||
//import FPSController from './controller/fps-controller';
|
||||
import {logger, enableLogs} from './utils/logger';
|
||||
import XhrLoader from './utils/xhr-loader';
|
||||
import EventEmitter from 'events';
|
||||
import KeyLoader from './loader/key-loader';
|
||||
|
||||
class Hls {
|
||||
|
||||
static isSupported() {
|
||||
return (window.MediaSource && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"'));
|
||||
}
|
||||
|
||||
static get Events() {
|
||||
return Event;
|
||||
}
|
||||
|
||||
static get ErrorTypes() {
|
||||
return ErrorTypes;
|
||||
}
|
||||
|
||||
static get ErrorDetails() {
|
||||
return ErrorDetails;
|
||||
}
|
||||
|
||||
constructor(config = {}) {
|
||||
var configDefault = {
|
||||
autoStartLoad: true,
|
||||
debug: false,
|
||||
maxBufferLength: 30,
|
||||
maxBufferSize: 60 * 1000 * 1000,
|
||||
liveSyncDurationCount:3,
|
||||
liveMaxLatencyDurationCount: Infinity,
|
||||
maxMaxBufferLength: 600,
|
||||
enableWorker: true,
|
||||
enableSoftwareAES: true,
|
||||
fragLoadingTimeOut: 20000,
|
||||
fragLoadingMaxRetry: 1,
|
||||
fragLoadingRetryDelay: 1000,
|
||||
fragLoadingLoopThreshold: 3,
|
||||
manifestLoadingTimeOut: 10000,
|
||||
manifestLoadingMaxRetry: 1,
|
||||
manifestLoadingRetryDelay: 1000,
|
||||
// fpsDroppedMonitoringPeriod: 5000,
|
||||
// fpsDroppedMonitoringThreshold: 0.2,
|
||||
appendErrorMaxRetry: 200,
|
||||
loader: XhrLoader,
|
||||
fLoader: undefined,
|
||||
pLoader: undefined,
|
||||
abrController : AbrController,
|
||||
mediaController: MSEMediaController
|
||||
};
|
||||
for (var prop in configDefault) {
|
||||
if (prop in config) { continue; }
|
||||
config[prop] = configDefault[prop];
|
||||
}
|
||||
|
||||
if (config.liveMaxLatencyDurationCount !== undefined && config.liveMaxLatencyDurationCount <= config.liveSyncDurationCount) {
|
||||
throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be gt "liveSyncDurationCount"');
|
||||
}
|
||||
|
||||
enableLogs(config.debug);
|
||||
this.config = config;
|
||||
// observer setup
|
||||
var observer = this.observer = new EventEmitter();
|
||||
observer.trigger = function trigger (event, ...data) {
|
||||
observer.emit(event, event, ...data);
|
||||
};
|
||||
|
||||
observer.off = function off (event, ...data) {
|
||||
observer.removeListener(event, ...data);
|
||||
};
|
||||
this.on = observer.on.bind(observer);
|
||||
this.off = observer.off.bind(observer);
|
||||
this.trigger = observer.trigger.bind(observer);
|
||||
this.playlistLoader = new PlaylistLoader(this);
|
||||
this.fragmentLoader = new FragmentLoader(this);
|
||||
this.levelController = new LevelController(this);
|
||||
this.abrController = new config.abrController(this);
|
||||
this.mediaController = new config.mediaController(this);
|
||||
this.keyLoader = new KeyLoader(this);
|
||||
//this.fpsController = new FPSController(this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
logger.log('destroy');
|
||||
this.trigger(Event.DESTROYING);
|
||||
this.detachMedia();
|
||||
this.playlistLoader.destroy();
|
||||
this.fragmentLoader.destroy();
|
||||
this.levelController.destroy();
|
||||
this.mediaController.destroy();
|
||||
this.keyLoader.destroy();
|
||||
//this.fpsController.destroy();
|
||||
this.url = null;
|
||||
this.observer.removeAllListeners();
|
||||
}
|
||||
|
||||
attachMedia(media) {
|
||||
logger.log('attachMedia');
|
||||
this.media = media;
|
||||
this.trigger(Event.MEDIA_ATTACHING, {media: media});
|
||||
}
|
||||
|
||||
detachMedia() {
|
||||
logger.log('detachMedia');
|
||||
this.trigger(Event.MEDIA_DETACHING);
|
||||
this.media = null;
|
||||
}
|
||||
|
||||
loadSource(url) {
|
||||
logger.log(`loadSource:${url}`);
|
||||
this.url = url;
|
||||
// when attaching to a source URL, trigger a playlist load
|
||||
this.trigger(Event.MANIFEST_LOADING, {url: url});
|
||||
}
|
||||
|
||||
startLoad() {
|
||||
logger.log('startLoad');
|
||||
this.mediaController.startLoad();
|
||||
}
|
||||
|
||||
swapAudioCodec() {
|
||||
logger.log('swapAudioCodec');
|
||||
this.mediaController.swapAudioCodec();
|
||||
}
|
||||
|
||||
recoverMediaError() {
|
||||
logger.log('recoverMediaError');
|
||||
var media = this.media;
|
||||
this.detachMedia();
|
||||
this.attachMedia(media);
|
||||
}
|
||||
|
||||
/** Return all quality levels **/
|
||||
get levels() {
|
||||
return this.levelController.levels;
|
||||
}
|
||||
|
||||
/** Return current playback quality level **/
|
||||
get currentLevel() {
|
||||
return this.mediaController.currentLevel;
|
||||
}
|
||||
|
||||
/* set quality level immediately (-1 for automatic level selection) */
|
||||
set currentLevel(newLevel) {
|
||||
logger.log(`set currentLevel:${newLevel}`);
|
||||
this.loadLevel = newLevel;
|
||||
this.mediaController.immediateLevelSwitch();
|
||||
}
|
||||
|
||||
/** Return next playback quality level (quality level of next fragment) **/
|
||||
get nextLevel() {
|
||||
return this.mediaController.nextLevel;
|
||||
}
|
||||
|
||||
/* set quality level for next fragment (-1 for automatic level selection) */
|
||||
set nextLevel(newLevel) {
|
||||
logger.log(`set nextLevel:${newLevel}`);
|
||||
this.levelController.manualLevel = newLevel;
|
||||
this.mediaController.nextLevelSwitch();
|
||||
}
|
||||
|
||||
/** Return the quality level of current/last loaded fragment **/
|
||||
get loadLevel() {
|
||||
return this.levelController.level;
|
||||
}
|
||||
|
||||
/* set quality level for current/next loaded fragment (-1 for automatic level selection) */
|
||||
set loadLevel(newLevel) {
|
||||
logger.log(`set loadLevel:${newLevel}`);
|
||||
this.levelController.manualLevel = newLevel;
|
||||
}
|
||||
|
||||
/** Return the quality level of next loaded fragment **/
|
||||
get nextLoadLevel() {
|
||||
return this.levelController.nextLoadLevel();
|
||||
}
|
||||
|
||||
/** set quality level of next loaded fragment **/
|
||||
set nextLoadLevel(level) {
|
||||
this.levelController.level = level;
|
||||
}
|
||||
|
||||
/** Return first level (index of first level referenced in manifest)
|
||||
**/
|
||||
get firstLevel() {
|
||||
return this.levelController.firstLevel;
|
||||
}
|
||||
|
||||
/** set first level (index of first level referenced in manifest)
|
||||
**/
|
||||
set firstLevel(newLevel) {
|
||||
logger.log(`set firstLevel:${newLevel}`);
|
||||
this.levelController.firstLevel = newLevel;
|
||||
}
|
||||
|
||||
/** Return start level (level of first fragment that will be played back)
|
||||
if not overrided by user, first level appearing in manifest will be used as start level
|
||||
if -1 : automatic start level selection, playback will start from level matching download bandwidth (determined from download of first segment)
|
||||
**/
|
||||
get startLevel() {
|
||||
return this.levelController.startLevel;
|
||||
}
|
||||
|
||||
/** set start level (level of first fragment that will be played back)
|
||||
if not overrided by user, first level appearing in manifest will be used as start level
|
||||
if -1 : automatic start level selection, playback will start from level matching download bandwidth (determined from download of first segment)
|
||||
**/
|
||||
set startLevel(newLevel) {
|
||||
logger.log(`set startLevel:${newLevel}`);
|
||||
this.levelController.startLevel = newLevel;
|
||||
}
|
||||
|
||||
/** Return the capping/max level value that could be used by automatic level selection algorithm **/
|
||||
get autoLevelCapping() {
|
||||
return this.abrController.autoLevelCapping;
|
||||
}
|
||||
|
||||
/** set the capping/max level value that could be used by automatic level selection algorithm **/
|
||||
set autoLevelCapping(newLevel) {
|
||||
logger.log(`set autoLevelCapping:${newLevel}`);
|
||||
this.abrController.autoLevelCapping = newLevel;
|
||||
}
|
||||
|
||||
/* check if we are in automatic level selection mode */
|
||||
get autoLevelEnabled() {
|
||||
return (this.levelController.manualLevel === -1);
|
||||
}
|
||||
|
||||
/* return manual level */
|
||||
get manualLevel() {
|
||||
return this.levelController.manualLevel;
|
||||
}
|
||||
}
|
||||
|
||||
export default Hls;
|
57
dashboard-ui/bower_components/hls.js/src/loader/fragment-loader.js
vendored
Normal file
57
dashboard-ui/bower_components/hls.js/src/loader/fragment-loader.js
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Fragment Loader
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
|
||||
class FragmentLoader {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
this.onfl = this.onFragLoading.bind(this);
|
||||
hls.on(Event.FRAG_LOADING, this.onfl);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.loader) {
|
||||
this.loader.destroy();
|
||||
this.loader = null;
|
||||
}
|
||||
this.hls.off(Event.FRAG_LOADING, this.onfl);
|
||||
}
|
||||
|
||||
onFragLoading(event, data) {
|
||||
var frag = data.frag;
|
||||
this.frag = frag;
|
||||
this.frag.loaded = 0;
|
||||
var config = this.hls.config;
|
||||
frag.loader = this.loader = typeof(config.fLoader) !== 'undefined' ? new config.fLoader(config) : new config.loader(config);
|
||||
this.loader.load(frag.url, 'arraybuffer', this.loadsuccess.bind(this), this.loaderror.bind(this), this.loadtimeout.bind(this), config.fragLoadingTimeOut, config.fragLoadingMaxRetry, config.fragLoadingRetryDelay, this.loadprogress.bind(this), frag);
|
||||
}
|
||||
|
||||
loadsuccess(event, stats) {
|
||||
var payload = event.currentTarget.response;
|
||||
stats.length = payload.byteLength;
|
||||
// detach fragment loader on load success
|
||||
this.frag.loader = undefined;
|
||||
this.hls.trigger(Event.FRAG_LOADED, {payload: payload, frag: this.frag, stats: stats});
|
||||
}
|
||||
|
||||
loaderror(event) {
|
||||
this.loader.abort();
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag: this.frag, response: event});
|
||||
}
|
||||
|
||||
loadtimeout() {
|
||||
this.loader.abort();
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag: this.frag});
|
||||
}
|
||||
|
||||
loadprogress(event, stats) {
|
||||
this.frag.loaded = stats.loaded;
|
||||
this.hls.trigger(Event.FRAG_LOAD_PROGRESS, {frag: this.frag, stats: stats});
|
||||
}
|
||||
}
|
||||
|
||||
export default FragmentLoader;
|
67
dashboard-ui/bower_components/hls.js/src/loader/key-loader.js
vendored
Normal file
67
dashboard-ui/bower_components/hls.js/src/loader/key-loader.js
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Decrypt key Loader
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
|
||||
class KeyLoader {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
this.decryptkey = null;
|
||||
this.decrypturl = null;
|
||||
this.ondkl = this.onDecryptKeyLoading.bind(this);
|
||||
hls.on(Event.KEY_LOADING, this.ondkl);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.loader) {
|
||||
this.loader.destroy();
|
||||
this.loader = null;
|
||||
}
|
||||
this.hls.off(Event.KEY_LOADING, this.ondkl);
|
||||
}
|
||||
|
||||
onDecryptKeyLoading(event, data) {
|
||||
var frag = this.frag = data.frag,
|
||||
decryptdata = frag.decryptdata,
|
||||
uri = decryptdata.uri;
|
||||
// if uri is different from previous one or if decrypt key not retrieved yet
|
||||
if (uri !== this.decrypturl || this.decryptkey === null) {
|
||||
var config = this.hls.config;
|
||||
frag.loader = this.loader = new config.loader(config);
|
||||
this.decrypturl = uri;
|
||||
this.decryptkey = null;
|
||||
frag.loader.load(uri, 'arraybuffer', this.loadsuccess.bind(this), this.loaderror.bind(this), this.loadtimeout.bind(this), config.fragLoadingTimeOut, config.fragLoadingMaxRetry, config.fragLoadingRetryDelay, this.loadprogress.bind(this), frag);
|
||||
} else if (this.decryptkey) {
|
||||
// we already loaded this key, return it
|
||||
decryptdata.key = this.decryptkey;
|
||||
this.hls.trigger(Event.KEY_LOADED, {frag: frag});
|
||||
}
|
||||
}
|
||||
|
||||
loadsuccess(event) {
|
||||
var frag = this.frag;
|
||||
this.decryptkey = frag.decryptdata.key = new Uint8Array(event.currentTarget.response);
|
||||
// detach fragment loader on load success
|
||||
frag.loader = undefined;
|
||||
this.hls.trigger(Event.KEY_LOADED, {frag: frag});
|
||||
}
|
||||
|
||||
loaderror(event) {
|
||||
this.loader.abort();
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.KEY_LOAD_ERROR, fatal: false, frag: this.frag, response: event});
|
||||
}
|
||||
|
||||
loadtimeout() {
|
||||
this.loader.abort();
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.KEY_LOAD_TIMEOUT, fatal: false, frag: this.frag});
|
||||
}
|
||||
|
||||
loadprogress() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default KeyLoader;
|
276
dashboard-ui/bower_components/hls.js/src/loader/playlist-loader.js
vendored
Normal file
276
dashboard-ui/bower_components/hls.js/src/loader/playlist-loader.js
vendored
Normal file
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* Playlist Loader
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
import URLHelper from '../utils/url';
|
||||
//import {logger} from '../utils/logger';
|
||||
|
||||
class PlaylistLoader {
|
||||
|
||||
constructor(hls) {
|
||||
this.hls = hls;
|
||||
this.onml = this.onManifestLoading.bind(this);
|
||||
this.onll = this.onLevelLoading.bind(this);
|
||||
hls.on(Event.MANIFEST_LOADING, this.onml);
|
||||
hls.on(Event.LEVEL_LOADING, this.onll);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.loader) {
|
||||
this.loader.destroy();
|
||||
this.loader = null;
|
||||
}
|
||||
this.url = this.id = null;
|
||||
this.hls.off(Event.MANIFEST_LOADING, this.onml);
|
||||
this.hls.off(Event.LEVEL_LOADING, this.onll);
|
||||
}
|
||||
|
||||
onManifestLoading(event, data) {
|
||||
this.load(data.url, null);
|
||||
}
|
||||
|
||||
onLevelLoading(event, data) {
|
||||
this.load(data.url, data.level, data.id);
|
||||
}
|
||||
|
||||
load(url, id1, id2) {
|
||||
var config = this.hls.config;
|
||||
this.url = url;
|
||||
this.id = id1;
|
||||
this.id2 = id2;
|
||||
this.loader = typeof(config.pLoader) !== 'undefined' ? new config.pLoader(config) : new config.loader(config);
|
||||
this.loader.load(url, '', this.loadsuccess.bind(this), this.loaderror.bind(this), this.loadtimeout.bind(this), config.manifestLoadingTimeOut, config.manifestLoadingMaxRetry, config.manifestLoadingRetryDelay);
|
||||
}
|
||||
|
||||
resolve(url, baseUrl) {
|
||||
return URLHelper.buildAbsoluteURL(baseUrl, url);
|
||||
}
|
||||
|
||||
parseMasterPlaylist(string, baseurl) {
|
||||
var levels = [], level = {}, result, codecs, codec;
|
||||
// https://regex101.com is your friend
|
||||
var re = /#EXT-X-STREAM-INF:([^\n\r]*(BAND)WIDTH=(\d+))?([^\n\r]*(CODECS)=\"([^\"\n\r]*)\",?)?([^\n\r]*(RES)OLUTION=(\d+)x(\d+))?([^\n\r]*(NAME)=\"(.*)\")?[^\n\r]*[\r\n]+([^\r\n]+)/g;
|
||||
while ((result = re.exec(string)) != null){
|
||||
result.shift();
|
||||
result = result.filter(function(n) { return (n !== undefined); });
|
||||
level.url = this.resolve(result.pop(), baseurl);
|
||||
while (result.length > 0) {
|
||||
switch (result.shift()) {
|
||||
case 'RES':
|
||||
level.width = parseInt(result.shift());
|
||||
level.height = parseInt(result.shift());
|
||||
break;
|
||||
case 'BAND':
|
||||
level.bitrate = parseInt(result.shift());
|
||||
break;
|
||||
case 'NAME':
|
||||
level.name = result.shift();
|
||||
break;
|
||||
case 'CODECS':
|
||||
codecs = result.shift().split(',');
|
||||
while (codecs.length > 0) {
|
||||
codec = codecs.shift();
|
||||
if (codec.indexOf('avc1') !== -1) {
|
||||
level.videoCodec = this.avc1toavcoti(codec);
|
||||
} else {
|
||||
level.audioCodec = codec;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
levels.push(level);
|
||||
level = {};
|
||||
}
|
||||
return levels;
|
||||
}
|
||||
|
||||
avc1toavcoti(codec) {
|
||||
var result, avcdata = codec.split('.');
|
||||
if (avcdata.length > 2) {
|
||||
result = avcdata.shift() + '.';
|
||||
result += parseInt(avcdata.shift()).toString(16);
|
||||
result += ('00' + parseInt(avcdata.shift()).toString(16)).substr(-4);
|
||||
} else {
|
||||
result = codec;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseKeyParamsByRegex(string, regexp) {
|
||||
var result = regexp.exec(string);
|
||||
if (result) {
|
||||
result.shift();
|
||||
result = result.filter(function(n) { return (n !== undefined); });
|
||||
if (result.length === 2) {
|
||||
return result[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
cloneObj(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
parseLevelPlaylist(string, baseurl, id) {
|
||||
var currentSN = 0, totalduration = 0, level = {url: baseurl, fragments: [], live: true, startSN: 0}, result, regexp, cc = 0, frag, byteRangeEndOffset, byteRangeStartOffset;
|
||||
var levelkey = {method : null, key : null, iv : null, uri : null};
|
||||
regexp = /(?:#EXT-X-(MEDIA-SEQUENCE):(\d+))|(?:#EXT-X-(TARGETDURATION):(\d+))|(?:#EXT-X-(KEY):(.*))|(?:#EXT(INF):([\d\.]+)[^\r\n]*([\r\n]+[^#|\r\n]+)?)|(?:#EXT-X-(BYTERANGE):([\d]+[@[\d]*)]*[\r\n]+([^#|\r\n]+)?|(?:#EXT-X-(ENDLIST))|(?:#EXT-X-(DIS)CONTINUITY))/g;
|
||||
while ((result = regexp.exec(string)) !== null) {
|
||||
result.shift();
|
||||
result = result.filter(function(n) { return (n !== undefined); });
|
||||
switch (result[0]) {
|
||||
case 'MEDIA-SEQUENCE':
|
||||
currentSN = level.startSN = parseInt(result[1]);
|
||||
break;
|
||||
case 'TARGETDURATION':
|
||||
level.targetduration = parseFloat(result[1]);
|
||||
break;
|
||||
case 'ENDLIST':
|
||||
level.live = false;
|
||||
break;
|
||||
case 'DIS':
|
||||
cc++;
|
||||
break;
|
||||
case 'BYTERANGE':
|
||||
var params = result[1].split('@');
|
||||
if (params.length === 1) {
|
||||
byteRangeStartOffset = byteRangeEndOffset;
|
||||
} else {
|
||||
byteRangeStartOffset = parseInt(params[1]);
|
||||
}
|
||||
byteRangeEndOffset = parseInt(params[0]) + byteRangeStartOffset;
|
||||
frag = level.fragments.length ? level.fragments[level.fragments.length - 1] : null;
|
||||
if (frag && !frag.url) {
|
||||
frag.byteRangeStartOffset = byteRangeStartOffset;
|
||||
frag.byteRangeEndOffset = byteRangeEndOffset;
|
||||
frag.url = this.resolve(result[2], baseurl);
|
||||
}
|
||||
break;
|
||||
case 'INF':
|
||||
var duration = parseFloat(result[1]);
|
||||
if (!isNaN(duration)) {
|
||||
var fragdecryptdata,
|
||||
sn = currentSN++;
|
||||
if (levelkey.method && levelkey.uri && !levelkey.iv) {
|
||||
fragdecryptdata = this.cloneObj(levelkey);
|
||||
var uint8View = new Uint8Array(16);
|
||||
for (var i = 12; i < 16; i++) {
|
||||
uint8View[i] = (sn >> 8*(15-i)) & 0xff;
|
||||
}
|
||||
fragdecryptdata.iv = uint8View;
|
||||
} else {
|
||||
fragdecryptdata = levelkey;
|
||||
}
|
||||
level.fragments.push({url: result[2] ? this.resolve(result[2], baseurl) : null, duration: duration, start: totalduration, sn: sn, level: id, cc: cc, byteRangeStartOffset: byteRangeStartOffset, byteRangeEndOffset: byteRangeEndOffset, decryptdata : fragdecryptdata});
|
||||
totalduration += duration;
|
||||
byteRangeStartOffset = null;
|
||||
}
|
||||
break;
|
||||
case 'KEY':
|
||||
// https://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-3.4.4
|
||||
var decryptparams = result[1];
|
||||
var decryptmethod = this.parseKeyParamsByRegex(decryptparams, /(METHOD)=([^,]*)/),
|
||||
decrypturi = this.parseKeyParamsByRegex(decryptparams, /(URI)=["]([^,]*)["]/),
|
||||
decryptiv = this.parseKeyParamsByRegex(decryptparams, /(IV)=([^,]*)/);
|
||||
if (decryptmethod) {
|
||||
levelkey = { method: null, key: null, iv: null, uri: null };
|
||||
if ((decrypturi) && (decryptmethod === 'AES-128')) {
|
||||
levelkey.method = decryptmethod;
|
||||
// URI to get the key
|
||||
levelkey.uri = this.resolve(decrypturi, baseurl);
|
||||
levelkey.key = null;
|
||||
// Initialization Vector (IV)
|
||||
if (decryptiv) {
|
||||
levelkey.iv = decryptiv;
|
||||
if (levelkey.iv.substring(0, 2) === '0x') {
|
||||
levelkey.iv = levelkey.iv.substring(2);
|
||||
}
|
||||
levelkey.iv = levelkey.iv.match(/.{8}/g);
|
||||
levelkey.iv[0] = parseInt(levelkey.iv[0], 16);
|
||||
levelkey.iv[1] = parseInt(levelkey.iv[1], 16);
|
||||
levelkey.iv[2] = parseInt(levelkey.iv[2], 16);
|
||||
levelkey.iv[3] = parseInt(levelkey.iv[3], 16);
|
||||
levelkey.iv = new Uint32Array(levelkey.iv);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
//logger.log('found ' + level.fragments.length + ' fragments');
|
||||
level.totalduration = totalduration;
|
||||
level.endSN = currentSN - 1;
|
||||
return level;
|
||||
}
|
||||
|
||||
loadsuccess(event, stats) {
|
||||
var string = event.currentTarget.responseText, url = event.currentTarget.responseURL, id = this.id, id2 = this.id2, hls = this.hls, levels;
|
||||
// responseURL not supported on some browsers (it is used to detect URL redirection)
|
||||
if (url === undefined) {
|
||||
// fallback to initial URL
|
||||
url = this.url;
|
||||
}
|
||||
stats.tload = performance.now();
|
||||
stats.mtime = new Date(event.currentTarget.getResponseHeader('Last-Modified'));
|
||||
if (string.indexOf('#EXTM3U') === 0) {
|
||||
if (string.indexOf('#EXTINF:') > 0) {
|
||||
// 1 level playlist
|
||||
// if first request, fire manifest loaded event, level will be reloaded afterwards
|
||||
// (this is to have a uniform logic for 1 level/multilevel playlists)
|
||||
if (this.id === null) {
|
||||
hls.trigger(Event.MANIFEST_LOADED, {levels: [{url: url}], url: url, stats: stats});
|
||||
} else {
|
||||
var levelDetails = this.parseLevelPlaylist(string, url, id);
|
||||
stats.tparsed = performance.now();
|
||||
hls.trigger(Event.LEVEL_LOADED, {details: levelDetails, level: id, id: id2, stats: stats});
|
||||
}
|
||||
} else {
|
||||
levels = this.parseMasterPlaylist(string, url);
|
||||
// multi level playlist, parse level info
|
||||
if (levels.length) {
|
||||
hls.trigger(Event.MANIFEST_LOADED, {levels: levels, url: url, stats: stats});
|
||||
} else {
|
||||
hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.MANIFEST_PARSING_ERROR, fatal: true, url: url, reason: 'no level found in manifest'});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.MANIFEST_PARSING_ERROR, fatal: true, url: url, reason: 'no EXTM3U delimiter'});
|
||||
}
|
||||
}
|
||||
|
||||
loaderror(event) {
|
||||
var details, fatal;
|
||||
if (this.id === null) {
|
||||
details = ErrorDetails.MANIFEST_LOAD_ERROR;
|
||||
fatal = true;
|
||||
} else {
|
||||
details = ErrorDetails.LEVEL_LOAD_ERROR;
|
||||
fatal = false;
|
||||
}
|
||||
this.loader.abort();
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: details, fatal: fatal, url: this.url, loader: this.loader, response: event.currentTarget, level: this.id, id: this.id2});
|
||||
}
|
||||
|
||||
loadtimeout() {
|
||||
var details, fatal;
|
||||
if (this.id === null) {
|
||||
details = ErrorDetails.MANIFEST_LOAD_TIMEOUT;
|
||||
fatal = true;
|
||||
} else {
|
||||
details = ErrorDetails.LEVEL_LOAD_TIMEOUT;
|
||||
fatal = false;
|
||||
}
|
||||
this.loader.abort();
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: details, fatal: fatal, url: this.url, loader: this.loader, level: this.id, id: this.id2});
|
||||
}
|
||||
}
|
||||
|
||||
export default PlaylistLoader;
|
65
dashboard-ui/bower_components/hls.js/src/remux/dummy-remuxer.js
vendored
Normal file
65
dashboard-ui/bower_components/hls.js/src/remux/dummy-remuxer.js
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* dummy remuxer
|
||||
*/
|
||||
|
||||
class DummyRemuxer {
|
||||
constructor(observer) {
|
||||
this.PES_TIMESCALE = 90000;
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
get timescale() {
|
||||
return this.PES_TIMESCALE;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
}
|
||||
|
||||
insertDiscontinuity() {
|
||||
}
|
||||
|
||||
remux(audioTrack,videoTrack,id3Track,timeOffset) {
|
||||
this._remuxAACSamples(audioTrack,timeOffset);
|
||||
this._remuxAVCSamples(videoTrack,timeOffset);
|
||||
this._remuxID3Samples(id3Track,timeOffset);
|
||||
}
|
||||
|
||||
_remuxAVCSamples(track, timeOffset) {
|
||||
var avcSample, unit;
|
||||
// loop through track.samples
|
||||
while (track.samples.length) {
|
||||
avcSample = track.samples.shift();
|
||||
// loop through AVC sample NALUs
|
||||
while (avcSample.units.units.length) {
|
||||
unit = avcSample.units.units.shift();
|
||||
}
|
||||
}
|
||||
//please lint
|
||||
timeOffset = timeOffset;
|
||||
}
|
||||
|
||||
_remuxAACSamples(track,timeOffset) {
|
||||
var aacSample,unit;
|
||||
// loop through track.samples
|
||||
while (track.samples.length) {
|
||||
aacSample = track.samples.shift();
|
||||
unit = aacSample.unit;
|
||||
}
|
||||
//please lint
|
||||
timeOffset = timeOffset;
|
||||
}
|
||||
|
||||
_remuxID3Samples(track,timeOffset) {
|
||||
var id3Sample,unit;
|
||||
// loop through track.samples
|
||||
while (track.samples.length) {
|
||||
id3Sample = track.samples.shift();
|
||||
unit = id3Sample.unit;
|
||||
}
|
||||
//please lint
|
||||
timeOffset = timeOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export default DummyRemuxer;
|
||||
|
578
dashboard-ui/bower_components/hls.js/src/remux/mp4-generator.js
vendored
Normal file
578
dashboard-ui/bower_components/hls.js/src/remux/mp4-generator.js
vendored
Normal file
|
@ -0,0 +1,578 @@
|
|||
/**
|
||||
* Generate MP4 Box
|
||||
*/
|
||||
|
||||
//import Hex from '../utils/hex';
|
||||
class MP4 {
|
||||
static init() {
|
||||
MP4.types = {
|
||||
avc1: [], // codingname
|
||||
avcC: [],
|
||||
btrt: [],
|
||||
dinf: [],
|
||||
dref: [],
|
||||
esds: [],
|
||||
ftyp: [],
|
||||
hdlr: [],
|
||||
mdat: [],
|
||||
mdhd: [],
|
||||
mdia: [],
|
||||
mfhd: [],
|
||||
minf: [],
|
||||
moof: [],
|
||||
moov: [],
|
||||
mp4a: [],
|
||||
mvex: [],
|
||||
mvhd: [],
|
||||
sdtp: [],
|
||||
stbl: [],
|
||||
stco: [],
|
||||
stsc: [],
|
||||
stsd: [],
|
||||
stsz: [],
|
||||
stts: [],
|
||||
tfdt: [],
|
||||
tfhd: [],
|
||||
traf: [],
|
||||
trak: [],
|
||||
trun: [],
|
||||
trex: [],
|
||||
tkhd: [],
|
||||
vmhd: [],
|
||||
smhd: []
|
||||
};
|
||||
|
||||
var i;
|
||||
for (i in MP4.types) {
|
||||
if (MP4.types.hasOwnProperty(i)) {
|
||||
MP4.types[i] = [
|
||||
i.charCodeAt(0),
|
||||
i.charCodeAt(1),
|
||||
i.charCodeAt(2),
|
||||
i.charCodeAt(3)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
MP4.MAJOR_BRAND = new Uint8Array([
|
||||
'i'.charCodeAt(0),
|
||||
's'.charCodeAt(0),
|
||||
'o'.charCodeAt(0),
|
||||
'm'.charCodeAt(0)
|
||||
]);
|
||||
|
||||
MP4.AVC1_BRAND = new Uint8Array([
|
||||
'a'.charCodeAt(0),
|
||||
'v'.charCodeAt(0),
|
||||
'c'.charCodeAt(0),
|
||||
'1'.charCodeAt(0)
|
||||
]);
|
||||
|
||||
MP4.MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
|
||||
|
||||
MP4.VIDEO_HDLR = new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // pre_defined
|
||||
0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x56, 0x69, 0x64, 0x65,
|
||||
0x6f, 0x48, 0x61, 0x6e,
|
||||
0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
|
||||
]);
|
||||
|
||||
MP4.AUDIO_HDLR = new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // pre_defined
|
||||
0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x53, 0x6f, 0x75, 0x6e,
|
||||
0x64, 0x48, 0x61, 0x6e,
|
||||
0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
|
||||
]);
|
||||
|
||||
MP4.HDLR_TYPES = {
|
||||
'video': MP4.VIDEO_HDLR,
|
||||
'audio': MP4.AUDIO_HDLR
|
||||
};
|
||||
|
||||
MP4.DREF = new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x01, // entry_count
|
||||
0x00, 0x00, 0x00, 0x0c, // entry_size
|
||||
0x75, 0x72, 0x6c, 0x20, // 'url' type
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x01 // entry_flags
|
||||
]);
|
||||
MP4.STCO = new Uint8Array([
|
||||
0x00, // version
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00 // entry_count
|
||||
]);
|
||||
MP4.STSC = MP4.STCO;
|
||||
MP4.STTS = MP4.STCO;
|
||||
MP4.STSZ = new Uint8Array([
|
||||
0x00, // version
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // sample_size
|
||||
0x00, 0x00, 0x00, 0x00, // sample_count
|
||||
]);
|
||||
MP4.VMHD = new Uint8Array([
|
||||
0x00, // version
|
||||
0x00, 0x00, 0x01, // flags
|
||||
0x00, 0x00, // graphicsmode
|
||||
0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
0x00, 0x00 // opcolor
|
||||
]);
|
||||
MP4.SMHD = new Uint8Array([
|
||||
0x00, // version
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, // balance
|
||||
0x00, 0x00 // reserved
|
||||
]);
|
||||
|
||||
MP4.STSD = new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x01]);// entry_count
|
||||
|
||||
MP4.FTYP = MP4.box(MP4.types.ftyp, MP4.MAJOR_BRAND, MP4.MINOR_VERSION, MP4.MAJOR_BRAND, MP4.AVC1_BRAND);
|
||||
MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, MP4.DREF));
|
||||
}
|
||||
|
||||
static box(type) {
|
||||
var
|
||||
payload = Array.prototype.slice.call(arguments, 1),
|
||||
size = 0,
|
||||
i = payload.length,
|
||||
result,
|
||||
view;
|
||||
// calculate the total size we need to allocate
|
||||
while (i--) {
|
||||
size += payload[i].byteLength;
|
||||
}
|
||||
result = new Uint8Array(size + 8);
|
||||
view = new DataView(result.buffer);
|
||||
view.setUint32(0, result.byteLength);
|
||||
result.set(type, 4);
|
||||
// copy the payload into the result
|
||||
for (i = 0, size = 8; i < payload.length; i++) {
|
||||
result.set(payload[i], size);
|
||||
size += payload[i].byteLength;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static hdlr(type) {
|
||||
return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]);
|
||||
}
|
||||
|
||||
static mdat(data) {
|
||||
return MP4.box(MP4.types.mdat, data);
|
||||
}
|
||||
|
||||
static mdhd(timescale, duration) {
|
||||
return MP4.box(MP4.types.mdhd, new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x02, // creation_time
|
||||
0x00, 0x00, 0x00, 0x03, // modification_time
|
||||
(timescale >> 24) & 0xFF,
|
||||
(timescale >> 16) & 0xFF,
|
||||
(timescale >> 8) & 0xFF,
|
||||
timescale & 0xFF, // timescale
|
||||
(duration >> 24),
|
||||
(duration >> 16) & 0xFF,
|
||||
(duration >> 8) & 0xFF,
|
||||
duration & 0xFF, // duration
|
||||
0x55, 0xc4, // 'und' language (undetermined)
|
||||
0x00, 0x00
|
||||
]));
|
||||
}
|
||||
|
||||
static mdia(track) {
|
||||
return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track));
|
||||
}
|
||||
|
||||
static mfhd(sequenceNumber) {
|
||||
return MP4.box(MP4.types.mfhd, new Uint8Array([
|
||||
0x00,
|
||||
0x00, 0x00, 0x00, // flags
|
||||
(sequenceNumber >> 24),
|
||||
(sequenceNumber >> 16) & 0xFF,
|
||||
(sequenceNumber >> 8) & 0xFF,
|
||||
sequenceNumber & 0xFF, // sequence_number
|
||||
]));
|
||||
}
|
||||
|
||||
static minf(track) {
|
||||
if (track.type === 'audio') {
|
||||
return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track));
|
||||
} else {
|
||||
return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track));
|
||||
}
|
||||
}
|
||||
|
||||
static moof(sn, baseMediaDecodeTime, track) {
|
||||
return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track,baseMediaDecodeTime));
|
||||
}
|
||||
/**
|
||||
* @param tracks... (optional) {array} the tracks associated with this movie
|
||||
*/
|
||||
static moov(tracks) {
|
||||
var
|
||||
i = tracks.length,
|
||||
boxes = [];
|
||||
|
||||
while (i--) {
|
||||
boxes[i] = MP4.trak(tracks[i]);
|
||||
}
|
||||
|
||||
return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(tracks[0].timescale, tracks[0].duration)].concat(boxes).concat(MP4.mvex(tracks)));
|
||||
}
|
||||
|
||||
static mvex(tracks) {
|
||||
var
|
||||
i = tracks.length,
|
||||
boxes = [];
|
||||
|
||||
while (i--) {
|
||||
boxes[i] = MP4.trex(tracks[i]);
|
||||
}
|
||||
return MP4.box.apply(null, [MP4.types.mvex].concat(boxes));
|
||||
}
|
||||
|
||||
static mvhd(timescale,duration) {
|
||||
var
|
||||
bytes = new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x01, // creation_time
|
||||
0x00, 0x00, 0x00, 0x02, // modification_time
|
||||
(timescale >> 24) & 0xFF,
|
||||
(timescale >> 16) & 0xFF,
|
||||
(timescale >> 8) & 0xFF,
|
||||
timescale & 0xFF, // timescale
|
||||
(duration >> 24) & 0xFF,
|
||||
(duration >> 16) & 0xFF,
|
||||
(duration >> 8) & 0xFF,
|
||||
duration & 0xFF, // duration
|
||||
0x00, 0x01, 0x00, 0x00, // 1.0 rate
|
||||
0x01, 0x00, // 1.0 volume
|
||||
0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // pre_defined
|
||||
0xff, 0xff, 0xff, 0xff // next_track_ID
|
||||
]);
|
||||
return MP4.box(MP4.types.mvhd, bytes);
|
||||
}
|
||||
|
||||
static sdtp(track) {
|
||||
var
|
||||
samples = track.samples || [],
|
||||
bytes = new Uint8Array(4 + samples.length),
|
||||
flags,
|
||||
i;
|
||||
// leave the full box header (4 bytes) all zero
|
||||
// write the sample table
|
||||
for (i = 0; i < samples.length; i++) {
|
||||
flags = samples[i].flags;
|
||||
bytes[i + 4] = (flags.dependsOn << 4) |
|
||||
(flags.isDependedOn << 2) |
|
||||
(flags.hasRedundancy);
|
||||
}
|
||||
|
||||
return MP4.box(MP4.types.sdtp, bytes);
|
||||
}
|
||||
|
||||
static stbl(track) {
|
||||
return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO));
|
||||
}
|
||||
|
||||
static avc1(track) {
|
||||
var sps = [], pps = [], i, data, len;
|
||||
// assemble the SPSs
|
||||
|
||||
for (i = 0; i < track.sps.length; i++) {
|
||||
data = track.sps[i];
|
||||
len = data.byteLength;
|
||||
sps.push((len >>> 8) & 0xFF);
|
||||
sps.push((len & 0xFF));
|
||||
sps = sps.concat(Array.prototype.slice.call(data)); // SPS
|
||||
}
|
||||
|
||||
// assemble the PPSs
|
||||
for (i = 0; i < track.pps.length; i++) {
|
||||
data = track.pps[i];
|
||||
len = data.byteLength;
|
||||
pps.push((len >>> 8) & 0xFF);
|
||||
pps.push((len & 0xFF));
|
||||
pps = pps.concat(Array.prototype.slice.call(data));
|
||||
}
|
||||
|
||||
var avcc = MP4.box(MP4.types.avcC, new Uint8Array([
|
||||
0x01, // version
|
||||
sps[3], // profile
|
||||
sps[4], // profile compat
|
||||
sps[5], // level
|
||||
0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes
|
||||
0xE0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets
|
||||
].concat(sps).concat([
|
||||
track.pps.length // numOfPictureParameterSets
|
||||
]).concat(pps))); // "PPS"
|
||||
//console.log('avcc:' + Hex.hexDump(avcc));
|
||||
return MP4.box(MP4.types.avc1, new Uint8Array([
|
||||
0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x01, // data_reference_index
|
||||
0x00, 0x00, // pre_defined
|
||||
0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // pre_defined
|
||||
(track.width >> 8) & 0xFF,
|
||||
track.width & 0xff, // width
|
||||
(track.height >> 8) & 0xFF,
|
||||
track.height & 0xff, // height
|
||||
0x00, 0x48, 0x00, 0x00, // horizresolution
|
||||
0x00, 0x48, 0x00, 0x00, // vertresolution
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x01, // frame_count
|
||||
0x13,
|
||||
0x76, 0x69, 0x64, 0x65,
|
||||
0x6f, 0x6a, 0x73, 0x2d,
|
||||
0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x69, 0x62, 0x2d,
|
||||
0x68, 0x6c, 0x73, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, // compressorname
|
||||
0x00, 0x18, // depth = 24
|
||||
0x11, 0x11]), // pre_defined = -1
|
||||
avcc,
|
||||
MP4.box(MP4.types.btrt, new Uint8Array([
|
||||
0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
|
||||
0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
|
||||
0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
|
||||
);
|
||||
}
|
||||
|
||||
static esds(track) {
|
||||
return new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
|
||||
0x03, // descriptor_type
|
||||
0x17+track.config.length, // length
|
||||
0x00, 0x01, //es_id
|
||||
0x00, // stream_priority
|
||||
|
||||
0x04, // descriptor_type
|
||||
0x0f+track.config.length, // length
|
||||
0x40, //codec : mpeg4_audio
|
||||
0x15, // stream_type
|
||||
0x00, 0x00, 0x00, // buffer_size
|
||||
0x00, 0x00, 0x00, 0x00, // maxBitrate
|
||||
0x00, 0x00, 0x00, 0x00, // avgBitrate
|
||||
|
||||
0x05 // descriptor_type
|
||||
].concat([track.config.length]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor
|
||||
}
|
||||
|
||||
static mp4a(track) {
|
||||
return MP4.box(MP4.types.mp4a, new Uint8Array([
|
||||
0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x01, // data_reference_index
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, track.channelCount, // channelcount
|
||||
0x00, 0x10, // sampleSize:16bits
|
||||
0x00, 0x00, 0x00, 0x00, // reserved2
|
||||
(track.audiosamplerate >> 8) & 0xFF,
|
||||
track.audiosamplerate & 0xff, //
|
||||
0x00, 0x00]),
|
||||
MP4.box(MP4.types.esds, MP4.esds(track)));
|
||||
}
|
||||
|
||||
static stsd(track) {
|
||||
if (track.type === 'audio') {
|
||||
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
||||
} else {
|
||||
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
||||
}
|
||||
}
|
||||
|
||||
static tkhd(track) {
|
||||
return MP4.box(MP4.types.tkhd, new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x07, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // creation_time
|
||||
0x00, 0x00, 0x00, 0x00, // modification_time
|
||||
(track.id >> 24) & 0xFF,
|
||||
(track.id >> 16) & 0xFF,
|
||||
(track.id >> 8) & 0xFF,
|
||||
track.id & 0xFF, // track_ID
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
(track.duration >> 24),
|
||||
(track.duration >> 16) & 0xFF,
|
||||
(track.duration >> 8) & 0xFF,
|
||||
track.duration & 0xFF, // duration
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, // layer
|
||||
0x00, 0x00, // alternate_group
|
||||
0x00, 0x00, // non-audio track volume
|
||||
0x00, 0x00, // reserved
|
||||
0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
|
||||
(track.width >> 8) & 0xFF,
|
||||
track.width & 0xFF,
|
||||
0x00, 0x00, // width
|
||||
(track.height >> 8) & 0xFF,
|
||||
track.height & 0xFF,
|
||||
0x00, 0x00 // height
|
||||
]));
|
||||
}
|
||||
|
||||
static traf(track,baseMediaDecodeTime) {
|
||||
var sampleDependencyTable = MP4.sdtp(track);
|
||||
return MP4.box(MP4.types.traf,
|
||||
MP4.box(MP4.types.tfhd, new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
(track.id >> 24),
|
||||
(track.id >> 16) & 0XFF,
|
||||
(track.id >> 8) & 0XFF,
|
||||
(track.id & 0xFF) // track_ID
|
||||
])),
|
||||
MP4.box(MP4.types.tfdt, new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
(baseMediaDecodeTime >>24),
|
||||
(baseMediaDecodeTime >> 16) & 0XFF,
|
||||
(baseMediaDecodeTime >> 8) & 0XFF,
|
||||
(baseMediaDecodeTime & 0xFF) // baseMediaDecodeTime
|
||||
])),
|
||||
MP4.trun(track,
|
||||
sampleDependencyTable.length +
|
||||
16 + // tfhd
|
||||
16 + // tfdt
|
||||
8 + // traf header
|
||||
16 + // mfhd
|
||||
8 + // moof header
|
||||
8), // mdat header
|
||||
sampleDependencyTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a track box.
|
||||
* @param track {object} a track definition
|
||||
* @return {Uint8Array} the track box
|
||||
*/
|
||||
static trak(track) {
|
||||
track.duration = track.duration || 0xffffffff;
|
||||
return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));
|
||||
}
|
||||
|
||||
static trex(track) {
|
||||
return MP4.box(MP4.types.trex, new Uint8Array([
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
(track.id >> 24),
|
||||
(track.id >> 16) & 0XFF,
|
||||
(track.id >> 8) & 0XFF,
|
||||
(track.id & 0xFF), // track_ID
|
||||
0x00, 0x00, 0x00, 0x01, // default_sample_description_index
|
||||
0x00, 0x00, 0x00, 0x00, // default_sample_duration
|
||||
0x00, 0x00, 0x00, 0x00, // default_sample_size
|
||||
0x00, 0x01, 0x00, 0x01 // default_sample_flags
|
||||
]));
|
||||
}
|
||||
|
||||
static trun(track, offset) {
|
||||
var samples, sample, i, array;
|
||||
samples = track.samples || [];
|
||||
array = new Uint8Array(12 + (16 * samples.length));
|
||||
offset += 8 + array.byteLength;
|
||||
array.set([
|
||||
0x00, // version 0
|
||||
0x00, 0x0f, 0x01, // flags
|
||||
(samples.length >>> 24) & 0xFF,
|
||||
(samples.length >>> 16) & 0xFF,
|
||||
(samples.length >>> 8) & 0xFF,
|
||||
samples.length & 0xFF, // sample_count
|
||||
(offset >>> 24) & 0xFF,
|
||||
(offset >>> 16) & 0xFF,
|
||||
(offset >>> 8) & 0xFF,
|
||||
offset & 0xFF // data_offset
|
||||
],0);
|
||||
for (i = 0; i < samples.length; i++) {
|
||||
sample = samples[i];
|
||||
array.set([
|
||||
(sample.duration >>> 24) & 0xFF,
|
||||
(sample.duration >>> 16) & 0xFF,
|
||||
(sample.duration >>> 8) & 0xFF,
|
||||
sample.duration & 0xFF, // sample_duration
|
||||
(sample.size >>> 24) & 0xFF,
|
||||
(sample.size >>> 16) & 0xFF,
|
||||
(sample.size >>> 8) & 0xFF,
|
||||
sample.size & 0xFF, // sample_size
|
||||
(sample.flags.isLeading << 2) | sample.flags.dependsOn,
|
||||
(sample.flags.isDependedOn << 6) |
|
||||
(sample.flags.hasRedundancy << 4) |
|
||||
(sample.flags.paddingValue << 1) |
|
||||
sample.flags.isNonSync,
|
||||
sample.flags.degradPrio & 0xF0 << 8,
|
||||
sample.flags.degradPrio & 0x0F, // sample_flags
|
||||
(sample.cts >>> 24) & 0xFF,
|
||||
(sample.cts >>> 16) & 0xFF,
|
||||
(sample.cts >>> 8) & 0xFF,
|
||||
sample.cts & 0xFF // sample_composition_time_offset
|
||||
],12+16*i);
|
||||
}
|
||||
return MP4.box(MP4.types.trun, array);
|
||||
}
|
||||
|
||||
static initSegment(tracks) {
|
||||
if (!MP4.types) {
|
||||
MP4.init();
|
||||
}
|
||||
var movie = MP4.moov(tracks), result;
|
||||
result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength);
|
||||
result.set(MP4.FTYP);
|
||||
result.set(movie, MP4.FTYP.byteLength);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default MP4;
|
379
dashboard-ui/bower_components/hls.js/src/remux/mp4-remuxer.js
vendored
Normal file
379
dashboard-ui/bower_components/hls.js/src/remux/mp4-remuxer.js
vendored
Normal file
|
@ -0,0 +1,379 @@
|
|||
/**
|
||||
* fMP4 remuxer
|
||||
*/
|
||||
|
||||
|
||||
import Event from '../events';
|
||||
import {logger} from '../utils/logger';
|
||||
import MP4 from '../remux/mp4-generator';
|
||||
import {ErrorTypes, ErrorDetails} from '../errors';
|
||||
|
||||
class MP4Remuxer {
|
||||
constructor(observer) {
|
||||
this.observer = observer;
|
||||
this.ISGenerated = false;
|
||||
this.PES2MP4SCALEFACTOR = 4;
|
||||
this.PES_TIMESCALE = 90000;
|
||||
this.MP4_TIMESCALE = this.PES_TIMESCALE / this.PES2MP4SCALEFACTOR;
|
||||
}
|
||||
|
||||
get timescale() {
|
||||
return this.MP4_TIMESCALE;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
}
|
||||
|
||||
insertDiscontinuity() {
|
||||
this._initPTS = this._initDTS = this.nextAacPts = this.nextAvcDts = undefined;
|
||||
}
|
||||
|
||||
switchLevel() {
|
||||
this.ISGenerated = false;
|
||||
}
|
||||
|
||||
remux(audioTrack,videoTrack,id3Track,timeOffset, contiguous) {
|
||||
// generate Init Segment if needed
|
||||
if (!this.ISGenerated) {
|
||||
this.generateIS(audioTrack,videoTrack,timeOffset);
|
||||
}
|
||||
//logger.log('nb AVC samples:' + videoTrack.samples.length);
|
||||
if (videoTrack.samples.length) {
|
||||
this.remuxVideo(videoTrack,timeOffset,contiguous);
|
||||
}
|
||||
//logger.log('nb AAC samples:' + audioTrack.samples.length);
|
||||
if (audioTrack.samples.length) {
|
||||
this.remuxAudio(audioTrack,timeOffset,contiguous);
|
||||
}
|
||||
//logger.log('nb ID3 samples:' + audioTrack.samples.length);
|
||||
if (id3Track.samples.length) {
|
||||
this.remuxID3(id3Track,timeOffset);
|
||||
}
|
||||
//notify end of parsing
|
||||
this.observer.trigger(Event.FRAG_PARSED);
|
||||
}
|
||||
|
||||
generateIS(audioTrack,videoTrack,timeOffset) {
|
||||
var observer = this.observer,
|
||||
audioSamples = audioTrack.samples,
|
||||
videoSamples = videoTrack.samples,
|
||||
nbAudio = audioSamples.length,
|
||||
nbVideo = videoSamples.length,
|
||||
pesTimeScale = this.PES_TIMESCALE;
|
||||
|
||||
if(nbAudio === 0 && nbVideo === 0) {
|
||||
observer.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, reason: 'no audio/video samples found'});
|
||||
} else if (nbVideo === 0) {
|
||||
//audio only
|
||||
if (audioTrack.config) {
|
||||
observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {
|
||||
audioMoov: MP4.initSegment([audioTrack]),
|
||||
audioCodec : audioTrack.codec,
|
||||
audioChannelCount : audioTrack.channelCount
|
||||
});
|
||||
this.ISGenerated = true;
|
||||
}
|
||||
if (this._initPTS === undefined) {
|
||||
// remember first PTS of this demuxing context
|
||||
this._initPTS = audioSamples[0].pts - pesTimeScale * timeOffset;
|
||||
this._initDTS = audioSamples[0].dts - pesTimeScale * timeOffset;
|
||||
}
|
||||
} else
|
||||
if (nbAudio === 0) {
|
||||
//video only
|
||||
if (videoTrack.sps && videoTrack.pps) {
|
||||
observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {
|
||||
videoMoov: MP4.initSegment([videoTrack]),
|
||||
videoCodec: videoTrack.codec,
|
||||
videoWidth: videoTrack.width,
|
||||
videoHeight: videoTrack.height
|
||||
});
|
||||
this.ISGenerated = true;
|
||||
if (this._initPTS === undefined) {
|
||||
// remember first PTS of this demuxing context
|
||||
this._initPTS = videoSamples[0].pts - pesTimeScale * timeOffset;
|
||||
this._initDTS = videoSamples[0].dts - pesTimeScale * timeOffset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//audio and video
|
||||
if (audioTrack.config && videoTrack.sps && videoTrack.pps) {
|
||||
observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {
|
||||
audioMoov: MP4.initSegment([audioTrack]),
|
||||
audioCodec: audioTrack.codec,
|
||||
audioChannelCount: audioTrack.channelCount,
|
||||
videoMoov: MP4.initSegment([videoTrack]),
|
||||
videoCodec: videoTrack.codec,
|
||||
videoWidth: videoTrack.width,
|
||||
videoHeight: videoTrack.height
|
||||
});
|
||||
this.ISGenerated = true;
|
||||
if (this._initPTS === undefined) {
|
||||
// remember first PTS of this demuxing context
|
||||
this._initPTS = Math.min(videoSamples[0].pts, audioSamples[0].pts) - pesTimeScale * timeOffset;
|
||||
this._initDTS = Math.min(videoSamples[0].dts, audioSamples[0].dts) - pesTimeScale * timeOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remuxVideo(track, timeOffset, contiguous) {
|
||||
var view,
|
||||
i = 8,
|
||||
pesTimeScale = this.PES_TIMESCALE,
|
||||
pes2mp4ScaleFactor = this.PES2MP4SCALEFACTOR,
|
||||
avcSample,
|
||||
mp4Sample,
|
||||
mp4SampleLength,
|
||||
unit,
|
||||
mdat, moof,
|
||||
firstPTS, firstDTS, lastDTS,
|
||||
pts, dts, ptsnorm, dtsnorm,
|
||||
samples = [];
|
||||
/* concatenate the video data and construct the mdat in place
|
||||
(need 8 more bytes to fill length and mpdat type) */
|
||||
mdat = new Uint8Array(track.len + (4 * track.nbNalu) + 8);
|
||||
view = new DataView(mdat.buffer);
|
||||
view.setUint32(0, mdat.byteLength);
|
||||
mdat.set(MP4.types.mdat, 4);
|
||||
while (track.samples.length) {
|
||||
avcSample = track.samples.shift();
|
||||
mp4SampleLength = 0;
|
||||
// convert NALU bitstream to MP4 format (prepend NALU with size field)
|
||||
while (avcSample.units.units.length) {
|
||||
unit = avcSample.units.units.shift();
|
||||
view.setUint32(i, unit.data.byteLength);
|
||||
i += 4;
|
||||
mdat.set(unit.data, i);
|
||||
i += unit.data.byteLength;
|
||||
mp4SampleLength += 4 + unit.data.byteLength;
|
||||
}
|
||||
pts = avcSample.pts - this._initDTS;
|
||||
dts = avcSample.dts - this._initDTS;
|
||||
//logger.log('Video/PTS/DTS:' + pts + '/' + dts);
|
||||
// if not first AVC sample of video track, normalize PTS/DTS with previous sample value
|
||||
// and ensure that sample duration is positive
|
||||
if (lastDTS !== undefined) {
|
||||
ptsnorm = this._PTSNormalize(pts, lastDTS);
|
||||
dtsnorm = this._PTSNormalize(dts, lastDTS);
|
||||
mp4Sample.duration = (dtsnorm - lastDTS) / pes2mp4ScaleFactor;
|
||||
if (mp4Sample.duration < 0) {
|
||||
//logger.log('invalid sample duration at PTS/DTS::' + avcSample.pts + '/' + avcSample.dts + ':' + mp4Sample.duration);
|
||||
mp4Sample.duration = 0;
|
||||
}
|
||||
} else {
|
||||
var nextAvcDts = this.nextAvcDts,delta;
|
||||
// first AVC sample of video track, normalize PTS/DTS
|
||||
ptsnorm = this._PTSNormalize(pts, nextAvcDts);
|
||||
dtsnorm = this._PTSNormalize(dts, nextAvcDts);
|
||||
delta = Math.round((dtsnorm - nextAvcDts) / 90);
|
||||
// if fragment are contiguous, or delta less than 600ms, ensure there is no overlap/hole between fragments
|
||||
if (contiguous || Math.abs(delta) < 600) {
|
||||
if (delta) {
|
||||
if (delta > 1) {
|
||||
logger.log(`AVC:${delta} ms hole between fragments detected,filling it`);
|
||||
} else if (delta < -1) {
|
||||
logger.log(`AVC:${(-delta)} ms overlapping between fragments detected`);
|
||||
}
|
||||
// set DTS to next DTS
|
||||
dtsnorm = nextAvcDts;
|
||||
// offset PTS as well, ensure that PTS is smaller or equal than new DTS
|
||||
ptsnorm = Math.max(ptsnorm - delta, dtsnorm);
|
||||
logger.log('Video/PTS/DTS adjusted:' + ptsnorm + '/' + dtsnorm);
|
||||
}
|
||||
}
|
||||
// remember first PTS of our avcSamples, ensure value is positive
|
||||
firstPTS = Math.max(0, ptsnorm);
|
||||
firstDTS = Math.max(0, dtsnorm);
|
||||
}
|
||||
//console.log('PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${avcSample.pts}/${avcSample.dts}/${this._initDTS}/${ptsnorm}/${dtsnorm}/${(avcSample.pts/4294967296).toFixed(3)}');
|
||||
mp4Sample = {
|
||||
size: mp4SampleLength,
|
||||
duration: 0,
|
||||
cts: (ptsnorm - dtsnorm) / pes2mp4ScaleFactor,
|
||||
flags: {
|
||||
isLeading: 0,
|
||||
isDependedOn: 0,
|
||||
hasRedundancy: 0,
|
||||
degradPrio: 0
|
||||
}
|
||||
};
|
||||
if (avcSample.key === true) {
|
||||
// the current sample is a key frame
|
||||
mp4Sample.flags.dependsOn = 2;
|
||||
mp4Sample.flags.isNonSync = 0;
|
||||
} else {
|
||||
mp4Sample.flags.dependsOn = 1;
|
||||
mp4Sample.flags.isNonSync = 1;
|
||||
}
|
||||
samples.push(mp4Sample);
|
||||
lastDTS = dtsnorm;
|
||||
}
|
||||
if (samples.length >= 2) {
|
||||
mp4Sample.duration = samples[samples.length - 2].duration;
|
||||
}
|
||||
// next AVC sample DTS should be equal to last sample DTS + last sample duration
|
||||
this.nextAvcDts = dtsnorm + mp4Sample.duration * pes2mp4ScaleFactor;
|
||||
track.len = 0;
|
||||
track.nbNalu = 0;
|
||||
if(navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
|
||||
// chrome workaround, mark first sample as being a Random Access Point to avoid sourcebuffer append issue
|
||||
// https://code.google.com/p/chromium/issues/detail?id=229412
|
||||
samples[0].flags.dependsOn = 2;
|
||||
samples[0].flags.isNonSync = 0;
|
||||
}
|
||||
track.samples = samples;
|
||||
moof = MP4.moof(track.sequenceNumber++, firstDTS / pes2mp4ScaleFactor, track);
|
||||
track.samples = [];
|
||||
this.observer.trigger(Event.FRAG_PARSING_DATA, {
|
||||
moof: moof,
|
||||
mdat: mdat,
|
||||
startPTS: firstPTS / pesTimeScale,
|
||||
endPTS: (ptsnorm + pes2mp4ScaleFactor * mp4Sample.duration) / pesTimeScale,
|
||||
startDTS: firstDTS / pesTimeScale,
|
||||
endDTS: (dtsnorm + pes2mp4ScaleFactor * mp4Sample.duration) / pesTimeScale,
|
||||
type: 'video',
|
||||
nb: samples.length
|
||||
});
|
||||
}
|
||||
|
||||
remuxAudio(track,timeOffset, contiguous) {
|
||||
var view,
|
||||
i = 8,
|
||||
pesTimeScale = this.PES_TIMESCALE,
|
||||
pes2mp4ScaleFactor = this.PES2MP4SCALEFACTOR,
|
||||
aacSample, mp4Sample,
|
||||
unit,
|
||||
mdat, moof,
|
||||
firstPTS, firstDTS, lastDTS,
|
||||
pts, dts, ptsnorm, dtsnorm,
|
||||
samples = [];
|
||||
/* concatenate the audio data and construct the mdat in place
|
||||
(need 8 more bytes to fill length and mdat type) */
|
||||
mdat = new Uint8Array(track.len + 8);
|
||||
view = new DataView(mdat.buffer);
|
||||
view.setUint32(0, mdat.byteLength);
|
||||
mdat.set(MP4.types.mdat, 4);
|
||||
while (track.samples.length) {
|
||||
aacSample = track.samples.shift();
|
||||
unit = aacSample.unit;
|
||||
mdat.set(unit, i);
|
||||
i += unit.byteLength;
|
||||
pts = aacSample.pts - this._initDTS;
|
||||
dts = aacSample.dts - this._initDTS;
|
||||
//logger.log('Audio/PTS:' + aacSample.pts.toFixed(0));
|
||||
if (lastDTS !== undefined) {
|
||||
ptsnorm = this._PTSNormalize(pts, lastDTS);
|
||||
dtsnorm = this._PTSNormalize(dts, lastDTS);
|
||||
// we use DTS to compute sample duration, but we use PTS to compute initPTS which is used to sync audio and video
|
||||
mp4Sample.duration = (dtsnorm - lastDTS) / pes2mp4ScaleFactor;
|
||||
if (mp4Sample.duration < 0) {
|
||||
logger.log(`invalid AAC sample duration at PTS:${aacSample.pts}:${mp4Sample.duration}`);
|
||||
mp4Sample.duration = 0;
|
||||
}
|
||||
} else {
|
||||
var nextAacPts = this.nextAacPts,delta;
|
||||
ptsnorm = this._PTSNormalize(pts, nextAacPts);
|
||||
dtsnorm = this._PTSNormalize(dts, nextAacPts);
|
||||
delta = Math.round(1000 * (ptsnorm - nextAacPts) / pesTimeScale);
|
||||
// if fragment are contiguous, or delta less than 600ms, ensure there is no overlap/hole between fragments
|
||||
if (contiguous || Math.abs(delta) < 600) {
|
||||
// log delta
|
||||
if (delta) {
|
||||
if (delta > 1) {
|
||||
logger.log(`${delta} ms hole between AAC samples detected,filling it`);
|
||||
// set PTS to next PTS, and ensure PTS is greater or equal than last DTS
|
||||
} else if (delta < -1) {
|
||||
logger.log(`${(-delta)} ms overlapping between AAC samples detected`);
|
||||
}
|
||||
// set DTS to next DTS
|
||||
ptsnorm = dtsnorm = nextAacPts;
|
||||
}
|
||||
}
|
||||
// remember first PTS of our aacSamples, ensure value is positive
|
||||
firstPTS = Math.max(0, ptsnorm);
|
||||
firstDTS = Math.max(0, dtsnorm);
|
||||
}
|
||||
//console.log('PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${aacSample.pts}/${aacSample.dts}/${this._initDTS}/${ptsnorm}/${dtsnorm}/${(aacSample.pts/4294967296).toFixed(3)}');
|
||||
mp4Sample = {
|
||||
size: unit.byteLength,
|
||||
cts: 0,
|
||||
duration:0,
|
||||
flags: {
|
||||
isLeading: 0,
|
||||
isDependedOn: 0,
|
||||
hasRedundancy: 0,
|
||||
degradPrio: 0,
|
||||
dependsOn: 1,
|
||||
}
|
||||
};
|
||||
samples.push(mp4Sample);
|
||||
lastDTS = dtsnorm;
|
||||
}
|
||||
//set last sample duration as being identical to previous sample
|
||||
if (samples.length >= 2) {
|
||||
mp4Sample.duration = samples[samples.length - 2].duration;
|
||||
}
|
||||
// next aac sample PTS should be equal to last sample PTS + duration
|
||||
this.nextAacPts = ptsnorm + pes2mp4ScaleFactor * mp4Sample.duration;
|
||||
//logger.log('Audio/PTS/PTSend:' + aacSample.pts.toFixed(0) + '/' + this.nextAacDts.toFixed(0));
|
||||
track.len = 0;
|
||||
track.samples = samples;
|
||||
moof = MP4.moof(track.sequenceNumber++, firstDTS / pes2mp4ScaleFactor, track);
|
||||
track.samples = [];
|
||||
this.observer.trigger(Event.FRAG_PARSING_DATA, {
|
||||
moof: moof,
|
||||
mdat: mdat,
|
||||
startPTS: firstPTS / pesTimeScale,
|
||||
endPTS: this.nextAacPts / pesTimeScale,
|
||||
startDTS: firstDTS / pesTimeScale,
|
||||
endDTS: (dtsnorm + pes2mp4ScaleFactor * mp4Sample.duration) / pesTimeScale,
|
||||
type: 'audio',
|
||||
nb: samples.length
|
||||
});
|
||||
}
|
||||
|
||||
remuxID3(track,timeOffset) {
|
||||
var length = track.samples.length, sample;
|
||||
// consume samples
|
||||
if(length) {
|
||||
for(var index = 0; index < length; index++) {
|
||||
sample = track.samples[index];
|
||||
// setting id3 pts, dts to relative time
|
||||
// using this._initPTS and this._initDTS to calculate relative time
|
||||
sample.pts = ((sample.pts - this._initPTS) / this.PES_TIMESCALE);
|
||||
sample.dts = ((sample.dts - this._initDTS) / this.PES_TIMESCALE);
|
||||
}
|
||||
this.observer.trigger(Event.FRAG_PARSING_METADATA, {
|
||||
samples:track.samples
|
||||
});
|
||||
}
|
||||
|
||||
track.samples = [];
|
||||
timeOffset = timeOffset;
|
||||
}
|
||||
|
||||
_PTSNormalize(value, reference) {
|
||||
var offset;
|
||||
if (reference === undefined) {
|
||||
return value;
|
||||
}
|
||||
if (reference < value) {
|
||||
// - 2^33
|
||||
offset = -8589934592;
|
||||
} else {
|
||||
// + 2^33
|
||||
offset = 8589934592;
|
||||
}
|
||||
/* PTS is 33bit (from 0 to 2^33 -1)
|
||||
if diff between value and reference is bigger than half of the amplitude (2^32) then it means that
|
||||
PTS looping occured. fill the gap */
|
||||
while (Math.abs(value - reference) > 4294967296) {
|
||||
value += offset;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default MP4Remuxer;
|
43
dashboard-ui/bower_components/hls.js/src/utils/binary-search.js
vendored
Normal file
43
dashboard-ui/bower_components/hls.js/src/utils/binary-search.js
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
var BinarySearch = {
|
||||
/**
|
||||
* Searches for an item in an array which matches a certain condition.
|
||||
* This requires the condition to only match one item in the array,
|
||||
* and for the array to be ordered.
|
||||
*
|
||||
* @param {Array} list The array to search.
|
||||
* @param {Function} comparisonFunction
|
||||
* Called and provided a candidate item as the first argument.
|
||||
* Should return:
|
||||
* > -1 if the item should be located at a lower index than the provided item.
|
||||
* > 1 if the item should be located at a higher index than the provided item.
|
||||
* > 0 if the item is the item you're looking for.
|
||||
*
|
||||
* @return {*} The object if it is found or null otherwise.
|
||||
*/
|
||||
search: function(list, comparisonFunction) {
|
||||
var minIndex = 0;
|
||||
var maxIndex = list.length - 1;
|
||||
var currentIndex = null;
|
||||
var currentElement = null;
|
||||
|
||||
while (minIndex <= maxIndex) {
|
||||
currentIndex = (minIndex + maxIndex) / 2 | 0;
|
||||
currentElement = list[currentIndex];
|
||||
|
||||
var comparisonResult = comparisonFunction(currentElement);
|
||||
if (comparisonResult > 0) {
|
||||
minIndex = currentIndex + 1;
|
||||
}
|
||||
else if (comparisonResult < 0) {
|
||||
maxIndex = currentIndex - 1;
|
||||
}
|
||||
else {
|
||||
return currentElement;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = BinarySearch;
|
16
dashboard-ui/bower_components/hls.js/src/utils/hex.js
vendored
Normal file
16
dashboard-ui/bower_components/hls.js/src/utils/hex.js
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
class Hex {
|
||||
|
||||
static hexDump(array) {
|
||||
var i, str = '';
|
||||
for(i = 0; i < array.length; i++) {
|
||||
var h = array[i].toString(16);
|
||||
if (h.length < 2) {
|
||||
h = '0' + h;
|
||||
}
|
||||
str += h;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
export default Hex;
|
73
dashboard-ui/bower_components/hls.js/src/utils/logger.js
vendored
Normal file
73
dashboard-ui/bower_components/hls.js/src/utils/logger.js
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
'use strict';
|
||||
|
||||
function noop() {}
|
||||
|
||||
const fakeLogger = {
|
||||
trace: noop,
|
||||
debug: noop,
|
||||
log: noop,
|
||||
warn: noop,
|
||||
info: noop,
|
||||
error: noop
|
||||
};
|
||||
|
||||
let exportedLogger = fakeLogger;
|
||||
|
||||
//let lastCallTime;
|
||||
// function formatMsgWithTimeInfo(type, msg) {
|
||||
// const now = Date.now();
|
||||
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
||||
// lastCallTime = now;
|
||||
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
||||
// return msg;
|
||||
// }
|
||||
|
||||
function formatMsg(type, msg) {
|
||||
msg = '[' + type + '] > ' + msg;
|
||||
return msg;
|
||||
}
|
||||
|
||||
function consolePrintFn(type) {
|
||||
const func = window.console[type];
|
||||
if (func) {
|
||||
return function(...args) {
|
||||
if(args[0]) {
|
||||
args[0] = formatMsg(type, args[0]);
|
||||
}
|
||||
func.apply(window.console, args);
|
||||
};
|
||||
}
|
||||
return noop;
|
||||
}
|
||||
|
||||
function exportLoggerFunctions(debugConfig, ...functions) {
|
||||
functions.forEach(function(type) {
|
||||
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
||||
});
|
||||
}
|
||||
|
||||
export var enableLogs = function(debugConfig) {
|
||||
if (debugConfig === true || typeof debugConfig === 'object') {
|
||||
exportLoggerFunctions(debugConfig,
|
||||
// Remove out from list here to hard-disable a log-level
|
||||
//'trace',
|
||||
'debug',
|
||||
'log',
|
||||
'info',
|
||||
'warn',
|
||||
'error'
|
||||
);
|
||||
// Some browsers don't allow to use bind on console object anyway
|
||||
// fallback to default if needed
|
||||
try {
|
||||
exportedLogger.log();
|
||||
} catch (e) {
|
||||
exportedLogger = fakeLogger;
|
||||
}
|
||||
}
|
||||
else {
|
||||
exportedLogger = fakeLogger;
|
||||
}
|
||||
};
|
||||
|
||||
export var logger = exportedLogger;
|
77
dashboard-ui/bower_components/hls.js/src/utils/url.js
vendored
Normal file
77
dashboard-ui/bower_components/hls.js/src/utils/url.js
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
var URLHelper = {
|
||||
|
||||
// build an absolute URL from a relative one using the provided baseURL
|
||||
// if relativeURL is an absolute URL it will be returned as is.
|
||||
buildAbsoluteURL: function(baseURL, relativeURL) {
|
||||
// remove any remaining space and CRLF
|
||||
relativeURL = relativeURL.trim();
|
||||
if (/^[a-z]+:/i.test(relativeURL)) {
|
||||
// complete url, not relative
|
||||
return relativeURL;
|
||||
}
|
||||
|
||||
var relativeURLQuery = null;
|
||||
var relativeURLHash = null;
|
||||
|
||||
var relativeURLHashSplit = /^([^#]*)(.*)$/.exec(relativeURL);
|
||||
if (relativeURLHashSplit) {
|
||||
relativeURLHash = relativeURLHashSplit[2];
|
||||
relativeURL = relativeURLHashSplit[1];
|
||||
}
|
||||
var relativeURLQuerySplit = /^([^\?]*)(.*)$/.exec(relativeURL);
|
||||
if (relativeURLQuerySplit) {
|
||||
relativeURLQuery = relativeURLQuerySplit[2];
|
||||
relativeURL = relativeURLQuerySplit[1];
|
||||
}
|
||||
|
||||
var baseURLHashSplit = /^([^#]*)(.*)$/.exec(baseURL);
|
||||
if (baseURLHashSplit) {
|
||||
baseURL = baseURLHashSplit[1];
|
||||
}
|
||||
var baseURLQuerySplit = /^([^\?]*)(.*)$/.exec(baseURL);
|
||||
if (baseURLQuerySplit) {
|
||||
baseURL = baseURLQuerySplit[1];
|
||||
}
|
||||
|
||||
var baseURLDomainSplit = /^((([a-z]+):)?\/\/[a-z0-9\.-]+(:[0-9]+)?\/)(.*)$/i.exec(baseURL);
|
||||
var baseURLProtocol = baseURLDomainSplit[3];
|
||||
var baseURLDomain = baseURLDomainSplit[1];
|
||||
var baseURLPath = baseURLDomainSplit[5];
|
||||
|
||||
var builtURL = null;
|
||||
if (/^\/\//.test(relativeURL)) {
|
||||
builtURL = baseURLProtocol+'://'+URLHelper.buildAbsolutePath('', relativeURL.substring(2));
|
||||
}
|
||||
else if (/^\//.test(relativeURL)) {
|
||||
builtURL = baseURLDomain+URLHelper.buildAbsolutePath('', relativeURL.substring(1));
|
||||
}
|
||||
else {
|
||||
var newPath = URLHelper.buildAbsolutePath(baseURLPath, relativeURL);
|
||||
builtURL = baseURLDomain + newPath;
|
||||
}
|
||||
|
||||
// put the query and hash parts back
|
||||
if (relativeURLQuery) {
|
||||
builtURL += relativeURLQuery;
|
||||
}
|
||||
if (relativeURLHash) {
|
||||
builtURL += relativeURLHash;
|
||||
}
|
||||
return builtURL;
|
||||
},
|
||||
|
||||
// build an absolute path using the provided basePath
|
||||
// adapted from https://developer.mozilla.org/en-US/docs/Web/API/document/cookie#Using_relative_URLs_in_the_path_parameter
|
||||
// this does not handle the case where relativePath is "/" or "//". These cases should be handled outside this.
|
||||
buildAbsolutePath: function(basePath, relativePath) {
|
||||
var sRelPath = relativePath;
|
||||
var nUpLn, sDir = '', sPath = basePath.replace(/[^\/]*$/, sRelPath.replace(/(\/|^)(?:\.?\/+)+/g, '$1'));
|
||||
for (var nEnd, nStart = 0; nEnd = sPath.indexOf('/../', nStart), nEnd > -1; nStart = nEnd + nUpLn) {
|
||||
nUpLn = /^\/(?:\.\.\/)*/.exec(sPath.slice(nEnd))[0].length;
|
||||
sDir = (sDir + sPath.substring(nStart, nEnd)).replace(new RegExp('(?:\\\/+[^\\\/]*){0,' + ((nUpLn - 1) / 3) + '}$'), '/');
|
||||
}
|
||||
return sDir + sPath.substr(nStart);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = URLHelper;
|
104
dashboard-ui/bower_components/hls.js/src/utils/xhr-loader.js
vendored
Normal file
104
dashboard-ui/bower_components/hls.js/src/utils/xhr-loader.js
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* XHR based logger
|
||||
*/
|
||||
|
||||
import {logger} from '../utils/logger';
|
||||
|
||||
class XhrLoader {
|
||||
|
||||
constructor(config) {
|
||||
if (config && config.xhrSetup) {
|
||||
this.xhrSetup = config.xhrSetup;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.abort();
|
||||
this.loader = null;
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this.loader && this.loader.readyState !== 4) {
|
||||
this.stats.aborted = true;
|
||||
this.loader.abort();
|
||||
}
|
||||
if (this.timeoutHandle) {
|
||||
window.clearTimeout(this.timeoutHandle);
|
||||
}
|
||||
}
|
||||
|
||||
load(url, responseType, onSuccess, onError, onTimeout, timeout, maxRetry, retryDelay, onProgress = null, frag = null) {
|
||||
this.url = url;
|
||||
if (frag && !isNaN(frag.byteRangeStartOffset) && !isNaN(frag.byteRangeEndOffset)) {
|
||||
this.byteRange = frag.byteRangeStartOffset + '-' + frag.byteRangeEndOffset;
|
||||
}
|
||||
this.responseType = responseType;
|
||||
this.onSuccess = onSuccess;
|
||||
this.onProgress = onProgress;
|
||||
this.onTimeout = onTimeout;
|
||||
this.onError = onError;
|
||||
this.stats = {trequest: performance.now(), retry: 0};
|
||||
this.timeout = timeout;
|
||||
this.maxRetry = maxRetry;
|
||||
this.retryDelay = retryDelay;
|
||||
this.timeoutHandle = window.setTimeout(this.loadtimeout.bind(this), timeout);
|
||||
this.loadInternal();
|
||||
}
|
||||
|
||||
loadInternal() {
|
||||
var xhr = this.loader = new XMLHttpRequest();
|
||||
xhr.onload = this.loadsuccess.bind(this);
|
||||
xhr.onerror = this.loaderror.bind(this);
|
||||
xhr.onprogress = this.loadprogress.bind(this);
|
||||
xhr.open('GET', this.url, true);
|
||||
if (this.byteRange) {
|
||||
xhr.setRequestHeader('Range', 'bytes=' + this.byteRange);
|
||||
}
|
||||
xhr.responseType = this.responseType;
|
||||
this.stats.tfirst = null;
|
||||
this.stats.loaded = 0;
|
||||
if (this.xhrSetup) {
|
||||
this.xhrSetup(xhr, this.url);
|
||||
}
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
loadsuccess(event) {
|
||||
window.clearTimeout(this.timeoutHandle);
|
||||
this.stats.tload = performance.now();
|
||||
this.onSuccess(event, this.stats);
|
||||
}
|
||||
|
||||
loaderror(event) {
|
||||
if (this.stats.retry < this.maxRetry) {
|
||||
logger.warn(`${event.type} while loading ${this.url}, retrying in ${this.retryDelay}...`);
|
||||
this.destroy();
|
||||
window.setTimeout(this.loadInternal.bind(this), this.retryDelay);
|
||||
// exponential backoff
|
||||
this.retryDelay = Math.min(2 * this.retryDelay, 64000);
|
||||
this.stats.retry++;
|
||||
} else {
|
||||
window.clearTimeout(this.timeoutHandle);
|
||||
logger.error(`${event.type} while loading ${this.url}` );
|
||||
this.onError(event);
|
||||
}
|
||||
}
|
||||
|
||||
loadtimeout(event) {
|
||||
logger.warn(`timeout while loading ${this.url}` );
|
||||
this.onTimeout(event, this.stats);
|
||||
}
|
||||
|
||||
loadprogress(event) {
|
||||
var stats = this.stats;
|
||||
if (stats.tfirst === null) {
|
||||
stats.tfirst = performance.now();
|
||||
}
|
||||
stats.loaded = event.loaded;
|
||||
if (this.onProgress) {
|
||||
this.onProgress(event, stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default XhrLoader;
|
Loading…
Add table
Add a link
Reference in a new issue