如何显示使用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并提取文件,但失败。我一直在使用webContent链接(通常以&export=download
结尾),但是我无法正确获取访问标头。我收到CORS或其他奇怪的权限问题。在<image>
和<video>
src标记中,webContent链接可以正常工作。我希望这是由于特殊的权限处理或某些标头信息所致,我缺少浏览器专门为这些媒体标签处理的标头信息。我的解决方案必须能够读取私有(非公共,不可共享)链接,因此必须使用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.
当然,等待无济于事,如果我不请求流,则可以得到成功的响应。
我改用直接使用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()
})