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

news2024/11/15 13:34:18

WebRTC既可以收发音频和视频,还可以传输其它数据,如短消息、二进制数据、文本数据等。可以实现文本聊天、文件传输等场景。

八、数据通道

1、基本概念

WebRTC的数据通道好比高速公路、文本、文件、图像以及其它数据 好比货物,通道打通 ,即可源源不断地发送数据。数据通道的创建依赖于RTCPeerConnection连接建立,数据通道相关API:

RTCDataChannel通信与WebSocket通信之间区别:

(1)WebRTCDataChannel通信基于Peer与Peer之间直接连接,WebSocket通信需要服务器中转,但WebRTC依靠ICE Servers穿透NAT,有时可能多一层TURN服务器的转发。

(2)WebSocket协议是基于TCP传输,RTCDataChannel是基于SCTP(同TCP 、UDP同级的传输协议)

 (3)构造WebSocket需要一个URL,与服务器建立连接,创建一个唯一的SocketSessionId。DataChannel的连接依赖RTCPeerConnection 对象,可以包含多个RTCDataChannel。

2、文本传输示例

数据通道最基本应用场景是发送文本消息,使RTCPeerConnection的createData-Channel方法创建一个可以发送任意数据的数据通道,创建时需要传递一个字符串作为通道id。通道建立后,发送端通过 send方法发送消息,接收端连接通过监听ondatachannel事件,可以获取远端数据通道,此时远端数据通道监听onmessage事件,就可以接收文本消息。以下是一个发送的文本的示例,主体步骤如下:

主体步骤:

(1)创建本地/远端连接,发送/接收数据通道。如下所示:

                本地连接对象:localConnection

                远端连接对象: remoteConnection

                        发送通道: sendChannel

                        接收通道: receiveChannel 

   (2)   建立本地及远端连接

 (3)实例化本地发送数据通道,并指定通道id,然后添加onopen及onclose事件监听。大致代码如下:

sendChannel=localConnection.createDataChannel('webrtc-datachannel')



sendChannel.onpen={...}

sendChannel.onclose{...}

(4)在远端连接里添加ondatachannel事件,然后拉收端数据通道receiveChannel再添加onmessage事件,用于接收发送端发过来的文本消息.

remoteConnection.ondatachannel=(event){

         receiveChannel=event.channel;
         
         //接收事件监听
         receiveChannel.onmessage=(event){
          //消息event.data
         
         }

         receiveChannel.onopen={...}
         receiveChannel.oclose={...}
}

(5) 在界面渲染部分添加两个文本框,一个用于输入发送的内容,一个用于接收文本消息,通过调用dataChannel的send方法将数据发送出去,

完整代码如下:

import React from "react";
import { Button } from "antd";
import '../public/dataChannel.css'

//本地连接对象
let localConnection;
//远端连接对象
let remoteConnection;
//发送通道
let sendChannel;
//接收通道
let receiveChannel;
/**
 * 数据通道示例
 */
class DataChannel extends React.Component {

  //呼叫
  call = async () => {
    console.log('开始呼叫...');

    //设置ICE Server,使用Google服务器
    let configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] };

    //创建RTCPeerConnection对象
    localConnection = new RTCPeerConnection(configuration);
    console.log('创建本地PeerConnection成功:localConnection');
    //监听返回的Candidate信息
    localConnection.addEventListener('icecandidate', this.onLocalIceCandidate);

    //实例化发送通道
    sendChannel = localConnection.createDataChannel('webrtc-datachannel');
    //onopen事件监听
    sendChannel.onopen = this.onSendChannelStateChange;
    //onclose事件监听
    sendChannel.onclose = this.onSendChannelStateChange;

    //创建RTCPeerConnection对象
    remoteConnection = new RTCPeerConnection(configuration);
    console.log('创建本地PeerConnection成功:remoteConnection');
    //监听返回的Candidate信息
    remoteConnection.addEventListener('icecandidate', this.onRemoteIceCandidate);

    //远端连接数据到达事件监听
    remoteConnection.ondatachannel = this.receiveChannelCallback;

    //监听ICE状态变化
    localConnection.addEventListener('iceconnectionstatechange', this.onLocalIceStateChange);
    //监听ICE状态变化
    remoteConnection.addEventListener('iceconnectionstatechange', this.onRemoteIceStateChange);

    try {
      console.log('localConnection创建提议Offer开始');
      //创建提议Offer
      const 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 {
      //创建应答Answer
      const 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);
  }

  //断开连接
  hangup = () => {
    console.log('结束会话');
    //关闭localConnection
    localConnection.close();
    //关闭remoteConnection
    remoteConnection.close();
    //localConnection置为空
    localConnection = null;
    //remoteConnection置为空
    remoteConnection = null;
  }

  sendData = () => {
    let dataChannelSend =document.getElementById('dataChannelSend');
    const data = dataChannelSend.value;
    sendChannel.send(data);
    console.log('发送的数据:' + data);
  }

  //接收通道数据到达回调方法
  receiveChannelCallback = (event) => {
    console.log('Receive Channel Callback');
    //实例化接收通道
    receiveChannel = event.channel;
    //接收消息事件监听
    receiveChannel.onmessage = this.onReceiveMessageCallback;
    //onopen事件监听
    receiveChannel.onopen = this.onReceiveChannelStateChange;
    //onclose事件监听
    receiveChannel.onclose = this.onReceiveChannelStateChange;
  }
  
  //接收消息处理
  onReceiveMessageCallback = (event) => {
    console.log('接收的数据:' + event.data);
    let dataChannelReceive =document.getElementById('dataChannelReceive');
    dataChannelReceive.value = event.data;
  }
  
  //发送通道状态变化`
  onSendChannelStateChange = () => {
    const readyState = sendChannel.readyState;
    console.log('发送通道状态: ' + readyState);
  }
  
  //接收通道状态变化
  onReceiveChannelStateChange = () => {
    const readyState = receiveChannel.readyState;
    console.log('接收通道状态:' + readyState);
  }

  render() {
    return (
      <div className="container">
        <div>
          <div>
            <h2>发送</h2>
            <textarea id="dataChannelSend" disabled={false}
              placeholder="请输入要发送的文本..." />
          </div>
          <div>
            <h2>接收</h2>
            <textarea id="dataChannelReceive" disabled={false} />
          </div>
        </div>
        <div>
          <Button onClick={this.call} style={{ marginRight: "10px" }}>呼叫</Button>
          <Button onClick={this.sendData} style={{ marginRight: "10px" }}>发送</Button>
          <Button onClick={this.hangup} style={{ marginRight: "10px" }}>挂断</Button>
        </div>
      </div>
    );
  }
}
//导出组件
export default DataChannel;

