GDrive API v3 files.get прогресс загрузки? - PullRequest
0 голосов
/ 02 мая 2020

Как я могу показать прогресс загрузки большого файла из GDrive с помощью API версии 3 на стороне клиента gapi?

Я использую API версии 3 и пытался использовать запрос Range в заголовок, который работает, но загрузка идет очень медленно (ниже). Моя конечная цель - воспроизвести видео 4K. GDrive ограничивает воспроизведение до 1920x1280 . Мой план состоял в том, чтобы загружать фрагменты в IndexedDB через API v3 и воспроизводить данные из локального кэша. У меня это работает, используя код ниже через запросы Range, но это необычайно медленно. Обычная загрузка полного тестового файла в 438 МБ напрямую (например, через веб-страницу GDrive) занимает около 30-35 секунд на моем соединении, и, по совпадению, каждый запрос диапазона 1 МБ занимает почти точно те же 30-35 секунд. Такое чувство, что бэкэнд GDrive читает и отправляет полный файл для каждого поддиапазона?

Я также пытался использовать XHR и fetch для загрузки файла, что не удается. Я использую ссылку webContent (которая обычно заканчивается на &export=download), но я не могу получить правильные заголовки доступа. Я получаю либо CORS, либо другие странные проблемы с разрешениями. Ссылки webContent отлично работают в тегах <image> и <video> sr c. Я ожидаю, что это связано со специальной обработкой разрешений или отсутствующей информацией заголовка, которую браузер обрабатывает специально для этих тегов мультимедиа. Мое решение должно быть в состоянии читать частные (не опубликованные c, не разделяемые) ссылки, следовательно, использовать API v3.

Для видеофайлов, размер которых меньше предела GDrive, я могу установить вверх MediaRecorder и использовать элемент <video>, чтобы получить данные с прогрессом. К сожалению, ограничение 1920x1080 убивает этот подход для больших файлов, где обратная связь о прогрессе еще более важна.

Это код диапазона Range на стороне клиента, который работает, но необычно медленный для больших (400 МБ - 2). GB) файлы:

const getRange = (start, end, size, fileId, onProgress) => (
  new Promise((resolve, reject) => gapi.client.drive.files.get(
    { fileId, alt: 'media', Range: `bytes=${start}-${end}` },
    // { responseType: 'stream' }, Perhaps this fails in the browser?
  ).then(res => {
    if (onProgress) {
      const cancel = onProgress({ loaded: end, size, fileId })
      if (cancel) {
        reject(new Error(`Progress canceled download at range ${start} to ${end} in ${fileId}`))
      }
    }
    return resolve(res.body)
  }, err => reject(err)))
)

export const downloadFileId = async (fileId, size, onProgress) => {
  const batch = 1024 * 1024
  try {
    const chunks = []
    for (let start = 0; start < size; start += batch) {
      const end = Math.min(size, start + batch - 1)
      const data = await getRange(start, end, size, fileId, onProgress)
      if (!data) throw new Error(`Unable to get range ${start} to ${end} in ${fileId}`)
      chunks.push(data)
    }
    return chunks.join('')
  } catch (err) {
    return console.error(`Error downloading file: ${err.message}`)
  }
}

У меня отлично работает аутентификация, и я прекрасно использую другие команды GDrive. В настоящее время я использую область действия drive.photos.readonly, но у меня возникают те же проблемы, даже если я использую область полного разрешения на запись.

Тангенциально, я не могу получить поток при работе на стороне клиента используя gapi (прекрасно работает в узле на стороне сервера). Это просто странно. Если бы я мог получить поток, я думаю, я мог бы использовать это, чтобы добиться прогресса. Всякий раз, когда я добавляю закомментированную строку для responseType: 'stream', я получаю следующую ошибку: The server encountered a temporary error and could not complete your request. Please try again in 30 seconds. That’s all we know. Конечно, ожидание НЕ помогает, и я могу получить успешный ответ, если я не запрашиваю поток.

1 Ответ

1 голос
/ 03 мая 2020

Я переключился на использование XMLHttpRequest напрямую, а не на обертку gapi. Google предоставляет эти инструкции для использования CORS , которые показывают, как преобразовать любой запрос из использования gapi в XHR. Затем вы можете присоединиться к событию onprogress (и onload, onerror и другим), чтобы получить progres.

Вот код вставки замены для метода downloadFileId в вопросе с кучей отладочных скаффолдингов:

const xhrDownloadFileId = (fileId, onProgress) => new Promise((resolve, reject) => {
  const user = gapi.auth2.getAuthInstance().currentUser.get()
  const oauthToken = user.getAuthResponse().access_token
  const xhr = new XMLHttpRequest()
  xhr.open('GET', `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`)
  xhr.setRequestHeader('Authorization', `Bearer ${oauthToken}`)
  xhr.responseType = 'blob'
  xhr.onloadstart = event => {
    console.log(`xhr ${fileId}: on load start`)
    const { loaded, total } = event
    onProgress({ loaded, size: total })
  }
  xhr.onprogress = event => {
    console.log(`xhr ${fileId}: loaded ${event.loaded} of ${event.total} ${event.lengthComputable ? '' : 'non-'}computable`)
    const { loaded, total } = event
    onProgress({ loaded, size: total })
  }
  xhr.onabort = event => {
    console.warn(`xhr ${fileId}: download aborted at ${event.loaded} of ${event.total}`)
    reject(new Error('Download aborted'))
  }
  xhr.onerror = event => {
    console.error(`xhr ${fileId}: download error at ${event.loaded} of ${event.total}`)
    reject(new Error('Error downloading file'))
  }
  xhr.onload = event => {
    console.log(`xhr ${fileId}: download of ${event.total} succeeded`)
    const { loaded, total } = event
    onProgress({ loaded, size: total })
    resolve(xhr.response)
  }
  xhr.onloadend = event => console.log(`xhr ${fileId}: download of ${event.total} completed`)
  xhr.ontimeout = event => {
    console.warn(`xhr ${fileId}: download timeout after ${event.loaded} of ${event.total}`)
    reject(new Error('Timout downloading file'))
  }
  xhr.send()
})
...