WebRTC音视频开发读书笔记(六)

数据通道不仅可以发送文本消息, 还可以发送图片、二进制文件,将其类型binaryType属性设置成arraybuffer类型即可.

九\、文件传输

1、文件传输流程

(1)使用表单file打开本地文件

(2)使用FileReader读取文件的二进制数据

 (3)创建对等连接,本地连接及远端连接

  (4)创建发送数据通道

  (5)创建接收数据通道

   (6)将读取的二进制数据 切割成一个个切片,然后使用数据通道的send方法发送数据 。

  (7)使用接收数据通道二进制数据 将其放入数据缓存

  (8)根据数据缓存生成Blob 文件

   (10)生成文件连接并提供下载。

读取流程中文件二进制数据使用FileReader对象,还需要对数据进行切片处理,在接收方法中使用缓存将接收的数据缓存起来,最后生成blob下载文件。处理流程如下所示:

2、FileReader

FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件内容。常用事件如下所示:

使用File或Blob对象指定要读取的文件或数据,当监听到load事件后,即可通过事件的结果属性拿到数据,然后使用发送通道的send方法将数据发送出去。代码如下:

sendChannel.send(e.target.result)

读取二进制数据可以使用FileReader的readAsArrayBuffer方法,结果为ArrayBuffer对象。还有其它处理方法,如下所示:

读取数据 时需要将数据分成一段一段的,然后发送,接收时再按顺序读取到数据缓存中,数据流的走向如图所示:

上图中数据块叫做Chunk,DataChannel每次发送的数据即为Chunk,  使用对象的slice方法按顺序切割 出数据块。如下面代码所示:

let slice=file.slice(offset,offset+chunksize)

假设chunksize的大小为16348,整个文件的数据块如下所示:

3、发送文件示例

本示例为文件具休步骤如下:

(1)首先在界面上添加input,类型为file,用于选择并读取文件,progress两个,分别用于发送及接收文件进度展示。

 (2)创建本地和远端连接,发送和接收数据通道,发送和接收通道的数据类型设置为arraybuffer。

  (3)建立本地和远端连接

  (4)实例化FileReader对象,并添加如下事件:

                      error:  读取文件错误

                     abort:  读取文件取消

                      load:  文件加载完成

         load事件表示数据已经准备好,可以进行切割发送,具体处理逻辑如下所示:

