text: const PassThrough = require('stream').PassThrough;
  const deprecate   = require('util').deprecate;
  const getInfo     = require('./info');
  const util        = require('./util');
  const sig         = require('./sig');
  const request     = require('miniget');
  const m3u8stream  = require('m3u8stream');
  const parseTime   = require('m3u8stream/lib/parse-time');
  
  
  /**
   * @param {String} link
   * @param {!Object} options
   * @return {ReadableStream}
   */
  const ytdl = module.exports = function ytdl(link, options) {
    const stream = createStream(options);
    ytdl.getInfo(link, options, (err, info) => {
      if (err) {
        stream.emit('error', err);
        return;
      }
  
      downloadFromInfoCallback(stream, info, options);
    });
  
    return stream;
  };
  
  ytdl.getBasicInfo = getInfo.getBasicInfo;
  ytdl.getInfo = getInfo.getFullInfo;
  ytdl.chooseFormat = util.chooseFormat;
  ytdl.filterFormats = util.filterFormats;
  ytdl.validateID = util.validateID;
  ytdl.validateURL = util.validateURL;
  ytdl.validateLink = deprecate(util.validateURL,
    'ytdl.validateLink: Renamed to ytdl.validateURL');
  ytdl.getURLVideoID = util.getURLVideoID;
  ytdl.getVideoID = util.getVideoID;
  ytdl.cache = {
    sig: sig.cache,
    info: getInfo.cache,
  };
  
  
  function createStream(options) {
    const stream = new PassThrough({
      highWaterMark: options && options.highWaterMark || null,
    });
    stream.destroy = () => { stream._isDestroyed = true; };
    return stream;
  }
  
  
  /**
   * Chooses a format to download.
   *
   * @param {stream.Readable} stream
   * @param {Object} info
   * @param {Object} options
   */
  function downloadFromInfoCallback(stream, info, options) {
    options = options || {};
    const format = util.chooseFormat(info.formats, options);
    if (format instanceof Error) {
      // The caller expects this function to be async.
      setImmediate(() => {
        stream.emit('error', format);
      });
      return;
    }
    stream.emit('info', info, format);
    if (stream._isDestroyed) { return; }
  
    let url = format.url;
    if (format.live) {
      let req = m3u8stream(url, {
        chunkReadahead: +info.live_chunk_readahead,
        begin: options.begin || Date.now(),
        liveBuffer: options.liveBuffer,
        requestOptions: options.requestOptions,
        parser: /\/manifest\/dash\//.test(format.url) ? 'dash-mpd' : 'm3u8',
        id: format.itag,
      });
      req.on('error', stream.emit.bind(stream, 'error'));
      stream.destroy = req.end.bind(req);
      req.pipe(stream);
  
    } else {
      if (options.begin) {
        url += '&begin=' + parseTime(options.begin);
      }
      let requestOptions = Object.assign({}, options.requestOptions, {
        maxReconnects: 5
      });
      if (options.range && (options.range.start || options.range.end)) {
        requestOptions.headers = Object.assign({}, requestOptions.headers, {
          Range: `bytes=${options.range.start || '0'}-${options.range.end || ''}`
        });
      }
  
      const req = request(url, requestOptions);
      const ondata = (chunk) => {
        downloaded += chunk.length;
        stream.emit('progress', chunk.length, downloaded, contentLength);
      };
      stream.destroy = () => {
        stream._isDestroyed = true;
        req.abort();
        req.removeListener('data', ondata);
        req.unpipe();
      };
  
      // Forward events from the request to the stream.
      [
        'abort', 'request', 'response', 'error', 'retry', 'reconnect'
      ].forEach((event) => {
        req.on(event, (arg) => { stream.emit(event, arg); });
      });
  
      let contentLength, downloaded = 0;
      req.on('response', (res) => {
        if (stream._isDestroyed) { return; }
        if (!contentLength) {
          contentLength = parseInt(res.headers['content-length'], 10);
        }
      });
  
      req.on('data', ondata);
      req.pipe(stream);
    }
  }
  
  
  /**
   * Can be used to download video after its `info` is gotten through
   * `ytdl.getInfo()`. In case the user might want to look at the
   * `info` object before deciding to download.
   *
   * @param {Object} info
   * @param {!Object} options
   */
  ytdl.downloadFromInfo = (info, options) => {
    const stream = createStream(options);
    if (!info.full) {
      throw new Error('Cannot use `ytdl.downloadFromInfo()` when called ' +
        'with info from `ytdl.getBasicInfo()`');
    }
    setImmediate(() => {
      downloadFromInfoCallback(stream, info, options);
    });
    return stream;
  };
  