111 lines
3.7 KiB
JavaScript
111 lines
3.7 KiB
JavaScript
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
const stream_1 = require("stream");
|
||
|
/**
|
||
|
* A very simple m3u8 playlist file parser that detects tags and segments.
|
||
|
*/
|
||
|
class m3u8Parser extends stream_1.Writable {
|
||
|
constructor() {
|
||
|
super();
|
||
|
this._lastLine = '';
|
||
|
this._seq = 0;
|
||
|
this._nextItemDuration = null;
|
||
|
this._nextItemRange = null;
|
||
|
this._lastItemRangeEnd = 0;
|
||
|
this.on('finish', () => {
|
||
|
this._parseLine(this._lastLine);
|
||
|
this.emit('end');
|
||
|
});
|
||
|
}
|
||
|
_parseAttrList(value) {
|
||
|
let attrs = {};
|
||
|
let regex = /([A-Z0-9-]+)=(?:"([^"]*?)"|([^,]*?))/g;
|
||
|
let match;
|
||
|
while ((match = regex.exec(value)) !== null) {
|
||
|
attrs[match[1]] = match[2] || match[3];
|
||
|
}
|
||
|
return attrs;
|
||
|
}
|
||
|
_parseRange(value) {
|
||
|
if (!value)
|
||
|
return null;
|
||
|
let svalue = value.split('@');
|
||
|
let start = svalue[1] ? parseInt(svalue[1]) : this._lastItemRangeEnd + 1;
|
||
|
let end = start + parseInt(svalue[0]) - 1;
|
||
|
let range = { start, end };
|
||
|
this._lastItemRangeEnd = range.end;
|
||
|
return range;
|
||
|
}
|
||
|
_parseLine(line) {
|
||
|
let match = line.match(/^#(EXT[A-Z0-9-]+)(?::(.*))?/);
|
||
|
if (match) {
|
||
|
// This is a tag.
|
||
|
const tag = match[1];
|
||
|
const value = match[2] || '';
|
||
|
switch (tag) {
|
||
|
case 'EXT-X-PROGRAM-DATE-TIME':
|
||
|
this.emit('starttime', new Date(value).getTime());
|
||
|
break;
|
||
|
case 'EXT-X-MEDIA-SEQUENCE':
|
||
|
this._seq = parseInt(value);
|
||
|
break;
|
||
|
case 'EXT-X-MAP': {
|
||
|
let attrs = this._parseAttrList(value);
|
||
|
if (!attrs.URI) {
|
||
|
this.destroy(new Error('`EXT-X-MAP` found without required attribute `URI`'));
|
||
|
return;
|
||
|
}
|
||
|
this.emit('item', {
|
||
|
url: attrs.URI,
|
||
|
seq: this._seq,
|
||
|
init: true,
|
||
|
duration: 0,
|
||
|
range: this._parseRange(attrs.BYTERANGE),
|
||
|
});
|
||
|
break;
|
||
|
}
|
||
|
case 'EXT-X-BYTERANGE': {
|
||
|
this._nextItemRange = this._parseRange(value);
|
||
|
break;
|
||
|
}
|
||
|
case 'EXTINF':
|
||
|
this._nextItemDuration =
|
||
|
Math.round(parseFloat(value.split(',')[0]) * 1000);
|
||
|
break;
|
||
|
case 'EXT-X-ENDLIST':
|
||
|
this.emit('endlist');
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else if (!/^#/.test(line) && line.trim()) {
|
||
|
// This is a segment
|
||
|
this.emit('item', {
|
||
|
url: line.trim(),
|
||
|
seq: this._seq++,
|
||
|
duration: this._nextItemDuration,
|
||
|
range: this._nextItemRange,
|
||
|
});
|
||
|
this._nextItemRange = null;
|
||
|
}
|
||
|
}
|
||
|
_write(chunk, encoding, callback) {
|
||
|
let lines = chunk.toString('utf8').split('\n');
|
||
|
if (this._lastLine) {
|
||
|
lines[0] = this._lastLine + lines[0];
|
||
|
}
|
||
|
lines.forEach((line, i) => {
|
||
|
if (this.destroyed)
|
||
|
return;
|
||
|
if (i < lines.length - 1) {
|
||
|
this._parseLine(line);
|
||
|
}
|
||
|
else {
|
||
|
// Save the last line in case it has been broken up.
|
||
|
this._lastLine = line;
|
||
|
}
|
||
|
});
|
||
|
callback();
|
||
|
}
|
||
|
}
|
||
|
exports.default = m3u8Parser;
|
||
|
//# sourceMappingURL=m3u8-parser.js.map
|