[*] 会议

This commit is contained in:
acgist
2022-11-20 14:10:20 +08:00
parent f900bbb998
commit cf476b842b
14 changed files with 274 additions and 70 deletions

View File

@@ -44,34 +44,24 @@
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```
## 内网穿透
请用公共STUN/TURN服务或者自行搭建coturn服务。
> 只有公网Mesh架构才需要真正的内网穿透
## 直播
终端推流到服务端,由服务端分流。
## 会议
Mesh架构声音视频控制部分功能均在终端实现同时不会实现终端录制、美颜、AI识别、变声、混音等等功能。
MCU/SFU声音视频控制在服务端实现如果没有终端订阅并且没有录制是不会对终端进行拉流。
### Mesh
流媒体点对点连接,不经过服务端。
Mesh架构声音视频控制部分功能均在终端实现同时不会实现录制、美颜、变声、混音等等功能。
> 需要使用STUN/TURN实现内网穿透可以自己搭建coturn服务
### MCU
终端推流到服务端,由服务端分流并且混音。
终端推流到服务端,由服务端混音分流
### SFU
终端推流到服务端,由服务端分流没有混音
终端推流到服务端,由服务端直接分流。
## TODO
springdoc升级正式版本
springboot升级正式版本

33
docs/flow/README.md Normal file
View File

@@ -0,0 +1,33 @@
# 流程
## WebRTC流程
发起响应
## SFU流程
媒体流
## MCU流程
媒体流
## Mesh流程
媒体流
## 直播流程
创建直播,终端操作。
## 会议流程
创建会议,终端操作
## 终端流程
注册下线
## 媒体控制
拉流发布

BIN
docs/flow/WebRTC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -39,6 +39,9 @@ public class Meeting {
*/
public void addSn(String sn) {
synchronized (this.sns) {
if(this.sns.contains(sn)) {
return;
}
this.sns.add(sn);
}
}

View File

@@ -9,7 +9,6 @@ import com.acgist.taoyao.boot.model.Message;
import com.acgist.taoyao.meeting.Meeting;
import com.acgist.taoyao.meeting.MeetingManager;
import com.acgist.taoyao.signal.client.ClientSession;
import com.acgist.taoyao.signal.client.ClientSessionManager;
import com.acgist.taoyao.signal.event.meeting.MeetingCreateEvent;
import com.acgist.taoyao.signal.listener.ApplicationListenerAdapter;
@@ -23,8 +22,6 @@ public class MeetingCreateListener extends ApplicationListenerAdapter<MeetingCre
@Autowired
private MeetingManager meetingManager;
@Autowired
private ClientSessionManager clientSessionManager;
@Override
public void onApplicationEvent(MeetingCreateEvent event) {
@@ -32,7 +29,6 @@ public class MeetingCreateListener extends ApplicationListenerAdapter<MeetingCre
final Meeting meeting = this.meetingManager.create(session.sn());
final Message message = event.getMessage();
message.setBody(Map.of("id", meeting.getId()));
// 广播不改ID触发创建终端事件回调
this.clientSessionManager.broadcast(message);
}

View File

@@ -0,0 +1,42 @@
package com.acgist.taoyao.meeting.listener;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.acgist.taoyao.boot.model.Message;
import com.acgist.taoyao.meeting.Meeting;
import com.acgist.taoyao.meeting.MeetingManager;
import com.acgist.taoyao.signal.client.ClientSession;
import com.acgist.taoyao.signal.event.meeting.MeetingEnterEvent;
import com.acgist.taoyao.signal.listener.ApplicationListenerAdapter;
/**
* 进入会议监听
*
* @author acgist
*/
@Component
public class MeetingEnterListener extends ApplicationListenerAdapter<MeetingEnterEvent> {
@Autowired
private MeetingManager meetingManager;
@Override
public void onApplicationEvent(MeetingEnterEvent event) {
final Map<?, ?> body = event.getBody();
final ClientSession session = event.getSession();
final String sn = session.sn();
final String id = (String) body.get("id");
final Meeting meeting = this.meetingManager.meeting(id);
meeting.addSn(sn);
final Message message = event.getMessage();
message.setBody(Map.of(
"id", meeting.getId(),
"sn", sn
));
this.clientSessionManager.broadcast(sn, message);
}
}

View File

@@ -1,6 +1,6 @@
/** 桃夭WebRTC终端核心功能 */
/** 兼容 */
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
const RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
/** 默认音频配置 */
const defaultAudioConfig = {
// 音量0~1
@@ -39,6 +39,21 @@ const defaultVideoConfig = {
// 选摄像头user|left|right|environment
facingMode: 'environment'
}
/** 默认RTCPeerConnection配置 */
const defaultRPCConfig = {
// ICE代理的服务器
// iceServers: null,
// 证书
// certificates: null,
// 传输通道绑定策略balanced|max-compat|max-bundle
bundlePolicy: 'balanced',
// RTCP多路复用策略require|negotiate
rtcpMuxPolicy: 'negotiate',
// ICE传输策略all|relay
iceTransportPolicy: 'all'
// ICE候选个数
// iceCandidatePoolSize: 10
}
/** 信令配置 */
const signalConfig = {
/** 当前终端SN */
@@ -328,13 +343,9 @@ function TaoyaoClient(
/** 设置媒体流 */
this.buildStream = async function(stream) {
if(stream) {
if ('srcObject' in this.video) {
this.video.srcObject = stream;
} else {
this.video.src = URL.createObjectURL(stream);;
}
}
await this.play();
}
return this;
};
/** 设置音频流 */

View File

@@ -62,9 +62,9 @@
this.taoyao
.checkDevice()
.buildChannel(this.callback)
//.buildLocalMedia()
//.then(stream => this.taoyao.buildLocalClient('local', stream))
//.catch((e) => console.error('打开终端媒体失败', e));
.buildLocalMedia()
.then(stream => this.taoyao.buildLocalClient('local', stream))
.catch((e) => console.error('打开终端媒体失败', e));
},
beforeDestroy() {
},

View File

@@ -102,7 +102,7 @@
#### 消息流程:终端->服务端-)终端
终端注册成功响应同时[下发配置信令](#下发配置信令2004),并且广播[终端上线信令](#终端上线信令2002)。
终端注册成功以后响应同时[下发配置信令](#下发配置信令2004),并且广播[终端上线信令](#终端上线信令2002)。
### 终端关闭信令2001
@@ -119,11 +119,11 @@
#### 消息流程:终端-)服务端
广播[终端下线信令](#终端下线信令2003),同时释放所有相关资源(信令通道、媒体通道等等)
终端关闭以后广播[终端下线信令](#终端下线信令2003),同时释放所有相关资源(信令通道、媒体通道等等)
### 终端上线信令2002
服务端->终端:参考[终端注册信令](#终端注册信令)
#### 消息主体
```
{
@@ -131,9 +131,13 @@
}
```
#### 消息流程:服务端->终端
参考[终端注册信令](#终端注册信令2000)
### 终端下线信令2003
服务端->终端:参考[终端关闭信令](#关闭信令)
#### 消息主体
```
{
@@ -141,9 +145,13 @@
}
```
#### 消息流程:服务端->终端
参考[终端关闭信令](#终端关闭信令2001)
### 下发配置信令2004
服务端->终端:参考[注册信令](#注册信令)
#### 消息主体
```
{
@@ -153,56 +161,112 @@
}
```
#### 消息流程:服务端->终端
参考[终端注册信令](#终端注册信令2000)
### 心跳信令2005
心跳:响应
#### 消息主体
```
# 请求
{
"signal": "信号强度",
"battery": "电池电量"
}
# 响应
{}
```
#### 消息流程:终端->服务端->终端
### 单播信令2006
#### 消息主体
```
# 请求
{
"to": "接收终端标识",
// 主体信息
}
# 转发
{
// 主体信息
}
```
#### 消息流程:终端->服务端->终端
终端转发信令到指定的终端
### 广播信令2007
#### 消息主体
```
# 请求
{
// 主体信息
}
# 广播
{
// 主体信息
}
```
#### 消息流程:终端->服务端-)终端
终端广播信令到所有的终端
### 终端状态信令2998
#### 消息主体
```
# 请求
{
"sn": "终端标识"
}
# 响应
{
"sn": "终端标识",
"ip": "IP地址",
"mac": "MAC地址",
"signal": "信号强度",
"battery": "电池电量"
}
```
### 单播信令2006
#### 消息流程:终端->服务端->终端
发送到指定终端:删除`to`字段
```
{
"to": "接收终端标识",
// 主体信息
}
```
### 广播信令2007
发送到所有的终端:排除自己
```
{
// 主体信息
}
```
### 终端状态信令2998
返回指定终端状态(如果没有指定终端标识默认查询自己)
```
{
"sn": "终端标识"
}
```
响应指定终端状态(如果没有指定终端标识默认查询自己)
### 终端列表信令2999
返回所有终端状态列表
#### 消息主体
```
# 请求
{}
# 响应
[
{
"sn": "终端标识",
"ip": "IP地址",
"mac": "MAC地址",
"signal": "信号强度",
"battery": "电池电量"
},
...
]
```
#### 消息流程:终端->服务端->终端
响应所有终端状态列表
## 直播信令3000~3999
### 开启直播信令3000
@@ -225,7 +289,7 @@
{}
----
{
"id": "会议标识"
"id": "会议标识",
}
```
@@ -237,7 +301,23 @@
### 进入会议信令4002
广播
#### 消息主体
```
# 请求
{
"id": "会议标识"
}
# 广播
{
"id": "会议标识",
"sn": "终端标识"
}
```
#### 消息流程:终端->服务端-)终端
终端进入会议,广播通知其他终端。
### 离开会议信令4003