//监听load事件
fileReader.addEventListener('load',(e)=>{...//发送文件数据sendChannel.send(e.target.result);//偏移量offer+=e.target.result.byteLength;...//判断偏移量是否小于文件大小if(offer<file.size){//继续读取readSlice(offser)}
});//读取切片大小
let readSlice=(o)=>{...//开始切片let slice=file.slice(offset ,o+chunksize);//读取二进制数据  fileReader.readAsArrayBuffer(slice);}

   (5)数据接收处理,将收到第一个数据放入receiveBuffer缓存,处理逻辑如下所示:

//接收消息处理
onReceiveMessageCallback=(event)=>{
//将接收的数据添加到接收缓存里receiveBuffer.push(event.data);
//设置当前接收文件的大小receivedSize+=event.data.byteLength;...const file=fileInput.files[0];
//判断当前接收的文件大小是否等于文件的大小
if(receivedSize===file.size){//根据缓存生成Blob文件const received=new Blob(receiveBuffer)//将缓存数据设置为空receiveBuffer=[];...//创建下载文件对象及连接...}
}

完整代码如下:

import React from "react";
import { Button } from "antd";//本地连接对象
let localConnection;
//远端连接对象
let remoteConnection;
//发送通道
let sendChannel;
//接收通道
let receiveChannel;
//文件读取
let fileReader;
//接收数据缓存
let receiveBuffer = [];
//接收到的数据大小
let receivedSize = 0;
//文件选择
let fileInput;
//发送进度条
let sendProgress;
//接收进度条
let receiveProgress;/*** 数据通道发送文件示例*/
class DataChannelFile extends React.Component {componentDidMount() {sendProgress =document.getElementById('sendProgress') receiveProgress =document.getElementById('receiveProgress')fileInput = document.getElementById('fileInput');//监听change事件,判断文件是否选择fileInput.addEventListener('change', async () => {const file = fileInput.files[0];if (!file) {console.log('没有选择文件');} else {console.log('选择的文件是:' + file.name);}});}//建立对等连接并发送文件startSendFile = async () => {//创建RTCPeerConnection对象localConnection = new RTCPeerConnection();console.log('创建本地PeerConnection成功:localConnection');//监听返回的Candidate信息localConnection.addEventListener('icecandidate', this.onLocalIceCandidate);//实例化发送通道sendChannel = localConnection.createDataChannel('webrtc-datachannel');//数据类型为二进制sendChannel.binaryType = 'arraybuffer';//onopen事件监听sendChannel.addEventListener('open', this.onSendChannelStateChange);//onclose事件监听sendChannel.addEventListener('close', this.onSendChannelStateChange);//创建RTCPeerConnection对象remoteConnection = new RTCPeerConnection();console.log('创建本地PeerConnection成功:remoteConnection');//监听返回的Candidate信息remoteConnection.addEventListener('icecandidate', this.onRemoteIceCandidate);//远端连接数据到达事件监听remoteConnection.addEventListener('datachannel', this.receiveChannelCallback);//监听ICE状态变化localConnection.addEventListener('iceconnectionstatechange', this.onLocalIceStateChange);//监听ICE状态变化remoteConnection.addEventListener('iceconnectionstatechange', this.onRemoteIceStateChange);try {console.log('localConnection创建提议Offer开始');//创建提议Offerconst offer = await localConnection.createOffer();//创建Offer成功await this.onCreateOfferSuccess(offer);} catch (e) {//创建Offer失败this.onCreateSessionDescriptionError(e);}}//创建会话描述错误onCreateSessionDescriptionError = (error) => {console.log(`创建会话描述SD错误: ${error.toString()}`);}//创建提议Offer成功onCreateOfferSuccess = async (desc) => {//localConnection创建Offer返回的SDP信息console.log(`localConnection创建Offer返回的SDP信息\n${desc.sdp}`);console.log('设置localConnection的本地描述start');try {//设置localConnection的本地描述await localConnection.setLocalDescription(desc);this.onSetLocalSuccess(localConnection);} catch (e) {this.onSetSessionDescriptionError();}console.log('remoteConnection开始设置远端描述');try {//设置remoteConnection的远端描述await remoteConnection.setRemoteDescription(desc);this.onSetRemoteSuccess(remoteConnection);} catch (e) {//创建会话描述错误this.onSetSessionDescriptionError();}console.log('remoteConnection开始创建应答Answer');try {//创建应答Answerconst answer = await remoteConnection.createAnswer();//创建应答成功await this.onCreateAnswerSuccess(answer);} catch (e) {//创建会话描述错误this.onCreateSessionDescriptionError(e);}}//设置本地描述完成onSetLocalSuccess = (pc) => {console.log(`${this.getName(pc)}设置本地描述完成:setLocalDescription`);}//设置远端描述完成onSetRemoteSuccess = (pc) => {console.log(`${this.getName(pc)}设置远端描述完成:setRemoteDescription`);}//设置描述SD错误onSetSessionDescriptionError = (error) => {console.log(`设置描述SD错误: ${error.toString()}`);}getName = (pc) => {return (pc === localConnection) ? 'localConnection' : 'remoteConnection';}//创建应答成功onCreateAnswerSuccess = async (desc) => {//输出SDP信息console.log(`remoteConnection的应答Answer数据:\n${desc.sdp}`);console.log('remoteConnection设置本地描述开始:setLocalDescription');try {//设置remoteConnection的本地描述信息await remoteConnection.setLocalDescription(desc);this.onSetLocalSuccess(remoteConnection);} catch (e) {this.onSetSessionDescriptionError(e);}console.log('localConnection设置远端描述开始:setRemoteDescription');try {//设置localConnection的远端描述,即remoteConnection的应答信息await localConnection.setRemoteDescription(desc);this.onSetRemoteSuccess(localConnection);} catch (e) {this.onSetSessionDescriptionError(e);}}//Candidate事件回调方法onLocalIceCandidate = async (event) => {try {if (event.candidate) {//将会localConnection的Candidate添加至remoteConnection里await remoteConnection.addIceCandidate(event.candidate);this.onAddIceCandidateSuccess(remoteConnection);}} catch (e) {this.onAddIceCandidateError(remoteConnection, e);}console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);}//Candidate事件回调方法onRemoteIceCandidate = async (event) => {try {if (event.candidate) {//将会remoteConnection的Candidate添加至localConnection里await localConnection.addIceCandidate(event.candidate);this.onAddIceCandidateSuccess(localConnection);}} catch (e) {this.onAddIceCandidateError(localConnection, e);}console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);}//添加Candidate成功onAddIceCandidateSuccess = (pc) => {console.log(`${this.getName(pc)}添加IceCandidate成功`);}//添加Candidate失败onAddIceCandidateError = (pc, error) => {console.log(`${this.getName(pc)}添加IceCandidate失败: ${error.toString()}`);}//监听ICE状态变化事件回调方法onLocalIceStateChange = (event) => {console.log(`localConnection连接的ICE状态: ${localConnection.iceConnectionState}`);console.log('ICE状态改变事件: ', event);}//监听ICE状态变化事件回调方法onRemoteIceStateChange = (event) => {console.log(`remoteConnection连接的ICE状态: ${remoteConnection.iceConnectionState}`);console.log('ICE状态改变事件: ', event);}//关闭数据通道closeChannel = () => {console.log('关闭数据通道');sendChannel.close();if (receiveChannel) {receiveChannel.close();}//关闭localConnectionlocalConnection.close();//关闭remoteConnectionremoteConnection.close();//localConnection置为空localConnection = null;//remoteConnection置为空remoteConnection = null;}//发送数据sendData = () => {let file = fileInput.files[0];console.log(`文件是: ${[file.name, file.size, file.type].join(' ')}`);//设置发送进度条的最大值sendProgress.max = file.size;//设置接收进度条的最大值receiveProgress.max = file.size;//文件切片大小,即每次读取的文件大小let chunkSize = 16384;//实例化文件读取对象fileReader = new FileReader();//偏移量可用于表示进度let offset = 0;//监听error事件fileReader.addEventListener('error', (error) => {console.error('读取文件出错:', error)});//监听abort事件fileReader.addEventListener('abort', (event) => {console.log('读取文件取消:', event)});//监听load事件fileReader.addEventListener('load', (e) => {console.log('文件加载完成 ', e);//使用发送通道开始发送文件数据sendChannel.send(e.target.result);//使用文件二进制数据长度作为偏移量offset += e.target.result.byteLength;//使用偏移量作为发送进度sendProgress.value = offset;console.log('当前文件发送进度为:', offset);//判断偏移量是否小于文件大小if (offset < file.size) {//继续读取readSlice(offset);}});//读取切片大小let readSlice = (o) => {console.log('readSlice ', o);//将文件的某一段切割下来,从offset到offset + chunkSize位置切下let slice = file.slice(offset, o + chunkSize);//读取切片的二进制数据fileReader.readAsArrayBuffer(slice);};//首次读取0到chunkSize大小的切片数据readSlice(0);}//接收通道数据到达回调方法receiveChannelCallback = (event) => {//实例化接收通道receiveChannel = event.channel;//数据类型为二进制receiveChannel.binaryType = 'arraybuffer';//接收消息事件监听receiveChannel.onmessage = this.onReceiveMessageCallback;//onopen事件监听receiveChannel.onopen = this.onReceiveChannelStateChange;//onclose事件监听receiveChannel.onclose = this.onReceiveChannelStateChange;receivedSize = 0;}//接收消息处理onReceiveMessageCallback = (event) => {console.log(`接收的数据 ${event.data.byteLength}`);//将接收到的数据添加到接收缓存里receiveBuffer.push(event.data);//设置当前接收文件的大小receivedSize += event.data.byteLength;//使用接收文件的大小表示当前接收进度receiveProgress.value = receivedSize;const file = fileInput.files[0];//判断当前接收的文件大小是否等于文件的大小if (receivedSize === file.size) {//根据缓存数据生成Blob文件const received = new Blob(receiveBuffer);//将缓存数据置为空receiveBuffer = [];//获取下载连接对象let download = document.getElementById('download');//创建下载文件对象及链接download.href = URL.createObjectURL(received);download.download = file.name;download.textContent = `点击下载'${file.name}'(${file.size} bytes)`;download.style.display = 'block';}}//发送通道状态变化onSendChannelStateChange = () => {const readyState = sendChannel.readyState;console.log('发送通道状态: ' + readyState);if (readyState === 'open') {this.sendData();}}//接收通道状态变化onReceiveChannelStateChange = () => {const readyState = receiveChannel.readyState;console.log('接收通道状态:' + readyState);}//取消发送文件cancleSendFile = () => {if (fileReader && fileReader.readyState === 1) {console.log('取消读取文件');fileReader.abort();}}render() {return (<div className="container"><div><form id="fileInfo"><input type="file" id="fileInput" name="files" /></form><div><h2>发送</h2><progress id="sendProgress" max="0" value="0" style={{width:'500px'}}></progress></div><div><h2>接收</h2><progress id="receiveProgress" max="0" value="0" style={{width:'500px'}}></progress></div></div><a id="download"></a><div><Button onClick={this.startSendFile} style={{ marginRight: "10px" }}>发送</Button><Button onClick={this.cancleSendFile} style={{ marginRight: "10px" }}>取消</Button><Button onClick={this.closeChannel} style={{ marginRight: "10px" }}>关闭</Button></div></div>);}
}
//导出组件
export default DataChannelFile;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/404773.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础学习Redis(5) -- redis单线程模型介绍

前面我们提到过&#xff0c;redis是单线程的&#xff0c;这期我们详细介绍一下redis的单线程模型 1. redis单线程模型 redis只使用一个线程处理所有的请求&#xff0c;并不是redis服务器进程内部只有一个线程&#xff0c;其实也存在多个线程&#xff0c;只不过多个线程是在处…

SparkSQL遵循ANSI标准

ANSI简介 ANSI Compliance通常指的是遵循美国国家标准学会&#xff08;American National Standards Institute, ANSI&#xff09;制定的标准。在计算机科学和技术领域&#xff0c;这通常涉及到数据库管理系统&#xff08;DBMS&#xff09;对于SQL语言的支持程度。 ANSI为SQL…

基于vue框架的爱学习分享平台ud317(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,学科分类,交流答疑,论坛交流,学习资料 开题报告内容 基于Vue框架的爱学习分享平台 开题报告 一、项目背景与意义 随着互联网技术的飞速发展&#xff0c;知识的获取与传播方式正经历着前所未有的变革。在线教育平台逐渐成为满足…

如何理解:进程控制

文章目录 前言&#xff1a;进程创建&#xff1a;进程终止&#xff1a;如何终止进程&#xff1f;进程等待非阻塞等待&#xff1a; 总结&#xff1a; 前言&#xff1a; ​ 对于前面的地址空间的学习&#xff0c;我们现在了解到原来所谓变量的地址其实是虚拟地址&#xff0c;该虚…

【数学建模备赛】Ep05:斯皮尔曼spearman相关系数

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、斯皮尔曼spearman相关系数&#xff1a;☀️☀️☀️1. 回顾皮尔逊相关系数2. 斯皮尔曼spearman相关系数3. 斯皮尔曼相关系数公式4. 另外一种斯皮尔曼相关系数定义5. matlab的用法5. matlab的用法 三、对斯皮尔曼相…

MySQL(二)——CRUD

文章目录 CRUD新增全列插入指定列插入插入查询结果 查询全列查询指定列查询查询字段为表达式表达式不包含字段表达式包含一个字段表达式包含多个字段 补充&#xff1a;别名去重查询排序条件查询 补充&#xff1a;运算符区间查询模糊查询NULL的查询 分页查询聚合查询聚合函数 分…

C++实现——红黑树

目录 1.红黑树 1.1红黑树的概念 1.2红黑树的性质 1.3红黑树节点的定义 1.4红黑树的插入操作 1.5红黑树的验证 1.6红黑树的删除 1.7红黑树与AVL树的比较 1.8红黑树的应用 1.红黑树 1.1红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位…

Chat App 项目之解析(二)

Chat App 项目介绍与解析&#xff08;一&#xff09;-CSDN博客文章浏览阅读76次。Chat App 是一个实时聊天应用程序&#xff0c;旨在为用户提供一个简单、直观的聊天平台。该应用程序不仅支持普通用户的注册和登录&#xff0c;还提供了管理员登录功能&#xff0c;以便管理员可以…

初识指针4の学习笔记

目录 1>>前言 2>>字符指针变量 3>>数组指针变量 4>>函数指针变量 5>>函数指针数组 6>>回调函数是什么&#xff1f; 7>>结语 1>>前言 今天我会继续分享一些我做的笔记&#xff0c;以及我对指针的理解&#xff0c; 后续会…

Vue状态管理工具:vuex

目录 基本概念 使用步骤 核心概念 1.State 2.Getters 3.Mutations 4.Actions 5.Modules 辅助函数 基本概念 基础用法 基本概念 官方&#xff1a;Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以…

Android全面解析之context机制(三): 从源码角度分析context创建流程(下)

前言 前面已经讲了什么是context以及从源码角度分析context创建流程&#xff08;上&#xff09;。限于篇幅把四大组件中的广播和内容提供器的context获取流程放在了这篇文章。广播和内容提供器并不是context家族里的一员&#xff0c;所以他们本身并不是context&#xff0c;因而…

查找物理学领域文献的常用数据库

当我们查找文献时如果盲目去各个文献数据库查找不仅浪费时间和精力还不一定能找到自己需要的文献。我们需要对数据库有个简单的了解有方向的去寻找我们研究领域的文献资料&#xff0c;本文就向大家介绍一下查找物理学领域文献的数据库有哪些。 一、物理专业数据库&#xff08;…

Android平台无纸化同屏如何实现实时录像功能

技术背景 我们在做无纸化同屏的时候&#xff0c;好多开发者采集到屏幕、麦克风|扬声器数据&#xff0c;除了需要推RTMP出去&#xff0c;或者启动个轻量级RTSP服务&#xff0c;对外提供个拉流的RTSP URL&#xff0c;别的终端过来拉流&#xff08;小并发场景&#xff09;&#x…

vue3基础ref,reactive,toRef ,toRefs 使用和理解

文章目录 一. ref基本用法在模板中使用ref 与 reactive 的区别使用场景 二. reactive基本用法在模板中使用reactive 与 ref 的区别使用场景性能优化 三. toRef基本用法示例在组件中的应用主要用途对比 ref 和 toRef 四. toRefs基本用法示例在组件中的应用主要用途对比 ref 和 t…

基于Arch的轻量级发行版Archcraft结合内网穿透实现远程SSH连接

文章目录 前言1. 本地SSH连接测试2. Archcraft安装Cpolar3. 配置 SSH公网地址4. 公网远程SSH连接5. 固定SSH公网地址6. SSH固定地址连接 前言 本文主要介绍如何在Archcraft系统中安装Cpolar内网穿透工具,并以实现Windows环境ssh远程连接本地局域网Archcraft系统来说明使用内网…

高性能web服务器详解

一、Web服务的基础介绍 正常情况下单次web服务访问的流程简图&#xff1a; 1.1 Web服务介绍 这里介绍的是 Apache 和 NGINX 1.1.1 Apache 经典的Web服务端 Apache 起初由美国的伊利诺伊大学香槟分校的国家超级计算机应用中心开发 目前经历了两大版本分别是 1.X 和 2.X…

笔试练习day5

目录 游游的you题目解析解法方法一贪心方法二 腐烂的苹果题目解析例子1例子2解法多源BFS最短路径代码代码解析 JZ62 孩子们的游戏(圆圈中最后剩下的数)题目解析解法方法一模拟环形链表模拟数组模拟 方法二递推/递归/动态规划状态表示状态转移方程代码 感谢各位大佬对我的支持,如…

Mysql原理与调优-Mysql的内存结构

1.绪论 前面说过InnoDB每次查询数据或者更新数据&#xff0c;都是先以16kb的大小将数据读取到内存中&#xff0c;然后对内存中的数据页进行操作。为了减少磁盘IO&#xff0c;Innodb的会先单独的申请一块连续的空间&#xff0c;将从磁盘中的数据页缓存到这片内存中。这片内存就…

2D Inpainting 与NeRF 3D重建的多视角一致性问题

一 问题&#xff1a; NeRF依赖于输入图像的一致性。NeRF&#xff08;Neural Radiance Fields&#xff09;在生成三维场景时&#xff0c;依赖于从多个视角拍摄的输入图像之间的一致性来准确地推断场景的三维结构和颜色信息。 具体来说&#xff1a; 多视角一致性&#xff1a; Ne…

Hive3:三种常用的复杂数据类型

一、Array类型 1、数据示例 2、实操 元数据 zhangsan beijing,shanghai,tianjin,hangzhou wangwu changchun,chengdu,wuhan,beijin创建表 CREATE TABLE myhive.test_array(name string, work_locations array<string>) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t…