测试此例时,一定选单击"呼叫"按钮建立对等连接,然后才能发送消息.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot Web请求、响应

一、文章概述 请求方面主要讲&#xff0c;当前端向后端发出请求时&#xff0c;对于不同类型的参数后端都如何接收&#xff1b;对于响应&#xff0c;文章会讲解后端如何向前端响应数据以及如何使返回的数据具有统一的格式。 二、请求 2.1接收简单参数 Controller方法&#xf…

sqlserver的openquery配置

1.命令Demo ---openquery--开启Ad Hoc Distributed Queries组件&#xff0c;在sql查询编辑器中执行如下语句exec sp_configure show advanced options,1reconfigureexec sp_configure Ad Hoc Distributed Queries,1reconfigure--关闭Ad Hoc Distributed Queries组件&#xff0…

10、stm32实现adc读取数据

一、配置 二、代码 /* USER CODE BEGIN 2 */OLED_Init();OLED_Clear();OLED_ShowString(0,0,"Hello adc",12,0);uint16_t adc_number 0;/* USER CODE END 2 *//* USER CODE BEGIN WHILE */while (1){HAL_ADC_Start(&hadc1);HAL_ADC_PollForConversion(&hadc1…

2分钟搭建一个简单的WebSocket服务器

你好同学&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏和关注。个人知乎 如何用2分钟在本地搭建一个简单的 WebSocket 服务器&#xff1f;其实使用 Node.js&#xff0c;加上一些流行的库&#xff0c;是很容易实现的。前端同学通过自己搭建 WebSocket 服务器&#xff0c;对于…

PyTorch深度学习快速入门教程--学习笔记

目录 P4 PyCharm和Jupyter的对比 P5 PyTorch加载数据 P6 Dataset类代码实现 P7 Tensorboard 写日志 读取日志文件 Tensorboard 读图片 P10 Transforms使用 Transforms用途 常见的Transforms工具 P14 torchvision数据集使用 P15 Dataloader使用 P16 nn.Module模块使…

政务大数据解决方案(三)

政务大数据解决方案致力于通过整合来自不同部门和领域的海量数据&#xff0c;采用先进的数据分析技术与人工智能算法&#xff0c;建立一个全面、高效的数据管理平台&#xff0c;从而优化政府决策过程&#xff0c;提高政策制定的科学性与精准性。该方案涵盖数据采集、存储、处理…

C语言学习笔记 Day14(文件管理)

Day14 内容梳理&#xff1a; 目录 Chapter 10 文件操作 10.0 概述 10.1 打开、关闭文件 &#xff08;1&#xff09;文件指针 &#xff08;2&#xff09;打开文件 fopen() &#xff08;3&#xff09;读取文件 fgetc() &#xff08;4&#xff09;写入文件 fputc() Ch…

Arthas:Java应用诊断利器

1、简介 Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c…

【大数据算法】一文掌握大数据算法之:概述、特点、类型及难点等,值得收藏。

大数据算法概述 1、引言2、大数据算法概述2.1 什么是大数据算法2.2 大数据算法特点2.3 大数据算法类型2.4 大数据算法难点 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;大数据开篇反馈不错哦。 小鱼&#xff1a;嗯&#xff0c;是的呢&#xff0c; 咱这个专栏&#xff…

Fly Catcher:通过监测恶意信号来检测飞机欺骗

Fly Catcher 的开发者 Angelina Tsuboi 是一名飞行员、网络安全研究员和发明家。 她决定着手一个将这三个不同兴趣结合起来的项目&#xff0c;以解决航空雷达系统的一个重大问题。 ADS-B 系统最初用于基本的飞机定位和跟踪&#xff0c;Tsuboi 对该系统的网络安全方面进行了深…

正点原子阿尔法ARM开发板-IMX6ULL(一)——环境安装和资源介绍

文章目录 一、环境安装二、主目录的资源介绍2.1 alpzha2.2 linux2.3 c_linux 三、QT四、交叉编译五、VScode六、其他6.1 SecureCRT6.2 Putty6.3 MobaXterm6.4 NFS6.5 TFTP 这几天实习的强度下来了&#xff0c;就来看看嵌入式方面的知识了 也是买了正点原子的阿尔法的嵌入式开发…

一次日志记录中使用fastjson涉及到ByteBuffer的教训

背景 目前本人在公司负责的模块中&#xff0c;有一个模块是负责数据同步的&#xff0c;主要是将我们数据产线使用的 AWS Dynamodb 同步的我们的测试QA 的环境的 MongoDB 的库中&#xff0c;去年开始也提供了使用 EMR 批量同步的功能&#xff0c;但是有时候业务也需要少量的数据…

Linux内核分析(RCU机制和内存优化)

文章目录 前言一、RCU概念RCU 的基本概念1. **如何工作**2. **RCU 的工作流程** RCU 的主要优势RCU 的使用场景RCU 的挑战和局限RCU 的实现总结 二、RCU对链表的访问三、Linux中的屏障主要类型应用场景实现作用用途 前言 一、RCU概念 RCU&#xff08;Read-Copy-Update&#x…

【Web】巅峰极客2024 部分题解

目录 EncirclingGame GoldenHornKing php_online admin_Test EncirclingGame 玩赢游戏就行 GoldenHornKing 利用点在传入的app 可以打python内存马 /calc?calc_reqconfig.__init__.__globals__[__builtins__][exec](app.add_api_route("/flag",lambda:__i…

【数据结构】二叉树(三)精选Oj题

本篇已经是二叉树第三篇啦&#xff0c;下面讲解相关面试题&#xff0c;写作不易&#xff0c;求路过的朋友给个点赞与收藏呀~ 目录 1、相同的树 2、另一颗树的子树 3、翻转二叉树 4、对称二叉树 5、平衡二叉树 6、构建二叉树 7、二叉树的最近公共祖先 孩子双亲解法 二叉…

企业为何需要渗透测试

随着数字化时代的全面到来&#xff0c;互联网已成为企业运营不可或缺的一部分。然而&#xff0c;日益复杂的网络环境和不断演变的攻击手段&#xff0c;使得网络安全问题日益严峻。在这一背景下&#xff0c;渗透测试作为一种重要的安全评估手段&#xff0c;对于保障企业信息安全…

day24-测试之接口测试基础

目录 一、接口的定义 二、接口的优点 三、API接口 四、接口测试流程 五、网络基础概念 六、HTTP和RURL 七、get和post请求 八、数据格式 九、状态码 十、restful风格 十一、接口工具 一、接口的定义 程序之间协作所要遵循的一套规范、标准 二、接口的优点 2.1.责任…

探索和表征大型语言模型在嵌入式系统开发和调试中的应用

这篇论文的标题是《Exploring and Characterizing Large Language Models for Embedded System Development and Debugging》&#xff0c;作者是来自华盛顿大学的研究团队。论文主要探讨了大型语言模型&#xff08;LLMs&#xff09;在嵌入式系统开发和调试方面的应用潜力。以下…

前端技巧——复杂表格在html当中的实现

应用场景 有时候我们的表格比较复杂&#xff0c;表头可能到处割裂&#xff0c;我们还需要写代码去完成这个样式&#xff0c;所以学会在原生html处理复杂的表格还是比较重要的。 下面我们来看这一张图&#xff1a; 我们可以看到有些表头项的规格不太一样&#xff0c;有1*1 2*…

【深入理解SpringCloud微服务】Spring-Cloud-OpenFeign源码解析(下)——LoadBalancerFeignClient详解

【深入理解SpringCloud微服务】Spring-Cloud-OpenFeign源码解析&#xff08;下&#xff09;——LoadBalancerFeignClient详解 RxJava简单介绍RxJava示例Observable与Subscriber相关方法介绍Observable.create(OnSubscribe)Observable#just(T value)Observable#concatMap(Func1&…