[*] mesh多人
This commit is contained in:
12
README.md
12
README.md
@@ -9,7 +9,6 @@
|
|||||||
|taoyao|桃夭|桃之夭夭灼灼其华|
|
|taoyao|桃夭|桃之夭夭灼灼其华|
|
||||||
|taoyao-boot|基础|基础模块|
|
|taoyao-boot|基础|基础模块|
|
||||||
|taoyao-live|直播|直播、连麦、本地视频同看|
|
|taoyao-live|直播|直播、连麦、本地视频同看|
|
||||||
|taoyao-test|测试|测试模块|
|
|
||||||
|taoyao-media|媒体|录制<br />音频(降噪、混音、变声)<br />视频(水印、美颜、AI识别)|
|
|taoyao-media|媒体|录制<br />音频(降噪、混音、变声)<br />视频(水印、美颜、AI识别)|
|
||||||
|taoyao-signal|信令|信令服务|
|
|taoyao-signal|信令|信令服务|
|
||||||
|taoyao-server|服务|启动服务|
|
|taoyao-server|服务|启动服务|
|
||||||
@@ -29,9 +28,9 @@
|
|||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| taoyao-media |
|
| taoyao-media |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| taoyao-moon | |
|
| taoyao-webrtc-moon | |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ taoyao-mesh +
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ taoyao-webrtc-mesh +
|
||||||
| taoyao-kurento | |
|
| taoyao-webrtc-kurento | |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| taoyao-signal |
|
| taoyao-signal |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
@@ -50,9 +49,9 @@
|
|||||||
#### 功能简介
|
#### 功能简介
|
||||||
|
|
||||||
* ~~直播~~
|
* ~~直播~~
|
||||||
* 会议:一对一、~~多对多~~
|
|
||||||
* ~~媒体:降噪、变声、美颜录制、等等~~
|
* ~~媒体:降噪、变声、美颜录制、等等~~
|
||||||
* 可能需要自己搭建`coturn`服务实现`STUN`/`TURN`内网穿透功能
|
* 可能需要自己搭建`coturn`服务实现`STUN`/`TURN`功能
|
||||||
|
* 终端和终端之间各自建立一个独立媒体连接
|
||||||
|
|
||||||
### Moon
|
### Moon
|
||||||
|
|
||||||
@@ -65,3 +64,4 @@
|
|||||||
* 需要安装[KMS服务](./docs/Deploy.md#kmskurento-media-server)
|
* 需要安装[KMS服务](./docs/Deploy.md#kmskurento-media-server)
|
||||||
* 提供混音、变声、美颜、录制等等媒体功能
|
* 提供混音、变声、美颜、录制等等媒体功能
|
||||||
* 终端推送给服务端最高质量媒体,再由服务端根据订阅终端按配置分流。
|
* 终端推送给服务端最高质量媒体,再由服务端根据订阅终端按配置分流。
|
||||||
|
* 终端和服务器之间建立两个媒体连接,一个本地媒体,一个远程媒体。
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
@startuml
|
|
||||||
|
|
||||||
title WebRTC-Mesh
|
|
||||||
|
|
||||||
actor ClientA as ClientA
|
|
||||||
participant "Signal" as Signal
|
|
||||||
actor ClientB as ClientB
|
|
||||||
actor ClientC as ClientC
|
|
||||||
|
|
||||||
autonumber
|
|
||||||
|
|
||||||
ClientA -> Signal: 进入房间
|
|
||||||
activate ClientA
|
|
||||||
activate Signal
|
|
||||||
Signal -> ClientB: ClientA进入房间
|
|
||||||
activate ClientB
|
|
||||||
ClientB -> Signal: 订阅ClientA
|
|
||||||
Signal -> ClientA: ClientB订阅ClientA
|
|
||||||
ClientA -> Signal: ClientA发布ClientB
|
|
||||||
Signal -> ClientB: ClientA发布
|
|
||||||
deactivate ClientB
|
|
||||||
Signal -> ClientC: ClientA进入房间
|
|
||||||
activate ClientC
|
|
||||||
ClientC -> Signal: 订阅ClientA
|
|
||||||
Signal -> ClientA: ClientC订阅ClientA
|
|
||||||
ClientA -> Signal: ClientA发布ClientC
|
|
||||||
Signal -> ClientC: ClientA发布
|
|
||||||
deactivate ClientC
|
|
||||||
deactivate Signal
|
|
||||||
deactivate ClientA
|
|
||||||
|
|
||||||
@enduml
|
|
||||||
10
pom.xml
10
pom.xml
@@ -37,7 +37,6 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>taoyao-boot</module>
|
<module>taoyao-boot</module>
|
||||||
<module>taoyao-live</module>
|
<module>taoyao-live</module>
|
||||||
<module>taoyao-test</module>
|
|
||||||
<module>taoyao-media</module>
|
<module>taoyao-media</module>
|
||||||
<module>taoyao-signal</module>
|
<module>taoyao-signal</module>
|
||||||
<module>taoyao-server</module>
|
<module>taoyao-server</module>
|
||||||
@@ -113,11 +112,6 @@
|
|||||||
<artifactId>taoyao-live</artifactId>
|
<artifactId>taoyao-live</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.acgist</groupId>
|
|
||||||
<artifactId>taoyao-test</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.acgist</groupId>
|
<groupId>com.acgist</groupId>
|
||||||
<artifactId>taoyao-media</artifactId>
|
<artifactId>taoyao-media</artifactId>
|
||||||
@@ -125,12 +119,12 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.acgist</groupId>
|
<groupId>com.acgist</groupId>
|
||||||
<artifactId>taoyao-signal</artifactId>
|
<artifactId>taoyao-server</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.acgist</groupId>
|
<groupId>com.acgist</groupId>
|
||||||
<artifactId>taoyao-server</artifactId>
|
<artifactId>taoyao-signal</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -8,3 +8,6 @@
|
|||||||
|
|
||||||
:: Spring Boot : ${spring-boot.formatted-version}
|
:: Spring Boot : ${spring-boot.formatted-version}
|
||||||
:: ${spring.application.name} : https://gitee.com/acgist/taoyao
|
:: ${spring.application.name} : https://gitee.com/acgist/taoyao
|
||||||
|
|
||||||
|
中庭地白树栖鸦,冷露无声湿桂花。
|
||||||
|
今夜月明人尽望,不知秋思落谁家。
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class MeetingManager {
|
|||||||
*/
|
*/
|
||||||
public Meeting create(String sn) {
|
public Meeting create(String sn) {
|
||||||
final Meeting meeting = new Meeting();
|
final Meeting meeting = new Meeting();
|
||||||
meeting.setId(this.idService.buildIdToString());
|
meeting.setId("1");
|
||||||
meeting.setSns(new CopyOnWriteArrayList<>());
|
meeting.setSns(new CopyOnWriteArrayList<>());
|
||||||
meeting.setCreator(sn);
|
meeting.setCreator(sn);
|
||||||
meeting.addSn(sn);
|
meeting.addSn(sn);
|
||||||
|
|||||||
@@ -30,12 +30,6 @@
|
|||||||
<groupId>com.acgist</groupId>
|
<groupId>com.acgist</groupId>
|
||||||
<artifactId>taoyao-meeting</artifactId>
|
<artifactId>taoyao-meeting</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.acgist</groupId>
|
|
||||||
<artifactId>taoyao-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -91,19 +91,19 @@ taoyao:
|
|||||||
# 媒体端口范围
|
# 媒体端口范围
|
||||||
min-port: 45535
|
min-port: 45535
|
||||||
max-port: 65535
|
max-port: 65535
|
||||||
|
# 公共服务
|
||||||
stun:
|
stun:
|
||||||
- stun:stun1.l.google.com:19302
|
- stun:stun1.l.google.com:19302
|
||||||
- stun:stun2.l.google.com:19302
|
- stun:stun2.l.google.com:19302
|
||||||
- stun:stun3.l.google.com:19302
|
- stun:stun3.l.google.com:19302
|
||||||
- stun:stun4.l.google.com:19302
|
- stun:stun4.l.google.com:19302
|
||||||
- stun:stun.stunprotocol.org:3478
|
# 自己搭建:coturn
|
||||||
turn:
|
turn:
|
||||||
- stun:stun1.l.google.com:19302
|
- turn:127.0.0.1:8888
|
||||||
- stun:stun2.l.google.com:19302
|
- turn:127.0.0.1:8888
|
||||||
- stun:stun3.l.google.com:19302
|
- turn:127.0.0.1:8888
|
||||||
- stun:stun4.l.google.com:19302
|
- turn:127.0.0.1:8888
|
||||||
- stun:stun.stunprotocol.org:3478
|
# KMS服务配置:可以部署多个简单实现负载均衡
|
||||||
# KMS服务配置
|
|
||||||
kms:
|
kms:
|
||||||
host: 192.168.1.100
|
host: 192.168.1.100
|
||||||
port: 18888
|
port: 18888
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const defaultRPCConfig = {
|
|||||||
/** 信令配置 */
|
/** 信令配置 */
|
||||||
const signalConfig = {
|
const signalConfig = {
|
||||||
/** 当前终端SN */
|
/** 当前终端SN */
|
||||||
sn: localStorage.getItem('taoyao.sn') || 'taoyao',
|
sn: 'taoyao',
|
||||||
/** 当前版本 */
|
/** 当前版本 */
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
// 信令授权
|
// 信令授权
|
||||||
@@ -170,6 +170,7 @@ const signalChannel = {
|
|||||||
clearTimeout(self.heartbeatTimer);
|
clearTimeout(self.heartbeatTimer);
|
||||||
}
|
}
|
||||||
self.heartbeatTimer = setTimeout(function() {
|
self.heartbeatTimer = setTimeout(function() {
|
||||||
|
// 电池:navigator.getBattery()
|
||||||
if (self.channel && self.channel.readyState === WebSocket.OPEN) {
|
if (self.channel && self.channel.readyState === WebSocket.OPEN) {
|
||||||
self.push(signalProtocol.buildProtocol(
|
self.push(signalProtocol.buildProtocol(
|
||||||
signalProtocol.client.heartbeat,
|
signalProtocol.client.heartbeat,
|
||||||
@@ -341,9 +342,23 @@ const signalChannel = {
|
|||||||
},
|
},
|
||||||
/** 终端默认回调 */
|
/** 终端默认回调 */
|
||||||
defaultClientConfig: function(data) {
|
defaultClientConfig: function(data) {
|
||||||
this.taoyao
|
let self = this;
|
||||||
|
// 配置终端
|
||||||
|
self.taoyao
|
||||||
.configMedia(data.body.media.audio, data.body.media.video)
|
.configMedia(data.body.media.audio, data.body.media.video)
|
||||||
.configWebrtc(data.body.webrtc);
|
.configWebrtc(data.body.webrtc);
|
||||||
|
// 打开媒体通道
|
||||||
|
let videoId = self.taoyao.videoId;
|
||||||
|
if(videoId) {
|
||||||
|
self.taoyao.buildLocalMedia()
|
||||||
|
.then(stream => {
|
||||||
|
self.taoyao.buildMediaChannel(videoId, stream);
|
||||||
|
})
|
||||||
|
.catch(e => console.error('打开终端媒体失败', e));
|
||||||
|
console.debug('自动打开媒体通道', videoId);
|
||||||
|
} else {
|
||||||
|
console.debug('没有配置本地媒体信息跳过自动打开媒体通道');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
defaultClientReboot: function(data) {
|
defaultClientReboot: function(data) {
|
||||||
console.info('重启终端');
|
console.info('重启终端');
|
||||||
@@ -355,10 +370,10 @@ const signalChannel = {
|
|||||||
defaultMediaSubscribe: function(data) {
|
defaultMediaSubscribe: function(data) {
|
||||||
let self = this;
|
let self = this;
|
||||||
const from = data.body.from;
|
const from = data.body.from;
|
||||||
this.taoyao.remoteClientFilter(from, true);
|
const remote = this.taoyao.remoteClientFilter(from, true);
|
||||||
self.taoyao.localMediaChannel.createOffer().then(description => {
|
remote.localMediaChannel.createOffer().then(description => {
|
||||||
console.debug('Local Create Offer', description);
|
console.debug('Local Create Offer', description);
|
||||||
self.taoyao.localMediaChannel.setLocalDescription(description);
|
remote.localMediaChannel.setLocalDescription(description);
|
||||||
self.push(signalProtocol.buildProtocol(
|
self.push(signalProtocol.buildProtocol(
|
||||||
signalProtocol.media.offer,
|
signalProtocol.media.offer,
|
||||||
{
|
{
|
||||||
@@ -374,11 +389,11 @@ const signalChannel = {
|
|||||||
defaultMediaOffer: function(data) {
|
defaultMediaOffer: function(data) {
|
||||||
let self = this;
|
let self = this;
|
||||||
const from = data.body.from;
|
const from = data.body.from;
|
||||||
this.taoyao.remoteClientFilter(from, true);
|
const remote = this.taoyao.remoteClientFilter(from, true);
|
||||||
self.taoyao.remoteMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body.sdp));
|
remote.remoteMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body.sdp));
|
||||||
self.taoyao.remoteMediaChannel.createAnswer().then(description => {
|
remote.remoteMediaChannel.createAnswer().then(description => {
|
||||||
console.debug('Remote Create Answer', description);
|
console.debug('Remote Create Answer', description);
|
||||||
self.taoyao.remoteMediaChannel.setLocalDescription(description);
|
remote.remoteMediaChannel.setLocalDescription(description);
|
||||||
self.push(signalProtocol.buildProtocol(
|
self.push(signalProtocol.buildProtocol(
|
||||||
signalProtocol.media.answer,
|
signalProtocol.media.answer,
|
||||||
{
|
{
|
||||||
@@ -392,18 +407,27 @@ const signalChannel = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
defaultMediaAnswer: function(data) {
|
defaultMediaAnswer: function(data) {
|
||||||
this.taoyao.localMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body.sdp));
|
const from = data.body.from;
|
||||||
|
const remote = this.taoyao.remoteClientFilter(from, true);
|
||||||
|
remote.localMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body.sdp));
|
||||||
},
|
},
|
||||||
defaultMediaCandidate: function(data) {
|
defaultMediaCandidate: function(data) {
|
||||||
if(!this.taoyao.checkCandidate(data.body.candidate)) {
|
if(!this.taoyao.checkCandidate(data.body.candidate)) {
|
||||||
console.debug('候选缺失要素', data);
|
console.debug('候选缺失要素', data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.debug('Set ICE Candidate', this.taoyao.remoteMediaChannel);
|
console.debug('Set ICE Candidate', data.body);
|
||||||
|
const from = data.body.from;
|
||||||
|
const remote = this.taoyao.remoteClientFilter(from, true);
|
||||||
if(data.body.type === 'local') {
|
if(data.body.type === 'local') {
|
||||||
this.taoyao.remoteMediaChannel.addIceCandidate(new RTCIceCandidate(data.body.candidate));
|
remote.remoteMediaChannel.addIceCandidate(new RTCIceCandidate(data.body.candidate));
|
||||||
|
} else if(data.body.type === 'remote'){
|
||||||
|
remote.localMediaChannel.addIceCandidate(new RTCIceCandidate(data.body.candidate));
|
||||||
|
} else if(data.body.type === 'mesh') {
|
||||||
|
remote.localMediaChannel.addIceCandidate(new RTCIceCandidate(data.body.candidate));
|
||||||
|
// remote.remoteMediaChannel.addIceCandidate(new RTCIceCandidate(data.body.candidate));
|
||||||
} else {
|
} else {
|
||||||
this.taoyao.localMediaChannel.addIceCandidate(new RTCIceCandidate(data.body.candidate));
|
console.warn('不支持的候选类型', data.body.type);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** 会议默认回调 */
|
/** 会议默认回调 */
|
||||||
@@ -413,10 +437,14 @@ const signalChannel = {
|
|||||||
};
|
};
|
||||||
/** 终端 */
|
/** 终端 */
|
||||||
function TaoyaoClient(
|
function TaoyaoClient(
|
||||||
|
taoyao,
|
||||||
sn,
|
sn,
|
||||||
|
shareMediaChannel,
|
||||||
audioEnabled,
|
audioEnabled,
|
||||||
videoEnabled
|
videoEnabled
|
||||||
) {
|
) {
|
||||||
|
/** 桃夭 */
|
||||||
|
this.taoyao = taoyao;
|
||||||
/** 终端标识 */
|
/** 终端标识 */
|
||||||
this.sn = sn;
|
this.sn = sn;
|
||||||
/** 视频对象 */
|
/** 视频对象 */
|
||||||
@@ -429,6 +457,12 @@ function TaoyaoClient(
|
|||||||
this.audioStatus = false;
|
this.audioStatus = false;
|
||||||
this.videoStatus = false;
|
this.videoStatus = false;
|
||||||
this.recordStatus = false;
|
this.recordStatus = false;
|
||||||
|
/** 本地媒体通道 */
|
||||||
|
this.localMediaChannel = null;
|
||||||
|
/** 远程媒体通道 */
|
||||||
|
this.remoteMediaChannel = null;
|
||||||
|
/** 是否共享媒体通道 */
|
||||||
|
this.shareMediaChannel = shareMediaChannel;
|
||||||
/** 媒体状态:是否播放 */
|
/** 媒体状态:是否播放 */
|
||||||
this.audioEnabled = audioEnabled == undefined ? true : audioEnabled;
|
this.audioEnabled = audioEnabled == undefined ? true : audioEnabled;
|
||||||
this.videoEnabled = videoEnabled == undefined ? true : videoEnabled;
|
this.videoEnabled = videoEnabled == undefined ? true : videoEnabled;
|
||||||
@@ -453,6 +487,7 @@ function TaoyaoClient(
|
|||||||
/** 关闭视频 */
|
/** 关闭视频 */
|
||||||
this.close = async function() {
|
this.close = async function() {
|
||||||
await this.video.close();
|
await this.video.close();
|
||||||
|
// TODO:释放连接
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
/** 设置媒体 */
|
/** 设置媒体 */
|
||||||
@@ -474,15 +509,21 @@ function TaoyaoClient(
|
|||||||
if(track.kind === 'video') {
|
if(track.kind === 'video') {
|
||||||
this.buildVideoTrack(track);
|
this.buildVideoTrack(track);
|
||||||
}
|
}
|
||||||
} else {
|
} else if(stream) {
|
||||||
let audioTrack = stream.getAudioTracks();
|
let audioTrack = stream.getAudioTracks();
|
||||||
let videoTrack = stream.getVideoTracks();
|
let videoTrack = stream.getVideoTracks();
|
||||||
|
// TODO:验证API试试修改媒体
|
||||||
|
// audioTrack.getSettings
|
||||||
|
// audioTrack.getCapabilities
|
||||||
|
// audioTrack.applyCapabilities
|
||||||
if(audioTrack && audioTrack.length) {
|
if(audioTrack && audioTrack.length) {
|
||||||
audioTrack.forEach(v => this.buildAudioTrack(v));
|
audioTrack.forEach(v => this.buildAudioTrack(v));
|
||||||
}
|
}
|
||||||
if(videoTrack && videoTrack.length) {
|
if(videoTrack && videoTrack.length) {
|
||||||
videoTrack.forEach(v => this.buildVideoTrack(v));
|
videoTrack.forEach(v => this.buildVideoTrack(v));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('无效媒体信息');
|
||||||
}
|
}
|
||||||
console.debug('设置媒体', this.video, this.stream, this.audioTrack, this.videoTrack);
|
console.debug('设置媒体', this.video, this.stream, this.audioTrack, this.videoTrack);
|
||||||
await this.load();
|
await this.load();
|
||||||
@@ -509,13 +550,53 @@ function TaoyaoClient(
|
|||||||
this.stream.addTrack(track);
|
this.stream.addTrack(track);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/** 打开媒体通道 */
|
||||||
|
this.openMediaChannel = function() {
|
||||||
|
if(this.shareMediaChannel) {
|
||||||
|
this.localMediaChannel = this.taoyao.localMediaChannel;
|
||||||
|
this.remoteMediaChannel = this.taoyao.remoteMediaChannel;
|
||||||
|
} else {
|
||||||
|
let self = this;
|
||||||
|
// 本地通道
|
||||||
|
let mediaChannel = new RTCPeerConnection(defaultRPCConfig);
|
||||||
|
self.taoyao.localClient.audioTrack.forEach(v => mediaChannel.addTrack(v, self.taoyao.localClient.stream));
|
||||||
|
self.taoyao.localClient.videoTrack.forEach(v => mediaChannel.addTrack(v, self.taoyao.localClient.stream));
|
||||||
|
mediaChannel.ontrack = function(e) {
|
||||||
|
console.debug('Mesh Media Track', self.sn, e);
|
||||||
|
let remote = self.taoyao.remoteClientFilter(self.sn);
|
||||||
|
remote.buildStream(remote.sn, e.streams[0], e.track);
|
||||||
|
};
|
||||||
|
mediaChannel.onicecandidate = function(e) {
|
||||||
|
// TODO:判断给谁
|
||||||
|
let to = self.taoyao.remoteClient.map(v => v.sn)[0];
|
||||||
|
if(!self.taoyao.checkCandidate(e.candidate)) {
|
||||||
|
console.debug('Send Mesh ICE Candidate Fail', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.debug('Send Mesh ICE Candidate', to, e);
|
||||||
|
self.taoyao.push(signalProtocol.buildProtocol(
|
||||||
|
signalProtocol.media.candidate,
|
||||||
|
{
|
||||||
|
to: to,
|
||||||
|
type: 'mesh',
|
||||||
|
candidate: e.candidate
|
||||||
|
}
|
||||||
|
));
|
||||||
|
};
|
||||||
|
this.localMediaChannel = mediaChannel;
|
||||||
|
this.remoteMediaChannel = mediaChannel;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
/** 桃夭 */
|
/** 桃夭 */
|
||||||
function Taoyao(
|
function Taoyao(
|
||||||
|
videoId,
|
||||||
webSocket,
|
webSocket,
|
||||||
localClientAudioEnabled,
|
localClientAudioEnabled,
|
||||||
localClientVideoEnabled
|
localClientVideoEnabled
|
||||||
) {
|
) {
|
||||||
|
/** 本地视频ID */
|
||||||
|
this.videoId = videoId;
|
||||||
/** WebRTC配置 */
|
/** WebRTC配置 */
|
||||||
this.webrtc = null;
|
this.webrtc = null;
|
||||||
/** WebSocket地址 */
|
/** WebSocket地址 */
|
||||||
@@ -538,6 +619,8 @@ function Taoyao(
|
|||||||
/** 远程终端 */
|
/** 远程终端 */
|
||||||
this.remoteClient = [];
|
this.remoteClient = [];
|
||||||
this.remoteMediaChannel = null;
|
this.remoteMediaChannel = null;
|
||||||
|
/** 是否共享媒体通道 */
|
||||||
|
this.shareMediaChannel = true;
|
||||||
/** 信令通道 */
|
/** 信令通道 */
|
||||||
this.signalChannel = null;
|
this.signalChannel = null;
|
||||||
/** 媒体配置 */
|
/** 媒体配置 */
|
||||||
@@ -551,6 +634,7 @@ function Taoyao(
|
|||||||
this.configWebrtc = function(config = {}) {
|
this.configWebrtc = function(config = {}) {
|
||||||
this.webrtc = config;
|
this.webrtc = config;
|
||||||
this.webSocket = this.webrtc.signal.address;
|
this.webSocket = this.webrtc.signal.address;
|
||||||
|
this.shareMediaChannel = this.webrtc.framework === 'MOON';
|
||||||
defaultRPCConfig.iceServers = this.webrtc.stun.map(v => ({'urls': v}));
|
defaultRPCConfig.iceServers = this.webrtc.stun.map(v => ({'urls': v}));
|
||||||
console.debug('WebRTC配置', this.webrtc, defaultRPCConfig);
|
console.debug('WebRTC配置', this.webrtc, defaultRPCConfig);
|
||||||
return this;
|
return this;
|
||||||
@@ -560,8 +644,8 @@ function Taoyao(
|
|||||||
signalChannel.taoyao = this;
|
signalChannel.taoyao = this;
|
||||||
this.signalChannel = signalChannel;
|
this.signalChannel = signalChannel;
|
||||||
// 不能直接this.push = this.signalChannel.push这样导致this对象错误
|
// 不能直接this.push = this.signalChannel.push这样导致this对象错误
|
||||||
this.push = function(data, callback) {
|
this.push = function(data, pushCallback) {
|
||||||
this.signalChannel.push(data, callback)
|
this.signalChannel.push(data, pushCallback);
|
||||||
};
|
};
|
||||||
return this.signalChannel.connect(this.webSocket, callback);
|
return this.signalChannel.connect(this.webSocket, callback);
|
||||||
};
|
};
|
||||||
@@ -607,11 +691,6 @@ function Taoyao(
|
|||||||
})
|
})
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
// 兼容旧版
|
|
||||||
// navigator.getUserMedia({
|
|
||||||
// audio: self.audioConfig,
|
|
||||||
// video: self.videoConfig
|
|
||||||
// }, resolve, reject);
|
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error('检查终端设备异常', e);
|
console.error('检查终端设备异常', e);
|
||||||
@@ -625,12 +704,17 @@ function Taoyao(
|
|||||||
};
|
};
|
||||||
/** 远程终端过滤 */
|
/** 远程终端过滤 */
|
||||||
this.remoteClientFilter = function(sn, autoBuild) {
|
this.remoteClientFilter = function(sn, autoBuild) {
|
||||||
|
if(sn === signalConfig.sn) {
|
||||||
|
console.warn('远程终端等于本地终端');
|
||||||
|
return this.localClient;
|
||||||
|
}
|
||||||
let array = this.remoteClient.filter(v => v.sn === sn);
|
let array = this.remoteClient.filter(v => v.sn === sn);
|
||||||
let remote = null;
|
let remote = null;
|
||||||
if(array.length > 0) {
|
if(array.length > 0) {
|
||||||
remote = array[0];
|
remote = array[0];
|
||||||
} else if(autoBuild) {
|
} else if(autoBuild) {
|
||||||
remote = new TaoyaoClient(sn);
|
remote = new TaoyaoClient(this, sn, this.shareMediaChannel);
|
||||||
|
remote.openMediaChannel();
|
||||||
this.remoteClient.push(remote);
|
this.remoteClient.push(remote);
|
||||||
}
|
}
|
||||||
return remote;
|
return remote;
|
||||||
@@ -647,8 +731,9 @@ function Taoyao(
|
|||||||
this.buildMediaChannel = async function(localVideoId, stream) {
|
this.buildMediaChannel = async function(localVideoId, stream) {
|
||||||
let self = this;
|
let self = this;
|
||||||
// 本地视频
|
// 本地视频
|
||||||
this.localClient = new TaoyaoClient(signalConfig.sn, this.localClientAudioEnabled, this.localClientVideoEnabled);
|
this.localClient = new TaoyaoClient(this, signalConfig.sn, this.shareMediaChannel, this.localClientAudioEnabled, this.localClientVideoEnabled);
|
||||||
await this.localClient.buildStream(localVideoId, stream);
|
await this.localClient.buildStream(localVideoId, stream);
|
||||||
|
if(this.shareMediaChannel) {
|
||||||
// 本地通道
|
// 本地通道
|
||||||
this.localMediaChannel = new RTCPeerConnection(defaultRPCConfig);
|
this.localMediaChannel = new RTCPeerConnection(defaultRPCConfig);
|
||||||
this.localClient.audioTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
this.localClient.audioTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
||||||
@@ -656,20 +741,6 @@ function Taoyao(
|
|||||||
this.localMediaChannel.ontrack = function(e) {
|
this.localMediaChannel.ontrack = function(e) {
|
||||||
console.debug('Local Media Track', e);
|
console.debug('Local Media Track', e);
|
||||||
};
|
};
|
||||||
this.localMediaChannel.ondatachannel = function(channel) {
|
|
||||||
channel.onopen = function() {
|
|
||||||
console.debug('Local DataChannel Open');
|
|
||||||
}
|
|
||||||
channel.onmessage = function(data) {
|
|
||||||
console.debug('Local DataChannel Message', data);
|
|
||||||
}
|
|
||||||
channel.onclose = function() {
|
|
||||||
console.debug('Local DataChannel Close');
|
|
||||||
}
|
|
||||||
channel.onerror = function(e) {
|
|
||||||
console.debug('Local DataChannel Error', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.localMediaChannel.onicecandidate = function(e) {
|
this.localMediaChannel.onicecandidate = function(e) {
|
||||||
// TODO:判断给谁
|
// TODO:判断给谁
|
||||||
let to = self.remoteClient.map(v => v.sn)[0];
|
let to = self.remoteClient.map(v => v.sn)[0];
|
||||||
@@ -695,20 +766,6 @@ function Taoyao(
|
|||||||
let remote = self.remoteClient[0];
|
let remote = self.remoteClient[0];
|
||||||
remote.buildStream(remote.sn, e.streams[0], e.track);
|
remote.buildStream(remote.sn, e.streams[0], e.track);
|
||||||
};
|
};
|
||||||
this.remoteMediaChannel.ondatachannel = function(channel) {
|
|
||||||
channel.onopen = function() {
|
|
||||||
console.debug('Remote DataChannel Open');
|
|
||||||
}
|
|
||||||
channel.onmessage = function(data) {
|
|
||||||
console.debug('Remote DataChannel Message', data);
|
|
||||||
}
|
|
||||||
channel.onclose = function() {
|
|
||||||
console.debug('Remote DataChannel Close');
|
|
||||||
}
|
|
||||||
channel.onerror = function(e) {
|
|
||||||
console.debug('Remote DataChannel Error', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.remoteMediaChannel.onicecandidate = function(e) {
|
this.remoteMediaChannel.onicecandidate = function(e) {
|
||||||
// TODO:判断给谁
|
// TODO:判断给谁
|
||||||
let to = self.remoteClient.map(v => v.sn)[0];
|
let to = self.remoteClient.map(v => v.sn)[0];
|
||||||
@@ -726,7 +783,8 @@ function Taoyao(
|
|||||||
}
|
}
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
console.debug('打开媒体通道', this.localMediaChannel, this.remoteMediaChannel);
|
console.debug('打开共享媒体通道', this.localMediaChannel, this.remoteMediaChannel);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
/** 校验candidate */
|
/** 校验candidate */
|
||||||
|
|||||||
@@ -51,38 +51,27 @@
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
let self = this;
|
let self = this;
|
||||||
this.taoyao = new Taoyao();
|
this.taoyao = new Taoyao('local');
|
||||||
this.remoteClient = this.taoyao.remoteClient;
|
this.remoteClient = this.taoyao.remoteClient;
|
||||||
// 打开信令通道
|
// 打开信令通道
|
||||||
this.taoyao
|
this.taoyao
|
||||||
.buildChannel(self.callback)
|
.buildChannel(self.callback)
|
||||||
.then(e => console.debug('信令通道连接成功'));
|
.then(e => console.debug('信令通道连接成功'));
|
||||||
// 打开媒体通道
|
|
||||||
this.taoyao.buildLocalMedia()
|
|
||||||
.then(stream => {
|
|
||||||
self.taoyao.buildMediaChannel('local', stream);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.error('打开终端媒体失败', e);
|
|
||||||
// 方便相同电脑测试
|
|
||||||
self.taoyao.buildMediaChannel('local', null);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 信令回调:返回true表示已经处理
|
// 信令回调:true表示已经处理;false表示没有处理;
|
||||||
callback: function(data) {
|
callback: function(data) {
|
||||||
let self = this;
|
let self = this;
|
||||||
switch(data.header.pid) {
|
switch(data.header.pid) {
|
||||||
case signalProtocol.client.config:
|
|
||||||
// 如果需要下发配置生效需要在此打开媒体通道
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
// 创建会议
|
// 创建会议
|
||||||
create: function(event) {
|
create: function(event) {
|
||||||
|
let sn = prompt('你的账号', signalConfig.sn);
|
||||||
|
signalConfig.sn = sn;
|
||||||
let self = this;
|
let self = this;
|
||||||
this.taoyao.meetingCreate(data => {
|
this.taoyao.meetingCreate(data => {
|
||||||
self.taoyao.meetingEnter(data.body.id);
|
self.taoyao.meetingEnter(data.body.id);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.acgist.taoyao.test.annotation;
|
package com.acgist.taoyao.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.acgist.taoyao.test.annotation;
|
package com.acgist.taoyao.annotation;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.acgist.taoyao.test.annotation;
|
package com.acgist.taoyao.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
@@ -3,9 +3,9 @@ package com.acgist.taoyao.boot.service;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.annotation.CostedTest;
|
||||||
|
import com.acgist.taoyao.annotation.TaoyaoTest;
|
||||||
import com.acgist.taoyao.main.TaoyaoApplication;
|
import com.acgist.taoyao.main.TaoyaoApplication;
|
||||||
import com.acgist.taoyao.test.annotation.TaoyaoTest;
|
|
||||||
import com.acgist.taoyao.test.annotation.CostedTest;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import java.util.concurrent.CountDownLatch;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.annotation.TaoyaoTest;
|
||||||
import com.acgist.taoyao.main.TaoyaoApplication;
|
import com.acgist.taoyao.main.TaoyaoApplication;
|
||||||
import com.acgist.taoyao.test.annotation.TaoyaoTest;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import java.util.Map;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.annotation.TaoyaoTest;
|
||||||
import com.acgist.taoyao.main.TaoyaoApplication;
|
import com.acgist.taoyao.main.TaoyaoApplication;
|
||||||
import com.acgist.taoyao.signal.protocol.platform.ScriptProtocol;
|
import com.acgist.taoyao.signal.protocol.platform.ScriptProtocol;
|
||||||
import com.acgist.taoyao.test.annotation.TaoyaoTest;
|
|
||||||
|
|
||||||
@TaoyaoTest(classes = TaoyaoApplication.class)
|
@TaoyaoTest(classes = TaoyaoApplication.class)
|
||||||
class ScriptProtocolTest {
|
class ScriptProtocolTest {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.annotation.TaoyaoTest;
|
||||||
import com.acgist.taoyao.main.TaoyaoApplication;
|
import com.acgist.taoyao.main.TaoyaoApplication;
|
||||||
import com.acgist.taoyao.signal.protocol.platform.ShutdownProtocol;
|
import com.acgist.taoyao.signal.protocol.platform.ShutdownProtocol;
|
||||||
import com.acgist.taoyao.test.annotation.TaoyaoTest;
|
|
||||||
|
|
||||||
@TaoyaoTest(classes = TaoyaoApplication.class)
|
@TaoyaoTest(classes = TaoyaoApplication.class)
|
||||||
class ShutdownProtocolTest {
|
class ShutdownProtocolTest {
|
||||||
|
|||||||
@@ -395,15 +395,15 @@ Moon模式有效
|
|||||||
|
|
||||||
### Offer信令(5997)
|
### Offer信令(5997)
|
||||||
|
|
||||||
Offer
|
WebRTC信令:`Offer`
|
||||||
|
|
||||||
### Answer信令(5998)
|
### Answer信令(5998)
|
||||||
|
|
||||||
Answer
|
WebRTC信令:`Answer`
|
||||||
|
|
||||||
### 候选信令(5999)
|
### 候选信令(5999)
|
||||||
|
|
||||||
IceCandidate
|
WebRTC信令:`IceCandidate`主要用来解决`NAT`问题
|
||||||
|
|
||||||
## 测试
|
## 测试
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>com.acgist</groupId>
|
|
||||||
<artifactId>taoyao</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>taoyao-test</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<name>taoyao-test</name>
|
|
||||||
<description>测试:测试工具</description>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| HTTPS / WSS | | SCTP | SRTP / SRTCP |
|
| HTTPS / WSS | | SCTP | SRTP / SRTCP |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ICE / SDP / SIP +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ICE / SDP / SIP +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| TLS | | DTLS |
|
| TLS | | |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-++
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ DTLS +-+-+-+-+-+-+-+-+-+
|
||||||
| HTTP / WS | STUN / TURN | | RTP / RTCP |
|
| HTTP / WS | NAT / STUN / TURN | | RTP / RTCP |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| TCP | UDP |
|
| TCP | UDP |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|||||||
Reference in New Issue
Block a user