From d415202d1515625ff009ce8260cbe0c9033be6b0 Mon Sep 17 00:00:00 2001
From: acgist <289547414@qq.com>
Date: Fri, 7 Jul 2023 08:30:09 +0800
Subject: [PATCH] =?UTF-8?q?[*]=20=E6=AF=8F=E6=97=A5=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 22 ---
docs/Design.md | 112 +++++++++++++
docs/NetworkTopology.md | 2 +-
taoyao-client-web/README.md | 11 ++
taoyao-client-web/src/App.vue | 4 +-
taoyao-client-web/src/components/Config.js | 150 ++++++++++++------
.../src/components/LocalClient.vue | 1 +
.../src/components/RemoteClient.vue | 2 +
.../src/components/SessionClient.vue | 1 +
taoyao-client-web/src/components/Taoyao.js | 51 ++++--
10 files changed, 274 insertions(+), 82 deletions(-)
create mode 100644 docs/Design.md
diff --git a/README.md b/README.md
index c3ad8ec..d5fb60f 100644
--- a/README.md
+++ b/README.md
@@ -79,28 +79,6 @@ acgist/taoyao-signal-server
[部署文档](./docs/Deploy.md)
-### 集群
-
-信令服务支持下挂多个媒体服务,但是信令服务本身不具备分布式集群功能,如需实现给出以下两种实现建议:
-
-#### 信令分区
-
-将信令服务进行分区管理,分区不要直接管理终端,优先选择分区,然后选择信令服务。
-
-#### 代理终端
-
-将下级信令服务的终端全部使用代理终端注册到上级信令服务,上级信令服务代理终端处理信令时直接路由到下级路由服务,这样一级一级路由直到发送给真正的终端为止。
-
-## 重连
-
-### 信令重连
-
-所有终端信令默认支持重连
-
-### 媒体重连
-
-信令没有断开媒体重连依赖具体协议支持,如果信令断开默认关闭所有媒体,信令重连以后需要自己实现媒体重连(控制方主动邀请或者重连方主动进入)。
-
## 终端预览

