pipeline = async(args, validate, retryOptions, endpoints) => {
  let info;
  for (let func of endpoints) {
    try {
      const newInfo = await retryFunc(func, args.concat([info]), retryOptions);
      if (newInfo.player_response) {
        newInfo.player_response.videoDetails = assign(
          info && info.player_response && info.player_response.videoDetails,
          newInfo.player_response.videoDetails);
        newInfo.player_response = assign(info && info.player_response, newInfo.player_response);
      }
      info = assign(info, newInfo);
      if (validate(info, false)) {
        break;
      }
    } catch (err) {
      if (err instanceof UnrecoverableError || func === endpoints[endpoints.length - 1]) {
        throw err;
      }
      // Unable to find video metadata... so try next endpoint.
    }
  }
  return info;
}
Goes through each endpoint in the pipeline, retrying on failure if the error is recoverable.
If unable to succeed with one endpoint, moves onto the next one.

@param {Array.<Object>} args
@param {Function} validate
@param {Object} retryOptions
@param {Array.<Function>} endpoints
@returns {[Object, Object, Object]}