[*] mesh
This commit is contained in:
@@ -1,92 +0,0 @@
|
|||||||
# FFmpeg
|
|
||||||
|
|
||||||
默认使用`ffmpeg-platform`所以不用安装,如果使用本地FFmpeg需要自己安装。
|
|
||||||
|
|
||||||
## FFmpeg
|
|
||||||
|
|
||||||
```
|
|
||||||
# nasm
|
|
||||||
wget https://www.nasm.us/pub/nasm/releasebuilds/2.14/nasm-2.14.tar.gz
|
|
||||||
tar zxvf nasm-2.14.tar.gz
|
|
||||||
cd nasm-2.14
|
|
||||||
./configure --prefix=/usr/local/nasm
|
|
||||||
make -j && make install
|
|
||||||
# 环境变量
|
|
||||||
vim /etc/profile
|
|
||||||
export PATH=$PATH:/usr/local/nasm/bin
|
|
||||||
source /etc/profile
|
|
||||||
|
|
||||||
# yasm
|
|
||||||
wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
|
|
||||||
tar zxvf yasm-1.3.0.tar.gz
|
|
||||||
cd yasm-1.3.0
|
|
||||||
./configure --prefix=/usr/local/yasm
|
|
||||||
make -j && make install
|
|
||||||
# 环境变量
|
|
||||||
vim /etc/profile
|
|
||||||
export PATH=$PATH:/usr/local/yasm/bin
|
|
||||||
source /etc/profile
|
|
||||||
|
|
||||||
# x264
|
|
||||||
git clone https://code.videolan.org/videolan/x264.git
|
|
||||||
cd x264
|
|
||||||
./configure --prefix=/usr/local/x264 --libdir=/usr/local/lib --includedir=/usr/local/include --enable-shared --enable-static
|
|
||||||
make -j && make install
|
|
||||||
# 环境变量
|
|
||||||
vim /etc/profile
|
|
||||||
export PATH=$PATH:/usr/local/x264/bin
|
|
||||||
source /etc/profile
|
|
||||||
|
|
||||||
# 编码解码
|
|
||||||
# acc
|
|
||||||
https://github.com/mstorsjo/fdk-aac.git
|
|
||||||
--enable-libfdk_aac
|
|
||||||
# vpx
|
|
||||||
https://github.com/webmproject/libvpx.git
|
|
||||||
--enable-libvpx
|
|
||||||
# x265
|
|
||||||
https://bitbucket.org/multicoreware/x265
|
|
||||||
--enable-libx265
|
|
||||||
# opus
|
|
||||||
https://archive.mozilla.org/pub/opus/opus-1.2.1.tar.gz
|
|
||||||
--enable-libopus
|
|
||||||
|
|
||||||
# ffmpeg
|
|
||||||
wget http://www.ffmpeg.org/releases/ffmpeg-4.3.1.tar.xz
|
|
||||||
tar xvJf ffmpeg-4.3.1.tar.xz
|
|
||||||
cd ffmpeg-4.3.1
|
|
||||||
./configure --prefix=/usr/local/ffmpeg --enable-gpl --enable-shared --enable-libx264
|
|
||||||
# --enable-cuda --enable-cuvid --enable-nvenc --nvcc=/usr/local/cuda-11.0/bin/nvcc
|
|
||||||
make -j && make install
|
|
||||||
# 环境变量
|
|
||||||
vim /etc/profile
|
|
||||||
export PATH=$PATH:/usr/local/ffmpeg/bin
|
|
||||||
source /etc/profile
|
|
||||||
|
|
||||||
# lib
|
|
||||||
vim /etc/ld.so.conf
|
|
||||||
/usr/local/x264/lib/
|
|
||||||
/usr/local/ffmpeg/lib/
|
|
||||||
ldconfig
|
|
||||||
|
|
||||||
# 查看版本
|
|
||||||
ffmpeg -version
|
|
||||||
# 查看编解码
|
|
||||||
ffmpeg -codecs
|
|
||||||
# 格式化文件
|
|
||||||
ffmpeg -y -i source.mkv -c copy target.mp4
|
|
||||||
# 查看文件格式
|
|
||||||
ffprobe -v error -show_streams -print_format json source.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
## GPU
|
|
||||||
|
|
||||||
```
|
|
||||||
驱动
|
|
||||||
# cuda
|
|
||||||
https://developer.nvidia.com/cuda-downloads
|
|
||||||
# nv-codec-headers
|
|
||||||
https://git.videolan.org/git/ffmpeg/nv-codec-headers.git
|
|
||||||
# 验证
|
|
||||||
nvidia-smi
|
|
||||||
```
|
|
||||||
44
pom.xml
44
pom.xml
@@ -24,7 +24,6 @@
|
|||||||
<!-- 版本 -->
|
<!-- 版本 -->
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<javacv.version>1.5.8</javacv.version>
|
<javacv.version>1.5.8</javacv.version>
|
||||||
<ffmpeg.version>5.1.2</ffmpeg.version>
|
|
||||||
<lombok.version>1.18.24</lombok.version>
|
<lombok.version>1.18.24</lombok.version>
|
||||||
<kurento.version>6.18.0</kurento.version>
|
<kurento.version>6.18.0</kurento.version>
|
||||||
<springdoc.version>2.0.0-RC1</springdoc.version>
|
<springdoc.version>2.0.0-RC1</springdoc.version>
|
||||||
@@ -189,49 +188,6 @@
|
|||||||
<artifactId>kurento-client</artifactId>
|
<artifactId>kurento-client</artifactId>
|
||||||
<version>${kurento.version}</version>
|
<version>${kurento.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--
|
|
||||||
媒体:FFmpeg
|
|
||||||
android-arm
|
|
||||||
android-arm64
|
|
||||||
android-x86
|
|
||||||
android-x86_64
|
|
||||||
linux-arm64
|
|
||||||
linux-armhf
|
|
||||||
linux-ppc64le
|
|
||||||
linux-x86
|
|
||||||
linux-x86_64
|
|
||||||
macosx-arm64
|
|
||||||
macosx-x86_64
|
|
||||||
windows-x86
|
|
||||||
windows-x86_64
|
|
||||||
-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bytedeco</groupId>
|
|
||||||
<artifactId>ffmpeg-platform</artifactId>
|
|
||||||
<version>${ffmpeg.version}-${javacv.version}</version>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.bytedeco</groupId>
|
|
||||||
<artifactId>ffmpeg</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.bytedeco</groupId>
|
|
||||||
<artifactId>javacpp</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bytedeco</groupId>
|
|
||||||
<artifactId>ffmpeg</artifactId>
|
|
||||||
<version>${ffmpeg.version}-${javacv.version}</version>
|
|
||||||
<classifier>${javacv.os.version}</classifier>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bytedeco</groupId>
|
|
||||||
<artifactId>javacpp</artifactId>
|
|
||||||
<version>${javacv.version}</version>
|
|
||||||
<classifier>${javacv.os.version}</classifier>
|
|
||||||
</dependency>
|
|
||||||
<!-- 集合工具 -->
|
<!-- 集合工具 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ public class MediaAudioProperties {
|
|||||||
*/
|
*/
|
||||||
public enum Format {
|
public enum Format {
|
||||||
|
|
||||||
/**
|
|
||||||
* ACC
|
|
||||||
*/
|
|
||||||
ACC,
|
|
||||||
/**
|
/**
|
||||||
* PCM
|
* PCM
|
||||||
*/
|
*/
|
||||||
@@ -41,16 +37,16 @@ public class MediaAudioProperties {
|
|||||||
*/
|
*/
|
||||||
@Schema(title = "格式", description = "格式")
|
@Schema(title = "格式", description = "格式")
|
||||||
private Format format;
|
private Format format;
|
||||||
|
/**
|
||||||
|
* 采样数
|
||||||
|
*/
|
||||||
|
@Schema(title = "采样数", description = "采样数", example = "16")
|
||||||
|
private Integer sampleSize;
|
||||||
/**
|
/**
|
||||||
* 采样率
|
* 采样率
|
||||||
* 8000|16000|32000|48000
|
* 8000|16000|32000|48000
|
||||||
*/
|
*/
|
||||||
@Schema(title = "采样率", description = "采样率", example = "8000|16000|32000|48000")
|
@Schema(title = "采样率", description = "采样率", example = "8000|16000|32000|48000")
|
||||||
private Integer samplerate;
|
private Integer sampleRate;
|
||||||
/**
|
|
||||||
* 采样数
|
|
||||||
*/
|
|
||||||
@Schema(title = "采样数", description = "采样数", example = "16")
|
|
||||||
private Integer samplesize;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class MediaVideoProperties {
|
|||||||
* 帧率(流畅)
|
* 帧率(流畅)
|
||||||
*/
|
*/
|
||||||
@Schema(title = "帧率", description = "帧率影响流程", example = "20|24|30|60")
|
@Schema(title = "帧率", description = "帧率影响流程", example = "20|24|30|60")
|
||||||
private Integer framerate;
|
private Integer frameRate;
|
||||||
/**
|
/**
|
||||||
* 分辨率(画面大小)
|
* 分辨率(画面大小)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -37,20 +37,6 @@
|
|||||||
<groupId>com.acgist</groupId>
|
<groupId>com.acgist</groupId>
|
||||||
<artifactId>taoyao-webrtc-kurento</artifactId>
|
<artifactId>taoyao-webrtc-kurento</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.bytedeco</groupId>
|
|
||||||
<artifactId>ffmpeg-platform</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bytedeco</groupId>
|
|
||||||
<artifactId>ffmpeg</artifactId>
|
|
||||||
<classifier>${javacv.os.version}</classifier>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bytedeco</groupId>
|
|
||||||
<artifactId>javacpp</artifactId>
|
|
||||||
<classifier>${javacv.os.version}</classifier>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -21,6 +21,16 @@ public class Meeting {
|
|||||||
*/
|
*/
|
||||||
@Schema(title = "会议标识", description = "会议标识")
|
@Schema(title = "会议标识", description = "会议标识")
|
||||||
private String id;
|
private String id;
|
||||||
|
/**
|
||||||
|
* 会议名称
|
||||||
|
*/
|
||||||
|
@Schema(title = "会议名称", description = "会议名称")
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 会议密码
|
||||||
|
*/
|
||||||
|
@Schema(title = "会议密码", description = "会议密码")
|
||||||
|
private String password;
|
||||||
/**
|
/**
|
||||||
* 终端会话标识列表
|
* 终端会话标识列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ 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(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);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class MeetingEnterListener extends MeetingListenerAdapter<MeetingEnterEve
|
|||||||
"id", meeting.getId(),
|
"id", meeting.getId(),
|
||||||
"sn", sn
|
"sn", sn
|
||||||
));
|
));
|
||||||
|
// TODO:返回房间列表
|
||||||
meeting.getSns().stream()
|
meeting.getSns().stream()
|
||||||
.filter(v -> !sn.equals(v))
|
.filter(v -> !sn.equals(v))
|
||||||
.forEach(v -> this.clientSessionManager.unicast(v, message));
|
.forEach(v -> this.clientSessionManager.unicast(v, message));
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ taoyao:
|
|||||||
media:
|
media:
|
||||||
audio:
|
audio:
|
||||||
format: OPUS
|
format: OPUS
|
||||||
samplesize: 16
|
sample-size: 16
|
||||||
samplerate: 32000
|
sample-rate: 32000
|
||||||
video:
|
video:
|
||||||
format: H264
|
format: H264
|
||||||
bitrate: 1200
|
bitrate: 1200
|
||||||
framerate: 24
|
frame-rate: 24
|
||||||
resolution: 1280*760
|
resolution: 1280*760
|
||||||
quality: high|standard|quick
|
quality: high|standard|quick
|
||||||
# WebRTC配置
|
# WebRTC配置
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
/** 桃夭WebRTC终端核心功能 */
|
/**
|
||||||
|
* 桃夭WebRTC终端核心功能
|
||||||
|
*
|
||||||
|
* 代码注意:
|
||||||
|
* 1. undefined判断使用两个等号,其他情况使用三个。
|
||||||
|
*/
|
||||||
/** 兼容 */
|
/** 兼容 */
|
||||||
const RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
|
const RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
|
||||||
const RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
|
const RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
|
||||||
const RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
|
const RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
|
||||||
/** 默认音频配置 */
|
/** 默认音频配置 */
|
||||||
const defaultAudioConfig = {
|
const defaultAudioConfig = {
|
||||||
|
// 设备
|
||||||
|
// deviceId : '',
|
||||||
// 音量:0~1
|
// 音量:0~1
|
||||||
volume: 0.5,
|
volume: 0.5,
|
||||||
// 延迟大小(单位毫秒):500毫秒以内较好
|
// 延迟大小(单位毫秒):500毫秒以内较好
|
||||||
latency: 0.4,
|
latency: 0.4,
|
||||||
// 设备
|
|
||||||
// deviceId : '',
|
|
||||||
// 采样率:8000|16000|32000|48000
|
|
||||||
sampleRate: 48000,
|
|
||||||
// 采样数:16
|
// 采样数:16
|
||||||
sampleSize: 16,
|
sampleSize: 16,
|
||||||
|
// 采样率:8000|16000|32000|48000
|
||||||
|
sampleRate: 32000,
|
||||||
// 声道数量:1|2
|
// 声道数量:1|2
|
||||||
channelCount : 1,
|
channelCount : 1,
|
||||||
// 是否开启自动增益:true|false
|
// 是否开启自动增益:true|false
|
||||||
@@ -28,16 +33,14 @@ const defaultAudioConfig = {
|
|||||||
};
|
};
|
||||||
/** 默认视频配置 */
|
/** 默认视频配置 */
|
||||||
const defaultVideoConfig = {
|
const defaultVideoConfig = {
|
||||||
|
// 设备
|
||||||
|
// deviceId: '',
|
||||||
// 宽度
|
// 宽度
|
||||||
width: 1280,
|
width: 1280,
|
||||||
// 高度
|
// 高度
|
||||||
height: 720,
|
height: 720,
|
||||||
// 设备
|
|
||||||
// deviceId: '',
|
|
||||||
// 帧率
|
// 帧率
|
||||||
frameRate: 24,
|
frameRate: 24,
|
||||||
// 裁切
|
|
||||||
// resizeMode: '',
|
|
||||||
// 选摄像头:user|left|right|environment
|
// 选摄像头:user|left|right|environment
|
||||||
facingMode: 'environment'
|
facingMode: 'environment'
|
||||||
}
|
}
|
||||||
@@ -57,7 +60,7 @@ const defaultRPCConfig = {
|
|||||||
/** 信令配置 */
|
/** 信令配置 */
|
||||||
const signalConfig = {
|
const signalConfig = {
|
||||||
/** 当前终端SN */
|
/** 当前终端SN */
|
||||||
sn: localStorage.getItem('taoyao.sn', 'taoyao'),
|
sn: localStorage.getItem('taoyao.sn') || 'taoyao',
|
||||||
/** 当前版本 */
|
/** 当前版本 */
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
// 信令授权
|
// 信令授权
|
||||||
@@ -76,7 +79,11 @@ const signalProtocol = {
|
|||||||
/** 订阅 */
|
/** 订阅 */
|
||||||
subscribe: 5002,
|
subscribe: 5002,
|
||||||
/** 候选 */
|
/** 候选 */
|
||||||
candidate: 5004
|
offer: 5997,
|
||||||
|
/** Answer */
|
||||||
|
answer: 5998,
|
||||||
|
/** 候选 */
|
||||||
|
candidate: 5999
|
||||||
},
|
},
|
||||||
/** 终端信令 */
|
/** 终端信令 */
|
||||||
client: {
|
client: {
|
||||||
@@ -303,6 +310,12 @@ const signalChannel = {
|
|||||||
case signalProtocol.media.subscribe:
|
case signalProtocol.media.subscribe:
|
||||||
this.defaultMediaSubscribe(data);
|
this.defaultMediaSubscribe(data);
|
||||||
break;
|
break;
|
||||||
|
case signalProtocol.media.offer:
|
||||||
|
this.defaultMediaOffer(data);
|
||||||
|
break;
|
||||||
|
case signalProtocol.media.answer:
|
||||||
|
this.defaultMediaAnswer(data);
|
||||||
|
break;
|
||||||
case signalProtocol.media.candidate:
|
case signalProtocol.media.candidate:
|
||||||
this.defaultMediaCandidate(data);
|
this.defaultMediaCandidate(data);
|
||||||
break;
|
break;
|
||||||
@@ -329,7 +342,7 @@ const signalChannel = {
|
|||||||
/** 终端默认回调 */
|
/** 终端默认回调 */
|
||||||
defaultClientConfig: function(data) {
|
defaultClientConfig: function(data) {
|
||||||
this.taoyao
|
this.taoyao
|
||||||
.configMedia(data.body.media)
|
.configMedia(data.body.media.audio, data.body.media.video)
|
||||||
.configWebrtc(data.body.webrtc);
|
.configWebrtc(data.body.webrtc);
|
||||||
},
|
},
|
||||||
defaultClientReboot: function(data) {
|
defaultClientReboot: function(data) {
|
||||||
@@ -338,52 +351,71 @@ const signalChannel = {
|
|||||||
},
|
},
|
||||||
/** 默认媒体回调 */
|
/** 默认媒体回调 */
|
||||||
defaultMediaPublish: function(data) {
|
defaultMediaPublish: function(data) {
|
||||||
this.taoyao.localMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body));
|
|
||||||
},
|
},
|
||||||
defaultMediaSubscribe: function(data) {
|
defaultMediaSubscribe: function(data) {
|
||||||
let self = this;
|
let self = this;
|
||||||
const from = data.body.from;
|
const from = data.body.from;
|
||||||
let remote = this.taoyao.remoteClientFilter(from);
|
this.taoyao.remoteClientFilter(from, true);
|
||||||
if(!remote) {
|
self.taoyao.localMediaChannel.createOffer().then(description => {
|
||||||
remote = new TaoyaoClient(from);
|
console.debug('Local Create Offer', description);
|
||||||
this.taoyao.remoteClient.push(remote);
|
self.taoyao.localMediaChannel.setLocalDescription(description);
|
||||||
}
|
|
||||||
self.taoyao.remoteMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body));
|
|
||||||
self.taoyao.remoteMediaChannel.createAnswer().then(description => {
|
|
||||||
console.debug('Local Create Answer', description);
|
|
||||||
self.taoyao.remoteMediaChannel.setLocalDescription(description);
|
|
||||||
self.push(signalProtocol.buildProtocol(
|
self.push(signalProtocol.buildProtocol(
|
||||||
signalProtocol.media.publish,
|
signalProtocol.media.offer,
|
||||||
{
|
{
|
||||||
to: from,
|
to: from,
|
||||||
sdp: description.sdp,
|
sdp: {
|
||||||
type: description.type
|
sdp: description.sdp,
|
||||||
|
type: description.type
|
||||||
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
defaultMediaOffer: function(data) {
|
||||||
|
let self = this;
|
||||||
|
const from = data.body.from;
|
||||||
|
this.taoyao.remoteClientFilter(from, true);
|
||||||
|
self.taoyao.remoteMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body.sdp));
|
||||||
|
self.taoyao.remoteMediaChannel.createAnswer().then(description => {
|
||||||
|
console.debug('Remote Create Answer', description);
|
||||||
|
self.taoyao.remoteMediaChannel.setLocalDescription(description);
|
||||||
|
self.push(signalProtocol.buildProtocol(
|
||||||
|
signalProtocol.media.answer,
|
||||||
|
{
|
||||||
|
to: from,
|
||||||
|
sdp: {
|
||||||
|
sdp: description.sdp,
|
||||||
|
type: description.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
defaultMediaAnswer: function(data) {
|
||||||
|
this.taoyao.localMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body.sdp));
|
||||||
|
},
|
||||||
defaultMediaCandidate: function(data) {
|
defaultMediaCandidate: function(data) {
|
||||||
if(
|
if(!this.taoyao.checkCandidate(data.body.candidate)) {
|
||||||
!data.body.candidate ||
|
console.debug('候选缺失要素', data);
|
||||||
!data.body.candidate.candidate ||
|
|
||||||
!data.body.candidate.sdpMid ||
|
|
||||||
!data.body.candidate.sdpMLineIndex
|
|
||||||
) {
|
|
||||||
console.warn('候选缺失要素', data);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let candidate = new RTCIceCandidate(data.body.candidate);
|
console.debug('Set ICE Candidate', this.taoyao.remoteMediaChannel);
|
||||||
this.taoyao.remoteMediaChannel.addIceCandidate(candidate);
|
if(data.body.type === 'local') {
|
||||||
|
this.taoyao.remoteMediaChannel.addIceCandidate(new RTCIceCandidate(data.body.candidate));
|
||||||
|
} else {
|
||||||
|
this.taoyao.localMediaChannel.addIceCandidate(new RTCIceCandidate(data.body.candidate));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/** 会议默认回调 */
|
/** 会议默认回调 */
|
||||||
defaultMeetingEnter: function(data) {
|
defaultMeetingEnter: function(data) {
|
||||||
this.taoyao
|
this.taoyao.mediaSubscribe(data.body.sn);
|
||||||
.mediaSubscribe(data.body.sn);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/** 终端 */
|
/** 终端 */
|
||||||
function TaoyaoClient(
|
function TaoyaoClient(
|
||||||
sn
|
sn,
|
||||||
|
audioEnabled,
|
||||||
|
videoEnabled
|
||||||
) {
|
) {
|
||||||
/** 终端标识 */
|
/** 终端标识 */
|
||||||
this.sn = sn;
|
this.sn = sn;
|
||||||
@@ -391,12 +423,15 @@ function TaoyaoClient(
|
|||||||
this.video = null;
|
this.video = null;
|
||||||
/** 媒体信息 */
|
/** 媒体信息 */
|
||||||
this.stream = null;
|
this.stream = null;
|
||||||
this.audioTrack = null;
|
this.audioTrack = [];
|
||||||
this.videoTrack = null;
|
this.videoTrack = [];
|
||||||
/** 媒体状态 */
|
/** 媒体状态:是否含有 */
|
||||||
this.audioStatus = false;
|
this.audioStatus = false;
|
||||||
this.videoStatus = false;
|
this.videoStatus = false;
|
||||||
this.recordStatus = false;
|
this.recordStatus = false;
|
||||||
|
/** 媒体状态:是否播放 */
|
||||||
|
this.audioEnabled = audioEnabled == undefined ? true : audioEnabled;
|
||||||
|
this.videoEnabled = videoEnabled == undefined ? true : videoEnabled;
|
||||||
/** 重置 */
|
/** 重置 */
|
||||||
this.reset = function() {
|
this.reset = function() {
|
||||||
}
|
}
|
||||||
@@ -406,80 +441,80 @@ function TaoyaoClient(
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
/** 重新加载 */
|
/** 重新加载 */
|
||||||
this.load = function() {
|
this.load = async function() {
|
||||||
this.video.load();
|
await this.video.load();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/** 暂停视频 */
|
/** 暂停视频 */
|
||||||
this.pause = function() {
|
this.pause = async function() {
|
||||||
this.video.pause();
|
await this.video.pause();
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
/** 关闭视频 */
|
/** 关闭视频 */
|
||||||
this.close = function() {
|
this.close = async function() {
|
||||||
this.video.close();
|
await this.video.close();
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
/** 设置视频对象 */
|
/** 设置媒体 */
|
||||||
this.buildVideo = async function(videoId, stream, track) {
|
this.buildStream = async function(videoId, stream, track) {
|
||||||
if(!this.video) {
|
if(!this.video && videoId) {
|
||||||
this.video = document.getElementById(videoId);
|
this.video = document.getElementById(videoId);
|
||||||
}
|
}
|
||||||
await this.buildStream(stream, track);
|
if(!this.video) {
|
||||||
return this;
|
throw new Error('视频对象无效:' + videoId);
|
||||||
};
|
|
||||||
/** 设置媒体流 */
|
|
||||||
this.buildStream = async function(stream, track) {
|
|
||||||
if(stream) {
|
|
||||||
if(track) {
|
|
||||||
if(!this.stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
this.video.srcObject = this.stream;
|
|
||||||
}
|
|
||||||
// TODO:删除旧的
|
|
||||||
this.stream.addTrack(track);
|
|
||||||
if(track.kind === 'audio') {
|
|
||||||
this.audioTrack = track;
|
|
||||||
this.audioStatus = true;
|
|
||||||
} else if(track.kind === 'video') {
|
|
||||||
this.videoTrack = track;
|
|
||||||
this.videoStatus = true;
|
|
||||||
}
|
|
||||||
await this.video.load();
|
|
||||||
} else {
|
|
||||||
this.stream = stream;
|
|
||||||
this.video.srcObject = stream;
|
|
||||||
let audioTrack = stream.getAudioTracks();
|
|
||||||
let videoTrack = stream.getVideoTracks();
|
|
||||||
if(audioTrack && audioTrack.length) {
|
|
||||||
this.audioTrack = audioTrack;
|
|
||||||
this.audioStatus = true;
|
|
||||||
}
|
|
||||||
if(videoTrack && videoTrack.length) {
|
|
||||||
this.videoTrack = videoTrack;
|
|
||||||
this.videoStatus = true;
|
|
||||||
}
|
|
||||||
await this.video.load();
|
|
||||||
}
|
|
||||||
console.debug('设置媒体流', this.video, this.stream, this.audioTrack, this.videoTrack);
|
|
||||||
await this.play();
|
|
||||||
}
|
}
|
||||||
|
if(!this.stream) {
|
||||||
|
this.stream = new MediaStream();
|
||||||
|
this.video.srcObject = this.stream;
|
||||||
|
}
|
||||||
|
if(track) {
|
||||||
|
if(track.kind === 'audio') {
|
||||||
|
this.buildAudioTrack(track);
|
||||||
|
}
|
||||||
|
if(track.kind === 'video') {
|
||||||
|
this.buildVideoTrack(track);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let audioTrack = stream.getAudioTracks();
|
||||||
|
let videoTrack = stream.getVideoTracks();
|
||||||
|
if(audioTrack && audioTrack.length) {
|
||||||
|
audioTrack.forEach(v => this.buildAudioTrack(v));
|
||||||
|
}
|
||||||
|
if(videoTrack && videoTrack.length) {
|
||||||
|
videoTrack.forEach(v => this.buildVideoTrack(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.debug('设置媒体', this.video, this.stream, this.audioTrack, this.videoTrack);
|
||||||
|
await this.load();
|
||||||
|
await this.play();
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
/** 设置音频流 */
|
/** 设置音频流 */
|
||||||
this.buildAudioTrack = function() {
|
this.buildAudioTrack = function(track) {
|
||||||
// 关闭旧的
|
// 关闭旧的
|
||||||
// 创建新的
|
// 创建新的
|
||||||
|
this.audioStatus = true;
|
||||||
|
this.audioTrack.push(track);
|
||||||
|
if(this.audioEnabled) {
|
||||||
|
this.stream.addTrack(track);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
/** 设置视频流 */
|
/** 设置视频流 */
|
||||||
this.buildVideoTrack = function() {
|
this.buildVideoTrack = function(track) {
|
||||||
// 关闭旧的
|
// 关闭旧的
|
||||||
// 创建新的
|
// 创建新的
|
||||||
|
this.videoStatus = true;
|
||||||
|
this.videoTrack.push(track);
|
||||||
|
if(this.videoEnabled) {
|
||||||
|
this.stream.addTrack(track);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/** 桃夭 */
|
/** 桃夭 */
|
||||||
function Taoyao(
|
function Taoyao(
|
||||||
webSocket
|
webSocket,
|
||||||
|
localClientAudioEnabled,
|
||||||
|
localClientVideoEnabled
|
||||||
) {
|
) {
|
||||||
/** WebRTC配置 */
|
/** WebRTC配置 */
|
||||||
this.webrtc = null;
|
this.webrtc = null;
|
||||||
@@ -497,6 +532,9 @@ function Taoyao(
|
|||||||
/** 本地终端 */
|
/** 本地终端 */
|
||||||
this.localClient = null;
|
this.localClient = null;
|
||||||
this.localMediaChannel = null;
|
this.localMediaChannel = null;
|
||||||
|
/** 本地媒体状态 */
|
||||||
|
this.localClientAudioEnabled = localClientAudioEnabled == undefined ? false : localClientAudioEnabled;
|
||||||
|
this.localClientVideoEnabled = localClientVideoEnabled == undefined ? true : localClientVideoEnabled;
|
||||||
/** 远程终端 */
|
/** 远程终端 */
|
||||||
this.remoteClient = [];
|
this.remoteClient = [];
|
||||||
this.remoteMediaChannel = null;
|
this.remoteMediaChannel = null;
|
||||||
@@ -505,7 +543,7 @@ function Taoyao(
|
|||||||
/** 媒体配置 */
|
/** 媒体配置 */
|
||||||
this.configMedia = function(audio = {}, video = {}) {
|
this.configMedia = function(audio = {}, video = {}) {
|
||||||
this.audioConfig = {...this.audioConfig, ...audio};
|
this.audioConfig = {...this.audioConfig, ...audio};
|
||||||
this.videoCofnig = {...this.videoCofnig, ...video};
|
this.videoConfig = {...this.videoConfig, ...video};
|
||||||
console.debug('终端媒体配置', this.audioConfig, this.videoConfig);
|
console.debug('终端媒体配置', this.audioConfig, this.videoConfig);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@@ -586,13 +624,17 @@ function Taoyao(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
/** 远程终端过滤 */
|
/** 远程终端过滤 */
|
||||||
this.remoteClientFilter = function(sn) {
|
this.remoteClientFilter = function(sn, autoBuild) {
|
||||||
let array = this.remoteClient.filter(v => v.sn === sn);
|
let array = this.remoteClient.filter(v => v.sn === sn);
|
||||||
if(array.length <= 0) {
|
let remote = null;
|
||||||
return null;
|
if(array.length > 0) {
|
||||||
|
remote = array[0];
|
||||||
|
} else if(autoBuild) {
|
||||||
|
remote = new TaoyaoClient(sn);
|
||||||
|
this.remoteClient.push(remote);
|
||||||
}
|
}
|
||||||
return this.remoteClient.filter(v => v.sn === sn)[0];
|
return remote;
|
||||||
}
|
};
|
||||||
/** 关闭:关闭媒体 */
|
/** 关闭:关闭媒体 */
|
||||||
this.close = function() {
|
this.close = function() {
|
||||||
// TODO:释放资源
|
// TODO:释放资源
|
||||||
@@ -605,16 +647,12 @@ 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.localClient = new TaoyaoClient(signalConfig.sn, this.localClientAudioEnabled, this.localClientVideoEnabled);
|
||||||
await this.localClient.buildVideo(localVideoId, stream);
|
await this.localClient.buildStream(localVideoId, stream);
|
||||||
// 本地通道
|
// 本地通道
|
||||||
this.localMediaChannel = new RTCPeerConnection(defaultRPCConfig);
|
this.localMediaChannel = new RTCPeerConnection(defaultRPCConfig);
|
||||||
if(this.localClient.audioTrack) {
|
this.localClient.audioTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
||||||
this.localClient.audioTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
this.localClient.videoTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
||||||
}
|
|
||||||
if(this.localClient.videoTrack) {
|
|
||||||
this.localClient.videoTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
|
||||||
}
|
|
||||||
this.localMediaChannel.ontrack = function(e) {
|
this.localMediaChannel.ontrack = function(e) {
|
||||||
console.debug('Local Media Track', e);
|
console.debug('Local Media Track', e);
|
||||||
};
|
};
|
||||||
@@ -633,12 +671,18 @@ function Taoyao(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.localMediaChannel.onicecandidate = function(e) {
|
this.localMediaChannel.onicecandidate = function(e) {
|
||||||
let sns = self.remoteClient.filter(v => v.video === null).map(v => v.sn);
|
// TODO:判断给谁
|
||||||
console.debug('Local ICE Candidate', sns, e);
|
let to = self.remoteClient.map(v => v.sn)[0];
|
||||||
|
if(!self.checkCandidate(e.candidate)) {
|
||||||
|
console.debug('Send Local ICE Candidate Fail', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.debug('Send Local ICE Candidate', to, e);
|
||||||
self.push(signalProtocol.buildProtocol(
|
self.push(signalProtocol.buildProtocol(
|
||||||
signalProtocol.media.candidate,
|
signalProtocol.media.candidate,
|
||||||
{
|
{
|
||||||
sns: sns,
|
to: to,
|
||||||
|
type: 'local',
|
||||||
candidate: e.candidate
|
candidate: e.candidate
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@@ -649,7 +693,7 @@ function Taoyao(
|
|||||||
console.debug('Remote Media Track', e);
|
console.debug('Remote Media Track', e);
|
||||||
// TODO:匹配
|
// TODO:匹配
|
||||||
let remote = self.remoteClient[0];
|
let remote = self.remoteClient[0];
|
||||||
remote.buildVideo(remote.sn, e.streams[0], e.track);
|
remote.buildStream(remote.sn, e.streams[0], e.track);
|
||||||
};
|
};
|
||||||
this.remoteMediaChannel.ondatachannel = function(channel) {
|
this.remoteMediaChannel.ondatachannel = function(channel) {
|
||||||
channel.onopen = function() {
|
channel.onopen = function() {
|
||||||
@@ -666,12 +710,18 @@ function Taoyao(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.remoteMediaChannel.onicecandidate = function(e) {
|
this.remoteMediaChannel.onicecandidate = function(e) {
|
||||||
let sns = self.remoteClient.filter(v => v.video === null).map(v => v.sn);
|
// TODO:判断给谁
|
||||||
console.debug('Remote ICE Candidate', sns, e);
|
let to = self.remoteClient.map(v => v.sn)[0];
|
||||||
|
if(!self.checkCandidate(e.candidate)) {
|
||||||
|
console.debug('Send Remote ICE Candidate Fail', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.debug('Send Remote ICE Candidate', to, e);
|
||||||
self.push(signalProtocol.buildProtocol(
|
self.push(signalProtocol.buildProtocol(
|
||||||
signalProtocol.media.candidate,
|
signalProtocol.media.candidate,
|
||||||
{
|
{
|
||||||
sns: sns,
|
to: to,
|
||||||
|
type: 'remote',
|
||||||
candidate: e.candidate
|
candidate: e.candidate
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@@ -679,30 +729,30 @@ function Taoyao(
|
|||||||
console.debug('打开媒体通道', this.localMediaChannel, this.remoteMediaChannel);
|
console.debug('打开媒体通道', this.localMediaChannel, this.remoteMediaChannel);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
/** 校验candidate */
|
||||||
|
this.checkCandidate = function(candidate) {
|
||||||
|
if(
|
||||||
|
!candidate ||
|
||||||
|
!candidate.candidate ||
|
||||||
|
candidate.sdpMid === null ||
|
||||||
|
candidate.sdpMid === null ||
|
||||||
|
candidate.sdpMLineIndex === undefined ||
|
||||||
|
candidate.sdpMLineIndex === undefined
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
/** 媒体信令 */
|
/** 媒体信令 */
|
||||||
this.mediaSubscribe = function(sn, callback) {
|
this.mediaSubscribe = function(sn, callback) {
|
||||||
let self = this;
|
let self = this;
|
||||||
let remote = self.remoteClientFilter(sn);
|
self.remoteClientFilter(sn, true);
|
||||||
if(remote) {
|
self.push(signalProtocol.buildProtocol(
|
||||||
remote.reset();
|
signalProtocol.media.subscribe,
|
||||||
} else {
|
{
|
||||||
remote = new TaoyaoClient(sn);
|
to: sn
|
||||||
this.remoteClient.push(remote);
|
}
|
||||||
}
|
), callback);
|
||||||
if(self.webrtc.model === 'MESH') {
|
|
||||||
self.localMediaChannel.createOffer().then(description => {
|
|
||||||
console.debug('Local Create Offer', description);
|
|
||||||
self.localMediaChannel.setLocalDescription(description);
|
|
||||||
self.push(signalProtocol.buildProtocol(
|
|
||||||
signalProtocol.media.subscribe,
|
|
||||||
{
|
|
||||||
to: sn,
|
|
||||||
sdp: description.sdp,
|
|
||||||
type: description.type
|
|
||||||
}
|
|
||||||
), callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
/** 会议信令 */
|
/** 会议信令 */
|
||||||
this.meetingCreate = function(callback) {
|
this.meetingCreate = function(callback) {
|
||||||
|
|||||||
@@ -73,10 +73,11 @@
|
|||||||
methods: {
|
methods: {
|
||||||
// 信令回调:返回true表示已经处理
|
// 信令回调:返回true表示已经处理
|
||||||
callback: function(data) {
|
callback: function(data) {
|
||||||
|
let self = this;
|
||||||
switch(data.header.pid) {
|
switch(data.header.pid) {
|
||||||
case signalProtocol.client.heartbeat:
|
case signalProtocol.client.config:
|
||||||
// 心跳
|
// 如果需要下发配置生效需要在此打开媒体通道
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -373,10 +373,6 @@
|
|||||||
|
|
||||||
取消订阅终端媒体流(终端取消拉流)
|
取消订阅终端媒体流(终端取消拉流)
|
||||||
|
|
||||||
### 候选信令(5004)
|
|
||||||
|
|
||||||
IceCandidate
|
|
||||||
|
|
||||||
### 暂停信令(5004)
|
### 暂停信令(5004)
|
||||||
|
|
||||||
终端->服务端
|
终端->服务端
|
||||||
@@ -397,6 +393,18 @@ MCU/SFU模式有效
|
|||||||
|
|
||||||
配置订阅媒体:码率、帧率、分辨率等等
|
配置订阅媒体:码率、帧率、分辨率等等
|
||||||
|
|
||||||
|
### Offer信令(5997)
|
||||||
|
|
||||||
|
Offer
|
||||||
|
|
||||||
|
### Answer信令(5998)
|
||||||
|
|
||||||
|
Answer
|
||||||
|
|
||||||
|
### 候选信令(5999)
|
||||||
|
|
||||||
|
IceCandidate
|
||||||
|
|
||||||
## 测试
|
## 测试
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public abstract class ClientSessionAdapter<T extends AutoCloseable> implements C
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean timeout(long timeout) {
|
public boolean timeout(long timeout) {
|
||||||
return !this.authorized && System.currentTimeMillis() - this.time > timeout;
|
return System.currentTimeMillis() - this.time > timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class ClientSessionManager {
|
|||||||
|
|
||||||
@Scheduled(cron = "${taoyao.scheduled.session:0 * * * * ?}")
|
@Scheduled(cron = "${taoyao.scheduled.session:0 * * * * ?}")
|
||||||
public void scheduled() {
|
public void scheduled() {
|
||||||
this.closeTimeoutSession();
|
this.closeTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +65,9 @@ public class ClientSessionManager {
|
|||||||
* @param message 消息
|
* @param message 消息
|
||||||
*/
|
*/
|
||||||
public void unicast(String to, Message message) {
|
public void unicast(String to, Message message) {
|
||||||
this.sessions.stream().filter(v -> v.filterSn(to)).forEach(v -> {
|
this.sessions().stream()
|
||||||
|
.filter(v -> v.filterSn(to))
|
||||||
|
.forEach(v -> {
|
||||||
message.getHeader().setSn(v.sn());
|
message.getHeader().setSn(v.sn());
|
||||||
v.push(message);
|
v.push(message);
|
||||||
});
|
});
|
||||||
@@ -77,7 +79,7 @@ public class ClientSessionManager {
|
|||||||
* @param message 消息
|
* @param message 消息
|
||||||
*/
|
*/
|
||||||
public void broadcast(Message message) {
|
public void broadcast(Message message) {
|
||||||
this.sessions.forEach(v -> {
|
this.sessions().forEach(v -> {
|
||||||
message.getHeader().setSn(v.sn());
|
message.getHeader().setSn(v.sn());
|
||||||
v.push(message);
|
v.push(message);
|
||||||
});
|
});
|
||||||
@@ -90,7 +92,9 @@ public class ClientSessionManager {
|
|||||||
* @param message 消息
|
* @param message 消息
|
||||||
*/
|
*/
|
||||||
public void broadcast(String from, Message message) {
|
public void broadcast(String from, Message message) {
|
||||||
this.sessions.stream().filter(v -> v.filterNoneSn(from)).forEach(v -> {
|
this.sessions().stream().
|
||||||
|
filter(v -> v.filterNoneSn(from))
|
||||||
|
.forEach(v -> {
|
||||||
message.getHeader().setSn(v.sn());
|
message.getHeader().setSn(v.sn());
|
||||||
v.push(message);
|
v.push(message);
|
||||||
});
|
});
|
||||||
@@ -102,7 +106,7 @@ public class ClientSessionManager {
|
|||||||
* @return 终端会话
|
* @return 终端会话
|
||||||
*/
|
*/
|
||||||
public ClientSession session(String sn) {
|
public ClientSession session(String sn) {
|
||||||
return this.sessions.stream()
|
return this.sessions().stream()
|
||||||
.filter(v -> StringUtils.equals(sn, v.sn()))
|
.filter(v -> StringUtils.equals(sn, v.sn()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
@@ -122,14 +126,18 @@ public class ClientSessionManager {
|
|||||||
* @return 所有终端会话
|
* @return 所有终端会话
|
||||||
*/
|
*/
|
||||||
public List<ClientSession> sessions() {
|
public List<ClientSession> sessions() {
|
||||||
return this.sessions;
|
return this.sessions.stream()
|
||||||
|
.filter(ClientSession::authorized)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 所有终端状态
|
* @return 所有终端状态
|
||||||
*/
|
*/
|
||||||
public List<ClientSessionStatus> status() {
|
public List<ClientSessionStatus> status() {
|
||||||
return this.sessions().stream().map(ClientSession::status).toList();
|
return this.sessions().stream()
|
||||||
|
.map(ClientSession::status)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,9 +168,10 @@ public class ClientSessionManager {
|
|||||||
/**
|
/**
|
||||||
* 定时关闭超时会话
|
* 定时关闭超时会话
|
||||||
*/
|
*/
|
||||||
private void closeTimeoutSession() {
|
private void closeTimeout() {
|
||||||
log.debug("定时关闭超时会话");
|
log.debug("定时关闭超时会话");
|
||||||
this.sessions.stream()
|
this.sessions.stream()
|
||||||
|
.filter(v -> !v.authorized())
|
||||||
.filter(v -> v.timeout(this.taoyaoProperties.getTimeout()))
|
.filter(v -> v.timeout(this.taoyaoProperties.getTimeout()))
|
||||||
.forEach(v -> {
|
.forEach(v -> {
|
||||||
log.debug("关闭超时会话:{}", v);
|
log.debug("关闭超时会话:{}", v);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.acgist.taoyao.signal.event.media;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answer事件
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class MediaAnswerEvent extends ApplicationEventAdapter {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MediaAnswerEvent(String sn, Map<?, ?> body, Message message, ClientSession session) {
|
||||||
|
super(sn, body, message, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 接收终端标识
|
||||||
|
*/
|
||||||
|
public String getTo() {
|
||||||
|
return this.get("to");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.acgist.taoyao.signal.event.media;
|
package com.acgist.taoyao.signal.event.media;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
@@ -26,10 +25,10 @@ public class MediaCandidateEvent extends ApplicationEventAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 终端列表
|
* @return 接收终端标识
|
||||||
*/
|
*/
|
||||||
public List<String> getSns() {
|
public String getTo() {
|
||||||
return this.get("sns");
|
return this.get("to");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.acgist.taoyao.signal.event.media;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offer事件
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class MediaOfferEvent extends ApplicationEventAdapter {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MediaOfferEvent(String sn, Map<?, ?> body, Message message, ClientSession session) {
|
||||||
|
super(sn, body, message, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 接收终端标识
|
||||||
|
*/
|
||||||
|
public String getTo() {
|
||||||
|
return this.get("to");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ public class MediaPublishEvent extends ApplicationEventAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 终端标识(发布给谁)
|
* @return 接收终端标识
|
||||||
*/
|
*/
|
||||||
public String getTo() {
|
public String getTo() {
|
||||||
return this.get("to");
|
return this.get("to");
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class MediaSubscribeEvent extends ApplicationEventAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 终端标识(订阅的谁)
|
* @return 接收终端标识
|
||||||
*/
|
*/
|
||||||
public String getTo() {
|
public String getTo() {
|
||||||
return this.get("to");
|
return this.get("to");
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.acgist.taoyao.signal.protocol.media;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||||
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
|
import com.acgist.taoyao.signal.client.ClientSession;
|
||||||
|
import com.acgist.taoyao.signal.event.media.MediaAnswerEvent;
|
||||||
|
import com.acgist.taoyao.signal.protocol.ProtocolMapAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answer信令
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
@Protocol
|
||||||
|
public class MediaAnswerProtocol extends ProtocolMapAdapter {
|
||||||
|
|
||||||
|
public static final Integer PID = 5998;
|
||||||
|
|
||||||
|
public MediaAnswerProtocol() {
|
||||||
|
super(PID, "Answer信令");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(String sn, Map<?, ?> body, Message message, ClientSession session) {
|
||||||
|
this.publishEvent(new MediaAnswerEvent(sn, body, message, session));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolMapAdapter;
|
|||||||
@Protocol
|
@Protocol
|
||||||
public class MediaCandidateProtocol extends ProtocolMapAdapter {
|
public class MediaCandidateProtocol extends ProtocolMapAdapter {
|
||||||
|
|
||||||
public static final Integer PID = 5004;
|
public static final Integer PID = 5999;
|
||||||
|
|
||||||
public MediaCandidateProtocol() {
|
public MediaCandidateProtocol() {
|
||||||
super(PID, "候选信令");
|
super(PID, "候选信令");
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.acgist.taoyao.signal.protocol.media;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||||
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
|
import com.acgist.taoyao.signal.client.ClientSession;
|
||||||
|
import com.acgist.taoyao.signal.event.media.MediaOfferEvent;
|
||||||
|
import com.acgist.taoyao.signal.protocol.ProtocolMapAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offer信令
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
@Protocol
|
||||||
|
public class MediaOfferProtocol extends ProtocolMapAdapter {
|
||||||
|
|
||||||
|
public static final Integer PID = 5997;
|
||||||
|
|
||||||
|
public MediaOfferProtocol() {
|
||||||
|
super(PID, "Offer信令");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(String sn, Map<?, ?> body, Message message, ClientSession session) {
|
||||||
|
this.publishEvent(new MediaOfferEvent(sn, body, message, session));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.webrtc.mesh.listener.MediaAnswerListener;
|
||||||
import com.acgist.taoyao.webrtc.mesh.listener.MediaCandidateListener;
|
import com.acgist.taoyao.webrtc.mesh.listener.MediaCandidateListener;
|
||||||
|
import com.acgist.taoyao.webrtc.mesh.listener.MediaOfferListener;
|
||||||
import com.acgist.taoyao.webrtc.mesh.listener.MediaPublishListener;
|
import com.acgist.taoyao.webrtc.mesh.listener.MediaPublishListener;
|
||||||
import com.acgist.taoyao.webrtc.mesh.listener.MediaSubscribeListener;
|
import com.acgist.taoyao.webrtc.mesh.listener.MediaSubscribeListener;
|
||||||
|
|
||||||
@@ -30,6 +32,18 @@ public class MeshAutoConfiguration {
|
|||||||
return new MediaSubscribeListener();
|
return new MediaSubscribeListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public MediaOfferListener mediaOfferListener() {
|
||||||
|
return new MediaOfferListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public MediaAnswerListener mediaAnswerListener() {
|
||||||
|
return new MediaAnswerListener();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public MediaCandidateListener mediaCandidateListener() {
|
public MediaCandidateListener mediaCandidateListener() {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.acgist.taoyao.webrtc.mesh.listener;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
|
import com.acgist.taoyao.signal.event.media.MediaAnswerEvent;
|
||||||
|
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answer监听
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MediaAnswerListener extends MediaListenerAdapter<MediaAnswerEvent> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(MediaAnswerEvent event) {
|
||||||
|
final String sn = event.getSn();
|
||||||
|
final String to = event.getTo();
|
||||||
|
if(sn.equals(to)) {
|
||||||
|
log.debug("忽略Answer消息(相同终端):{}-{}", sn, to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Message message = event.getMessage();
|
||||||
|
final Map<String, Object> mergeBody = event.mergeBody();
|
||||||
|
mergeBody.put("from", sn);
|
||||||
|
this.clientSessionManager.unicast(to, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,32 +1,33 @@
|
|||||||
package com.acgist.taoyao.webrtc.mesh.listener;
|
package com.acgist.taoyao.webrtc.mesh.listener;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
import com.acgist.taoyao.signal.event.media.MediaCandidateEvent;
|
import com.acgist.taoyao.signal.event.media.MediaCandidateEvent;
|
||||||
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 候选监听
|
* 候选监听
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class MediaCandidateListener extends MediaListenerAdapter<MediaCandidateEvent> {
|
public class MediaCandidateListener extends MediaListenerAdapter<MediaCandidateEvent> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(MediaCandidateEvent event) {
|
public void onApplicationEvent(MediaCandidateEvent event) {
|
||||||
final String sn = event.getSn();
|
final String sn = event.getSn();
|
||||||
final List<String> sns = event.getSns();
|
final String to = event.getTo();
|
||||||
if(CollectionUtils.isEmpty(sns)) {
|
if(sn.equals(to)) {
|
||||||
|
log.debug("忽略候选消息(相同终端):{}-{}", sn, to);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Message message = event.getMessage();
|
final Message message = event.getMessage();
|
||||||
final Map<String, Object> mergeBody = event.mergeBody();
|
final Map<String, Object> mergeBody = event.mergeBody();
|
||||||
mergeBody.put("from", sn);
|
mergeBody.put("from", sn);
|
||||||
sns.forEach(to -> this.clientSessionManager.unicast(to, message));
|
this.clientSessionManager.unicast(to, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.acgist.taoyao.webrtc.mesh.listener;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
|
import com.acgist.taoyao.signal.event.media.MediaOfferEvent;
|
||||||
|
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offer监听
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MediaOfferListener extends MediaListenerAdapter<MediaOfferEvent> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(MediaOfferEvent event) {
|
||||||
|
final String sn = event.getSn();
|
||||||
|
final String to = event.getTo();
|
||||||
|
if(sn.equals(to)) {
|
||||||
|
log.debug("忽略Offer消息(相同终端):{}-{}", sn, to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Message message = event.getMessage();
|
||||||
|
final Map<String, Object> mergeBody = event.mergeBody();
|
||||||
|
mergeBody.put("from", sn);
|
||||||
|
this.clientSessionManager.unicast(to, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,17 +6,24 @@ import com.acgist.taoyao.boot.model.Message;
|
|||||||
import com.acgist.taoyao.signal.event.media.MediaPublishEvent;
|
import com.acgist.taoyao.signal.event.media.MediaPublishEvent;
|
||||||
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布监听
|
* 发布监听
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class MediaPublishListener extends MediaListenerAdapter<MediaPublishEvent> {
|
public class MediaPublishListener extends MediaListenerAdapter<MediaPublishEvent> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(MediaPublishEvent event) {
|
public void onApplicationEvent(MediaPublishEvent event) {
|
||||||
final String sn = event.getSn();
|
final String sn = event.getSn();
|
||||||
final String to = event.getTo();
|
final String to = event.getTo();
|
||||||
|
if(sn.equals(to)) {
|
||||||
|
log.debug("忽略发布消息(相同终端):{}-{}", sn, to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final Message message = event.getMessage();
|
final Message message = event.getMessage();
|
||||||
final Map<String, Object> mergeBody = event.mergeBody();
|
final Map<String, Object> mergeBody = event.mergeBody();
|
||||||
mergeBody.put("from", sn);
|
mergeBody.put("from", sn);
|
||||||
|
|||||||
@@ -6,17 +6,24 @@ import com.acgist.taoyao.boot.model.Message;
|
|||||||
import com.acgist.taoyao.signal.event.media.MediaSubscribeEvent;
|
import com.acgist.taoyao.signal.event.media.MediaSubscribeEvent;
|
||||||
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅监听
|
* 订阅监听
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class MediaSubscribeListener extends MediaListenerAdapter<MediaSubscribeEvent> {
|
public class MediaSubscribeListener extends MediaListenerAdapter<MediaSubscribeEvent> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(MediaSubscribeEvent event) {
|
public void onApplicationEvent(MediaSubscribeEvent event) {
|
||||||
final String sn = event.getSn();
|
final String sn = event.getSn();
|
||||||
final String to = event.getTo();
|
final String to = event.getTo();
|
||||||
|
if(sn.equals(to)) {
|
||||||
|
log.debug("忽略订阅消息(相同终端):{}-{}", sn, to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final Message message = event.getMessage();
|
final Message message = event.getMessage();
|
||||||
final Map<String, Object> mergeBody = event.mergeBody();
|
final Map<String, Object> mergeBody = event.mergeBody();
|
||||||
mergeBody.put("from", sn);
|
mergeBody.put("from", sn);
|
||||||
|
|||||||
Reference in New Issue
Block a user