Haha-Yes/node_modules/m3u8stream/lib/dash-mpd-parser.js
2018-09-09 21:20:36 +02:00

122 lines
3.3 KiB
JavaScript

const Writable = require('stream').Writable;
const sax = require('sax');
/**
* A wrapper around sax that emits segments.
*
* @extends WRitableStream
* @constructor
*/
module.exports = class DashMPDParser extends Writable {
constructor(targetID) {
super();
this._parser = sax.createStream(false, { lowercasetags: true });
this._parser.on('error', this.emit.bind(this, 'error'));
let lastTag;
let starttime = 0;
let seq = 0;
let timescale, offset, duration, baseURL;
let timeline = [];
let getSegments = false, startEmitted = false;
let isStatic;
let treeLevel;
this._parser.on('opentag', (node) => {
switch (node.name) {
case 'mpd':
starttime =
new Date(node.attributes.availabilitystarttime).getTime();
isStatic = node.attributes.type !== 'dynamic';
break;
case 'period':
// Reset everything on <Period> tag.
seq = 0;
timescale = 1000;
duration = 0;
offset = 0;
baseURL = [];
treeLevel = 0;
break;
case 'segmentlist':
seq = parseInt(node.attributes.startnumber, 10) || seq;
timescale = parseInt(node.attributes.timescale, 10) || timescale;
duration = parseInt(node.attributes.duration, 10) || duration;
offset = parseInt(node.attributes.presentationtimeoffset, 10) || offset;
if (!startEmitted) {
startEmitted = true;
if (offset) {
starttime += offset;
}
this.emit('starttime', starttime);
}
break;
case 'segmenttimeline':
case 'baseurl':
lastTag = node.name;
break;
case 's':
timeline.push(parseInt(node.attributes.d, 10));
break;
case 'adaptationset':
case 'representation':
treeLevel++;
if (targetID == null) {
targetID = node.attributes.id;
}
getSegments = node.attributes.id === targetID + '';
break;
case 'segmenturl':
if (getSegments) {
let tl = timeline.shift();
let segmentDuration = (tl || duration) / timescale * 1000;
this.emit('item', {
url: baseURL.filter(s => !!s).join('') + node.attributes.media,
seq: seq++,
duration: segmentDuration,
});
}
break;
}
});
const onEnd = () => {
if (isStatic) { this.emit('endlist'); }
if (!getSegments) {
this.emit('error', new Error(`Representation '${targetID}' not found`));
}
this.emit('end');
};
this._parser.on('closetag', (tagName) => {
switch (tagName) {
case 'adaptationset':
case 'representation':
treeLevel--;
break;
case 'segmentlist':
if (getSegments) {
this.emit('endearly');
onEnd();
this._parser.removeAllListeners();
}
break;
}
});
this._parser.on('text', (text) => {
if (lastTag === 'baseurl') {
baseURL[treeLevel] = text;
lastTag = null;
}
});
this.on('finish', onEnd);
}
_write(chunk, encoding, callback) {
this._parser.write(chunk, encoding);
callback();
}
};