view release on metacpan or search on metacpan
share/public_html/static/hls.js view on Meta::CPAN
return true;
}
}
}
}
return false;
};
/**
* Returns any adjacent ID3 tags found in data starting at offset, as one block of data
* @param {Uint8Array} data - The data to search in
* @param {number} offset - The offset at which to start searching
* @return {Uint8Array} - The block of data containing any ID3 tags found
*/
ID3.getID3Data = function getID3Data(data, offset) {
var front = offset;
var length = 0;
while (ID3.isHeader(data, offset)) {
// ID3 header is 10 bytes
length += 10;
var size = ID3._readSize(data, offset + 6);
length += size;
if (ID3.isFooter(data, offset + 10)) {
// ID3 footer is 10 bytes
length += 10;
}
offset += length;
}
if (length > 0) {
return data.subarray(front, front + length);
}
return undefined;
};
ID3._readSize = function _readSize(data, offset) {
var size = 0;
size = (data[offset] & 0x7f) << 21;
size |= (data[offset + 1] & 0x7f) << 14;
size |= (data[offset + 2] & 0x7f) << 7;
size |= data[offset + 3] & 0x7f;
return size;
};
/**
* Searches for the Elementary Stream timestamp found in the ID3 data chunk
* @param {Uint8Array} data - Block of data containing one or more ID3 tags
* @return {number} - The timestamp
*/
ID3.getTimeStamp = function getTimeStamp(data) {
var frames = ID3.getID3Frames(data);
for (var i = 0; i < frames.length; i++) {
var frame = frames[i];
if (ID3.isTimeStampFrame(frame)) {
return ID3._readTimeStamp(frame);
}
}
return undefined;
};
/**
* Returns true if the ID3 frame is an Elementary Stream timestamp frame
* @param {ID3 frame} frame
*/
ID3.isTimeStampFrame = function isTimeStampFrame(frame) {
return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp';
};
ID3._getFrameData = function _getFrameData(data) {
/*
Frame ID $xx xx xx xx (four characters)
Size $xx xx xx xx
Flags $xx xx
*/
var type = String.fromCharCode(data[0], data[1], data[2], data[3]);
var size = ID3._readSize(data, 4);
// skip frame id, size, and flags
var offset = 10;
return { type: type, size: size, data: data.subarray(offset, offset + size) };
};
/**
* Returns an array of ID3 frames found in all the ID3 tags in the id3Data
* @param {Uint8Array} id3Data - The ID3 data containing one or more ID3 tags
* @return {ID3 frame[]} - Array of ID3 frame objects
*/
ID3.getID3Frames = function getID3Frames(id3Data) {
var offset = 0;
var frames = [];
while (ID3.isHeader(id3Data, offset)) {
var size = ID3._readSize(id3Data, offset + 6);
// skip past ID3 header
offset += 10;
var end = offset + size;
// loop through frames in the ID3 tag
while (offset + 8 < end) {
var frameData = ID3._getFrameData(id3Data.subarray(offset));
var frame = ID3._decodeFrame(frameData);
if (frame) {
frames.push(frame);
}
// skip frame header and frame data
offset += frameData.size + 10;
}
if (ID3.isFooter(id3Data, offset)) {
offset += 10;
}
}
return frames;
};
ID3._decodeFrame = function _decodeFrame(frame) {
if (frame.type === 'PRIV') {
return ID3._decodePrivFrame(frame);
} else if (frame.type[0] === 'T') {
return ID3._decodeTextFrame(frame);
} else if (frame.type[0] === 'W') {
return ID3._decodeURLFrame(frame);
}
return undefined;
};
ID3._readTimeStamp = function _readTimeStamp(timeStampFrame) {
if (timeStampFrame.data.byteLength === 8) {
var data = new Uint8Array(timeStampFrame.data);
// timestamp is 33 bit expressed as a big-endian eight-octet number,
// with the upper 31 bits set to zero.
var pts33Bit = data[3] & 0x1;
var timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];
timestamp /= 45;
if (pts33Bit) {
timestamp += 47721858.84;
} // 2^32 / 90
return Math.round(timestamp);
}
return undefined;
};
ID3._decodePrivFrame = function _decodePrivFrame(frame) {
/*
Format: <text string>\0<binary data>
*/
if (frame.size < 2) {
return undefined;
}
var owner = ID3._utf8ArrayToStr(frame.data, true);
var privateData = new Uint8Array(frame.data.subarray(owner.length + 1));
return { key: frame.type, info: owner, data: privateData.buffer };
};
ID3._decodeTextFrame = function _decodeTextFrame(frame) {
if (frame.size < 2) {
return undefined;
}
if (frame.type === 'TXXX') {
/*
Format:
[0] = {Text Encoding}
[1-?] = {Description}\0{Value}
*/
var index = 1;
var description = ID3._utf8ArrayToStr(frame.data.subarray(index));
index += description.length + 1;
var value = ID3._utf8ArrayToStr(frame.data.subarray(index));
return { key: frame.type, info: description, data: value };
} else {
/*
Format:
[0] = {Text Encoding}
[1-?] = {Value}
*/
var text = ID3._utf8ArrayToStr(frame.data.subarray(1));
return { key: frame.type, data: text };
}
};
ID3._decodeURLFrame = function _decodeURLFrame(frame) {
if (frame.type === 'WXXX') {
/*
Format:
[0] = {Text Encoding}
[1-?] = {Description}\0{URL}
*/
if (frame.size < 2) {
return undefined;
}
var index = 1;
var description = ID3._utf8ArrayToStr(frame.data.subarray(index));
index += description.length + 1;
var value = ID3._utf8ArrayToStr(frame.data.subarray(index));
return { key: frame.type, info: description, data: value };
} else {
/*
Format:
[0-?] = {URL}
*/
var url = ID3._utf8ArrayToStr(frame.data);
return { key: frame.type, data: url };
}
};
// http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197
// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
/* utf.js - UTF-8 <=> UTF-16 convertion
*
* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
* Version: 1.0
* LastModified: Dec 25 1999
* This library is free. You can redistribute it and/or modify it.
*/
ID3._utf8ArrayToStr = function _utf8ArrayToStr(array) {
var exitOnNull = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var len = array.length;
var c = void 0;
var char2 = void 0;
var char3 = void 0;
var out = '';
var i = 0;
while (i < len) {
c = array[i++];
if (c === 0x00 && exitOnNull) {
return out;
} else if (c === 0x00 || c === 0x03) {
// If the character is 3 (END_OF_TEXT) or 0 (NULL) then skip it
continue;
}
switch (c >> 4) {
case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode((c & 0x1F) << 6 | char2 & 0x3F);
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode((c & 0x0F) << 12 | (char2 & 0x3F) << 6 | (char3 & 0x3F) << 0);
break;
default:
}
}
return out;
};
return ID3;
}();
var utf8ArrayToStr = ID3._utf8ArrayToStr;
/* harmony default export */ __webpack_exports__["a"] = (ID3);
share/public_html/static/hls.js view on Meta::CPAN
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// EXTERNAL MODULE: ./src/events.js
var events = __webpack_require__(1);
// EXTERNAL MODULE: ./src/errors.js
var errors = __webpack_require__(2);
// EXTERNAL MODULE: ./src/crypt/decrypter.js + 3 modules
var crypt_decrypter = __webpack_require__(7);
// EXTERNAL MODULE: ./src/utils/logger.js
var logger = __webpack_require__(0);
// EXTERNAL MODULE: ./src/utils/get-self-scope.js
var get_self_scope = __webpack_require__(3);
// CONCATENATED MODULE: ./src/demux/adts.js
/**
* ADTS parser helper
*/
function getAudioConfig(observer, data, offset, audioCodec) {
var adtsObjectType = void 0,
// :int
adtsSampleingIndex = void 0,
// :int
adtsExtensionSampleingIndex = void 0,
// :int
adtsChanelConfig = void 0,
// :int
config = void 0,
userAgent = navigator.userAgent.toLowerCase(),
manifestCodec = audioCodec,
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) {
observer.trigger(events["a" /* default */].ERROR, { type: errors["b" /* ErrorTypes */].MEDIA_ERROR, details: errors["a" /* 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["b" /* 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 (/firefox/i.test(userAgent)) {
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/Vivaldi/Opera ...)
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 AND nb channel is 1) OR (manifest codec not specified and mono audio)
// Chrome fails to play back with low frequency 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 || /vivaldi/i.test(userAgent)) || !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, manifestCodec: manifestCodec };
}
function isHeaderPattern(data, offset) {
return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0;
}
function getHeaderLength(data, offset) {
return data[offset + 1] & 0x01 ? 7 : 9;
}
function getFullFrameLength(data, offset) {
return (data[offset + 3] & 0x03) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 0xE0) >>> 5;
}
function isHeader(data, offset) {
// Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
// Layer bits (position 14 and 15) in header should be always 0 for ADTS
// More info https://wiki.multimedia.cx/index.php?title=ADTS
if (offset + 1 < data.length && isHeaderPattern(data, offset)) {
return true;
}
return false;
}
function adts_probe(data, offset) {
// same as isHeader but we also check that ADTS frame follows last ADTS frame
// or end of data is reached
if (offset + 1 < data.length && isHeaderPattern(data, offset)) {
// ADTS header Length
var headerLength = getHeaderLength(data, offset);
// ADTS frame Length
var frameLength = headerLength;
if (offset + 5 < data.length) {
frameLength = getFullFrameLength(data, offset);
}
var newOffset = offset + frameLength;
if (newOffset === data.length || newOffset + 1 < data.length && isHeaderPattern(data, newOffset)) {
return true;
}
}
return false;
}
function initTrackConfig(track, observer, data, offset, audioCodec) {
if (!track.samplerate) {
var config = getAudioConfig(observer, data, offset, audioCodec);
track.config = config.config;
track.samplerate = config.samplerate;
track.channelCount = config.channelCount;
track.codec = config.codec;
track.manifestCodec = config.manifestCodec;
logger["b" /* logger */].log('parsed codec:' + track.codec + ',rate:' + config.samplerate + ',nb channel:' + config.channelCount);
}
}
function getFrameDuration(samplerate) {
return 1024 * 90000 / samplerate;
}
function parseFrameHeader(data, offset, pts, frameIndex, frameDuration) {
var headerLength = void 0,
frameLength = void 0,
stamp = void 0;
var length = data.length;
// The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header
headerLength = getHeaderLength(data, offset);
// retrieve frame size
frameLength = getFullFrameLength(data, offset);
frameLength -= headerLength;
if (frameLength > 0 && offset + headerLength + frameLength <= length) {
stamp = pts + frameIndex * frameDuration;
// logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}/${(stamp/90).toFixed(0)}`);
return { headerLength: headerLength, frameLength: frameLength, stamp: stamp };
}
return undefined;
}
function appendFrame(track, data, offset, pts, frameIndex) {
var frameDuration = getFrameDuration(track.samplerate);
var header = parseFrameHeader(data, offset, pts, frameIndex, frameDuration);
if (header) {
var stamp = header.stamp;
var headerLength = header.headerLength;
var frameLength = header.frameLength;
// logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}/${(stamp/90).toFixed(0)}`);
var aacSample = {
unit: data.subarray(offset + headerLength, offset + headerLength + frameLength),
pts: stamp,
dts: stamp
};
track.samples.push(aacSample);
track.len += frameLength;
return { sample: aacSample, length: frameLength + headerLength };
}
return undefined;
}
// EXTERNAL MODULE: ./src/demux/id3.js
var id3 = __webpack_require__(5);
// CONCATENATED MODULE: ./src/demux/aacdemuxer.js
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* AAC demuxer
*/
var aacdemuxer_AACDemuxer = function () {
function AACDemuxer(observer, remuxer, config) {
_classCallCheck(this, AACDemuxer);
this.observer = observer;
this.config = config;
this.remuxer = remuxer;
}
AACDemuxer.prototype.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, duration) {
this._audioTrack = { container: 'audio/adts', type: 'audio', id: 0, sequenceNumber: 0, isAAC: true, samples: [], len: 0, manifestCodec: audioCodec, duration: duration, inputTimeScale: 90000 };
};
AACDemuxer.prototype.resetTimeStamp = function resetTimeStamp() {};
AACDemuxer.probe = function probe(data) {
if (!data) {
return false;
}
// Check for the ADTS sync word
// Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
// Layer bits (position 14 and 15) in header should be always 0 for ADTS
// More info https://wiki.multimedia.cx/index.php?title=ADTS
var id3Data = id3["a" /* default */].getID3Data(data, 0) || [];
var offset = id3Data.length;
for (var length = data.length; offset < length; offset++) {
if (adts_probe(data, offset)) {
logger["b" /* logger */].log('ADTS sync word found !');
return true;
}
}
return false;
};
// feed incoming data to the front of the parsing pipeline
AACDemuxer.prototype.append = function append(data, timeOffset, contiguous, accurateTimeOffset) {
var track = this._audioTrack;
var id3Data = id3["a" /* default */].getID3Data(data, 0) || [];
var timestamp = id3["a" /* default */].getTimeStamp(id3Data);
var pts = timestamp ? 90 * timestamp : timeOffset * 90000;
var frameIndex = 0;
var stamp = pts;
var length = data.length;
var offset = id3Data.length;
var id3Samples = [{ pts: stamp, dts: stamp, data: id3Data }];
while (offset < length - 1) {
if (isHeader(data, offset) && offset + 5 < length) {
initTrackConfig(track, this.observer, data, offset, track.manifestCodec);
var frame = appendFrame(track, data, offset, pts, frameIndex);
if (frame) {
offset += frame.length;
stamp = frame.sample.pts;
frameIndex++;
} else {
logger["b" /* logger */].log('Unable to parse AAC frame');
break;
}
} else if (id3["a" /* default */].isHeader(data, offset)) {
id3Data = id3["a" /* default */].getID3Data(data, offset);
id3Samples.push({ pts: stamp, dts: stamp, data: id3Data });
offset += id3Data.length;
} else {
// nothing found, keep looking
offset++;
}
}
this.remuxer.remux(track, { samples: [] }, { samples: id3Samples, inputTimeScale: 90000 }, { samples: [] }, timeOffset, contiguous, accurateTimeOffset);
};
AACDemuxer.prototype.destroy = function destroy() {};
return AACDemuxer;
}();
/* harmony default export */ var aacdemuxer = (aacdemuxer_AACDemuxer);
// EXTERNAL MODULE: ./src/demux/mp4demuxer.js
var mp4demuxer = __webpack_require__(8);
// CONCATENATED MODULE: ./src/demux/mpegaudio.js
/**
* MPEG parser helper
*/
var MpegAudio = {
BitratesMap: [32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 1...
SamplingRateMap: [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000],
SamplesCoefficients: [
// MPEG 2.5
[0, // Reserved
72, // Layer3
144, // Layer2
12 // Layer1
],
// Reserved
[0, // Reserved
0, // Layer3
0, // Layer2
0 // Layer1
],
// MPEG 2
[0, // Reserved
72, // Layer3
144, // Layer2
12 // Layer1
],
// MPEG 1
[0, // Reserved
144, // Layer3
144, // Layer2
12 // Layer1
]],
BytesInSlot: [0, // Reserved
1, // Layer3
1, // Layer2
4 // Layer1
],
appendFrame: function appendFrame(track, data, offset, pts, frameIndex) {
// Using http://www.datavoyage.com/mpgscript/mpeghdr.htm as a reference
if (offset + 24 > data.length) {
return undefined;
}
var header = this.parseHeader(data, offset);
if (header && offset + header.frameLength <= data.length) {
var frameDuration = header.samplesPerFrame * 90000 / header.sampleRate;
var stamp = pts + frameIndex * frameDuration;
var sample = { unit: data.subarray(offset, offset + header.frameLength), pts: stamp, dts: stamp };
track.config = [];
track.channelCount = header.channelCount;
track.samplerate = header.sampleRate;
track.samples.push(sample);
track.len += header.frameLength;
return { sample: sample, length: header.frameLength };
}
return undefined;
},
parseHeader: function parseHeader(data, offset) {
var headerB = data[offset + 1] >> 3 & 3;
var headerC = data[offset + 1] >> 1 & 3;
var headerE = data[offset + 2] >> 4 & 15;
var headerF = data[offset + 2] >> 2 & 3;
var headerG = data[offset + 2] >> 1 & 1;
if (headerB !== 1 && headerE !== 0 && headerE !== 15 && headerF !== 3) {
var columnInBitrates = headerB === 3 ? 3 - headerC : headerC === 3 ? 3 : 4;
var bitRate = MpegAudio.BitratesMap[columnInBitrates * 14 + headerE - 1] * 1000;
var columnInSampleRates = headerB === 3 ? 0 : headerB === 2 ? 1 : 2;
var sampleRate = MpegAudio.SamplingRateMap[columnInSampleRates * 3 + headerF];
var channelCount = data[offset + 3] >> 6 === 3 ? 1 : 2; // If bits of channel mode are `11` then it is a single channel (Mono)
var sampleCoefficient = MpegAudio.SamplesCoefficients[headerB][headerC];
var bytesInSlot = MpegAudio.BytesInSlot[headerC];
var samplesPerFrame = sampleCoefficient * 8 * bytesInSlot;
var frameLength = parseInt(sampleCoefficient * bitRate / sampleRate + headerG, 10) * bytesInSlot;
return { sampleRate: sampleRate, channelCount: channelCount, frameLength: frameLength, samplesPerFrame: samplesPerFrame };
}
return undefined;
},
isHeaderPattern: function isHeaderPattern(data, offset) {
return data[offset] === 0xff && (data[offset + 1] & 0xe0) === 0xe0 && (data[offset + 1] & 0x06) !== 0x00;
},
isHeader: function isHeader(data, offset) {
// Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
// Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
// More info http://www.mp3-tech.org/programmer/frame_header.html
if (offset + 1 < data.length && this.isHeaderPattern(data, offset)) {
return true;
}
return false;
},
probe: function probe(data, offset) {
// same as isHeader but we also check that MPEG frame follows last MPEG frame
// or end of data is reached
if (offset + 1 < data.length && this.isHeaderPattern(data, offset)) {
// MPEG header Length
var headerLength = 4;
// MPEG frame Length
var header = this.parseHeader(data, offset);
var frameLength = headerLength;
if (header && header.frameLength) {
frameLength = header.frameLength;
}
var newOffset = offset + frameLength;
if (newOffset === data.length || newOffset + 1 < data.length && this.isHeaderPattern(data, newOffset)) {
return true;
}
}
return false;
}
};
/* harmony default export */ var mpegaudio = (MpegAudio);
// CONCATENATED MODULE: ./src/demux/exp-golomb.js
function exp_golomb__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
*/
var exp_golomb_ExpGolomb = function () {
function ExpGolomb(data) {
exp_golomb__classCallCheck(this, ExpGolomb);
this.data = data;
// the number of bytes left to examine in this.data
this.bytesAvailable = 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
ExpGolomb.prototype.loadWord = function loadWord() {
var data = this.data,
bytesAvailable = this.bytesAvailable,
position = data.byteLength - bytesAvailable,
workingBytes = new Uint8Array(4),
availableBytes = Math.min(4, bytesAvailable);
if (availableBytes === 0) {
throw new Error('no bytes available');
}
workingBytes.set(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
ExpGolomb.prototype.skipBits = function skipBits(count) {
var skipBytes = void 0; // :int
if (this.bitsAvailable > count) {
this.word <<= count;
this.bitsAvailable -= count;
} else {
share/public_html/static/hls.js view on Meta::CPAN
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
ExpGolomb.prototype.readBoolean = function readBoolean() {
return this.readBits(1) === 1;
};
// ():int
ExpGolomb.prototype.readUByte = function readUByte() {
return this.readBits(8);
};
// ():int
ExpGolomb.prototype.readUShort = function readUShort() {
return this.readBits(16);
};
// ():int
ExpGolomb.prototype.readUInt = function readUInt() {
return this.readBits(32);
};
/**
* 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
*/
ExpGolomb.prototype.skipScalingList = function skipScalingList(count) {
var lastScale = 8,
nextScale = 8,
j = void 0,
deltaScale = void 0;
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.
*/
ExpGolomb.prototype.readSPS = function readSPS() {
var frameCropLeftOffset = 0,
frameCropRightOffset = 0,
frameCropTopOffset = 0,
frameCropBottomOffset = 0,
profileIdc = void 0,
profileCompat = void 0,
levelIdc = void 0,
numRefFramesInPicOrderCntCycle = void 0,
picWidthInMbsMinus1 = void 0,
picHeightInMapUnitsMinus1 = void 0,
frameMbsOnlyFlag = void 0,
scalingListCount = void 0,
i = void 0,
readUByte = this.readUByte.bind(this),
readBits = this.readBits.bind(this),
readUEG = this.readUEG.bind(this),
readBoolean = this.readBoolean.bind(this),
skipBits = this.skipBits.bind(this),
skipEG = this.skipEG.bind(this),
skipUEG = this.skipUEG.bind(this),
skipScalingList = this.skipScalingList.bind(this);
readUByte();
profileIdc = readUByte(); // profile_idc
profileCompat = readBits(5); // constraint_set[0-4]_flag, u(5)
skipBits(3); // reserved_zero_3bits u(3),
levelIdc = readUByte(); // level_idc u(8)
skipUEG(); // seq_parameter_set_id
// some profiles have more optional data we don't need
if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
var chromaFormatIdc = readUEG();
if (chromaFormatIdc === 3) {
skipBits(1);
} // separate_colour_plane_flag
skipUEG(); // bit_depth_luma_minus8
skipUEG(); // bit_depth_chroma_minus8
skipBits(1); // qpprime_y_zero_transform_bypass_flag
if (readBoolean()) {
// seq_scaling_matrix_present_flag
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
for (i = 0; i < scalingListCount; i++) {
if (readBoolean()) {
// seq_scaling_list_present_flag[ i ]
if (i < 6) {
skipScalingList(16);
} else {
skipScalingList(64);
}
}
}
}
}
skipUEG(); // log2_max_frame_num_minus4
var picOrderCntType = readUEG();
if (picOrderCntType === 0) {
readUEG(); // log2_max_pic_order_cnt_lsb_minus4
} else if (picOrderCntType === 1) {
skipBits(1); // delta_pic_order_always_zero_flag
skipEG(); // offset_for_non_ref_pic
skipEG(); // offset_for_top_to_bottom_field
numRefFramesInPicOrderCntCycle = readUEG();
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
skipEG();
} // offset_for_ref_frame[ i ]
}
skipUEG(); // max_num_ref_frames
skipBits(1); // gaps_in_frame_num_value_allowed_flag
picWidthInMbsMinus1 = readUEG();
picHeightInMapUnitsMinus1 = readUEG();
frameMbsOnlyFlag = readBits(1);
if (frameMbsOnlyFlag === 0) {
skipBits(1);
} // mb_adaptive_frame_field_flag
skipBits(1); // direct_8x8_inference_flag
if (readBoolean()) {
// frame_cropping_flag
frameCropLeftOffset = readUEG();
frameCropRightOffset = readUEG();
frameCropTopOffset = readUEG();
frameCropBottomOffset = readUEG();
}
var pixelRatio = [1, 1];
if (readBoolean()) {
// vui_parameters_present_flag
if (readBoolean()) {
// aspect_ratio_info_present_flag
var aspectRatioIdc = readUByte();
switch (aspectRatioIdc) {
case 1:
pixelRatio = [1, 1];break;
case 2:
pixelRatio = [12, 11];break;
case 3:
pixelRatio = [10, 11];break;
case 4:
pixelRatio = [16, 11];break;
case 5:
pixelRatio = [40, 33];break;
case 6:
pixelRatio = [24, 11];break;
case 7:
pixelRatio = [20, 11];break;
case 8:
pixelRatio = [32, 11];break;
case 9:
pixelRatio = [80, 33];break;
case 10:
pixelRatio = [18, 11];break;
case 11:
pixelRatio = [15, 11];break;
case 12:
pixelRatio = [64, 33];break;
case 13:
pixelRatio = [160, 99];break;
case 14:
pixelRatio = [4, 3];break;
case 15:
pixelRatio = [3, 2];break;
case 16:
pixelRatio = [2, 1];break;
case 255:
{
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
break;
}
}
}
}
return {
width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
pixelRatio: pixelRatio
};
};
ExpGolomb.prototype.readSliceType = function readSliceType() {
// skip NALu type
this.readUByte();
// discard first_mb_in_slice
this.readUEG();
// return slice_type
return this.readUEG();
};
return ExpGolomb;
}();
/* harmony default export */ var exp_golomb = (exp_golomb_ExpGolomb);
// CONCATENATED MODULE: ./src/demux/sample-aes.js
function sample_aes__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* SAMPLE-AES decrypter
*/
var sample_aes_SampleAesDecrypter = function () {
function SampleAesDecrypter(observer, config, decryptdata, discardEPB) {
sample_aes__classCallCheck(this, SampleAesDecrypter);
this.decryptdata = decryptdata;
this.discardEPB = discardEPB;
this.decrypter = new crypt_decrypter["a" /* default */](observer, config, { removePKCS7Padding: false });
}
SampleAesDecrypter.prototype.decryptBuffer = function decryptBuffer(encryptedData, callback) {
this.decrypter.decrypt(encryptedData, this.decryptdata.key.buffer, this.decryptdata.iv.buffer, callback);
};
// AAC - encrypt all full 16 bytes blocks starting from offset 16
SampleAesDecrypter.prototype.decryptAacSample = function decryptAacSample(samples, sampleIndex, callback, sync) {
var curUnit = samples[sampleIndex].unit;
var encryptedData = curUnit.subarray(16, curUnit.length - curUnit.length % 16);
var encryptedBuffer = encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length);
var localthis = this;
this.decryptBuffer(encryptedBuffer, function (decryptedData) {
decryptedData = new Uint8Array(decryptedData);
curUnit.set(decryptedData, 16);
if (!sync) {
localthis.decryptAacSamples(samples, sampleIndex + 1, callback);
}
});
};
SampleAesDecrypter.prototype.decryptAacSamples = function decryptAacSamples(samples, sampleIndex, callback) {
for (;; sampleIndex++) {
share/public_html/static/hls.js view on Meta::CPAN
// 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;
}
if (pesPts - pesDts > 60 * 90000) {
logger["b" /* logger */].warn(Math.round((pesPts - pesDts) / 90000) + 's delta between PTS and DTS, align them');
pesPts = pesDts;
}
} else {
pesDts = pesPts;
}
}
pesHdrLen = frag[8];
// 9 bytes : 6 bytes for PES header + 3 bytes for PES extension
payloadStartOffset = pesHdrLen + 9;
stream.size -= payloadStartOffset;
// reassemble PES packet
pesData = new Uint8Array(stream.size);
for (var j = 0, dataLen = data.length; j < dataLen; j++) {
frag = data[j];
var len = frag.byteLength;
if (payloadStartOffset) {
if (payloadStartOffset > len) {
// trim full frag if PES header bigger than frag
payloadStartOffset -= len;
continue;
} else {
// trim partial frag if PES header smaller than frag
frag = frag.subarray(payloadStartOffset);
len -= payloadStartOffset;
payloadStartOffset = 0;
}
}
pesData.set(frag, i);
i += len;
}
if (pesLen) {
// payload size : remove PES header + PES extension
pesLen -= pesHdrLen + 3;
}
return { data: pesData, pts: pesPts, dts: pesDts, len: pesLen };
} else {
return null;
}
};
TSDemuxer.prototype.pushAccesUnit = function pushAccesUnit(avcSample, avcTrack) {
if (avcSample.units.length && avcSample.frame) {
var samples = avcTrack.samples;
var nbSamples = samples.length;
// only push AVC sample if starting with a keyframe is not mandatory OR
// if keyframe already found in this fragment OR
// keyframe found in last fragment (track.sps) AND
// samples already appended (we already found a keyframe in this fragment) OR fragment is contiguous
if (!this.config.forceKeyFrameOnDiscontinuity || avcSample.key === true || avcTrack.sps && (nbSamples || this.contiguous)) {
avcSample.id = nbSamples;
samples.push(avcSample);
} else {
// dropped samples, track it
avcTrack.dropped++;
}
}
if (avcSample.debug.length) {
logger["b" /* logger */].log(avcSample.pts + '/' + avcSample.dts + ':' + avcSample.debug);
}
};
TSDemuxer.prototype._parseAVCPES = function _parseAVCPES(pes, last) {
var _this = this;
// logger.log('parse new PES');
var track = this._avcTrack,
units = this._parseAVCNALu(pes.data),
debug = false,
expGolombDecoder = void 0,
avcSample = this.avcSample,
push = void 0,
spsfound = false,
i = void 0,
pushAccesUnit = this.pushAccesUnit.bind(this),
createAVCSample = function createAVCSample(key, pts, dts, debug) {
return { key: key, pts: pts, dts: dts, units: [], debug: debug };
};
// free pes.data to save up some memory
pes.data = null;
// if new NAL units found and last sample still there, let's push ...
// this helps parsing streams with missing AUD (only do this if AUD never found)
if (avcSample && units.length && !track.audFound) {
pushAccesUnit(avcSample, track);
avcSample = this.avcSample = createAVCSample(false, pes.pts, pes.dts, '');
}
units.forEach(function (unit) {
switch (unit.type) {
// NDR
case 1:
push = true;
if (!avcSample) {
avcSample = _this.avcSample = createAVCSample(true, pes.pts, pes.dts, '');
}
if (debug) {
avcSample.debug += 'NDR ';
}
avcSample.frame = true;
var data = unit.data;
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
if (spsfound && data.length > 4) {
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
var sliceType = new exp_golomb(data).readSliceType();
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
// if (sliceType === 2 || sliceType === 7) {
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
avcSample.key = true;
}
}
break;
// IDR
case 5:
push = true;
// handle PES not starting with AUD
if (!avcSample) {
avcSample = _this.avcSample = createAVCSample(true, pes.pts, pes.dts, '');
}
if (debug) {
avcSample.debug += 'IDR ';
}
avcSample.key = true;
avcSample.frame = true;
break;
// SEI
case 6:
push = true;
if (debug && avcSample) {
avcSample.debug += 'SEI ';
}
expGolombDecoder = new exp_golomb(_this.discardEPB(unit.data));
// skip frameType
expGolombDecoder.readUByte();
var payloadType = 0;
var payloadSize = 0;
var endOfCaptions = false;
var b = 0;
while (!endOfCaptions && expGolombDecoder.bytesAvailable > 1) {
payloadType = 0;
do {
b = expGolombDecoder.readUByte();
payloadType += b;
} while (b === 0xFF);
// Parse payload size.
payloadSize = 0;
do {
b = expGolombDecoder.readUByte();
payloadSize += b;
} while (b === 0xFF);
// TODO: there can be more than one payload in an SEI packet...
// TODO: need to read type and size in a while loop to get them all
if (payloadType === 4 && expGolombDecoder.bytesAvailable !== 0) {
endOfCaptions = true;
var countryCode = expGolombDecoder.readUByte();
if (countryCode === 181) {
var providerCode = expGolombDecoder.readUShort();
if (providerCode === 49) {
var userStructure = expGolombDecoder.readUInt();
if (userStructure === 0x47413934) {
var userDataType = expGolombDecoder.readUByte();
// Raw CEA-608 bytes wrapped in CEA-708 packet
if (userDataType === 3) {
var firstByte = expGolombDecoder.readUByte();
var secondByte = expGolombDecoder.readUByte();
var totalCCs = 31 & firstByte;
var byteArray = [firstByte, secondByte];
for (i = 0; i < totalCCs; i++) {
// 3 bytes per CC
byteArray.push(expGolombDecoder.readUByte());
byteArray.push(expGolombDecoder.readUByte());
byteArray.push(expGolombDecoder.readUByte());
}
_this._insertSampleInOrder(_this._txtTrack.samples, { type: 3, pts: pes.pts, bytes: byteArray });
}
}
}
}
} else if (payloadSize < expGolombDecoder.bytesAvailable) {
for (i = 0; i < payloadSize; i++) {
expGolombDecoder.readUByte();
share/public_html/static/hls.js view on Meta::CPAN
_tmp.set(array, _lastUnit.data.byteLength);
_lastUnit.data = _tmp;
}
}
track.naluState = state;
return units;
};
/**
* remove Emulation Prevention bytes from a RBSP
*/
TSDemuxer.prototype.discardEPB = function discardEPB(data) {
var length = data.byteLength,
EPBPositions = [],
i = 1,
newLength = void 0,
newData = void 0;
// Find all `Emulation Prevention Bytes`
while (i < length - 2) {
if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
EPBPositions.push(i + 2);
i += 2;
} else {
i++;
}
}
// If no Emulation Prevention Bytes were found just return the original
// array
if (EPBPositions.length === 0) {
return data;
}
// Create a new array to hold the NAL unit data
newLength = length - EPBPositions.length;
newData = new Uint8Array(newLength);
var sourceIndex = 0;
for (i = 0; i < newLength; sourceIndex++, i++) {
if (sourceIndex === EPBPositions[0]) {
// Skip this byte
sourceIndex++;
// Remove this position index
EPBPositions.shift();
}
newData[i] = data[sourceIndex];
}
return newData;
};
TSDemuxer.prototype._parseAACPES = function _parseAACPES(pes) {
var track = this._audioTrack,
data = pes.data,
pts = pes.pts,
startOffset = 0,
aacOverFlow = this.aacOverFlow,
aacLastPTS = this.aacLastPTS,
frameDuration = void 0,
frameIndex = void 0,
offset = void 0,
stamp = void 0,
len = void 0;
if (aacOverFlow) {
var tmp = new Uint8Array(aacOverFlow.byteLength + data.byteLength);
tmp.set(aacOverFlow, 0);
tmp.set(data, aacOverFlow.byteLength);
// logger.log(`AAC: append overflowing ${aacOverFlow.byteLength} bytes to beginning of new PES`);
data = tmp;
}
// look for ADTS header (0xFFFx)
for (offset = startOffset, len = data.length; offset < len - 1; offset++) {
if (isHeader(data, offset)) {
break;
}
}
// if ADTS header does not start straight from the beginning of the PES payload, raise an error
if (offset) {
var reason = void 0,
fatal = void 0;
if (offset < len - 1) {
reason = 'AAC PES did not start with ADTS header,offset:' + offset;
fatal = false;
} else {
reason = 'no ADTS header found in AAC PES';
fatal = true;
}
logger["b" /* logger */].warn('parsing error:' + reason);
this.observer.trigger(events["a" /* default */].ERROR, { type: errors["b" /* ErrorTypes */].MEDIA_ERROR, details: errors["a" /* ErrorDetails */].FRAG_PARSING_ERROR, fatal: fatal, reason: reason });
if (fatal) {
return;
}
}
initTrackConfig(track, this.observer, data, offset, this.audioCodec);
frameIndex = 0;
frameDuration = getFrameDuration(track.samplerate);
// if last AAC frame is overflowing, we should ensure timestamps are contiguous:
// first sample PTS should be equal to last sample PTS + frameDuration
if (aacOverFlow && aacLastPTS) {
var newPTS = aacLastPTS + frameDuration;
if (Math.abs(newPTS - pts) > 1) {
logger["b" /* logger */].log('AAC: align PTS for overlapping frames by ' + Math.round((newPTS - pts) / 90));
pts = newPTS;
}
}
// scan for aac samples
while (offset < len) {
if (isHeader(data, offset) && offset + 5 < len) {
var frame = appendFrame(track, data, offset, pts, frameIndex);
if (frame) {
// logger.log(`${Math.round(frame.sample.pts)} : AAC`);
offset += frame.length;
stamp = frame.sample.pts;
frameIndex++;
} else {
// logger.log('Unable to parse AAC frame');
break;
}
} else {
// nothing found, keep looking
offset++;
}
}
if (offset < len) {
aacOverFlow = data.subarray(offset, len);
// logger.log(`AAC: overflow detected:${len-offset}`);
} else {
aacOverFlow = null;
}
this.aacOverFlow = aacOverFlow;
this.aacLastPTS = stamp;
};
TSDemuxer.prototype._parseMPEGPES = function _parseMPEGPES(pes) {
var data = pes.data;
var length = data.length;
var frameIndex = 0;
var offset = 0;
var pts = pes.pts;
while (offset < length) {
if (mpegaudio.isHeader(data, offset)) {
var frame = mpegaudio.appendFrame(this._audioTrack, data, offset, pts, frameIndex);
if (frame) {
offset += frame.length;
frameIndex++;
} else {
// logger.log('Unable to parse Mpeg audio frame');
break;
}
} else {
// nothing found, keep looking
offset++;
}
}
};
TSDemuxer.prototype._parseID3PES = function _parseID3PES(pes) {
this._id3Track.samples.push(pes);
};
return TSDemuxer;
}();
/* harmony default export */ var tsdemuxer = (tsdemuxer_TSDemuxer);
// CONCATENATED MODULE: ./src/demux/mp3demuxer.js
function mp3demuxer__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* MP3 demuxer
*/
var mp3demuxer_MP3Demuxer = function () {
function MP3Demuxer(observer, remuxer, config) {
mp3demuxer__classCallCheck(this, MP3Demuxer);
this.observer = observer;
this.config = config;
this.remuxer = remuxer;
}
MP3Demuxer.prototype.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, duration) {
this._audioTrack = { container: 'audio/mpeg', type: 'audio', id: -1, sequenceNumber: 0, isAAC: false, samples: [], len: 0, manifestCodec: audioCodec, duration: duration, inputTimeScale: 90000 };
};
MP3Demuxer.prototype.resetTimeStamp = function resetTimeStamp() {};
MP3Demuxer.probe = function probe(data) {
// check if data contains ID3 timestamp and MPEG sync word
var offset = void 0,
length = void 0;
var id3Data = id3["a" /* default */].getID3Data(data, 0);
if (id3Data && id3["a" /* default */].getTimeStamp(id3Data) !== undefined) {
// Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
// Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
// More info http://www.mp3-tech.org/programmer/frame_header.html
for (offset = id3Data.length, length = Math.min(data.length - 1, offset + 100); offset < length; offset++) {
if (mpegaudio.probe(data, offset)) {
logger["b" /* logger */].log('MPEG Audio sync word found !');
return true;
}
}
}
return false;
};
// feed incoming data to the front of the parsing pipeline
MP3Demuxer.prototype.append = function append(data, timeOffset, contiguous, accurateTimeOffset) {
var id3Data = id3["a" /* default */].getID3Data(data, 0);
var timestamp = id3["a" /* default */].getTimeStamp(id3Data);
var pts = timestamp ? 90 * timestamp : timeOffset * 90000;
var offset = id3Data.length;
var length = data.length;
var frameIndex = 0,
stamp = 0;
var track = this._audioTrack;
var id3Samples = [{ pts: pts, dts: pts, data: id3Data }];
while (offset < length) {
if (mpegaudio.isHeader(data, offset)) {
var frame = mpegaudio.appendFrame(track, data, offset, pts, frameIndex);
if (frame) {
offset += frame.length;
stamp = frame.sample.pts;
frameIndex++;
} else {
// logger.log('Unable to parse Mpeg audio frame');
break;
}
} else if (id3["a" /* default */].isHeader(data, offset)) {
id3Data = id3["a" /* default */].getID3Data(data, offset);
id3Samples.push({ pts: stamp, dts: stamp, data: id3Data });
offset += id3Data.length;
} else {
// nothing found, keep looking
offset++;
}
}
this.remuxer.remux(track, { samples: [] }, { samples: id3Samples, inputTimeScale: 90000 }, { samples: [] }, timeOffset, contiguous, accurateTimeOffset);
};
MP3Demuxer.prototype.destroy = function destroy() {};
return MP3Demuxer;
}();
/* harmony default export */ var mp3demuxer = (mp3demuxer_MP3Demuxer);
// CONCATENATED MODULE: ./src/remux/aac-helper.js
function aac_helper__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* AAC helper
*/
var AAC = function () {
function AAC() {
aac_helper__classCallCheck(this, AAC);
}
AAC.getSilentFrame = function getSilentFrame(codec, channelCount) {
switch (codec) {
case 'mp4a.40.2':
if (channelCount === 1) {
return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]);
} else if (channelCount === 2) {
return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]);
} else if (channelCount === 3) {
return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]);
} else if (channelCount === 4) {
return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]);
} else if (channelCount === 5) {
return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]);
} else if (channelCount === 6) {
return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2, 0x00, 0x20, 0x08, 0xe0]);
}
break;
// handle HE-AAC below (mp4a.40.5 / mp4a.40.29)
default:
if (channelCount === 1) {
// ffmpeg -y -f lavfi -i "aevalsrc=0:d=0.05" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac
return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x...
} else if (channelCount === 2) {
// ffmpeg -y -f lavfi -i "aevalsrc=0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac
return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a...
} else if (channelCount === 3) {
share/public_html/static/hls.js view on Meta::CPAN
return MP4.box(MP4.types.sdtp, bytes);
};
MP4.stbl = function 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));
};
MP4.avc1 = function avc1(track) {
var sps = [],
pps = [],
i = void 0,
data = void 0,
len = void 0;
// 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 = sps.concat(Array.prototype.slice.call(data));
}
// 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"
width = track.width,
height = track.height,
hSpacing = track.pixelRatio[0],
vSpacing = track.pixelRatio[1];
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
width >> 8 & 0xFF, width & 0xff, // width
height >> 8 & 0xFF, height & 0xff, // height
0x00, 0x48, 0x00, 0x00, // horizresolution
0x00, 0x48, 0x00, 0x00, // vertresolution
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x01, // frame_count
0x12, 0x64, 0x61, 0x69, 0x6C, // dailymotion/hls.js
0x79, 0x6D, 0x6F, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x68, 0x6C, 0x73, 0x2E, 0x6A, 0x73, 0x00, 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
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24, // hSpacing
hSpacing >> 16 & 0xFF, hSpacing >> 8 & 0xFF, hSpacing & 0xFF, vSpacing >> 24, // vSpacing
vSpacing >> 16 & 0xFF, vSpacing >> 8 & 0xFF, vSpacing & 0xFF])));
};
MP4.esds = function esds(track) {
var configlen = track.config.length;
return new Uint8Array([0x00, // version 0
0x00, 0x00, 0x00, // flags
0x03, // descriptor_type
0x17 + configlen, // length
0x00, 0x01, // es_id
0x00, // stream_priority
0x04, // descriptor_type
0x0f + configlen, // 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([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor
};
MP4.mp4a = function mp4a(track) {
var samplerate = track.samplerate;
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
samplerate >> 8 & 0xFF, samplerate & 0xff, //
0x00, 0x00]), MP4.box(MP4.types.esds, MP4.esds(track)));
};
MP4.mp3 = function mp3(track) {
var samplerate = track.samplerate;
return MP4.box(MP4.types['.mp3'], 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
samplerate >> 8 & 0xFF, samplerate & 0xff, //
0x00, 0x00]));
};
share/public_html/static/hls.js view on Meta::CPAN
logger["b" /* logger */].warn('regenerate InitSegment as audio detected');
this.generateIS(audioTrack, videoTrack, timeOffset);
}
var audioData = this.remuxAudio(audioTrack, audioTimeOffset, contiguous, accurateTimeOffset);
// logger.log('nb AVC samples:' + videoTrack.samples.length);
if (nbVideoSamples) {
var audioTrackLength = void 0;
if (audioData) {
audioTrackLength = audioData.endPTS - audioData.startPTS;
}
// if initSegment was generated without video samples, regenerate it again
if (!videoTrack.timescale) {
logger["b" /* logger */].warn('regenerate InitSegment as video detected');
this.generateIS(audioTrack, videoTrack, timeOffset);
}
this.remuxVideo(videoTrack, videoTimeOffset, contiguous, audioTrackLength, accurateTimeOffset);
}
} else {
// logger.log('nb AVC samples:' + videoTrack.samples.length);
if (nbVideoSamples) {
var videoData = this.remuxVideo(videoTrack, videoTimeOffset, contiguous, 0, accurateTimeOffset);
if (videoData && audioTrack.codec) {
this.remuxEmptyAudio(audioTrack, audioTimeOffset, contiguous, videoData);
}
}
}
}
// logger.log('nb ID3 samples:' + audioTrack.samples.length);
if (id3Track.samples.length) {
this.remuxID3(id3Track, timeOffset);
}
// logger.log('nb ID3 samples:' + audioTrack.samples.length);
if (textTrack.samples.length) {
this.remuxText(textTrack, timeOffset);
}
// notify end of parsing
this.observer.trigger(events["a" /* default */].FRAG_PARSED);
};
MP4Remuxer.prototype.generateIS = function generateIS(audioTrack, videoTrack, timeOffset) {
var observer = this.observer,
audioSamples = audioTrack.samples,
videoSamples = videoTrack.samples,
typeSupported = this.typeSupported,
container = 'audio/mp4',
tracks = {},
data = { tracks: tracks },
computePTSDTS = this._initPTS === undefined,
initPTS = void 0,
initDTS = void 0;
if (computePTSDTS) {
initPTS = initDTS = Infinity;
}
if (audioTrack.config && audioSamples.length) {
// let's use audio sampling rate as MP4 time scale.
// rationale is that there is a integer nb of audio frames per audio sample (1024 for AAC)
// using audio sampling rate here helps having an integer MP4 frame duration
// this avoids potential rounding issue and AV sync issue
audioTrack.timescale = audioTrack.samplerate;
logger["b" /* logger */].log('audio sampling rate : ' + audioTrack.samplerate);
if (!audioTrack.isAAC) {
if (typeSupported.mpeg) {
// Chrome and Safari
container = 'audio/mpeg';
audioTrack.codec = '';
} else if (typeSupported.mp3) {
// Firefox
audioTrack.codec = 'mp3';
}
}
tracks.audio = {
container: container,
codec: audioTrack.codec,
initSegment: !audioTrack.isAAC && typeSupported.mpeg ? new Uint8Array() : mp4_generator.initSegment([audioTrack]),
metadata: {
channelCount: audioTrack.channelCount
}
};
if (computePTSDTS) {
// remember first PTS of this demuxing context. for audio, PTS = DTS
initPTS = initDTS = audioSamples[0].pts - audioTrack.inputTimeScale * timeOffset;
}
}
if (videoTrack.sps && videoTrack.pps && videoSamples.length) {
// let's use input time scale as MP4 video timescale
// we use input time scale straight away to avoid rounding issues on frame duration / cts computation
var inputTimeScale = videoTrack.inputTimeScale;
videoTrack.timescale = inputTimeScale;
tracks.video = {
container: 'video/mp4',
codec: videoTrack.codec,
initSegment: mp4_generator.initSegment([videoTrack]),
metadata: {
width: videoTrack.width,
height: videoTrack.height
}
};
if (computePTSDTS) {
initPTS = Math.min(initPTS, videoSamples[0].pts - inputTimeScale * timeOffset);
initDTS = Math.min(initDTS, videoSamples[0].dts - inputTimeScale * timeOffset);
this.observer.trigger(events["a" /* default */].INIT_PTS_FOUND, { initPTS: initPTS });
}
}
if (Object.keys(tracks).length) {
observer.trigger(events["a" /* default */].FRAG_PARSING_INIT_SEGMENT, data);
this.ISGenerated = true;
if (computePTSDTS) {
this._initPTS = initPTS;
this._initDTS = initDTS;
}
} else {
observer.trigger(events["a" /* default */].ERROR, { type: errors["b" /* ErrorTypes */].MEDIA_ERROR, details: errors["a" /* ErrorDetails */].FRAG_PARSING_ERROR, fatal: false, reason: 'no audio/video samples found' });
}
};
MP4Remuxer.prototype.remuxVideo = function remuxVideo(track, timeOffset, contiguous, audioTrackLength, accurateTimeOffset) {
var offset = 8,
timeScale = track.timescale,
mp4SampleDuration = void 0,
mdat = void 0,
moof = void 0,
firstPTS = void 0,
firstDTS = void 0,
nextDTS = void 0,
lastPTS = void 0,
lastDTS = void 0,
inputSamples = track.samples,
outputSamples = [],
nbSamples = inputSamples.length,
ptsNormalize = this._PTSNormalize,
initDTS = this._initDTS;
// for (let i = 0; i < track.samples.length; i++) {
// let avcSample = track.samples[i];
// let units = avcSample.units;
// let unitsString = '';
// for (let j = 0; j < units.length ; j++) {
// unitsString += units[j].type + ',';
// if (units[j].data.length < 500) {
// unitsString += Hex.hexDump(units[j].data);
// }
// }
// logger.log(avcSample.pts + '/' + avcSample.dts + ',' + unitsString + avcSample.units.length);
// }
share/public_html/static/hls.js view on Meta::CPAN
}
naluLen += sampleLen;
nbNalu += nbUnits;
_sample.length = sampleLen;
// normalize PTS/DTS
if (isSafari) {
// sample DTS is computed using a constant decoding offset (mp4SampleDuration) between samples
_sample.dts = firstDTS + _i * mp4SampleDuration;
} else {
// ensure sample monotonic DTS
_sample.dts = Math.max(_sample.dts, firstDTS);
}
// ensure that computed value is greater or equal than sample DTS
_sample.pts = Math.max(_sample.pts, _sample.dts);
}
/* concatenate the video data and construct the mdat in place
(need 8 more bytes to fill length and mpdat type) */
var mdatSize = naluLen + 4 * nbNalu + 8;
try {
mdat = new Uint8Array(mdatSize);
} catch (err) {
this.observer.trigger(events["a" /* default */].ERROR, { type: errors["b" /* ErrorTypes */].MUX_ERROR, details: errors["a" /* ErrorDetails */].REMUX_ALLOC_ERROR, fatal: false, bytes: mdatSize, reason: 'fail allocating video mdat ' + mdatSize })...
return;
}
var view = new DataView(mdat.buffer);
view.setUint32(0, mdatSize);
mdat.set(mp4_generator.types.mdat, 4);
for (var _i2 = 0; _i2 < nbSamples; _i2++) {
var avcSample = inputSamples[_i2],
avcSampleUnits = avcSample.units,
mp4SampleLength = 0,
compositionTimeOffset = void 0;
// convert NALU bitstream to MP4 format (prepend NALU with size field)
for (var _j = 0, _nbUnits = avcSampleUnits.length; _j < _nbUnits; _j++) {
var unit = avcSampleUnits[_j],
unitData = unit.data,
unitDataLen = unit.data.byteLength;
view.setUint32(offset, unitDataLen);
offset += 4;
mdat.set(unitData, offset);
offset += unitDataLen;
mp4SampleLength += 4 + unitDataLen;
}
if (!isSafari) {
// expected sample duration is the Decoding Timestamp diff of consecutive samples
if (_i2 < nbSamples - 1) {
mp4SampleDuration = inputSamples[_i2 + 1].dts - avcSample.dts;
} else {
var config = this.config,
lastFrameDuration = avcSample.dts - inputSamples[_i2 > 0 ? _i2 - 1 : _i2].dts;
if (config.stretchShortVideoTrack) {
// In some cases, a segment's audio track duration may exceed the video track duration.
// Since we've already remuxed audio, and we know how long the audio track is, we look to
// see if the delta to the next segment is longer than maxBufferHole.
// If so, playback would potentially get stuck, so we artificially inflate
// the duration of the last frame to minimize any potential gap between segments.
var maxBufferHole = config.maxBufferHole,
gapTolerance = Math.floor(maxBufferHole * timeScale),
deltaToFrameEnd = (audioTrackLength ? firstPTS + audioTrackLength * timeScale : this.nextAudioPts) - avcSample.pts;
if (deltaToFrameEnd > gapTolerance) {
// We subtract lastFrameDuration from deltaToFrameEnd to try to prevent any video
// frame overlap. maxBufferHole should be >> lastFrameDuration anyway.
mp4SampleDuration = deltaToFrameEnd - lastFrameDuration;
if (mp4SampleDuration < 0) {
mp4SampleDuration = lastFrameDuration;
}
logger["b" /* logger */].log('It is approximately ' + deltaToFrameEnd / 90 + ' ms to the next segment; using duration ' + mp4SampleDuration / 90 + ' ms for the last video frame.');
} else {
mp4SampleDuration = lastFrameDuration;
}
} else {
mp4SampleDuration = lastFrameDuration;
}
}
compositionTimeOffset = Math.round(avcSample.pts - avcSample.dts);
} else {
compositionTimeOffset = Math.max(0, mp4SampleDuration * Math.round((avcSample.pts - avcSample.dts) / mp4SampleDuration));
}
// console.log('PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${avcSample.pts}/${avcSample.dts}/${initDTS}/${ptsnorm}/${dtsnorm}/${(avcSample.pts/4294967296).toFixed(3)}');
outputSamples.push({
size: mp4SampleLength,
// constant duration
duration: mp4SampleDuration,
cts: compositionTimeOffset,
flags: {
isLeading: 0,
isDependedOn: 0,
hasRedundancy: 0,
degradPrio: 0,
dependsOn: avcSample.key ? 2 : 1,
isNonSync: avcSample.key ? 0 : 1
}
});
}
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
this.nextAvcDts = lastDTS + mp4SampleDuration;
var dropped = track.dropped;
track.len = 0;
track.nbNalu = 0;
track.dropped = 0;
if (outputSamples.length && navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
var flags = outputSamples[0].flags;
// 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
flags.dependsOn = 2;
flags.isNonSync = 0;
}
track.samples = outputSamples;
moof = mp4_generator.moof(track.sequenceNumber++, firstDTS, track);
track.samples = [];
var data = {
data1: moof,
data2: mdat,
startPTS: firstPTS / timeScale,
endPTS: (lastPTS + mp4SampleDuration) / timeScale,
startDTS: firstDTS / timeScale,
endDTS: this.nextAvcDts / timeScale,
type: 'video',
hasAudio: false,
hasVideo: true,
nb: outputSamples.length,
dropped: dropped
};
this.observer.trigger(events["a" /* default */].FRAG_PARSING_DATA, data);
return data;
};
MP4Remuxer.prototype.remuxAudio = function remuxAudio(track, timeOffset, contiguous, accurateTimeOffset) {
var inputTimeScale = track.inputTimeScale,
mp4timeScale = track.timescale,
scaleFactor = inputTimeScale / mp4timeScale,
mp4SampleDuration = track.isAAC ? 1024 : 1152,
inputSampleDuration = mp4SampleDuration * scaleFactor,
ptsNormalize = this._PTSNormalize,
initDTS = this._initDTS,
rawMPEG = !track.isAAC && this.typeSupported.mpeg;
var offset = void 0,
mp4Sample = void 0,
fillFrame = void 0,
mdat = void 0,
moof = void 0,
firstPTS = void 0,
lastPTS = void 0,
inputSamples = track.samples,
outputSamples = [],
nextAudioPts = this.nextAudioPts;
// for audio samples, also consider consecutive fragments as being contiguous (even if a level switch occurs),
// for sake of clarity:
// consecutive fragments are frags with
// - less than 100ms gaps between new time offset (if accurate) and next expected PTS OR
// - less than 20 audio frames distance
// contiguous fragments are consecutive fragments from same quality level (same level, new SN = old SN + 1)
// this helps ensuring audio continuity
// and this also avoids audio glitches/cut when switching quality, or reporting wrong duration on first audio frame
contiguous |= inputSamples.length && nextAudioPts && (accurateTimeOffset && Math.abs(timeOffset - nextAudioPts / inputTimeScale) < 0.1 || Math.abs(inputSamples[0].pts - nextAudioPts - initDTS) < 20 * inputSampleDuration);
// compute normalized PTS
inputSamples.forEach(function (sample) {
sample.pts = sample.dts = ptsNormalize(sample.pts - initDTS, timeOffset * inputTimeScale);
});
// filter out sample with negative PTS that are not playable anyway
// if we don't remove these negative samples, they will shift all audio samples forward.
// leading to audio overlap between current / next fragment
inputSamples = inputSamples.filter(function (sample) {
return sample.pts >= 0;
});
// in case all samples have negative PTS, and have been filtered out, return now
if (inputSamples.length === 0) {
return;
}
if (!contiguous) {
if (!accurateTimeOffset) {
// if frag are mot contiguous and if we cant trust time offset, let's use first sample PTS as next audio PTS
nextAudioPts = inputSamples[0].pts;
} else {
// if timeOffset is accurate, let's use it as predicted next audio PTS
nextAudioPts = timeOffset * inputTimeScale;
}
}
// If the audio track is missing samples, the frames seem to get "left-shifted" within the
// resulting mp4 segment, causing sync issues and leaving gaps at the end of the audio segment.
// In an effort to prevent this from happening, we inject frames here where there are gaps.
// When possible, we inject a silent frame; when that's not possible, we duplicate the last
// frame.
if (track.isAAC) {
var maxAudioFramesDrift = this.config.maxAudioFramesDrift;
for (var i = 0, nextPts = nextAudioPts; i < inputSamples.length;) {
// First, let's see how far off this frame is from where we expect it to be
var sample = inputSamples[i],
delta;
var pts = sample.pts;
delta = pts - nextPts;
var duration = Math.abs(1000 * delta / inputTimeScale);
// If we're overlapping by more than a duration, drop this sample
if (delta <= -maxAudioFramesDrift * inputSampleDuration) {
logger["b" /* logger */].warn('Dropping 1 audio frame @ ' + (nextPts / inputTimeScale).toFixed(3) + 's due to ' + Math.round(duration) + ' ms overlap.');
inputSamples.splice(i, 1);
track.len -= sample.unit.length;
// Don't touch nextPtsNorm or i
} // eslint-disable-line brace-style
// Insert missing frames if:
// 1: We're more than maxAudioFramesDrift frame away
// 2: Not more than MAX_SILENT_FRAME_DURATION away
// 3: currentTime (aka nextPtsNorm) is not 0
else if (delta >= maxAudioFramesDrift * inputSampleDuration && duration < MAX_SILENT_FRAME_DURATION && nextPts) {
var missing = Math.round(delta / inputSampleDuration);
logger["b" /* logger */].warn('Injecting ' + missing + ' audio frame @ ' + (nextPts / inputTimeScale).toFixed(3) + 's due to ' + Math.round(1000 * delta / inputTimeScale) + ' ms gap.');
for (var j = 0; j < missing; j++) {
var newStamp = Math.max(nextPts, 0);
fillFrame = aac_helper.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
if (!fillFrame) {
logger["b" /* logger */].log('Unable to get silent frame for given audio codec; duplicating last frame instead.');
fillFrame = sample.unit.subarray();
}
inputSamples.splice(i, 0, { unit: fillFrame, pts: newStamp, dts: newStamp });
track.len += fillFrame.length;
nextPts += inputSampleDuration;
i++;
}
// Adjust sample to next expected pts
sample.pts = sample.dts = nextPts;
nextPts += inputSampleDuration;
i++;
} else {
// Otherwise, just adjust pts
if (Math.abs(delta) > 0.1 * inputSampleDuration) {
// logger.log(`Invalid frame delta ${Math.round(delta + inputSampleDuration)} at PTS ${Math.round(pts / 90)} (should be ${Math.round(inputSampleDuration)}).`);
}
sample.pts = sample.dts = nextPts;
nextPts += inputSampleDuration;
i++;
}
}
}
for (var _j2 = 0, _nbSamples = inputSamples.length; _j2 < _nbSamples; _j2++) {
var audioSample = inputSamples[_j2];
var unit = audioSample.unit;
var _pts = audioSample.pts;
// logger.log(`Audio/PTS:${Math.round(pts/90)}`);
// if not first sample
if (lastPTS !== undefined) {
mp4Sample.duration = Math.round((_pts - lastPTS) / scaleFactor);
} else {
var _delta = Math.round(1000 * (_pts - nextAudioPts) / inputTimeScale),
numMissingFrames = 0;
// if fragment are contiguous, detect hole/overlapping between fragments
// contiguous fragments are consecutive fragments from same quality level (same level, new SN = old SN + 1)
if (contiguous && track.isAAC) {
// log delta
if (_delta) {
if (_delta > 0 && _delta < MAX_SILENT_FRAME_DURATION) {
numMissingFrames = Math.round((_pts - nextAudioPts) / inputSampleDuration);
logger["b" /* logger */].log(_delta + ' ms hole between AAC samples detected,filling it');
if (numMissingFrames > 0) {
fillFrame = aac_helper.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
if (!fillFrame) {
fillFrame = unit.subarray();
}
track.len += numMissingFrames * fillFrame.length;
}
// if we have frame overlap, overlapping for more than half a frame duraion
} else if (_delta < -12) {
// drop overlapping audio frames... browser will deal with it
logger["b" /* logger */].log('drop overlapping AAC sample, expected/parsed/delta:' + (nextAudioPts / inputTimeScale).toFixed(3) + 's/' + (_pts / inputTimeScale).toFixed(3) + 's/' + -_delta + 'ms');
track.len -= unit.byteLength;
continue;
}
// set PTS/DTS to expected PTS/DTS
_pts = nextAudioPts;
}
}
// remember first PTS of our audioSamples
firstPTS = _pts;
if (track.len > 0) {
/* concatenate the audio data and construct the mdat in place
(need 8 more bytes to fill length and mdat type) */
var mdatSize = rawMPEG ? track.len : track.len + 8;
offset = rawMPEG ? 0 : 8;
try {
mdat = new Uint8Array(mdatSize);
} catch (err) {
this.observer.trigger(events["a" /* default */].ERROR, { type: errors["b" /* ErrorTypes */].MUX_ERROR, details: errors["a" /* ErrorDetails */].REMUX_ALLOC_ERROR, fatal: false, bytes: mdatSize, reason: 'fail allocating audio mdat ' + mdatS...
return;
}
if (!rawMPEG) {
var view = new DataView(mdat.buffer);
view.setUint32(0, mdatSize);
mdat.set(mp4_generator.types.mdat, 4);
}
} else {
// no audio samples
return;
}
for (var _i3 = 0; _i3 < numMissingFrames; _i3++) {
fillFrame = aac_helper.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
if (!fillFrame) {
logger["b" /* logger */].log('Unable to get silent frame for given audio codec; duplicating this frame instead.');
fillFrame = unit.subarray();
}
mdat.set(fillFrame, offset);
offset += fillFrame.byteLength;
mp4Sample = {
size: fillFrame.byteLength,
cts: 0,
duration: 1024,
flags: {
isLeading: 0,
isDependedOn: 0,
hasRedundancy: 0,
degradPrio: 0,
dependsOn: 1
}
};
outputSamples.push(mp4Sample);
}
}
mdat.set(unit, offset);
var unitLen = unit.byteLength;
offset += unitLen;
// console.log('PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${audioSample.pts}/${audioSample.dts}/${initDTS}/${ptsnorm}/${dtsnorm}/${(audioSample.pts/4294967296).toFixed(3)}');
mp4Sample = {
size: unitLen,
cts: 0,
duration: 0,
flags: {
isLeading: 0,
isDependedOn: 0,
hasRedundancy: 0,
degradPrio: 0,
dependsOn: 1
}
};
outputSamples.push(mp4Sample);
lastPTS = _pts;
}
var lastSampleDuration = 0;
var nbSamples = outputSamples.length;
// set last sample duration as being identical to previous sample
if (nbSamples >= 2) {
lastSampleDuration = outputSamples[nbSamples - 2].duration;
mp4Sample.duration = lastSampleDuration;
}
if (nbSamples) {
// next audio sample PTS should be equal to last sample PTS + duration
this.nextAudioPts = nextAudioPts = lastPTS + scaleFactor * lastSampleDuration;
// logger.log('Audio/PTS/PTSend:' + audioSample.pts.toFixed(0) + '/' + this.nextAacDts.toFixed(0));
track.len = 0;
track.samples = outputSamples;
if (rawMPEG) {
moof = new Uint8Array();
} else {
moof = mp4_generator.moof(track.sequenceNumber++, firstPTS / scaleFactor, track);
}
track.samples = [];
var start = firstPTS / inputTimeScale;
var end = nextAudioPts / inputTimeScale;
var audioData = {
data1: moof,
data2: mdat,
startPTS: start,
endPTS: end,
startDTS: start,
endDTS: end,
type: 'audio',
hasAudio: true,
hasVideo: false,
nb: nbSamples
};
this.observer.trigger(events["a" /* default */].FRAG_PARSING_DATA, audioData);
return audioData;
}
return null;
};
MP4Remuxer.prototype.remuxEmptyAudio = function remuxEmptyAudio(track, timeOffset, contiguous, videoData) {
var inputTimeScale = track.inputTimeScale,
mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale,
scaleFactor = inputTimeScale / mp4timeScale,
nextAudioPts = this.nextAudioPts,
// sync with video's timestamp
startDTS = (nextAudioPts !== undefined ? nextAudioPts : videoData.startDTS * inputTimeScale) + this._initDTS,
endDTS = videoData.endDTS * inputTimeScale + this._initDTS,
// one sample's duration value
sampleDuration = 1024,
frameDuration = scaleFactor * sampleDuration,
// samples count of this segment's duration
nbSamples = Math.ceil((endDTS - startDTS) / frameDuration),
// silent frame
silentFrame = aac_helper.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
logger["b" /* logger */].warn('remux empty Audio');
// Can't remux if we can't generate a silent frame...
if (!silentFrame) {
logger["b" /* logger */].trace('Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec!');
return;
}
var samples = [];
for (var i = 0; i < nbSamples; i++) {
var stamp = startDTS + i * frameDuration;
samples.push({ unit: silentFrame, pts: stamp, dts: stamp });
track.len += silentFrame.length;
}
track.samples = samples;
this.remuxAudio(track, timeOffset, contiguous);
};
MP4Remuxer.prototype.remuxID3 = function remuxID3(track, timeOffset) {
var length = track.samples.length,
sample = void 0;
var inputTimeScale = track.inputTimeScale;
var initPTS = this._initPTS;
var initDTS = this._initDTS;
// 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 - initPTS) / inputTimeScale;
sample.dts = (sample.dts - initDTS) / inputTimeScale;
}
this.observer.trigger(events["a" /* default */].FRAG_PARSING_METADATA, {
samples: track.samples
});
}
track.samples = [];
timeOffset = timeOffset;
};
MP4Remuxer.prototype.remuxText = function remuxText(track, timeOffset) {
track.samples.sort(function (a, b) {
return a.pts - b.pts;
});
var length = track.samples.length,
sample = void 0;
var inputTimeScale = track.inputTimeScale;
var initPTS = this._initPTS;
// consume samples
if (length) {
for (var index = 0; index < length; index++) {
sample = track.samples[index];
// setting text pts, dts to relative time
// using this._initPTS and this._initDTS to calculate relative time
sample.pts = (sample.pts - initPTS) / inputTimeScale;
}
this.observer.trigger(events["a" /* default */].FRAG_PARSING_USERDATA, {
samples: track.samples
});
}
track.samples = [];
timeOffset = timeOffset;
};
MP4Remuxer.prototype._PTSNormalize = function _PTSNormalize(value, reference) {
var offset = void 0;
share/public_html/static/hls.js view on Meta::CPAN
var fragment_tracker_FragmentTracker = function (_EventHandler) {
fragment_tracker__inherits(FragmentTracker, _EventHandler);
function FragmentTracker(hls) {
fragment_tracker__classCallCheck(this, FragmentTracker);
var _this = fragment_tracker__possibleConstructorReturn(this, _EventHandler.call(this, hls, events["a" /* default */].BUFFER_APPENDED, events["a" /* default */].FRAG_BUFFERED, events["a" /* default */].FRAG_LOADED));
_this.bufferPadding = 0.2;
_this.fragments = Object.create(null);
_this.timeRanges = Object.create(null);
_this.config = hls.config;
return _this;
}
FragmentTracker.prototype.destroy = function destroy() {
this.fragments = null;
this.timeRanges = null;
this.config = null;
event_handler.prototype.destroy.call(this);
_EventHandler.prototype.destroy.call(this);
};
/**
* Return a Fragment that match the position and levelType.
* If not found any Fragment, return null
* @param {number} position
* @param {LevelType} levelType
* @returns {Fragment|null}
*/
FragmentTracker.prototype.getBufferedFrag = function getBufferedFrag(position, levelType) {
var fragments = this.fragments;
var bufferedFrags = Object.keys(fragments).filter(function (key) {
var fragmentEntity = fragments[key];
if (fragmentEntity.body.type !== levelType) {
return false;
}
if (!fragmentEntity.buffered) {
return false;
}
var frag = fragmentEntity.body;
return frag.startPTS <= position && position <= frag.endPTS;
});
if (bufferedFrags.length === 0) {
return null;
} else {
// https://github.com/video-dev/hls.js/pull/1545#discussion_r166229566
var bufferedFragKey = bufferedFrags.pop();
return fragments[bufferedFragKey].body;
}
};
/**
* Partial fragments effected by coded frame eviction will be removed
* The browser will unload parts of the buffer to free up memory for new buffer data
* Fragments will need to be reloaded when the buffer is freed up, removing partial fragments will allow them to reload(since there might be parts that are still playable)
* @param {String} elementaryStream The elementaryStream of media this is (eg. video/audio)
* @param {TimeRanges} timeRange TimeRange object from a sourceBuffer
*/
FragmentTracker.prototype.detectEvictedFragments = function detectEvictedFragments(elementaryStream, timeRange) {
var _this2 = this;
var fragmentTimes = void 0,
time = void 0;
// Check if any flagged fragments have been unloaded
Object.keys(this.fragments).forEach(function (key) {
var fragmentEntity = _this2.fragments[key];
if (fragmentEntity.buffered === true) {
var esData = fragmentEntity.range[elementaryStream];
if (esData) {
fragmentTimes = esData.time;
for (var i = 0; i < fragmentTimes.length; i++) {
time = fragmentTimes[i];
if (_this2.isTimeBuffered(time.startPTS, time.endPTS, timeRange) === false) {
// Unregister partial fragment as it needs to load again to be reused
_this2.removeFragment(fragmentEntity.body);
break;
}
}
}
}
});
};
/**
* Checks if the fragment passed in is loaded in the buffer properly
* Partially loaded fragments will be registered as a partial fragment
* @param {Object} fragment Check the fragment against all sourceBuffers loaded
*/
FragmentTracker.prototype.detectPartialFragments = function detectPartialFragments(fragment) {
var _this3 = this;
var fragKey = this.getFragmentKey(fragment);
var fragmentEntity = this.fragments[fragKey];
if (fragmentEntity) {
fragmentEntity.buffered = true;
Object.keys(this.timeRanges).forEach(function (elementaryStream) {
if (fragment.hasElementaryStream(elementaryStream) === true) {
var timeRange = _this3.timeRanges[elementaryStream];
// Check for malformed fragments
// Gaps need to be calculated for each elementaryStream
fragmentEntity.range[elementaryStream] = _this3.getBufferedTimes(fragment.startPTS, fragment.endPTS, timeRange);
}
});
}
};
FragmentTracker.prototype.getBufferedTimes = function getBufferedTimes(startPTS, endPTS, timeRange) {
share/public_html/static/hls.js view on Meta::CPAN
return fragPrevious.cc - frag.cc;
});
if (frag) {
logger["b" /* logger */].log('live playlist, switching playlist, load frag with same CC: ' + frag.sn);
}
}
} else {
// Relies on PDT in order to switch bitrates (Support EXT-X-DISCONTINUITY without EXT-X-DISCONTINUITY-SEQUENCE)
frag = findFragmentByPDT(fragments, fragPrevious.endPdt + 1);
}
}
if (!frag) {
/* we have no idea about which fragment should be loaded.
so let's load mid fragment. it will help computing playlist sliding and find the right one
*/
frag = fragments[Math.min(fragLen - 1, Math.round(fragLen / 2))];
logger["b" /* logger */].log('live playlist, switching playlist, unknown, load middle frag : ' + frag.sn);
}
}
return frag;
};
StreamController.prototype._findFragment = function _findFragment(start, fragPrevious, fragLen, fragments, bufferEnd, end, levelDetails) {
var config = this.hls.config;
var fragBySN = function fragBySN() {
return findFragmentBySN(fragPrevious, fragments, bufferEnd, end, config.maxFragLookUpTolerance);
};
var frag = void 0;
var foundFrag = void 0;
if (bufferEnd < end) {
if (!levelDetails.programDateTime) {
// Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
foundFrag = findFragmentBySN(fragPrevious, fragments, bufferEnd, end, config.maxFragLookUpTolerance);
} else {
// Relies on PDT in order to switch bitrates (Support EXT-X-DISCONTINUITY without EXT-X-DISCONTINUITY-SEQUENCE)
foundFrag = findFragmentByPDT(fragments, calculateNextPDT(start, bufferEnd, levelDetails));
if (!foundFrag || fragment_finders_fragmentWithinToleranceTest(bufferEnd, config.maxFragLookUpTolerance, foundFrag)) {
// Fall back to SN order if finding by PDT returns a frag which won't fit within the stream
// fragmentWithToleranceTest returns 0 if the frag is within tolerance; 1 or -1 otherwise
logger["b" /* logger */].warn('Frag found by PDT search did not fit within tolerance; falling back to finding by SN');
foundFrag = fragBySN();
}
}
} else {
// reach end of playlist
foundFrag = fragments[fragLen - 1];
}
if (foundFrag) {
frag = foundFrag;
var curSNIdx = frag.sn - levelDetails.startSN;
var sameLevel = fragPrevious && frag.level === fragPrevious.level;
var prevFrag = fragments[curSNIdx - 1];
var nextFrag = fragments[curSNIdx + 1];
// logger.log('find SN matching with pos:' + bufferEnd + ':' + frag.sn);
if (fragPrevious && frag.sn === fragPrevious.sn) {
if (sameLevel && !frag.backtracked) {
if (frag.sn < levelDetails.endSN) {
var deltaPTS = fragPrevious.deltaPTS;
// if there is a significant delta between audio and video, larger than max allowed hole,
// and if previous remuxed fragment did not start with a keyframe. (fragPrevious.dropped)
// let's try to load previous fragment again to get last keyframe
// then we will reload again current fragment (that way we should be able to fill the buffer hole ...)
if (deltaPTS && deltaPTS > config.maxBufferHole && fragPrevious.dropped && curSNIdx) {
frag = prevFrag;
logger["b" /* logger */].warn('SN just loaded, with large PTS gap between audio and video, maybe frag is not starting with a keyframe ? load previous one to try to overcome this');
} else {
frag = nextFrag;
logger["b" /* logger */].log('SN just loaded, load next one: ' + frag.sn);
}
} else {
frag = null;
}
} else if (frag.backtracked) {
// Only backtrack a max of 1 consecutive fragment to prevent sliding back too far when little or no frags start with keyframes
if (nextFrag && nextFrag.backtracked) {
logger["b" /* logger */].warn('Already backtracked from fragment ' + nextFrag.sn + ', will not backtrack to fragment ' + frag.sn + '. Loading fragment ' + nextFrag.sn);
frag = nextFrag;
} else {
// If a fragment has dropped frames and it's in a same level/sequence, load the previous fragment to try and find the keyframe
// Reset the dropped count now since it won't be reset until we parse the fragment again, which prevents infinite backtracking on the same segment
logger["b" /* logger */].warn('Loaded fragment with dropped frames, backtracking 1 segment to find a keyframe');
frag.dropped = 0;
if (prevFrag) {
frag = prevFrag;
frag.backtracked = true;
} else if (curSNIdx) {
// can't backtrack on very first fragment
frag = null;
}
}
}
}
}
return frag;
};
StreamController.prototype._loadKey = function _loadKey(frag) {
this.state = State.KEY_LOADING;
this.hls.trigger(events["a" /* default */].KEY_LOADING, { frag: frag });
};
StreamController.prototype._loadFragment = function _loadFragment(frag) {
// Check if fragment is not loaded
var fragState = this.fragmentTracker.getState(frag);
this.fragCurrent = frag;
this.startFragRequested = true;
// Don't update nextLoadPosition for fragments which are not buffered
if (!isNaN(frag.sn) && !frag.bitrateTest) {
this.nextLoadPosition = frag.start + frag.duration;
}
// Allow backtracked fragments to load
if (frag.backtracked || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
frag.autoLevel = this.hls.autoLevelEnabled;
frag.bitrateTest = this.bitrateTest;
this.hls.trigger(events["a" /* default */].FRAG_LOADING, { frag: frag });
// lazy demuxer init, as this could take some time ... do it during frag loading
if (!this.demuxer) {
this.demuxer = new demux_demuxer(this.hls, 'main');
}
this.state = State.FRAG_LOADING;
} else if (fragState === FragmentState.APPENDING) {
// Lower the buffer size and try again
if (this._reduceMaxBufferLength(frag.duration)) {
this.fragmentTracker.removeFragment(frag);
}
}
};
StreamController.prototype.getBufferedFrag = function getBufferedFrag(position) {
return this.fragmentTracker.getBufferedFrag(position, playlist_loader.LevelType.MAIN);
};
StreamController.prototype.followingBufferedFrag = function followingBufferedFrag(frag) {
if (frag) {
// try to get range of next fragment (500ms after this range)
return this.getBufferedFrag(frag.endPTS + 0.5);
}
return null;
};
StreamController.prototype._checkFragmentChanged = function _checkFragmentChanged() {
var fragPlayingCurrent = void 0,
currentTime = void 0,
video = this.media;
if (video && video.readyState && video.seeking === false) {
currentTime = video.currentTime;
/* if video element is in seeked state, currentTime can only increase.
(assuming that playback rate is positive ...)
As sometimes currentTime jumps back to zero after a
media decode error, check this, to avoid seeking back to
wrong position after a media decode error
*/
if (currentTime > this.lastCurrentTime) {
this.lastCurrentTime = currentTime;
}
if (BufferHelper.isBuffered(video, currentTime)) {
fragPlayingCurrent = this.getBufferedFrag(currentTime);
} else if (BufferHelper.isBuffered(video, currentTime + 0.1)) {
/* ensure that FRAG_CHANGED event is triggered at startup,
when first video frame is displayed and playback is paused.
add a tolerance of 100ms, in case current position is not buffered,
check if current pos+100ms is buffered and use that buffer range
for FRAG_CHANGED event reporting */
fragPlayingCurrent = this.getBufferedFrag(currentTime + 0.1);
}
if (fragPlayingCurrent) {
var fragPlaying = fragPlayingCurrent;
if (fragPlaying !== this.fragPlaying) {
this.hls.trigger(events["a" /* default */].FRAG_CHANGED, { frag: fragPlaying });
var fragPlayingLevel = fragPlaying.level;
if (!this.fragPlaying || this.fragPlaying.level !== fragPlayingLevel) {
this.hls.trigger(events["a" /* default */].LEVEL_SWITCHED, { level: fragPlayingLevel });
}
this.fragPlaying = fragPlaying;
}
}
}
};
/*
on immediate level switch :
- pause playback if playing
- cancel any pending load request
- and trigger a buffer flush
*/
StreamController.prototype.immediateLevelSwitch = function immediateLevelSwitch() {
logger["b" /* logger */].log('immediateLevelSwitch');
if (!this.immediateSwitch) {
this.immediateSwitch = true;
var media = this.media,
previouslyPaused = void 0;
if (media) {
previouslyPaused = media.paused;
media.pause();
} else {
// don't restart playback after instant level switch in case media not attached
previouslyPaused = true;
}
this.previouslyPaused = previouslyPaused;
}
var fragCurrent = this.fragCurrent;
if (fragCurrent && fragCurrent.loader) {
fragCurrent.loader.abort();
}
this.fragCurrent = null;
// flush everything
this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
};
/**
* on immediate level switch end, after new fragment has been buffered:
* - nudge video decoder by slightly adjusting video currentTime (if currentTime buffered)
* - resume the playback if needed
*/
StreamController.prototype.immediateLevelSwitchEnd = function immediateLevelSwitchEnd() {
var media = this.media;
if (media && media.buffered.length) {
this.immediateSwitch = false;
if (BufferHelper.isBuffered(media, media.currentTime)) {
// only nudge if currentTime is buffered
media.currentTime -= 0.0001;
}
if (!this.previouslyPaused) {
media.play();
}
}
};
/**
* try to switch ASAP without breaking video playback:
* in order to ensure smooth but quick level switching,
* we need to find the next flushable buffer range
* we should take into account new segment fetch time
*/
StreamController.prototype.nextLevelSwitch = function nextLevelSwitch() {
var media = this.media;
// ensure that media is defined and that metadata are available (to retrieve currentTime)
if (media && media.readyState) {
var fetchdelay = void 0,
fragPlayingCurrent = void 0,
nextBufferedFrag = void 0;
fragPlayingCurrent = this.getBufferedFrag(media.currentTime);
if (fragPlayingCurrent && fragPlayingCurrent.startPTS > 1) {
// flush buffer preceding current fragment (flush until current fragment start offset)
// minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ...
this.flushMainBuffer(0, fragPlayingCurrent.startPTS - 1);
}
if (!media.paused) {
// add a safety delay of 1s
var nextLevelId = this.hls.nextLoadLevel,
nextLevel = this.levels[nextLevelId],
fragLastKbps = this.fragLastKbps;
if (fragLastKbps && this.fragCurrent) {
fetchdelay = this.fragCurrent.duration * nextLevel.bitrate / (1000 * fragLastKbps) + 1;
} else {
fetchdelay = 0;
}
} else {
fetchdelay = 0;
}
// logger.log('fetchdelay:'+fetchdelay);
// find buffer range that will be reached once new fragment will be fetched
nextBufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay);
if (nextBufferedFrag) {
// we can flush buffer range following this one without stalling playback
nextBufferedFrag = this.followingBufferedFrag(nextBufferedFrag);
if (nextBufferedFrag) {
// if we are here, we can also cancel any loading/demuxing in progress, as they are useless
var fragCurrent = this.fragCurrent;
if (fragCurrent && fragCurrent.loader) {
fragCurrent.loader.abort();
}
this.fragCurrent = null;
// start flush position is the start PTS of next buffered frag.
// we use frag.naxStartPTS which is max(audio startPTS, video startPTS).
// in case there is a small PTS Delta between audio and video, using maxStartPTS avoids flushing last samples from current fragment
this.flushMainBuffer(nextBufferedFrag.maxStartPTS, Number.POSITIVE_INFINITY);
}
}
}
};
StreamController.prototype.flushMainBuffer = function flushMainBuffer(startOffset, endOffset) {
this.state = State.BUFFER_FLUSHING;
var flushScope = { startOffset: startOffset, endOffset: endOffset };
// if alternate audio tracks are used, only flush video, otherwise flush everything
if (this.altAudio) {
flushScope.type = 'video';
}
this.hls.trigger(events["a" /* default */].BUFFER_FLUSHING, flushScope);
};
StreamController.prototype.onMediaAttached = function onMediaAttached(data) {
var media = this.media = this.mediaBuffer = data.media;
this.onvseeking = this.onMediaSeeking.bind(this);
this.onvseeked = this.onMediaSeeked.bind(this);
this.onvended = this.onMediaEnded.bind(this);
media.addEventListener('seeking', this.onvseeking);
media.addEventListener('seeked', this.onvseeked);
media.addEventListener('ended', this.onvended);
var config = this.config;
if (this.levels && config.autoStartLoad) {
this.hls.startLoad(config.startPosition);
share/public_html/static/hls.js view on Meta::CPAN
ua.indexOf('firefox') === -1) {
audioCodec = 'mp4a.40.5';
}
}
// HE-AAC is broken on Android, always signal audio codec as AAC even if variant manifest states otherwise
if (ua.indexOf('android') !== -1 && track.container !== 'audio/mpeg') {
// Exclude mpeg audio
audioCodec = 'mp4a.40.2';
logger["b" /* logger */].log('Android: force audio codec to ' + audioCodec);
}
track.levelCodec = audioCodec;
track.id = data.id;
}
track = tracks.video;
if (track) {
track.levelCodec = this.levels[this.level].videoCodec;
track.id = data.id;
}
this.hls.trigger(events["a" /* default */].BUFFER_CODECS, tracks);
// loop through tracks that are going to be provided to bufferController
for (trackName in tracks) {
track = tracks[trackName];
logger["b" /* logger */].log('main track:' + trackName + ',container:' + track.container + ',codecs[level/parsed]=[' + track.levelCodec + '/' + track.codec + ']');
var initSegment = track.initSegment;
if (initSegment) {
this.appended = true;
// arm pending Buffering flag before appending a segment
this.pendingBuffering = true;
this.hls.trigger(events["a" /* default */].BUFFER_APPENDING, { type: trackName, data: initSegment, parent: 'main', content: 'initSegment' });
}
}
// trigger handler right now
this.tick();
}
};
StreamController.prototype.onFragParsingData = function onFragParsingData(data) {
var _this2 = this;
var fragCurrent = this.fragCurrent;
var fragNew = data.frag;
if (fragCurrent && data.id === 'main' && fragNew.sn === fragCurrent.sn && fragNew.level === fragCurrent.level && !(data.type === 'audio' && this.altAudio) && // filter out main audio if audio track is loaded through audio stream controller
this.state === State.PARSING) {
var level = this.levels[this.level],
frag = fragCurrent;
if (isNaN(data.endPTS)) {
data.endPTS = data.startPTS + fragCurrent.duration;
data.endDTS = data.startDTS + fragCurrent.duration;
}
if (data.hasAudio === true) {
frag.addElementaryStream(loader_fragment.ElementaryStreamTypes.AUDIO);
}
if (data.hasVideo === true) {
frag.addElementaryStream(loader_fragment.ElementaryStreamTypes.VIDEO);
}
logger["b" /* logger */].log('Parsed ' + data.type + ',PTS:[' + data.startPTS.toFixed(3) + ',' + data.endPTS.toFixed(3) + '],DTS:[' + data.startDTS.toFixed(3) + '/' + data.endDTS.toFixed(3) + '],nb:' + data.nb + ',dropped:' + (data.dropped || 0...
// Detect gaps in a fragment and try to fix it by finding a keyframe in the previous fragment (see _findFragments)
if (data.type === 'video') {
frag.dropped = data.dropped;
if (frag.dropped) {
if (!frag.backtracked) {
var levelDetails = level.details;
if (levelDetails && frag.sn === levelDetails.startSN) {
logger["b" /* logger */].warn('missing video frame(s) on first frag, appending with gap', frag.sn);
} else {
logger["b" /* logger */].warn('missing video frame(s), backtracking fragment', frag.sn);
// Return back to the IDLE state without appending to buffer
// Causes findFragments to backtrack a segment and find the keyframe
// Audio fragments arriving before video sets the nextLoadPosition, causing _findFragments to skip the backtracked fragment
this.fragmentTracker.removeFragment(frag);
frag.backtracked = true;
this.nextLoadPosition = data.startPTS;
this.state = State.IDLE;
this.fragPrevious = frag;
this.tick();
return;
}
} else {
logger["b" /* logger */].warn('Already backtracked on this fragment, appending with the gap', frag.sn);
}
} else {
// Only reset the backtracked flag if we've loaded the frag without any dropped frames
frag.backtracked = false;
}
}
var drift = updateFragPTSDTS(level.details, frag, data.startPTS, data.endPTS, data.startDTS, data.endDTS),
hls = this.hls;
hls.trigger(events["a" /* default */].LEVEL_PTS_UPDATED, { details: level.details, level: this.level, drift: drift, type: data.type, start: data.startPTS, end: data.endPTS });
// has remuxer dropped video frames located before first keyframe ?
[data.data1, data.data2].forEach(function (buffer) {
// only append in PARSING state (rationale is that an appending error could happen synchronously on first segment appending)
// in that case it is useless to append following segments
if (buffer && buffer.length && _this2.state === State.PARSING) {
_this2.appended = true;
// arm pending Buffering flag before appending a segment
_this2.pendingBuffering = true;
hls.trigger(events["a" /* default */].BUFFER_APPENDING, { type: data.type, data: buffer, parent: 'main', content: 'data' });
}
});
// trigger handler right now
this.tick();
}
};
StreamController.prototype.onFragParsed = function onFragParsed(data) {
var fragCurrent = this.fragCurrent;
var fragNew = data.frag;
if (fragCurrent && data.id === 'main' && fragNew.sn === fragCurrent.sn && fragNew.level === fragCurrent.level && this.state === State.PARSING) {
this.stats.tparsed = window.performance.now();
this.state = State.PARSED;
this._checkAppendedParsed();
}
};
StreamController.prototype.onAudioTrackSwitching = function onAudioTrackSwitching(data) {
// if any URL found on new audio track, it is an alternate audio track
var altAudio = !!data.url,
trackId = data.id;
// if we switch on main audio, ensure that main fragment scheduling is synced with media.buffered
// don't do anything if we switch to alt audio: audio stream controller is handling it.
// we will just have to change buffer scheduling on audioTrackSwitched
if (!altAudio) {
if (this.mediaBuffer !== this.media) {
logger["b" /* logger */].log('switching on main audio, use media.buffered to schedule main fragment loading');
this.mediaBuffer = this.media;
var fragCurrent = this.fragCurrent;
// we need to refill audio buffer from main: cancel any frag loading to speed up audio switch
if (fragCurrent.loader) {
logger["b" /* logger */].log('switching to main audio track, cancel main fragment load');
fragCurrent.loader.abort();
}
this.fragCurrent = null;
this.fragPrevious = null;
// destroy demuxer to force init segment generation (following audio switch)
if (this.demuxer) {
this.demuxer.destroy();
this.demuxer = null;
}
// switch to IDLE state to load new fragment
this.state = State.IDLE;
}
var hls = this.hls;
// switching to main audio, flush all audio and trigger track switched
hls.trigger(events["a" /* default */].BUFFER_FLUSHING, { startOffset: 0, endOffset: Number.POSITIVE_INFINITY, type: 'audio' });
hls.trigger(events["a" /* default */].AUDIO_TRACK_SWITCHED, { id: trackId });
this.altAudio = false;
}
};
share/public_html/static/hls.js view on Meta::CPAN
id3_track_controller__inherits(ID3TrackController, _EventHandler);
function ID3TrackController(hls) {
id3_track_controller__classCallCheck(this, ID3TrackController);
var _this = id3_track_controller__possibleConstructorReturn(this, _EventHandler.call(this, hls, events["a" /* default */].MEDIA_ATTACHED, events["a" /* default */].MEDIA_DETACHING, events["a" /* default */].FRAG_PARSING_METADATA));
_this.id3Track = undefined;
_this.media = undefined;
return _this;
}
ID3TrackController.prototype.destroy = function destroy() {
event_handler.prototype.destroy.call(this);
};
// Add ID3 metatadata text track.
ID3TrackController.prototype.onMediaAttached = function onMediaAttached(data) {
this.media = data.media;
if (!this.media) {}
};
ID3TrackController.prototype.onMediaDetaching = function onMediaDetaching() {
clearCurrentCues(this.id3Track);
this.id3Track = undefined;
this.media = undefined;
};
ID3TrackController.prototype.getID3Track = function getID3Track(textTracks) {
for (var i = 0; i < textTracks.length; i++) {
var textTrack = textTracks[i];
if (textTrack.kind === 'metadata' && textTrack.label === 'id3') {
// send 'addtrack' when reusing the textTrack for metadata,
// same as what we do for captions
sendAddTrackEvent(textTrack, this.media);
return textTrack;
}
}
return this.media.addTextTrack('metadata', 'id3');
};
ID3TrackController.prototype.onFragParsingMetadata = function onFragParsingMetadata(data) {
var fragment = data.frag;
var samples = data.samples;
// create track dynamically
if (!this.id3Track) {
this.id3Track = this.getID3Track(this.media.textTracks);
this.id3Track.mode = 'hidden';
}
// Attempt to recreate Safari functionality by creating
// WebKitDataCue objects when available and store the decoded
// ID3 data in the value property of the cue
var Cue = window.WebKitDataCue || window.VTTCue || window.TextTrackCue;
for (var i = 0; i < samples.length; i++) {
var frames = id3["a" /* default */].getID3Frames(samples[i].data);
if (frames) {
var startTime = samples[i].pts;
var endTime = i < samples.length - 1 ? samples[i + 1].pts : fragment.endPTS;
// Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE
if (startTime === endTime) {
endTime += 0.0001;
}
for (var j = 0; j < frames.length; j++) {
var frame = frames[j];
// Safari doesn't put the timestamp frame in the TextTrack
if (!id3["a" /* default */].isTimeStampFrame(frame)) {
var cue = new Cue(startTime, endTime, '');
cue.value = frame;
this.id3Track.addCue(cue);
}
}
}
}
};
return ID3TrackController;
}(event_handler);
/* harmony default export */ var id3_track_controller = (id3_track_controller_ID3TrackController);
// CONCATENATED MODULE: ./src/is-supported.js
function is_supported_isSupported() {
var mediaSource = getMediaSource();
var sourceBuffer = window.SourceBuffer || window.WebKitSourceBuffer;
var isTypeSupported = mediaSource && typeof mediaSource.isTypeSupported === 'function' && mediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"');
// if SourceBuffer is exposed ensure its API is valid
// safari and old version of Chrome doe not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible
var sourceBufferValidAPI = !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === 'function' && typeof sourceBuffer.prototype.remove === 'function';
return !!isTypeSupported && !!sourceBufferValidAPI;
}
// CONCATENATED MODULE: ./src/utils/ewma.js
function ewma__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/*
* compute an Exponential Weighted moving average
* - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
* - heavily inspired from shaka-player
*/
var EWMA = function () {
// About half of the estimated value will be from the last |halfLife| samples by weight.
function EWMA(halfLife) {
ewma__classCallCheck(this, EWMA);
// Larger values of alpha expire historical data more slowly.
this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0;
this.estimate_ = 0;
this.totalWeight_ = 0;
}
EWMA.prototype.sample = function sample(weight, value) {
var adjAlpha = Math.pow(this.alpha_, weight);
this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_;
this.totalWeight_ += weight;
};
EWMA.prototype.getTotalWeight = function getTotalWeight() {
return this.totalWeight_;
};
EWMA.prototype.getEstimate = function getEstimate() {
if (this.alpha_) {
var zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_);
return this.estimate_ / zeroFactor;
} else {
return this.estimate_;
share/public_html/static/hls.js view on Meta::CPAN
if (data.frag.bitrateTest) {
var stats = data.stats;
stats.tparsed = stats.tbuffered = stats.tload;
this.onFragBuffered(data);
}
}
};
AbrController.prototype.onFragBuffered = function onFragBuffered(data) {
var stats = data.stats;
var frag = data.frag;
// only update stats on first frag buffering
// if same frag is loaded multiple times, it might be in browser cache, and loaded quickly
// and leading to wrong bw estimation
// on bitrate test, also only update stats once (if tload = tbuffered == on FRAG_LOADED)
if (stats.aborted !== true && frag.type === 'main' && !isNaN(frag.sn) && (!frag.bitrateTest || stats.tload === stats.tbuffered)) {
// use tparsed-trequest instead of tbuffered-trequest to compute fragLoadingProcessing; rationale is that buffer appending only happens once media is attached
// in case we use config.startFragPrefetch while media is not attached yet, fragment might be parsed while media not attached yet, but it will only be buffered on media attached
// as a consequence it could happen really late in the process. meaning that appending duration might appears huge ... leading to underestimated throughput estimation
var fragLoadingProcessingMs = stats.tparsed - stats.trequest;
logger["b" /* logger */].log('latency/loading/parsing/append/kbps:' + Math.round(stats.tfirst - stats.trequest) + '/' + Math.round(stats.tload - stats.tfirst) + '/' + Math.round(stats.tparsed - stats.tload) + '/' + Math.round(stats.tbuffered - ...
this._bwEstimator.sample(fragLoadingProcessingMs, stats.loaded);
stats.bwEstimate = this._bwEstimator.getEstimate();
// if fragment has been loaded to perform a bitrate test, (hls.startLevel = -1), store bitrate test delay duration
if (frag.bitrateTest) {
this.bitrateTestDelay = fragLoadingProcessingMs / 1000;
} else {
this.bitrateTestDelay = 0;
}
}
};
AbrController.prototype.onError = function onError(data) {
// stop timer in case of frag loading error
switch (data.details) {
case errors["a" /* ErrorDetails */].FRAG_LOAD_ERROR:
case errors["a" /* ErrorDetails */].FRAG_LOAD_TIMEOUT:
this.clearTimer();
break;
default:
break;
}
};
AbrController.prototype.clearTimer = function clearTimer() {
clearInterval(this.timer);
this.timer = null;
};
// return next auto level
AbrController.prototype._findBestLevel = function _findBestLevel(currentLevel, currentFragDuration, currentBw, minAutoLevel, maxAutoLevel, maxFetchDuration, bwFactor, bwUpFactor, levels) {
for (var i = maxAutoLevel; i >= minAutoLevel; i--) {
var levelInfo = levels[i],
levelDetails = levelInfo.details,
avgDuration = levelDetails ? levelDetails.totalduration / levelDetails.fragments.length : currentFragDuration,
live = levelDetails ? levelDetails.live : false,
adjustedbw = void 0;
// 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.
// 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 <= currentLevel) {
adjustedbw = bwFactor * currentBw;
} else {
adjustedbw = bwUpFactor * currentBw;
}
var bitrate = levels[i].realBitrate ? Math.max(levels[i].realBitrate, levels[i].bitrate) : levels[i].bitrate,
fetchDuration = bitrate * avgDuration / adjustedbw;
logger["b" /* logger */].trace('level/adjustedbw/bitrate/avgDuration/maxFetchDuration/fetchDuration: ' + i + '/' + Math.round(adjustedbw) + '/' + bitrate + '/' + avgDuration + '/' + maxFetchDuration + '/' + fetchDuration);
// if adjusted bw is greater than level bitrate AND
if (adjustedbw > bitrate && (
// fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches
// we don't account for max Fetch Duration for live streams, this is to avoid switching down when near the edge of live sliding window ...
// special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that _findBestLevel will return -1
!fetchDuration || live && !this.bitrateTestDelay || fetchDuration < maxFetchDuration)) {
// as we are looping from highest to lowest, this will return the best achievable quality level
return i;
}
}
// not enough time budget even with quality level 0 ... rebuffering might happen
return -1;
};
abr_controller__createClass(AbrController, [{
key: 'nextAutoLevel',
get: function get() {
var forcedAutoLevel = this._nextAutoLevel;
var bwEstimator = this._bwEstimator;
// in case next auto level has been forced, and bw not available or not reliable, return forced value
if (forcedAutoLevel !== -1 && (!bwEstimator || !bwEstimator.canEstimate())) {
return forcedAutoLevel;
}
// compute next level using ABR logic
var nextABRAutoLevel = this._nextABRAutoLevel;
// if forced auto level has been defined, use it to cap ABR computed quality level
if (forcedAutoLevel !== -1) {
nextABRAutoLevel = Math.min(forcedAutoLevel, nextABRAutoLevel);
}
return nextABRAutoLevel;
},
set: function set(nextLevel) {
this._nextAutoLevel = nextLevel;
}
}, {
key: '_nextABRAutoLevel',
get: function get() {
var hls = this.hls,
maxAutoLevel = hls.maxAutoLevel,
levels = hls.levels,
config = hls.config,
minAutoLevel = hls.minAutoLevel;
var video = hls.media,
currentLevel = this.lastLoadedFragLevel,
share/public_html/static/hls.js view on Meta::CPAN
}
}]);
return AbrController;
}(event_handler);
/* harmony default export */ var abr_controller = (abr_controller_AbrController);
// CONCATENATED MODULE: ./src/controller/buffer-controller.js
function buffer_controller__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function buffer_controller__possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call :...
function buffer_controller__inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Ob...
/*
* Buffer Controller
*/
var buffer_controller_MediaSource = getMediaSource();
var buffer_controller_BufferController = function (_EventHandler) {
buffer_controller__inherits(BufferController, _EventHandler);
function BufferController(hls) {
buffer_controller__classCallCheck(this, BufferController);
// the value that we have set mediasource.duration to
// (the actual duration may be tweaked slighly by the browser)
var _this = buffer_controller__possibleConstructorReturn(this, _EventHandler.call(this, hls, events["a" /* default */].MEDIA_ATTACHING, events["a" /* default */].MEDIA_DETACHING, events["a" /* default */].MANIFEST_PARSED, events["a" /* default */...
_this._msDuration = null;
// the value that we want to set mediaSource.duration to
_this._levelDuration = null;
// current stream state: true - for live broadcast, false - for VoD content
_this._live = null;
// cache the self generated object url to detect hijack of video tag
_this._objectUrl = null;
// Source Buffer listeners
_this.onsbue = _this.onSBUpdateEnd.bind(_this);
_this.onsbe = _this.onSBUpdateError.bind(_this);
_this.pendingTracks = {};
_this.tracks = {};
return _this;
}
BufferController.prototype.destroy = function destroy() {
event_handler.prototype.destroy.call(this);
};
BufferController.prototype.onLevelPtsUpdated = function onLevelPtsUpdated(data) {
var type = data.type;
var audioTrack = this.tracks.audio;
// Adjusting `SourceBuffer.timestampOffset` (desired point in the timeline where the next frames should be appended)
// in Chrome browser when we detect MPEG audio container and time delta between level PTS and `SourceBuffer.timestampOffset`
// is greater than 100ms (this is enough to handle seek for VOD or level change for LIVE videos). At the time of change we issue
// `SourceBuffer.abort()` and adjusting `SourceBuffer.timestampOffset` if `SourceBuffer.updating` is false or awaiting `updateend`
// event if SB is in updating state.
// More info here: https://github.com/video-dev/hls.js/issues/332#issuecomment-257986486
if (type === 'audio' && audioTrack && audioTrack.container === 'audio/mpeg') {
// Chrome audio mp3 track
var audioBuffer = this.sourceBuffer.audio;
var delta = Math.abs(audioBuffer.timestampOffset - data.start);
// adjust timestamp offset if time delta is greater than 100ms
if (delta > 0.1) {
var updating = audioBuffer.updating;
try {
audioBuffer.abort();
} catch (err) {
updating = true;
logger["b" /* logger */].warn('can not abort audio buffer: ' + err);
}
if (!updating) {
logger["b" /* logger */].warn('change mpeg audio timestamp offset from ' + audioBuffer.timestampOffset + ' to ' + data.start);
audioBuffer.timestampOffset = data.start;
} else {
this.audioTimestampOffset = data.start;
}
}
}
};
BufferController.prototype.onManifestParsed = function onManifestParsed(data) {
var audioExpected = data.audio,
videoExpected = data.video || data.levels.length && data.altAudio,
sourceBufferNb = 0;
// in case of alt audio 2 BUFFER_CODECS events will be triggered, one per stream controller
// sourcebuffers will be created all at once when the expected nb of tracks will be reached
// in case alt audio is not used, only one BUFFER_CODEC event will be fired from main stream controller
// it will contain the expected nb of source buffers, no need to compute it
if (data.altAudio && (audioExpected || videoExpected)) {
sourceBufferNb = (audioExpected ? 1 : 0) + (videoExpected ? 1 : 0);
logger["b" /* logger */].log(sourceBufferNb + ' sourceBuffer(s) expected');
}
this.sourceBufferNb = sourceBufferNb;
};
BufferController.prototype.onMediaAttaching = function onMediaAttaching(data) {
var media = this.media = data.media;
if (media) {
// setup the media source
var ms = this.mediaSource = new buffer_controller_MediaSource();
// Media Source listeners
this.onmso = this.onMediaSourceOpen.bind(this);
this.onmse = this.onMediaSourceEnded.bind(this);
this.onmsc = this.onMediaSourceClose.bind(this);
ms.addEventListener('sourceopen', this.onmso);
ms.addEventListener('sourceended', this.onmse);
ms.addEventListener('sourceclose', this.onmsc);
// link video and media Source