1
0
mirror of https://github.com/musix-org/musix-oss synced 2025-06-16 12:36:01 +00:00
This commit is contained in:
MatteZ02
2019-05-30 12:06:47 +03:00
parent cbdffcf19c
commit 5eb0264906
2502 changed files with 360854 additions and 0 deletions

21
node_modules/m3u8stream/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (C) 2017 by fent
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

70
node_modules/m3u8stream/README.md generated vendored Normal file
View File

@ -0,0 +1,70 @@
# node-m3u8stream
Reads segments from a [m3u8 playlist][1] or [DASH MPD file][2] into a consumable stream.
[1]: https://tools.ietf.org/html/draft-pantos-http-live-streaming-20
[2]: https://dashif.org/docs/DASH-IF-IOP-v4.2-clean.pdf
[![Build Status](https://secure.travis-ci.org/fent/node-m3u8stream.svg)](http://travis-ci.org/fent/node-m3u8stream)
[![Dependency Status](https://david-dm.org/fent/node-m3u8stream.svg)](https://david-dm.org/fent/node-m3u8stream)
[![codecov](https://codecov.io/gh/fent/node-m3u8stream/branch/master/graph/badge.svg)](https://codecov.io/gh/fent/node-m3u8stream)
# Usage
```js
const fs = require('fs');
const m3u8stream = require('m3u8stream')
m3u8stream('http://somesite.com/link/to/the/playlist.m3u8')
.pipe(fs.createWriteStream('videofile.mp4'));
```
# API
### m3u8stream(url, [options])
Creates a readable stream of binary media data. `options` can have the following
* `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`.
* `chunkReadahead` - How many chunks to preload ahead. Default is `3`.
* `highWaterMark` - How much of the download to buffer into the stream. See [node's docs](https://nodejs.org/api/stream.html#stream_constructor_new_stream_writable_options) for more. Note that the actual amount buffered can be higher since each chunk request maintains its own buffer.
* `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 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#end()
If called, stops requesting segments, and refreshing the playlist.
#### Event: progress
* `Object` - Current segment with the following fields,
- `number` - number
- `number` - size
- `number` - duration
- `string` - url
* `number` - Total number of segments.
* `number` - Bytes downloaded up to this point.
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
Currently, it does not support [encrypted media segments](https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-4.3.2.4). This is because the sites where this was tested on and intended for, YouTube and Twitch, don't use it.
This does not parse master playlists, only media playlists. If you want to parse a master playlist to get links to media playlists, you can try the [m3u8 module](https://github.com/tedconf/node-m3u8).
# Install
npm install m3u8stream
# Tests
Tests are written with [mocha](https://mochajs.org)
```bash
npm test
```

178
node_modules/m3u8stream/lib/dash-mpd-parser.js generated vendored Normal file
View File

@ -0,0 +1,178 @@
const Writable = require('stream').Writable;
const sax = require('sax');
const parseTime = require('./parse-time');
/**
* 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 currtime = 0;
let seq = 0;
let segmentTemplate;
let timescale, offset, duration, baseURL;
let timeline = [];
let getSegments = false;
let isStatic;
let treeLevel;
let periodStart;
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':
currtime =
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;
periodStart = parseTime.durationStr(node.attributes.start) || 0;
break;
case 'segmentlist':
seq = parseInt(node.attributes.startnumber) || seq;
timescale = parseInt(node.attributes.timescale) || timescale;
duration = parseInt(node.attributes.duration) || duration;
offset = parseInt(node.attributes.presentationtimeoffset) || offset;
break;
case 'segmenttemplate':
segmentTemplate = node.attributes;
seq = parseInt(node.attributes.startnumber) || seq;
timescale = parseInt(node.attributes.timescale) || timescale;
break;
case 'segmenttimeline':
case 'baseurl':
lastTag = node.name;
break;
case 's':
timeline.push([
parseInt(node.attributes.d),
parseInt(node.attributes.r)
]);
break;
case 'adaptationset':
case 'representation':
treeLevel++;
if (targetID == null) {
targetID = node.attributes.id;
}
getSegments = node.attributes.id === targetID + '';
if (getSegments) {
if (periodStart) {
currtime += periodStart;
}
if (offset) {
currtime -= offset / timescale * 1000;
}
this.emit('starttime', currtime);
}
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;
case 'initialization':
if (getSegments) {
this.emit('item', {
url: baseURL.filter(s => !!s).join('') + node.attributes.sourceurl,
seq: seq++,
duration: 0,
});
}
break;
case 'segmenturl':
if (getSegments) {
let tl = timeline.shift();
let segmentDuration = (tl && tl[0] || duration) / timescale * 1000;
this.emit('item', {
url: baseURL.filter(s => !!s).join('') + node.attributes.media,
seq: seq++,
duration: segmentDuration,
});
currtime += segmentDuration;
}
break;
}
});
const onEnd = () => {
if (isStatic) { this.emit('endlist'); }
if (!getSegments) {
this.emit('error', 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();
}
};

178
node_modules/m3u8stream/lib/index.js generated vendored Normal file
View File

@ -0,0 +1,178 @@
const PassThrough = require('stream').PassThrough;
const urlResolve = require('url').resolve;
const miniget = require('miniget');
const m3u8Parser = require('./m3u8-parser');
const DashMPDParser = require('./dash-mpd-parser');
const Queue = require('./queue');
const parseTime = require('./parse-time');
/**
* @param {string} playlistURL
* @param {Object} options
* @return {stream.Readable}
*/
module.exports = (playlistURL, options) => {
const stream = new PassThrough();
options = options || {};
const chunkReadahead = options.chunkReadahead || 3;
const liveBuffer = options.liveBuffer || 20000; // 20 seconds
const requestOptions = options.requestOptions;
const Parser = {
'm3u8': m3u8Parser,
'dash-mpd': DashMPDParser,
}[options.parser || (/\.mpd$/.test(playlistURL) ? 'dash-mpd' : 'm3u8')];
if (!Parser) {
throw TypeError(`parser '${options.parser}' not supported`);
}
let relativeBegin = typeof options.begin === 'string';
let begin = relativeBegin ?
parseTime.humanStr(options.begin) :
Math.max(options.begin - liveBuffer, 0) || 0;
let liveBegin = Date.now() - liveBuffer;
let currSegment;
const streamQueue = new Queue((req, callback) => {
currSegment = req;
// Count the size manually, since the `content-length` header is not
// always there.
let size = 0;
req.on('data', (chunk) => size += chunk.length);
req.pipe(stream, { end: false });
req.on('end', () => callback(size));
}, { concurrency: 1 });
let segmentNumber = 0;
let downloaded = 0;
const requestQueue = new Queue((segment, callback) => {
let req = miniget(urlResolve(playlistURL, segment.url), requestOptions);
req.on('error', callback);
streamQueue.push(req, (size) => {
downloaded += size;
stream.emit('progress', {
num: ++segmentNumber,
size: size,
url: segment.url,
duration: segment.duration,
}, requestQueue.total, downloaded);
callback();
});
}, { concurrency: chunkReadahead });
const onError = (err) => {
if (ended) { return; }
stream.emit('error', err);
// Stop on any error.
stream.end();
};
// When to look for items again.
let refreshThreshold;
let minRefreshTime;
let refreshTimeout;
let fetchingPlaylist = false;
let ended = false;
let isStatic = false;
let lastRefresh;
const onQueuedEnd = (err) => {
currSegment = null;
if (err) {
onError(err);
} else if (!fetchingPlaylist && !ended && !isStatic &&
requestQueue.tasks.length + requestQueue.active === refreshThreshold) {
let ms = Math.max(0, minRefreshTime - (Date.now() - lastRefresh));
refreshTimeout = setTimeout(refreshPlaylist, ms);
} else if ((ended || isStatic) &&
!requestQueue.tasks.length && !requestQueue.active) {
stream.end();
}
};
let currPlaylist;
let lastSeq;
const refreshPlaylist = () => {
fetchingPlaylist = true;
lastRefresh = Date.now();
currPlaylist = miniget(playlistURL, requestOptions);
currPlaylist.on('error', onError);
const parser = currPlaylist.pipe(new Parser(options.id));
let starttime = null;
parser.on('starttime', (a) => {
starttime = a;
if (relativeBegin && begin >= 0) {
begin += starttime;
}
});
parser.on('endlist', () => { isStatic = true; });
parser.on('endearly', () => { currPlaylist.unpipe(parser); });
let addedItems = [];
let liveAddedItems = [];
const addItem = (item, isLive) => {
if (item.seq <= lastSeq) { return; }
lastSeq = item.seq;
begin = item.time;
requestQueue.push(item, onQueuedEnd);
addedItems.push(item);
if (isLive) {
liveAddedItems.push(item);
}
};
let tailedItems = [], tailedItemsDuration = 0;
parser.on('item', (item) => {
item.time = starttime;
if (!starttime || begin <= item.time) {
addItem(item, liveBegin <= item.time);
} else {
tailedItems.push(item);
tailedItemsDuration += item.duration;
// Only keep the last `liveBuffer` of items.
while (tailedItems.length > 1 &&
tailedItemsDuration - tailedItems[0].duration > liveBuffer) {
tailedItemsDuration -= tailedItems.shift().duration;
}
}
starttime += item.duration;
});
parser.on('end', () => {
currPlaylist = null;
// If we are too ahead of the stream, make sure to get the
// latest available items with a small buffer.
if (!addedItems.length && tailedItems.length) {
tailedItems.forEach((item) => { addItem(item, true); });
}
// Refresh the playlist when remaining segments get low.
refreshThreshold = Math.max(1, Math.ceil(addedItems.length * 0.01));
// Throttle refreshing the playlist by looking at the duration
// of live items added on this refresh.
minRefreshTime =
addedItems.reduce(((total, item) => item.duration + total), 0);
fetchingPlaylist = false;
});
};
refreshPlaylist();
stream.end = () => {
ended = true;
streamQueue.die();
requestQueue.die();
clearTimeout(refreshTimeout);
if (currPlaylist) {
currPlaylist.unpipe();
currPlaylist.abort();
}
if (currSegment) {
currSegment.unpipe();
currSegment.abort();
}
PassThrough.prototype.end.call(stream);
};
return stream;
};

67
node_modules/m3u8stream/lib/m3u8-parser.js generated vendored Normal file
View File

@ -0,0 +1,67 @@
const Writable = require('stream').Writable;
/**
* A very simple m3u8 playlist file parser that detects tags and segments.
*
* @extends WritableStream
* @constructor
*/
module.exports = class m3u8Parser extends Writable {
constructor() {
super();
this._lastLine = '';
this._seq = 0;
this._nextItemDuration = null;
this.on('finish', () => {
this._parseLine(this._lastLine);
this.emit('end');
});
}
_parseLine(line) {
let match = line.match(/^#(EXT[A-Z0-9-]+)(?::(.*))?/);
if (match) {
// This is a tag.
const tag = match[1];
const value = match[2] || null;
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 '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,
});
}
}
_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 (i < lines.length - 1) {
this._parseLine(line);
} else {
// Save the last line in case it has been broken up.
this._lastLine = line;
}
});
callback();
}
};

51
node_modules/m3u8stream/lib/parse-time.js generated vendored Normal file
View File

@ -0,0 +1,51 @@
/**
* Converts human friendly time to milliseconds. Supports the format
* 00:00:00.000 for hours, minutes, seconds, and milliseconds respectively.
* And 0ms, 0s, 0m, 0h, and together 1m1s.
*
* @param {string|number} time
* @return {number}
*/
const numberFormat = /^\d+$/;
const timeFormat = /^(?:(?:(\d+):)?(\d{1,2}):)?(\d{1,2})(?:\.(\d{3}))?$/;
const timeUnits = {
ms: 1,
s: 1000,
m: 60000,
h: 3600000,
};
exports.humanStr = (time) => {
if (typeof time === 'number') { return time; }
if (numberFormat.test(time)) { return +time; }
const firstFormat = timeFormat.exec(time);
if (firstFormat) {
return +(firstFormat[1] || 0) * timeUnits.h +
+(firstFormat[2] || 0) * timeUnits.m +
+firstFormat[3] * timeUnits.s +
+(firstFormat[4] || 0);
} else {
let total = 0;
const r = /(-?\d+)(ms|s|m|h)/g;
let rs;
while ((rs = r.exec(time)) != null) {
total += +rs[1] * timeUnits[rs[2]];
}
return total;
}
};
/**
* Parses a duration string in the form of "123.456S", returns milliseconds.
*
* @param {string} time
* @return {number}
*/
exports.durationStr = (time) => {
let total = 0;
const r = /(\d+(?:\.\d+)?)(S|M|H)/g;
let rs;
while ((rs = r.exec(time)) != null) {
total += +rs[1] * timeUnits[rs[2].toLowerCase()];
}
return total;
};

55
node_modules/m3u8stream/lib/queue.js generated vendored Normal file
View File

@ -0,0 +1,55 @@
module.exports = class Queue {
/**
* A really simple queue with concurrency.
*
* @param {Function(Object, Function)} worker
* @param {Object} options
*/
constructor(worker, options) {
this._worker = worker;
options = options || {};
this._concurrency = options.concurrency || 1;
this.tasks = [];
this.total = 0;
this.active = 0;
}
/**
* Push a task to the queue.
*
* @param {Object} item
* @param {Function(Error)} callback
*/
push(item, callback) {
this.tasks.push({ item, callback });
this.total++;
this._next();
}
/**
* Process next job in queue.
*/
_next() {
if (this.active >= this._concurrency || !this.tasks.length) { return; }
const { item, callback } = this.tasks.shift();
let callbackCalled = false;
this.active++;
this._worker(item, (err) => {
if (callbackCalled) { return; }
this.active--;
callbackCalled = true;
if (callback) { callback(err); }
this._next();
});
}
/**
* Stops processing queued jobs.
*/
die() {
this.tasks = [];
}
};

72
node_modules/m3u8stream/package.json generated vendored Normal file
View File

@ -0,0 +1,72 @@
{
"_from": "m3u8stream@^0.6.2",
"_id": "m3u8stream@0.6.2",
"_inBundle": false,
"_integrity": "sha512-WsuM2bd5pPN80xvfrB+1DZqr4M7+kJl8byi6+ZCy6cmVjEiHhmr/desN53Ngsa6Hs13kYumeVgT4wL0oIJ+v6g==",
"_location": "/m3u8stream",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "m3u8stream@^0.6.2",
"name": "m3u8stream",
"escapedName": "m3u8stream",
"rawSpec": "^0.6.2",
"saveSpec": null,
"fetchSpec": "^0.6.2"
},
"_requiredBy": [
"/ytdl-core"
],
"_resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.6.2.tgz",
"_shasum": "c2c62ddce88156d80378dd59be010c8b8e9d120b",
"_spec": "m3u8stream@^0.6.2",
"_where": "C:\\Users\\matia\\Bot Files\\node_modules\\ytdl-core",
"author": {
"name": "fent",
"url": "https://github.com/fent"
},
"bugs": {
"url": "https://github.com/fent/node-m3u8stream/issues"
},
"bundleDependencies": false,
"dependencies": {
"miniget": "^1.4.0",
"sax": "^1.2.4"
},
"deprecated": false,
"description": "Reads segments from a m3u8 or dash playlist into a consumable stream.",
"devDependencies": {
"istanbul": "^0.4.5",
"lolex": "^3.0.0",
"mocha": "^5.0.0",
"nock": "^10.0.0"
},
"engines": {
"node": ">=6"
},
"files": [
"lib"
],
"homepage": "https://github.com/fent/node-m3u8stream#readme",
"keywords": [
"m3u8",
"hls",
"dash",
"live",
"playlist",
"segments",
"stream"
],
"license": "MIT",
"main": "./lib/index.js",
"name": "m3u8stream",
"repository": {
"type": "git",
"url": "git://github.com/fent/node-m3u8stream.git"
},
"scripts": {
"test": "istanbul cover node_modules/.bin/_mocha -- test/*-test.js"
},
"version": "0.6.2"
}