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
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;
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue