diff --git a/README.md b/README.md index c46d057..96350eb 100644 --- a/README.md +++ b/README.md @@ -44,34 +44,24 @@ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` -## 内网穿透 - -请用公共STUN/TURN服务或者自行搭建coturn服务。 - -> 只有公网Mesh架构才需要真正的内网穿透 - ## 直播 终端推流到服务端,由服务端分流。 ## 会议 -Mesh架构声音视频控制部分功能均在终端实现,同时不会实现终端录制、美颜、AI识别、变声、混音等等功能。 -MCU/SFU声音视频控制在服务端实现,如果没有终端订阅并且没有录制是不会对终端进行拉流。 - ### Mesh 流媒体点对点连接,不经过服务端。 +Mesh架构声音视频控制部分功能均在终端实现,同时不会实现录制、美颜、变声、混音等等功能。 + +> 需要使用STUN/TURN实现内网穿透(可以自己搭建coturn服务) ### MCU -终端推流到服务端,由服务端分流并且混音。 +终端推流到服务端,由服务端混音分流。 ### SFU -终端推流到服务端,由服务端分流没有混音。 +终端推流到服务端,由服务端直接分流。 -## TODO - -springdoc升级正式版本 -springboot升级正式版本 \ No newline at end of file diff --git a/docs/flow/README.md b/docs/flow/README.md new file mode 100644 index 0000000..c277ab8 --- /dev/null +++ b/docs/flow/README.md @@ -0,0 +1,33 @@ +# 流程 + +## WebRTC流程 + +发起响应 + +## SFU流程 + +媒体流 + +## MCU流程 + +媒体流 + +## Mesh流程 + +媒体流 + +## 直播流程 + +创建直播,终端操作。 + +## 会议流程 + +创建会议,终端操作 + +## 终端流程 + +注册下线 + +## 媒体控制 + +拉流发布 diff --git a/docs/flow/WebRTC.png b/docs/flow/WebRTC.png new file mode 100644 index 0000000..555d237 Binary files /dev/null and b/docs/flow/WebRTC.png differ diff --git a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/Meeting.java b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/Meeting.java index 1291fa9..3dde72a 100644 --- a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/Meeting.java +++ b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/Meeting.java @@ -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); } } diff --git a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/listener/MeetingCreateListener.java b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/listener/MeetingCreateListener.java index e270c08..1a6a1a3 100644 --- a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/listener/MeetingCreateListener.java +++ b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/listener/MeetingCreateListener.java @@ -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 { + + @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); + } + +} diff --git a/taoyao-server/src/main/resources/static/javascript/taoyao.js b/taoyao-server/src/main/resources/static/javascript/taoyao.js index f2a6ffc..6f30618 100644 --- a/taoyao-server/src/main/resources/static/javascript/taoyao.js +++ b/taoyao-server/src/main/resources/static/javascript/taoyao.js @@ -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);; - } + this.video.srcObject = stream; + await this.play(); } - await this.play(); return this; }; /** 设置音频流 */ diff --git a/taoyao-server/src/main/resources/static/meeting.html b/taoyao-server/src/main/resources/static/meeting.html index 6a447ef..e880553 100644 --- a/taoyao-server/src/main/resources/static/meeting.html +++ b/taoyao-server/src/main/resources/static/meeting.html @@ -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() { }, diff --git a/taoyao-signal/README.md b/taoyao-signal/README.md index 2c381c3..5ea43d8 100644 --- a/taoyao-signal/README.md +++ b/taoyao-signal/README.md @@ -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) diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/meeting/MeetingEnterEvent.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/meeting/MeetingEnterEvent.java new file mode 100644 index 0000000..570d636 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/meeting/MeetingEnterEvent.java @@ -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); + } + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/listener/client/ClientRegisterListener.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/listener/client/ClientRegisterListener.java index 192cfcb..cf7cabb 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/listener/client/ClientRegisterListener.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/listener/client/ClientRegisterListener.java @@ -16,6 +16,8 @@ import com.acgist.taoyao.signal.protocol.client.ClientOnlineProtocol; /** * 终端注册监听 * + * TODO:如果已经在会议、直播中,自动推流。 + * * @author acgist */ @Component diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java index 9e78aaf..8d5aa8c 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java @@ -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(); diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/meeting/MeetingEnterProtocol.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/meeting/MeetingEnterProtocol.java index baa2f83..9602e44 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/meeting/MeetingEnterProtocol.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/meeting/MeetingEnterProtocol.java @@ -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)); + } } diff --git a/taoyao-webrtc/README.md b/taoyao-webrtc/README.md index 1a25a90..f2c13ed 100644 --- a/taoyao-webrtc/README.md +++ b/taoyao-webrtc/README.md @@ -49,4 +49,3 @@ SDP只是一种信息格式的描述标准,不属于传输协议,但是可 |信令通道|自己实现| |会话通道|SIP/SDP| |媒体通道|RTP/RTCP/SRTP/SRTCP| -