m3u8stream
Advanced tools
Comparing version 0.6.5 to 0.7.0
@@ -16,3 +16,3 @@ "use strict"; | ||
this._parser = sax_1.default.createStream(false, { lowercase: true }); | ||
this._parser.on('error', this.emit.bind(this, 'error')); | ||
this._parser.on('error', this.destroy.bind(this)); | ||
let lastTag; | ||
@@ -25,2 +25,3 @@ let currtime = 0; | ||
let getSegments = false; | ||
let gotSegments = false; | ||
let isStatic; | ||
@@ -71,6 +72,7 @@ let treeLevel; | ||
case 's': | ||
timeline.push([ | ||
parseInt(node.attributes.d), | ||
parseInt(node.attributes.r) | ||
]); | ||
timeline.push({ | ||
duration: parseInt(node.attributes.d), | ||
repeat: parseInt(node.attributes.r), | ||
time: parseInt(node.attributes.t), | ||
}); | ||
break; | ||
@@ -93,25 +95,2 @@ case 'adaptationset': | ||
} | ||
if (getSegments && segmentTemplate && timeline.length) { | ||
if (segmentTemplate.initialization) { | ||
this.emit('item', { | ||
url: baseURL.filter(s => !!s).join('') + | ||
tmpl(segmentTemplate.initialization), | ||
seq: seq - 1, | ||
duration: 0, | ||
}); | ||
} | ||
for (let [duration, repeat] of timeline) { | ||
duration = duration / timescale * 1000; | ||
repeat = repeat || 1; | ||
for (let i = 0; i < repeat; i++) { | ||
this.emit('item', { | ||
url: baseURL.filter(s => !!s).join('') + | ||
tmpl(segmentTemplate.media), | ||
seq: seq++, | ||
duration, | ||
}); | ||
currtime += duration; | ||
} | ||
} | ||
} | ||
break; | ||
@@ -122,3 +101,4 @@ case 'initialization': | ||
url: baseURL.filter(s => !!s).join('') + node.attributes.sourceurl, | ||
seq: seq++, | ||
seq: seq, | ||
init: true, | ||
duration: 0, | ||
@@ -130,4 +110,5 @@ }); | ||
if (getSegments) { | ||
gotSegments = true; | ||
let tl = timeline.shift(); | ||
let segmentDuration = (tl && tl[0] || duration) / timescale * 1000; | ||
let segmentDuration = (tl && tl.duration || duration) / timescale * 1000; | ||
this.emit('item', { | ||
@@ -148,5 +129,7 @@ url: baseURL.filter(s => !!s).join('') + node.attributes.media, | ||
if (!getSegments) { | ||
this.emit('error', Error(`Representation '${targetID}' not found`)); | ||
this.destroy(Error(`Representation '${targetID}' not found`)); | ||
} | ||
this.emit('end'); | ||
else { | ||
this.emit('end'); | ||
} | ||
}; | ||
@@ -158,8 +141,33 @@ this._parser.on('closetag', (tagName) => { | ||
treeLevel--; | ||
break; | ||
case 'segmentlist': | ||
if (getSegments) { | ||
if (segmentTemplate && timeline.length) { | ||
gotSegments = true; | ||
if (segmentTemplate.initialization) { | ||
this.emit('item', { | ||
url: baseURL.filter(s => !!s).join('') + | ||
tmpl(segmentTemplate.initialization), | ||
seq: seq, | ||
init: true, | ||
duration: 0, | ||
}); | ||
} | ||
for (let { duration, repeat, time } of timeline) { | ||
duration = duration / timescale * 1000; | ||
repeat = repeat || 1; | ||
currtime = time || currtime; | ||
for (let i = 0; i < repeat; i++) { | ||
this.emit('item', { | ||
url: baseURL.filter(s => !!s).join('') + | ||
tmpl(segmentTemplate.media), | ||
seq: seq++, | ||
duration, | ||
}); | ||
currtime += duration; | ||
} | ||
} | ||
} | ||
if (gotSegments) { | ||
this.emit('endearly'); | ||
onEnd(); | ||
this._parser.removeAllListeners(); | ||
this.removeAllListeners('finish'); | ||
} | ||
@@ -166,0 +174,0 @@ break; |
@@ -31,3 +31,2 @@ "use strict"; | ||
} | ||
let liveBegin = Date.now() - liveBuffer; | ||
let currSegment; | ||
@@ -46,3 +45,9 @@ const streamQueue = new queue_1.default((req, callback) => { | ||
const requestQueue = new queue_1.default((segment, callback) => { | ||
let req = miniget_1.default(url_1.resolve(playlistURL, segment.url), requestOptions); | ||
let options = Object.assign({}, requestOptions); | ||
if (segment.range) { | ||
options.headers = Object.assign({}, options.headers, { | ||
Range: `bytes=${segment.range.start}-${segment.range.end}`, | ||
}); | ||
} | ||
let req = miniget_1.default(url_1.resolve(playlistURL, segment.url), options); | ||
req.on('error', callback); | ||
@@ -112,14 +117,12 @@ streamQueue.push(req, (err, size) => { | ||
let addedItems = []; | ||
let liveAddedItems = []; | ||
const addItem = (item, isLive) => { | ||
if (item.seq <= lastSeq) { | ||
return; | ||
const addItem = (item) => { | ||
if (!item.init) { | ||
if (item.seq <= lastSeq) { | ||
return; | ||
} | ||
lastSeq = item.seq; | ||
} | ||
lastSeq = item.seq; | ||
begin = item.time; | ||
requestQueue.push(item, onQueuedEnd); | ||
addedItems.push(item); | ||
if (isLive) { | ||
liveAddedItems.push(item); | ||
} | ||
}; | ||
@@ -129,5 +132,4 @@ let tailedItems = [], tailedItemsDuration = 0; | ||
let timedItem = Object.assign({ time: starttime }, item); | ||
let isLive = liveBegin <= timedItem.time; | ||
if (begin <= timedItem.time) { | ||
addItem(timedItem, isLive); | ||
addItem(timedItem); | ||
} | ||
@@ -150,3 +152,3 @@ else { | ||
if (!addedItems.length && tailedItems.length) { | ||
tailedItems.forEach((item) => { addItem(item, true); }); | ||
tailedItems.forEach((item) => { addItem(item); }); | ||
} | ||
@@ -153,0 +155,0 @@ // Refresh the playlist when remaining segments get low. |
@@ -11,5 +11,14 @@ /// <reference types="node" /> | ||
private _nextItemDuration; | ||
private _nextItemRange; | ||
private _lastItemRangeEnd; | ||
constructor(); | ||
_parseAttrList(value: string): { | ||
[key: string]: string; | ||
}; | ||
_parseRange(value: string): { | ||
start: number; | ||
end: number; | ||
}; | ||
_parseLine(line: string): void; | ||
_write(chunk: Buffer, encoding: string, callback: () => void): void; | ||
} |
@@ -13,2 +13,4 @@ "use strict"; | ||
this._nextItemDuration = null; | ||
this._nextItemRange = null; | ||
this._lastItemRangeEnd = 0; | ||
this.on('finish', () => { | ||
@@ -19,2 +21,21 @@ this._parseLine(this._lastLine); | ||
} | ||
_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) { | ||
@@ -33,2 +54,21 @@ let match = line.match(/^#(EXT[A-Z0-9-]+)(?::(.*))?/); | ||
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': | ||
@@ -49,3 +89,5 @@ this._nextItemDuration = | ||
duration: this._nextItemDuration, | ||
range: this._nextItemRange, | ||
}); | ||
this._nextItemRange = null; | ||
} | ||
@@ -59,2 +101,4 @@ } | ||
lines.forEach((line, i) => { | ||
if (this.destroyed) | ||
return; | ||
if (i < lines.length - 1) { | ||
@@ -61,0 +105,0 @@ this._parseLine(line); |
@@ -8,2 +8,7 @@ /// <reference types="node" /> | ||
time?: number; | ||
range?: { | ||
start: number; | ||
end: number; | ||
}; | ||
init?: boolean; | ||
} | ||
@@ -10,0 +15,0 @@ export interface Parser extends Writable { |
@@ -13,3 +13,3 @@ { | ||
], | ||
"version": "0.6.5", | ||
"version": "0.7.0", | ||
"repository": { | ||
@@ -19,3 +19,3 @@ "type": "git", | ||
}, | ||
"author": "fent (https://github.com/fent)", | ||
"author": "fent <fentbox@gmail.com> (https://github.com/fent)", | ||
"main": "./dist/index.js", | ||
@@ -41,3 +41,3 @@ "files": [ | ||
"mocha": "^7.0.1", | ||
"nock": "^11.1.0", | ||
"nock": "^12.0.0", | ||
"nyc": "^15.0.0", | ||
@@ -44,0 +44,0 @@ "ts-node": "^8.4.1", |
@@ -29,3 +29,3 @@ # node-m3u8stream | ||
* `begin` - Where to begin playing the video. Accepts an absolute unix timestamp or date, and a relative time in the formats `1:23:45.123` and `1m2s`. | ||
* `begin` - Where to begin playing the video. Accepts an absolute unix timestamp or date and a relative time in the formats `1:23:45.123` and `1m2s`. | ||
* `liveBuffer` - How much buffer in milliseconds to have for live streams. Default is `20000`. | ||
@@ -44,3 +44,3 @@ * `chunkReadahead` - How many chunks to preload ahead. Default is `3`. | ||
* `Object` - Current segment with the following fields, | ||
- `number` - number | ||
- `number` - num | ||
- `number` - size | ||
@@ -47,0 +47,0 @@ - `number` - duration |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47159
689