使用 P2P WebRTC 发送超过 2GB 的文件

我在个人应用程序中使用 WebRTC 文件共享,但很快就发现了一个问题。问题在于文件的最大容量只能在 2GB 左右(对于 Chrome 浏览器)。如果尝试发送超过 2GB 的文件,就会出现错误。出现错误的原因是数组缓冲区只能有一定的大小(最大 2GB)。

幸运的是,有一些新的 JavaScript API 可以解决这个问题:writable streams APIFile 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 APIFile 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

(0)

相关推荐

发表回复

登录后才能评论