本篇不详细介绍websocket,只针对websocket整合rtc。
一、简单说下webrtc的流程
webrtc是P2P通信,也就是实际交流的只有两个人,而要建立通信,这两个人需要交换一些信息来保证通信安全。而且,webrtc必须通过ssh加密,也就是使用https协议、wss协议。
借用一幅图
1.1 创建端点的解析
以下解析不包括websockt,只针对stun做解析。与上图略有不同
-
首先,Client A创建端点(Create PeerConnection),并添加音视频流(Add Streams)。接下来通知Client B,让Client B也创建一个端点。
-
Client B收到通知后,Client B创建端点(Create PeerConnection),并添加音视频流(Add Streams),
-
接下来,Client B创建一个用于answer的SDP对象(Create Answer),保存并发送给Client A。
-
Client A收到用于answer的SDP后,保存下来。
-
然后, Client A创建一个用于offer的SDP对象(Create Office),保存并发送给Client B。
-
最后,Client B保存收到的用于offer的SDP对象
以上步骤完成之后:
1、rtc会自动收集Candidate信息,并通过回调函数通知你,用于交换Candidate信息。
2、交换完Candidate信息后,P2P连接就建立好了。并通过回调函数,将远程视频流给你
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓文章来源地址https://www.toymoban.com/news/detail-408518.html
1.2 交换Candidate信息
Candidate信息是交换完SDP对象之后,自动收集的信息。在创建端点(PeerConnection)的时候,添加回调函数(onIceCandidate)
创建回调函数(onIceCandidate)
将Candidate信息发送给另一端(a发给b,b发给a)
保存发过来的 Candidate信息(addIceCandidate)。注意是保存发过来的,不是保存自己的!!!
交换完Candidate信息后,P2P连接就建立好了。
二、新建springboot项目,并开启ssh
因为rtc必须使用ssh,所以springboot需要使用https协议才可以
2.1 生成ssh自签名文件
在终端中执行
keytool -genkey -alias webrtc -dname "CN=Andy,OU=kfit,O=kfit,L=HaiDian,ST=BeiJing,C=CN" -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore webrtc.keystore -validity 36500
执行时,会要求输入密码;
执行后,会在根目录下生成一个webrtc.keystore的文件
2.2 配置ssh信息
将webrtc.keystore文件放在resource目录下
在application.yaml中填写配置信息
server:
ssl:
#证书的路径
key-store: classpath:webrtc.keystore
#证书密码
key-store-password: 123456
#秘钥库类型
key-store-type: JKS
2.3 检测一下能不能跑起来
运行就行,能跑起来就OK。
三、编写websocket服务类
这个简单的demo只考虑一个房间,没有房间控制,所以websocket代码很少,主要代码都在js里面。
3.1 先放一下Message实体类
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Message
{
private String operation;
private Object msg;
public Message setOperation(String operation)
{
this.operation = operation;
return this;
}
public Message setMsg(Object msg)
{
this.msg = msg;
return this;
}
public String getMsgStr(){
return msg == null ? "" : msg.toString();
}
}
3.2 服务类
主要有以下信息:
- 加入房间(into)
- 发送 sdp 对象(send-sdp)
- 交换 candidate 信息(send-candidate)
package com.websocket.controller;
import com.alibaba.fastjson.JSONObject;
import com.websocket.pojo.Message;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
@Log4j2
@Controller
@ServerEndpoint("/webrtc")
public class WebrtcController
{
/**
* 这里只做一个最简单的, 只有一个房间, 后面有需要自己可以改
*/
private static Session offer;
private static Session answer;
@OnMessage
public void onMessage(Session session, String message)
{
final Message data = JSONObject.parseObject(message, Message.class);
final Message response = Message.builder()
.operation(data.getOperation())
.build();
switch (data.getOperation()) {
//加入房间
case "into": {
if (offer == null) {
offer = session;
response.setMsg("offer");
}
else if (answer == null) {
answer = session;
response.setMsg("answer");
}
else {
response.setMsg("none");
}
sendMessage(session, response);
break;
}
case "start":
sendMessage(offer, response);
break;
//发送 offer 的 SDP 对象
case "send-offer":
//发送 answer 的 SDP 对象
case "send-answer":
//交换 candidate 信息
case "send-candidate": {
sendOther(session, response.setMsg(data.getMsg()));
break;
}
}
}
@OnClose
public void onClose(Session session, CloseReason closeReason)
{
if (offer != null && session.getId().equals(offer.getId())) {
offer = null;
}
else if (answer != null && session.getId().equals(answer.getId())) {
answer = null;
}
}
public static void sendOther(Session session, Object msg)
{
if (offer != null && session.getId().equals(offer.getId())) {
sendMessage(answer, msg);
}
else if (answer != null && session.getId().equals(answer.getId())) {
sendMessage(offer, msg);
}
}
public static void sendMessage(Session session, Object msg)
{
sendMessage(session, JSONObject.toJSONString(msg));
}
@SneakyThrows
private static void sendMessage(Session session, String msg)
{
final RemoteEndpoint.Basic basic = session.getBasicRemote();
basic.sendText(msg);
}
}
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
四、页面
4.1 html
这部分不太重要,就直接放上来了
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<title>websocket-demo</title>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
</head>
<body>
<div class="container py-3">
<div class="row">
<div class="col-12">
<div id="addRoom" class="btn btn-primary">加入房间</div>
</div>
<div class="col-12 col-lg-6">
<p>本地视频:</p>
<video id="localVideo" width="500px" height="300px" autoplay style="border: 1px solid black;"></video>
</div>
<div class="col-12 col-lg-6">
<p>远程视频:</p>
<video id="remoteVideo" width="500px" height="300px" autoplay style="border: 1px solid black;"></video>
</div>
</div>
</div>
<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js" type="text/javascript"></script>
</body>
</html>
4.2 webrtc工具类 webrtc-util.js
包括以下方法:
打开本地音视频流
创建PeerConnection对象
创建用于office的SDP对象
创建用于answer的SDP对象
保存SDP对象
保存Candidate信息
收集 candidate 的回调
获得远程视频流的回调
需要注意的是:最后的两个回调,需要在创建PeerConnection对象之后,打开本地音视频流之前执行。
4.2.1 本地变量
其中的 ice对象,根据上一篇测试通过的stun服务器信息填写。
//端点对象
let rtcPeerConnection;
//本地视频流
let localMediaStream = null;
//ice服务器信息, 用于创建 SDP 对象
let iceServers = {
"iceServers": [
// {"url": "stun:stun.l.google.com:19302"},
{"urls": ["stun:159.75.239.36:3478"]},
{"urls": ["turn:159.75.239.36:3478"], "username": "chr", "credential": "123456"},
]
};
// 本地音视频信息, 用于 打开本地音视频流
const mediaConstraints = {
video: {width: 500, height: 300},
audio: true //由于没有麦克风,所有如果请求音频,会报错,不过不会影响视频流播放
};
// 创建 offer 的信息
const offerOptions = {
iceRestart: true,
offerToReceiveAudio: true, //由于没有麦克风,所有如果请求音频,会报错,不过不会影响视频流播放
};
4.2.2 打开本地音视频流
// 1、打开本地音视频流
const openLocalMedia = (callback) => {
console.log('打开本地视频流');
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(stream => {
localMediaStream = stream;
//将 音视频流 添加到 端点 中
for (const track of localMediaStream.getTracks()) {
rtcPeerConnection.addTrack(track, localMediaStream);
}
callback(localMediaStream);
})
}
4.2.3 创建 PeerConnection 对象
// 2、创建 PeerConnection 对象
const createPeerConnection = () => {
rtcPeerConnection = new RTCPeerConnection(iceServers);
}
4.2.4 创建用于 offer 的 SDP 对象
// 3、创建用于 offer 的 SDP 对象
const createOffer = (callback) => {
// 调用PeerConnection的 CreateOffer 方法创建一个用于 offer的SDP对象,SDP对象中保存当前音视频的相关参数。
rtcPeerConnection.createOffer(offerOptions)
.then(sdp => {
// 保存自己的 SDP 对象
rtcPeerConnection.setLocalDescription(sdp)
.then(() => callback(sdp));
})
.catch(() => console.log('createOffer 失败'));
}
4.2.5 创建用于 answer 的 SDP 对象
// 4、创建用于 answer 的 SDP 对象
const createAnswer = (callback) => {
// 调用PeerConnection的 CreateAnswer 方法创建一个 answer的SDP对象
rtcPeerConnection.createAnswer(offerOptions)
.then(sdp => {
// 保存自己的 SDP 对象
rtcPeerConnection.setLocalDescription(sdp)
.then(() => callback(sdp));
})
.catch(() => console.log('createAnswer 失败'))
}
4.2.6 保存远程的 SDP 对象
// 5、保存远程的 SDP 对象
const saveSdp = (answerSdp, callback) => {
rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(answerSdp))
.then(callback);
}
4.2.7 保存 candidate 信息
// 6、保存 candidate 信息
const saveIceCandidate = (candidate) => {
let iceCandidate = new RTCIceCandidate(candidate);
rtcPeerConnection.addIceCandidate(iceCandidate)
.then(() => console.log('addIceCandidate 成功'));
}
4.2.8 收集 candidate 的回调
// 7、收集 candidate 的回调
const bindOnIceCandidate = (callback) => {
// 绑定 收集 candidate 的回调
rtcPeerConnection.onicecandidate = (event) => {
if (event.candidate) {
callback(event.candidate);
}
};
};
4.2.9 获得 远程视频流 的回调
// 8、获得 远程视频流 的回调
const bindOnTrack = (callback) => {
rtcPeerConnection.ontrack = (event) => callback(event.streams[0]);
};
以上代码都写在 webrtc-util.js 中,是可以复用滴
接下来,就是在html中引入这个js,然后和websocket整合一下,把通知、candidate 信息等等,通过websocket发送给另一端。
End
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!文章来源:https://www.toymoban.com/news/detail-408518.html
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
到了这里,关于WebRTC音视频通话(二)简单音视频通话的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!