View File

@@ -0,0 +1,22 @@
package com.acgist.taoyao.signal.event.meeting;
import java.util.Map;
import com.acgist.taoyao.boot.model.Message;
import com.acgist.taoyao.signal.client.ClientSession;
import com.acgist.taoyao.signal.event.ApplicationEventAdapter;
/**
* 进入会议事件
*
* @author acgist
*/
public class MeetingEnterEvent extends ApplicationEventAdapter {
private static final long serialVersionUID = 1L;
public MeetingEnterEvent(Map<?, ?> body, Message message, ClientSession session) {
super(body, message, session);
}
}

View File

@@ -16,6 +16,8 @@ import com.acgist.taoyao.signal.protocol.client.ClientOnlineProtocol;
/**
* 终端注册监听
*
* TODO如果已经在会议、直播中自动推流。
*
* @author acgist
*/
@Component

View File

@@ -26,7 +26,7 @@ public class ClientHeartbeatProtocol extends ProtocolMapAdapter {
@Override
public void execute(String sn, Map<?, ?> body, Message message, ClientSession session) {
// 应心跳
// 应心跳
session.push(message.cloneWidthoutBody());
// 设置状态
final ClientSessionStatus status = session.status();

View File

@@ -1,5 +1,31 @@
package com.acgist.taoyao.signal.protocol.meeting;
public class MeetingEnterProtocol {
import java.util.Map;
import org.springframework.stereotype.Component;
import com.acgist.taoyao.boot.model.Message;
import com.acgist.taoyao.signal.client.ClientSession;
import com.acgist.taoyao.signal.event.meeting.MeetingEnterEvent;
import com.acgist.taoyao.signal.protocol.ProtocolMapAdapter;
/**
* 进入会议信令
*
* @author acgist
*/
@Component
public class MeetingEnterProtocol extends ProtocolMapAdapter {
public static final Integer PID = 4002;
public MeetingEnterProtocol() {
super(PID, "进入会议信令");
}
@Override
public void execute(String sn, Map<?, ?> body, Message message, ClientSession session) {
this.publishEvent(new MeetingEnterEvent(body, message, session));
}
}

View File

@@ -49,4 +49,3 @@ SDP只是一种信息格式的描述标准不属于传输协议但是可
|信令通道|自己实现|
|会话通道|SIP/SDP|
|媒体通道|RTP/RTCP/SRTP/SRTCP|