我在个人应用程序中使用 WebRTC 文件共享,但很快就发现了一个问题。问题在于文件的最大容量只能在 2GB 左右(对于 Chrome 浏览器)。如果尝试发送超过 2GB 的文件,就会出现错误。出现错误的原因是数组缓冲区只能有一定的大小(最大 2GB)。
幸运的是,有一些新的 JavaScript API 可以解决这个问题:writable streams API 和 File and Directory Entries API。
因此,在本文中,我将假设你已经有了一个正常工作的 WebRCT 数据通道,然后我们将从这里开始。
首先,我们需要选择一个要上传到对等程序的文件。为此,我们可以使用标准输入文件类型 HTML 标签,但既然我们已经打算使用新的 File and Directory Entries API
,那就不妨使用最新的 API。
// You need to wrap File and Directory Entries API in a event handler
// otherwise the browser will deny you
// So lets assume you already have a button
button.addEventListener("pointerdown", async () => {
let file_handler
try {
file_handler = await showOpenFilePicker() // this gives the file hanlder
}
catch (error) {
console.log(error) // if they click cancle
return
}
// file_handler is an array because you can pick more files
for (let meta of file_handler) { // meta for meta data
let file = await meta.getFile() // now we have the file instance just like using input file
}
})
问题来了,如果你选择的文件超过 2GB,你就无法对它做任何操作,因为它太大了。不过有一种方法可以处理文件实例,那就是把它变成流。
// ...
for (let meta of file_handler) { // meta for meta data
let file = await meta.getFile() // now we have the file instance just like using input file
let stream = file.stream() // make it a stream
// now we can stream this file
stream.pipeTo(new WritableStream({
async write(chunk, controller) {
// now we get chunk (s)
},
close() {
}
})
}
现在,我们可以流式传输该文件。我们将获得分块,然后发送给我们的对等方。但是 对于 WebRTC 数据通道来说,这些数据块还是太大了。因此,我们必须发送较小的分块。另一个问题是,我们需要编写一个 promise
,在完成分块后进行解析,以便从写入中接收另一个块。因此,下面这个函数将返回一个promise
。
// inside new WritableStream in pipeTo
async write(chunk, controller) {
// To get started we will make an event target
let custom_listener = new EventTarget() // this allows us to make custom event listners
let MAXIMUM_SIZE_DATA_TO_SEND = 65535 // maximum size you are allowed to send
let arrayBuffer = chunk // renaming it to make more sense
let index = 0
let listener = () => {
removeEventListener("bufferedamountlow", listener) // clean this up
send_file_chunk() // restart the sending
}
let send_file_chunk = () => {
while (index < arrayBuffer.byteLength) {
// channel is the data channel instance your sending the data to
if (channel.bufferedAmount > MAXIMUM_SIZE_DATA_TO_SEND) {
channel.addEventListener("bufferedamountlow", listener)
return // this function is done it has to wait for the listener now
}
else {
channel.send(arrayBuffer.slice(index, index + MAXIMUM_SIZE_DATA_TO_SEND)) // send the chunk
index += MAXIMUM_SIZE_DATA_TO_SEND
}
}
custom_listener.dispatchEvent(new Event("done")) // when this function is finished
}
// We need a Promise in the write so that it waits until this this chunk is done proccesing
return new Promise(resolve => {
let fun = () => {
resolve()
custom_listener.removeEventListener("done", fun) // clean this up
}
custom_listener.addEventListener("done", fun)
send_file_chunk() // Start the funtion
})
},
close() { // invoked when it's done proccesing chunks
channel.send(JSON.stringify({ state: "finished" })) // you have to tell the peer that it's done somehow
}
////
这个函数有点复杂,但之所以需要自定义事件,是因为 send_file_chunk 函数是递归函数,所以不能在其中加入promise
(该回调以某种方式丢失了),这也是为什么 send_file_chunk 完成后会派发一个事件,而这个事件是有效的。
回顾一下发生了什么。我们有一个大于 2GB 的文件,不能将数据存储在数组缓冲区中,否则会出错。因此,我们分块读取文件,每读取一小块,就向对等设备发送一小块,因为数据通道对一次发送的数据量有限制。
好了,现在在对等(接收对等)端,我们需要将数据写入某个文件。对等方也有同样的问题,我们不能将数据存储在数组缓冲区中,否则数据过大时就会出错。不过,File and Directory API
可以解决这个问题,这个 API 允许我们写入磁盘上的文件。
因此,让我们获取一个可写入流实例:
// We need to get a file that we can write to
// to do this we again need to click on a button to allow the browser access to this api
let writableStream
button.onpointerdown = async () => {
let draftHandle = await showSaveFilePicker({suggestedName: "something good.m4a"});
try {
writableStream = await draftHandle.createWritable();
}
catch (error) { // if user rejects
console.error(error)
return
}
}
现在有了一个可以写入的文件,我们可以接收数据并写入该文件。
// receiving peer
channel.onmessage = event => {
let data = event.data
if (data.byteLength || data.size) { // data.size for firefox
writableStream.write(data)
}
else {
data = JSON.parse(data) // passing useful info
if (data.state == "finished") {
writableStream.close() // this is to close the file
}
}
}
有了 writable streams API
和 File and Directory Entries API
,现在就可以使用 WebRTC 发送无限量的数据了。您永远不会有一个完整的数组缓冲区,因为我们会读取数据块,然后将这些数据块中较小的数据块发送给对等方。对等方会将这些小块数据写入文件,而不是将数据存储到有限的数组缓冲区中。
作者:Lorenzo Repenning
原文:https://medium.com/@hotrockxonotic/sending-more-than-2gb-with-p2p-webrtc-22032a86b783
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/49771.html