[*] mesh多人

This commit is contained in:
acgist
2022-12-03 14:02:11 +08:00
parent 18cc4e536d
commit 7f5ee58fbb
20 changed files with 197 additions and 218 deletions

View File

@@ -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)
* 提供混音、变声、美颜、录制等等媒体功能 * 提供混音、变声、美颜、录制等等媒体功能
* 终端推送给服务端最高质量媒体,再由服务端根据订阅终端按配置分流。 * 终端推送给服务端最高质量媒体,再由服务端根据订阅终端按配置分流。
* 终端和服务器之间建立两个媒体连接,一个本地媒体,一个远程媒体。

View File

@@ -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
View File

@@ -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>

View File

@@ -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
中庭地白树栖鸦,冷露无声湿桂花。
今夜月明人尽望,不知秋思落谁家。

View File

@@ -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);

View File

@@ -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>

View File

@@ -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

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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`问题
## 测试 ## 测试

View File

@@ -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>

View File

@@ -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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+