m3u8stream
Advanced tools
Comparing version 0.4.2 to 0.5.0
@@ -18,4 +18,5 @@ const Writable = require('stream').Writable; | ||
let lastTag; | ||
let starttime = 0; | ||
let currtime = 0; | ||
let seq = 0; | ||
let segmentTemplate; | ||
let timescale, offset, duration, baseURL; | ||
@@ -27,6 +28,15 @@ let timeline = []; | ||
const tmpl = (str) => { | ||
const context = { | ||
RepresentationID: targetID, | ||
Number: seq, | ||
Time: currtime, | ||
}; | ||
return str.replace(/\$(\w+)\$/g, (m, p1) => context[p1]); | ||
}; | ||
this._parser.on('opentag', (node) => { | ||
switch (node.name) { | ||
case 'mpd': | ||
starttime = | ||
currtime = | ||
new Date(node.attributes.availabilitystarttime).getTime(); | ||
@@ -45,14 +55,19 @@ isStatic = node.attributes.type !== 'dynamic'; | ||
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; | ||
seq = parseInt(node.attributes.startnumber) || seq; | ||
timescale = parseInt(node.attributes.timescale) || timescale; | ||
duration = parseInt(node.attributes.duration) || duration; | ||
offset = parseInt(node.attributes.presentationtimeoffset) || offset; | ||
if (!startEmitted) { | ||
startEmitted = true; | ||
if (offset) { | ||
starttime += offset; | ||
currtime += offset; | ||
} | ||
this.emit('starttime', starttime); | ||
this.emit('starttime', currtime); | ||
} | ||
break; | ||
case 'segmenttemplate': | ||
segmentTemplate = node.attributes; | ||
seq = parseInt(node.attributes.startnumber) || seq; | ||
timescale = parseInt(node.attributes.timescale) || timescale; | ||
break; | ||
case 'segmenttimeline': | ||
@@ -63,3 +78,6 @@ case 'baseurl': | ||
case 's': | ||
timeline.push(parseInt(node.attributes.d, 10)); | ||
timeline.push([ | ||
parseInt(node.attributes.d), | ||
parseInt(node.attributes.r) | ||
]); | ||
break; | ||
@@ -73,2 +91,25 @@ case 'adaptationset': | ||
getSegments = node.attributes.id === targetID + ''; | ||
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; | ||
@@ -87,3 +128,3 @@ case 'initialization': | ||
let tl = timeline.shift(); | ||
let segmentDuration = (tl || duration) / timescale * 1000; | ||
let segmentDuration = (tl && tl[0] || duration) / timescale * 1000; | ||
this.emit('item', { | ||
@@ -94,2 +135,3 @@ url: baseURL.filter(s => !!s).join('') + node.attributes.media, | ||
}); | ||
currtime += segmentDuration; | ||
} | ||
@@ -96,0 +138,0 @@ break; |
@@ -24,3 +24,3 @@ const PassThrough = require('stream').PassThrough; | ||
'dash-mpd': DashMPDParser, | ||
}[options.parser || 'm3u8']; | ||
}[options.parser || (/\.mpd$/.test(playlistURL) ? 'dash-mpd' : 'm3u8')]; | ||
if (!Parser) { | ||
@@ -36,12 +36,20 @@ throw TypeError(`parser '${options.parser}' not supported`); | ||
let currSegment; | ||
let bytesDownloaded = 0; | ||
const streamQueue = new Queue((segment, callback) => { | ||
currSegment = segment; | ||
segment.pipe(stream, { end: false }); | ||
segment.on('data', (chunk) => { bytesDownloaded += chunk.length; }); | ||
segment.on('end', callback); | ||
}, { concurrency: 1 }); | ||
let segmentNumber = 0; | ||
const requestQueue = new Queue((segmentURL, callback) => { | ||
let segment = miniget(urlResolve(playlistURL, segmentURL), requestOptions); | ||
segmentURL = urlResolve(playlistURL, segmentURL); | ||
let segment = miniget(segmentURL, requestOptions); | ||
segment.on('error', callback); | ||
streamQueue.push(segment, callback); | ||
streamQueue.push(segment, () => { | ||
stream.emit('progress', | ||
++segmentNumber, requestQueue.total, bytesDownloaded, segmentURL); | ||
callback(); | ||
}); | ||
}, { concurrency: chunkReadahead }); | ||
@@ -62,2 +70,3 @@ | ||
let ended = false; | ||
let static = false; | ||
let lastRefresh; | ||
@@ -69,7 +78,8 @@ | ||
onError(err); | ||
} else if (!fetchingPlaylist && !ended && | ||
} else if (!fetchingPlaylist && !ended && !static && | ||
requestQueue.tasks.length + requestQueue.active === refreshThreshold) { | ||
let ms = Math.max(0, minRefreshTime - (Date.now() - lastRefresh)); | ||
refreshTimeout = setTimeout(refreshPlaylist, ms); | ||
} else if (ended && !requestQueue.tasks.length && !requestQueue.active) { | ||
} else if ((ended || static) && | ||
!requestQueue.tasks.length && !requestQueue.active) { | ||
stream.end(); | ||
@@ -94,3 +104,3 @@ } | ||
}); | ||
parser.on('endlist', () => { ended = true; }); | ||
parser.on('endlist', () => { static = true; }); | ||
parser.on('endearly', () => { currPlaylist.unpipe(parser); }); | ||
@@ -97,0 +107,0 @@ |
@@ -33,7 +33,7 @@ const Writable = require('stream').Writable; | ||
case 'EXT-X-MEDIA-SEQUENCE': | ||
this._seq = parseInt(value, 10); | ||
this._seq = parseInt(value); | ||
break; | ||
case 'EXTINF': | ||
this._nextItemDuration = | ||
Math.round(parseFloat(value.split(',')[0], 10) * 1000); | ||
Math.round(parseFloat(value.split(',')[0]) * 1000); | ||
break; | ||
@@ -40,0 +40,0 @@ case 'EXT-X-ENDLIST': |
@@ -13,2 +13,3 @@ module.exports = class Queue { | ||
this.tasks = []; | ||
this.total = 0; | ||
this.active = 0; | ||
@@ -26,2 +27,3 @@ } | ||
this.tasks.push({ item, callback }); | ||
this.total++; | ||
this._next(); | ||
@@ -28,0 +30,0 @@ } |
@@ -13,3 +13,3 @@ { | ||
], | ||
"version": "0.4.2", | ||
"version": "0.5.0", | ||
"repository": { | ||
@@ -33,3 +33,3 @@ "type": "git", | ||
"istanbul": "^0.4.5", | ||
"lolex": "^2.7.1", | ||
"lolex": "^3.0.0", | ||
"mocha": "^5.0.0", | ||
@@ -36,0 +36,0 @@ "nock": "^10.0.0" |
@@ -35,7 +35,17 @@ # node-m3u8stream | ||
* `requestOptions` - Any options you want to pass to [miniget](https://github.com/fent/node-miniget), such as `headers`. | ||
* `parser` - Either "m3u8" or "dash-mpd". Defaults to "m3u8". | ||
* `id` - If playlist contains multiple media options. Otherwise, the first representation will be picked. | ||
* `parser` - Either "m3u8" or "dash-mpd". Defaults to guessing based on the playlist url ending in `.m3u8` or `.mpd`. | ||
* `id` - For playlist containing multiple media options. If not given, the first representation will be picked. | ||
Stream has an `.end()` method, that if called, stops requesting segments, and refreshing the playlist. | ||
### Stream#end() | ||
If called, stops requesting segments, and refreshing the playlist. | ||
#### Event: progress | ||
* `number` - Current segment number. | ||
* `number` - Total number of segments. | ||
* `number` - Bytes downloaded up to this point. | ||
* `string` - URL of current segment. | ||
For static non-live playlists, emitted each time a segment has finished downloading. Since total download size is unknown until all segment endpoints are hit, progress is calculated based on how many segments are available. | ||
### Limitations | ||
@@ -42,0 +52,0 @@ |
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
19008
464
68