UNPKG

4.2 kBJavaScriptView Raw
1const PassThrough = require('stream').PassThrough;
2const getInfo = require('./info');
3const util = require('./util');
4const sig = require('./sig');
5const miniget = require('miniget');
6const m3u8stream = require('m3u8stream');
7const parseTime = require('m3u8stream/dist/parse-time');
8
9
10/**
11 * @param {string} link
12 * @param {!Object} options
13 * @return {ReadableStream}
14 */
15const ytdl = (link, options) => {
16 const stream = createStream(options);
17 ytdl.getInfo(link, options, (err, info) => {
18 if (err) {
19 stream.emit('error', err);
20 return;
21 }
22
23 downloadFromInfoCallback(stream, info, options);
24 });
25
26 return stream;
27};
28module.exports = ytdl;
29
30ytdl.getBasicInfo = getInfo.getBasicInfo;
31ytdl.getInfo = getInfo.getFullInfo;
32ytdl.chooseFormat = util.chooseFormat;
33ytdl.filterFormats = util.filterFormats;
34ytdl.validateID = util.validateID;
35ytdl.validateURL = util.validateURL;
36ytdl.getURLVideoID = util.getURLVideoID;
37ytdl.getVideoID = util.getVideoID;
38ytdl.cache = {
39 sig: sig.cache,
40 info: getInfo.cache,
41};
42
43
44const createStream = (options) => {
45 const stream = new PassThrough({
46 highWaterMark: options && options.highWaterMark || null,
47 });
48 stream.destroy = () => { stream._isDestroyed = true; };
49 return stream;
50};
51
52
53/**
54 * Chooses a format to download.
55 *
56 * @param {stream.Readable} stream
57 * @param {Object} info
58 * @param {Object} options
59 */
60const downloadFromInfoCallback = (stream, info, options) => {
61 options = options || {};
62 let format;
63 try {
64 format = util.chooseFormat(info.formats, options);
65 } catch(e) {
66 setImmediate(() => {
67 stream.emit('error', e);
68 });
69 return;
70 }
71 stream.emit('info', info, format);
72 if (stream._isDestroyed) { return; }
73
74 let contentLength, downloaded = 0;
75 const ondata = (chunk) => {
76 downloaded += chunk.length;
77 stream.emit('progress', chunk.length, downloaded, contentLength);
78 };
79
80 let req;
81 if (format.isHLS || format.isDashMPD) {
82 req = m3u8stream(format.url, {
83 chunkReadahead: +info.live_chunk_readahead,
84 begin: options.begin || format.live && Date.now(),
85 liveBuffer: options.liveBuffer,
86 requestOptions: options.requestOptions,
87 parser: format.isDashMPD ? 'dash-mpd' : 'm3u8',
88 id: format.itag,
89 });
90
91 req.on('progress', (segment, totalSegments) => {
92 stream.emit('progress', segment.size, segment.num, totalSegments);
93 });
94
95 } else {
96 if (options.begin) {
97 format.url += '&begin=' + parseTime.humanStr(options.begin);
98 }
99 let requestOptions = Object.assign({}, options.requestOptions, {
100 maxReconnects: 6,
101 maxRetries: 3,
102 backoff: { inc: 500, max: 10000 },
103 });
104 if (options.range && (options.range.start || options.range.end)) {
105 requestOptions.headers = Object.assign({}, requestOptions.headers, {
106 Range: `bytes=${options.range.start || '0'}-${options.range.end || ''}`
107 });
108 }
109
110 req = miniget(format.url, requestOptions);
111
112 req.on('response', (res) => {
113 if (stream._isDestroyed) { return; }
114 if (!contentLength) {
115 contentLength = parseInt(res.headers['content-length'], 10);
116 }
117 });
118 req.on('data', ondata);
119 }
120
121 stream.destroy = () => {
122 stream._isDestroyed = true;
123 if (req.abort) req.abort();
124 req.end();
125 req.removeListener('data', ondata);
126 req.unpipe();
127 };
128
129 // Forward events from the request to the stream.
130 [
131 'abort', 'request', 'response', 'error', 'retry', 'reconnect'
132 ].forEach((event) => {
133 req.prependListener(event, (arg) => {
134 stream.emit(event, arg); });
135 });
136
137 req.pipe(stream);
138};
139
140
141/**
142 * Can be used to download video after its `info` is gotten through
143 * `ytdl.getInfo()`. In case the user might want to look at the
144 * `info` object before deciding to download.
145 *
146 * @param {Object} info
147 * @param {!Object} options
148 */
149ytdl.downloadFromInfo = (info, options) => {
150 const stream = createStream(options);
151 if (!info.full) {
152 throw Error('Cannot use `ytdl.downloadFromInfo()` when called ' +
153 'with info from `ytdl.getBasicInfo()`');
154 }
155 setImmediate(() => {
156 downloadFromInfoCallback(stream, info, options);
157 });
158 return stream;
159};