diff --git a/docs/Design.md b/docs/Design.md
new file mode 100644
index 0000000..935f619
--- /dev/null
+++ b/docs/Design.md
@@ -0,0 +1,112 @@
+# 整体设计
+
+## 设计
+
+项目当前采用方案:
+
+* 监控(WebRTC)
+* 会议(Mediasoup独立Router)
+
+### 监控(WebRTC)
+
+使用原始`WebRTC`实现,监控和被监控终端使用`P2P`连接。
+
+#### 优点
+
+* 实现逻辑简单
+* `P2P`媒体直连,服务端没有压力。
+
+#### 缺点
+
+* 终端上行流量较大(同时被多个终端监控时)
+
+### 监控(Mediasoup独立Router)
+
+使用`Mediasoup`实现,所有终端进入一个`Router`,通过`Mediasoup`转发媒体实现监控。
+
+#### 优点
+
+* 终端只会存在一路上行流量
+
+#### 缺点
+
+* 单个`Router`压力较大(监控终端数量较多时)
+
+### 监控(Mediasoup代理Router)
+
+使用`Mediasoup`实现,每个终端对应一个代理`Router`,通过`PipeTransport`转发代理`Router`媒体实现监控。
+
+#### 优点
+
+* 终端只会存在一路上行流量
+* 压力不会集中某个`Router`
+
+#### 缺点
+
+* 实现逻辑复杂
+* 服务端压力较大(媒体通道生产者消费者数量增加)
+
+### 会议(Mediasoup独立Router)
+
+使用`Mediasoup`实现,每个会议对应一个`Router`,`Router`内部媒体直接转发实现会议。
+
+#### 优点
+
+* 实现逻辑清晰
+
+#### 缺点
+
+* 单个终端进入多个会议上行流量较大
+
+### 会议(Mediasoup代理Router)
+
+会议使用`Mediasoup`实现,每个终端对应一个代理`Router`,通过`PipeTransport`转发代理`Router`媒体实现会议。
+
+#### 优点
+
+* 终端只会存在一路上行流量
+
+#### 缺点
+
+* 实现逻辑复杂
+* 服务端压力较大(媒体通道生产者消费者数量增加)
+
+## 子网媒体转发
+
+多个子网不能互通时解决方案
+
+### 监控
+
+如果只有两个子网可以直接通过`TURN`服务实现,如果超过两个子网需要`TURN`服务和防火墙自动转发实现。
+
+### 会议
+
+#### 实现多级平台
+
+实现多级平台的代理终端功能,最后通过`PipeTransport`转发媒体。
+
+#### 地址重写和防火墙自动转发(当前采用)
+
+如果只有两个子网可以通过双网卡服务器直接通过`IP`地址重写实现,如果超过两个子网可以通过`IP`地址重写和防火墙自动转发实现,参考方案(拓扑网络)[./NetworkTopology.md]
+
+## 多级平台
+
+信令服务支持下挂多个媒体服务,但是信令服务本身不具备分布式集群功能,如需实现给出以下两种实现建议:
+
+### 信令分区
+
+将信令服务进行分区管理,分区不要直接管理终端,优先选择分区,然后选择信令服务。
+
+### 代理终端
+
+将下级信令服务的终端全部使用代理终端注册到上级信令服务,上级信令服务代理终端处理信令时直接路由到下级路由服务,这样一级一级路由直到发送给真正的终端为止。
+
+## 重连
+
+### 信令重连
+
+所有终端信令默认支持重连
+
+### 媒体重连
+
+信令没有断开媒体重连依赖具体协议支持,如果信令断开默认关闭所有媒体,信令重连以后需要自己实现媒体重连(控制方主动邀请或者重连方主动进入)。
diff --git a/docs/NetworkTopology.md b/docs/NetworkTopology.md
index 284bd44..bca3b6e 100644
--- a/docs/NetworkTopology.md
+++ b/docs/NetworkTopology.md
@@ -1,6 +1,6 @@
# 拓扑网络
-多子网环境下的部署配置
+多子网环境下的部署配置,当前设计只在顶层子网部署服务,每层子网采用双网卡服务器通过防火墙自动转发实现。
## 两个子网
diff --git a/taoyao-client-web/README.md b/taoyao-client-web/README.md
index be507d1..0dab357 100644
--- a/taoyao-client-web/README.md
+++ b/taoyao-client-web/README.md
@@ -6,6 +6,17 @@
* [mediasoup-client文档](https://mediasoup.org/documentation/v3/mediasoup-client)
* [mediasoup-client接口](https://mediasoup.org/documentation/v3/mediasoup-client/api)
+## 终端媒体
+
+终端页面组件需要提供`media`方法,同时挂载到终端的`proxy`属性下面。
+媒体生成以后自动调用:
+
+```
+LocalClient.proxy.media(track, producer);
+RemoteClient.proxy.media(track, consumer);
+SessionClient.proxy.media(track);
+```
+
## 终端列表
`Web`终端并未对整个终端列表以及状态进行维护,所以需要开发者自己实现。
diff --git a/taoyao-client-web/src/App.vue b/taoyao-client-web/src/App.vue
index a84fe65..41faaa1 100644
--- a/taoyao-client-web/src/App.vue
+++ b/taoyao-client-web/src/App.vue
@@ -92,7 +92,7 @@
-
+
@@ -206,7 +206,7 @@ export default {
* @param {*} response 回调
* @param {*} error 异常
*
- * @return 是否继续执行
+ * @return 是否执行完成
*/
async callback(response, error) {
const me = this;
diff --git a/taoyao-client-web/src/components/Config.js b/taoyao-client-web/src/components/Config.js
index 9f36c62..1808f23 100644
--- a/taoyao-client-web/src/components/Config.js
+++ b/taoyao-client-web/src/components/Config.js
@@ -1,88 +1,147 @@
+/**
+ * 配置:{ min: 8000, exact: 32000, ideal: 32000, max: 48000 }
+ */
/**
* 音频默认配置
- * TODO:MediaStreamTrack.applyConstraints().then().catch();
- * const setting = {
- * autoGainControl: true,
- * noiseSuppression: true
- * }
- await track.applyConstraints(Object.assign(track.getSettings(), setting));
- * TODO:播放音量(audio标签配置)、采集音量
- * 支持属性:navigator.mediaDevices.getSupportedConstraints()
+ *
* https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings
*/
const defaultAudioConfig = {
- // 设备
- // deviceId : '',
+ // 指定设备
+ // deviceId : '',
+ // 标识会话
+ // groupId : '',
// 音量(废弃):0.0~1.0
- // volume: 1.0,
+ // volume : 1.0,
// 延迟时间(单位:秒):500毫秒以内较好
- // latency: 0.4,
+ // latency : 0.4,
// 采样位数:8|16|32
- sampleSize: { min: 8, ideal: 16, max: 32 },
+ sampleSize : { min: 8, ideal: 16, max: 32 },
// 采样率:8000|16000|32000|48000
- sampleRate: { min: 8000, ideal: 32000, max: 48000 },
+ sampleRate : { min: 8000, ideal: 32000, max: 48000 },
// 声道数量:1|2
- channelCount: 1,
+ channelCount : 1,
// 是否开启自动增益:true|false
- autoGainControl: true,
+ autoGainControl : true,
// 是否开启降噪功能:true|false
noiseSuppression: true,
// 是否开启回音消除:true|false
echoCancellation: true,
- // 消除回音方式:system|browser
- echoCancellationType: "system",
};
/**
* 视频默认配置
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings
*/
const defaultVideoConfig = {
- // 设备
- // deviceId: '',
+ // 指定设备
+ // deviceId : '',
+ // 标识会话
+ // groupId : '',
// 宽度
- width: { min: 720, ideal: 1280, max: 4096 },
+ width : { min: 720, ideal: 1280, max: 4096 },
// 高度
- height: { min: 480, ideal: 720, max: 2160 },
+ height : { min: 480, ideal: 720, max: 2160 },
// 帧率
- frameRate: { min: 15, ideal: 24, max: 45 },
- // 选摄像头:user|left|right|environment
- facingMode: "environment",
+ frameRate : { min: 15, ideal: 24, max: 45 },
+ // 摄像头:user|left|right|environment
+ facingMode : "environment",
+ // 裁剪
+ // resizeMode : null,
+ // 宽高比
+ // aspectRatio: 1.7777777778,
};
/**
- * VP9默认配置
+ * 共享屏幕默认配置
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings
*/
-const defaultKsvcEncodings = [{ scalabilityMode: "S3T3_KEY" }];
+const defaultShareScreenConfig = {
+ // 显示鼠标:always|motion|never
+ cursor : "always",
+ // 逻辑窗口捕获(没有完全显示)
+ logicalSurface: true,
+ // 视频来源:window|monitor|browser|application
+ displaySurface: "monitor",
+}
/**
- * simulcast默认配置
- * TODO:update
- * https://gitee.com/acgist/mediasoup-demo/commit/090c82920d1b8015d457e4fafbb06607cb232885
- * https://gitee.com/acgist/mediasoup-demo/commit/e4f70da0c69226b997d174c477d82f8dbb997e91
- * https://gitee.com/acgist/mediasoup-demo/commit/2c67601d0a231bf901242c8e14cdd0d1ba39f3a4
- * https://gitee.com/acgist/mediasoup-demo/commit/b9f3f28d2eab314b95392fa698d518177d5ad767
- * https://gitee.com/acgist/mediasoup-demo/commit/1c59132ca926a6f9ca0c5c2bb155fac58eed9b06
- * https://gitee.com/acgist/mediasoup-demo/commit/d15a859306e1ba5d031cde90d02593e095719cbc
- * https://gitee.com/acgist/mediasoup-demo/commit/13cf71cc608690ff96ec12e6d3f1262b40c4d8f3
+ * SVC默认配置
+ * 支持编码:VP9
+ *
+ * https://w3c.github.io/webrtc-svc/
+ * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/
+ * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/#SVC
+ */
+const defaultSvcEncodings = [
+ {
+ dtx : true,
+ maxBitrate : 5000000,
+ scalabilityMode: 'L3T3_KEY'
+ }
+];
+
+/**
+ * Simulcast默认配置
+ * 支持编码:VP8 H264
+ * 可以根据数量减少配置数量
+ * dtx:屏幕贡献开启效果显著
+ *
+ * https://w3c.github.io/webrtc-svc/
+ * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/
+ * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/#Simulcast
*/
const defaultSimulcastEncodings = [
- { scaleResolutionDownBy: 4, maxBitrate: 500000, scalabilityMode: "S1T2" },
- { scaleResolutionDownBy: 2, maxBitrate: 1000000, scalabilityMode: "S1T2" },
- { scaleResolutionDownBy: 1, maxBitrate: 5000000, scalabilityMode: "S1T2" },
+ {
+ dtx : true,
+ maxBitrate : 5000000,
+ scalabilityMode : 'L1T3',
+ scaleResolutionDownBy: 1,
+ },
+ {
+ dtx : true,
+ maxBitrate : 1000000,
+ scalabilityMode : 'L1T3',
+ scaleResolutionDownBy: 2,
+ },
+ {
+ dtx : true,
+ maxBitrate : 500000,
+ scalabilityMode : 'L1T3',
+ scaleResolutionDownBy: 4,
+ },
];
/**
* RTCPeerConnection默认配置
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection
*/
const defaultRTCPeerConnectionConfig = {
- // ICE代理的服务器
- iceServers: [],
+ // ICE代理服务器
+ iceServers : [
+ {
+ // { "url": "stun:stun1.l.google.com:19302" },
+ urls: [
+ "stun:stun1.l.google.com:19302",
+ "stun:stun2.l.google.com:19302",
+ "stun:stun3.l.google.com:19302",
+ "stun:stun4.l.google.com:19302"
+ ]
+ }
+ ],
+ // 目标对等身份
+ // peerIdentity : null,
// 传输通道绑定策略:balanced|max-compat|max-bundle
- bundlePolicy: "balanced",
+ bundlePolicy : "balanced",
// RTCP多路复用策略:require|negotiate
- rtcpMuxPolicy: "require",
+ rtcpMuxPolicy : "require",
+ // 连接证书
+ // certificates : null,
// ICE传输策略:all|relay
- iceTransportPolicy: "all",
+ iceTransportPolicy : "all",
// ICE候选个数
iceCandidatePoolSize: 8,
};
@@ -90,7 +149,8 @@ const defaultRTCPeerConnectionConfig = {
export {
defaultAudioConfig,
defaultVideoConfig,
- defaultKsvcEncodings,
+ defaultShareScreenConfig,
+ defaultSvcEncodings,
defaultSimulcastEncodings,
defaultRTCPeerConnectionConfig,
};
diff --git a/taoyao-client-web/src/components/LocalClient.vue b/taoyao-client-web/src/components/LocalClient.vue
index df82d12..cd11d8d 100644
--- a/taoyao-client-web/src/components/LocalClient.vue
+++ b/taoyao-client-web/src/components/LocalClient.vue
@@ -126,6 +126,7 @@ export default {
},
};
+
diff --git a/taoyao-client-web/src/components/RemoteClient.vue b/taoyao-client-web/src/components/RemoteClient.vue
index 18fbc1f..21f7ea4 100644
--- a/taoyao-client-web/src/components/RemoteClient.vue
+++ b/taoyao-client-web/src/components/RemoteClient.vue
@@ -18,6 +18,7 @@
+
视频质量
@@ -120,6 +121,7 @@ export default {
}
};
+
diff --git a/taoyao-client-web/src/components/SessionClient.vue b/taoyao-client-web/src/components/SessionClient.vue
index 64639d7..415be70 100644
--- a/taoyao-client-web/src/components/SessionClient.vue
+++ b/taoyao-client-web/src/components/SessionClient.vue
@@ -116,6 +116,7 @@ export default {
}
};
+
diff --git a/taoyao-client-web/src/components/Taoyao.js b/taoyao-client-web/src/components/Taoyao.js
index f6c2ef7..2c10999 100644
--- a/taoyao-client-web/src/components/Taoyao.js
+++ b/taoyao-client-web/src/components/Taoyao.js
@@ -2,7 +2,8 @@ import * as mediasoupClient from "mediasoup-client";
import {
defaultAudioConfig,
defaultVideoConfig,
- defaultKsvcEncodings,
+ defaultShareScreenConfig,
+ defaultSvcEncodings,
defaultSimulcastEncodings,
defaultRTCPeerConnectionConfig,
} from "./Config.js";
@@ -484,12 +485,14 @@ class Taoyao extends RemoteClient {
videoSource = "camera";
// 强制使用TCP
forceTcp;
+ // 强制使用VP8
+ forceVP8;
// 强制使用VP9
forceVP9;
// 强制使用H264
forceH264;
// 同时上送多种质量媒体
- useSimulcast;
+ useLayers;
// 是否消费数据
dataConsume;
// 是否消费音频
@@ -858,6 +861,7 @@ class Taoyao extends RemoteClient {
track.getCapabilities()
);
} else if (self.videoSource === "screen") {
+ // TODO:默认配置
const stream = await navigator.mediaDevices.getDisplayMedia({
// 如果需要共享声音
audio: false,
@@ -1605,10 +1609,6 @@ class Taoyao extends RemoteClient {
*/
async roomEnter(roomId, password) {
const me = this;
- if (!roomId) {
- this.callbackError("无效房间");
- return;
- }
// TODO:已经进入房间忽略
me.roomId = roomId;
let response = await me.request(
@@ -1983,6 +1983,7 @@ class Taoyao extends RemoteClient {
// await this.produceAudio();
// await this.produceVideo();
// await this.produceData();
+ // TODO:返回通道还有音视频生产者
}
/**
* 生产音频
@@ -2113,17 +2114,23 @@ class Taoyao extends RemoteClient {
if (!codec) {
self.callbackError("不支持VP9视频编码");
}
+ } else if(self.forceVP8) {
+ codec = self.mediasoupDevice.rtpCapabilities.codecs.find(
+ (c) => c.mimeType.toLowerCase() === "video/vp8"
+ );
+ if (!codec) {
+ self.callbackError("不支持VP8视频编码");
+ }
}
- if (this.useSimulcast) {
- const firstVideoCodec =
- this.mediasoupDevice.rtpCapabilities.codecs.find(
- (c) => c.kind === "video"
- );
+ if (this.useLayers) {
+ const firstVideoCodec = this.mediasoupDevice.rtpCapabilities.codecs.find(
+ (c) => c.kind === "video"
+ );
if (
(this.forceVP9 && codec) ||
firstVideoCodec.mimeType.toLowerCase() === "video/vp9"
) {
- encodings = defaultKsvcEncodings;
+ encodings = defaultSvcEncodings;
} else {
encodings = defaultSimulcastEncodings;
}
@@ -2599,6 +2606,26 @@ class Taoyao extends RemoteClient {
}
}
+ /**
+ * TODO:设置track配置
+ *
+ * @param {*} track
+ * @param {*} setting
+ */
+ setTrack(track, setting) {
+ /*
+ * TODO:MediaStreamTrack.applyConstraints().then().catch();
+ * const setting = {
+ * autoGainControl: true,
+ * noiseSuppression: true
+ * }
+ await track.applyConstraints(Object.assign(track.getSettings(), setting));
+ * TODO:播放音量(audio标签配置)、采集音量
+ * 支持属性:navigator.mediaDevices.getSupportedConstraints()
+ * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings
+ */
+ }
+
/**
* 关闭视频房间媒体
*/