【问题标题】:GDrive API v3 files.get download progress?GDrive API v3 files.get 下载进度?
【发布时间】:2020-05-01 23:10:59
【问题描述】:

如何使用 gapi 客户端 v3 API 显示从 GDrive 下载大文件的进度?

我正在使用 v3 API,并且我尝试在标头中使用 Range 请求,这可行,但下载速度很慢(如下)。我的最终目标是播放 4K 视频。 GDrive limits playback to 1920x1280。我的计划是通过 v3 API 将块下载到 IndexedDB 并从本地缓存的数据中播放。我通过 Range 请求使用下面的代码进行了这项工作,但速度非常慢。直接正常下载完整的 438 MB 测试文件(例如通过 GDrive 网页)在我的连接上大约需要 30-35 秒,巧合的是,每个 1 MB 范围请求几乎完全相同 30-35 秒。感觉 GDrive 后端正在读取和发送每个子范围的完整文件?

我也尝试过使用 XHR 和 fetch 下载文件,但失败了。我一直在使用 webContent 链接(通常以 &export=download 结尾),但我无法正确获取访问标题。我得到了 CORS 或其他奇怪的权限问题。 webContent 链接在<image><video> src 标记中工作正常。我预计这是由于特殊权限处理或我缺少浏览器专门为这些媒体标签处理的一些标头信息。我的解决方案必须能够读取私有(非公共、不可共享)链接,因此需要使用 v3 API。

对于小于 GDrive 限制的视频文件,我可以设置一个 MediaRecorder 并使用 <video> 元素来获取进度数据。不幸的是,对于较大的文件,1920x1080 的限制扼杀了这种方法,因为进度反馈更为重要。

这是客户端的 gapi Range 代码,它可以工作,但对于大型 (400 MB - 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. 当然等待没有帮助,如果我不请求流,我可以获得成功的响应。

【问题讨论】:

    标签: google-drive-api video-streaming html5-video google-api-js-client


    【解决方案1】:

    我转而直接使用 XMLHttpRequest,而不是使用 gapi 包装器。 Google provides these instructions for using CORS 展示了如何将任何请求从使用 gapi 转换为 XHR。然后您可以附加到 onprogress 事件(以及 onload、onerror 等)以获取进度。

    这是问题中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()
    })
    

    【讨论】:

      猜你喜欢
      • 2021-02-07
      • 2016-09-26
      • 1970-01-01
      • 1970-01-01
      • 2023-02-06
      • 2018-02-25
      • 1970-01-01
      • 2020-08-27
      • 1970-01-01
      相关资源
      最近更新 更多