index.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. var PassThrough = require('stream').PassThrough;
  2. var request = require('./request');
  3. var getInfo = require('./info');
  4. var util = require('./util');
  5. var cache = require('./cache');
  6. /**
  7. * @param {String} link
  8. * @param {!Object} options
  9. * @return {ReadableStream}
  10. */
  11. var ytdl = module.exports = function ytdl(link, options) {
  12. options = options || {};
  13. var stream = new PassThrough();
  14. getInfo(link, options, function(err, info) {
  15. if (err) {
  16. stream.emit('error', err);
  17. return;
  18. }
  19. downloadFromInfoCallback(stream, info, options);
  20. });
  21. return stream;
  22. };
  23. ytdl.getInfo = getInfo;
  24. ytdl.cache = cache;
  25. /**
  26. * Chooses a format to download.
  27. *
  28. * @param {stream.Readable} stream
  29. * @param {Object} info
  30. * @param {Object} options
  31. */
  32. function downloadFromInfoCallback(stream, info, options) {
  33. var format = util.chooseFormat(info.formats, options);
  34. if (format instanceof Error) {
  35. // The caller expects this function to be async.
  36. setImmediate(function() {
  37. stream.emit('error', format);
  38. });
  39. return;
  40. }
  41. stream.emit('info', info, format);
  42. var url = format.url;
  43. if (options.range) {
  44. url += '&range=' + options.range;
  45. }
  46. doDownload(stream, url, 3);
  47. }
  48. /**
  49. * Tries to download the video. Youtube might respond with a 302 redirect
  50. * status code. In which case, this function will call itself again.
  51. *
  52. * @param {stream.Readable} stream
  53. * @param {String} url
  54. * @param {Number} tryCount Prevent infinite redirects.
  55. */
  56. function doDownload(stream, url, tryCount) {
  57. if (tryCount === 0) {
  58. stream.emit('error', new Error('Too many redirects'));
  59. return;
  60. }
  61. // Start downloading the video.
  62. var req = request(url);
  63. req.on('error', function(err) {
  64. stream.emit('error', err);
  65. });
  66. req.on('response', function(res) {
  67. if (res.statusCode !== 200) {
  68. if (res.statusCode === 302) {
  69. // Redirection header.
  70. doDownload(stream, res.headers.location, tryCount - 1);
  71. return;
  72. }
  73. stream.emit('response', res);
  74. stream.emit('error', new Error('status code ' + res.statusCode));
  75. return;
  76. }
  77. stream.emit('response', res);
  78. res.pipe(stream);
  79. });
  80. }
  81. /**
  82. * Can be used to download video after its `info` is gotten through
  83. * `ytdl.getInfo()`. In case the user might want to look at the
  84. * `info` object before deciding to download.
  85. *
  86. * @param {Object} info
  87. * @param {!Object} options
  88. */
  89. ytdl.downloadFromInfo = function(info, options) {
  90. var stream = new PassThrough();
  91. options = options || {};
  92. setImmediate(function() {
  93. downloadFromInfoCallback(stream, info, options);
  94. });
  95. return stream;
  96. };