[*] 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>
|
||||
<javacv.version>1.5.8</javacv.version>
|
||||
<ffmpeg.version>5.1.2</ffmpeg.version>
|
||||
<lombok.version>1.18.24</lombok.version>
|
||||
<kurento.version>6.18.0</kurento.version>
|
||||
<springdoc.version>2.0.0-RC1</springdoc.version>
|
||||
@@ -189,49 +188,6 @@
|
||||
<artifactId>kurento-client</artifactId>
|
||||
<version>${kurento.version}</version>
|
||||
</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>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
||||
@@ -21,10 +21,6 @@ public class MediaAudioProperties {
|
||||
*/
|
||||
public enum Format {
|
||||
|
||||
/**
|
||||
* ACC
|
||||
*/
|
||||
ACC,
|
||||
/**
|
||||
* PCM
|
||||
*/
|
||||
@@ -41,16 +37,16 @@ public class MediaAudioProperties {
|
||||
*/
|
||||
@Schema(title = "格式", description = "格式")
|
||||
private Format format;
|
||||
/**
|
||||
* 采样数
|
||||
*/
|
||||
@Schema(title = "采样数", description = "采样数", example = "16")
|
||||
private Integer sampleSize;
|
||||
/**
|
||||
* 采样率
|
||||
* 8000|16000|32000|48000
|
||||
*/
|
||||
@Schema(title = "采样率", description = "采样率", example = "8000|16000|32000|48000")
|
||||
private Integer samplerate;
|
||||
/**
|
||||
* 采样数
|
||||
*/
|
||||
@Schema(title = "采样数", description = "采样数", example = "16")
|
||||
private Integer samplesize;
|
||||
private Integer sampleRate;
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class MediaVideoProperties {
|
||||
* 帧率(流畅)
|
||||
*/
|
||||
@Schema(title = "帧率", description = "帧率影响流程", example = "20|24|30|60")
|
||||
private Integer framerate;
|
||||
private Integer frameRate;
|
||||
/**
|
||||
* 分辨率(画面大小)
|
||||
*/
|
||||
|
||||
@@ -37,20 +37,6 @@
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-webrtc-kurento</artifactId>
|
||||
</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>
|
||||
|
||||
</project>
|
||||
@@ -21,6 +21,16 @@ public class Meeting {
|
||||
*/
|
||||
@Schema(title = "会议标识", description = "会议标识")
|
||||
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) {
|
||||
final Meeting meeting = new Meeting();
|
||||
meeting.setId(this.idService.buildIdToString());
|
||||
// meeting.setId(this.idService.buildIdToString());
|
||||
meeting.setId("1");
|
||||
meeting.setSns(new CopyOnWriteArrayList<>());
|
||||
meeting.setCreator(sn);
|
||||
meeting.addSn(sn);
|
||||
|
||||
@@ -32,6 +32,7 @@ public class MeetingEnterListener extends MeetingListenerAdapter<MeetingEnterEve
|
||||
"id", meeting.getId(),
|
||||
"sn", sn
|
||||
));
|
||||
// TODO:返回房间列表
|
||||
meeting.getSns().stream()
|
||||
.filter(v -> !sn.equals(v))
|
||||
.forEach(v -> this.clientSessionManager.unicast(v, message));
|
||||
|
||||
@@ -59,12 +59,12 @@ taoyao:
|
||||
media:
|
||||
audio:
|
||||
format: OPUS
|
||||
samplesize: 16
|
||||
samplerate: 32000
|
||||
sample-size: 16
|
||||
sample-rate: 32000
|
||||
video:
|
||||
format: H264
|
||||
bitrate: 1200
|
||||
framerate: 24
|
||||
frame-rate: 24
|
||||
resolution: 1280*760
|
||||
quality: high|standard|quick
|
||||
# WebRTC配置
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
/** 桃夭WebRTC终端核心功能 */
|
||||
/**
|
||||
* 桃夭WebRTC终端核心功能
|
||||
*
|
||||
* 代码注意:
|
||||
* 1. undefined判断使用两个等号,其他情况使用三个。
|
||||
*/
|
||||
/** 兼容 */
|
||||
const RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
|
||||
const RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
|
||||
const RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
|
||||
/** 默认音频配置 */
|
||||
const defaultAudioConfig = {
|
||||
// 设备
|
||||
// deviceId : '',
|
||||
// 音量:0~1
|
||||
volume: 0.5,
|
||||
// 延迟大小(单位毫秒):500毫秒以内较好
|
||||
latency: 0.4,
|
||||
// 设备
|
||||
// deviceId : '',
|
||||
// 采样率:8000|16000|32000|48000
|
||||
sampleRate: 48000,
|
||||
// 采样数:16
|
||||
sampleSize: 16,
|
||||
// 采样率:8000|16000|32000|48000
|
||||
sampleRate: 32000,
|
||||
// 声道数量:1|2
|
||||
channelCount : 1,
|
||||
// 是否开启自动增益:true|false
|
||||
@@ -28,16 +33,14 @@ const defaultAudioConfig = {
|
||||
};
|
||||
/** 默认视频配置 */
|
||||
const defaultVideoConfig = {
|
||||
// 设备
|
||||
// deviceId: '',
|
||||
// 宽度
|
||||
width: 1280,
|
||||
// 高度
|
||||
height: 720,
|
||||
// 设备
|
||||
// deviceId: '',
|
||||
// 帧率
|
||||
frameRate: 24,
|
||||
// 裁切
|
||||
// resizeMode: '',
|
||||
// 选摄像头:user|left|right|environment
|
||||
facingMode: 'environment'
|
||||
}
|
||||
@@ -57,7 +60,7 @@ const defaultRPCConfig = {
|
||||
/** 信令配置 */
|
||||
const signalConfig = {
|
||||
/** 当前终端SN */
|
||||
sn: localStorage.getItem('taoyao.sn', 'taoyao'),
|
||||
sn: localStorage.getItem('taoyao.sn') || 'taoyao',
|
||||
/** 当前版本 */
|
||||
version: '1.0.0',
|
||||
// 信令授权
|
||||
@@ -76,7 +79,11 @@ const signalProtocol = {
|
||||
/** 订阅 */
|
||||
subscribe: 5002,
|
||||
/** 候选 */
|
||||
candidate: 5004
|
||||
offer: 5997,
|
||||
/** Answer */
|
||||
answer: 5998,
|
||||
/** 候选 */
|
||||
candidate: 5999
|
||||
},
|
||||
/** 终端信令 */
|
||||
client: {
|
||||
@@ -303,6 +310,12 @@ const signalChannel = {
|
||||
case signalProtocol.media.subscribe:
|
||||
this.defaultMediaSubscribe(data);
|
||||
break;
|
||||
case signalProtocol.media.offer:
|
||||
this.defaultMediaOffer(data);
|
||||
break;
|
||||
case signalProtocol.media.answer:
|
||||
this.defaultMediaAnswer(data);
|
||||
break;
|
||||
case signalProtocol.media.candidate:
|
||||
this.defaultMediaCandidate(data);
|
||||
break;
|
||||
@@ -329,7 +342,7 @@ const signalChannel = {
|
||||
/** 终端默认回调 */
|
||||
defaultClientConfig: function(data) {
|
||||
this.taoyao
|
||||
.configMedia(data.body.media)
|
||||
.configMedia(data.body.media.audio, data.body.media.video)
|
||||
.configWebrtc(data.body.webrtc);
|
||||
},
|
||||
defaultClientReboot: function(data) {
|
||||
@@ -338,52 +351,71 @@ const signalChannel = {
|
||||
},
|
||||
/** 默认媒体回调 */
|
||||
defaultMediaPublish: function(data) {
|
||||
this.taoyao.localMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body));
|
||||
},
|
||||
defaultMediaSubscribe: function(data) {
|
||||
let self = this;
|
||||
const from = data.body.from;
|
||||
let remote = this.taoyao.remoteClientFilter(from);
|
||||
if(!remote) {
|
||||
remote = new TaoyaoClient(from);
|
||||
this.taoyao.remoteClient.push(remote);
|
||||
}
|
||||
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);
|
||||
this.taoyao.remoteClientFilter(from, true);
|
||||
self.taoyao.localMediaChannel.createOffer().then(description => {
|
||||
console.debug('Local Create Offer', description);
|
||||
self.taoyao.localMediaChannel.setLocalDescription(description);
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalProtocol.media.publish,
|
||||
signalProtocol.media.offer,
|
||||
{
|
||||
to: from,
|
||||
sdp: description.sdp,
|
||||
type: description.type
|
||||
sdp: {
|
||||
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) {
|
||||
if(
|
||||
!data.body.candidate ||
|
||||
!data.body.candidate.candidate ||
|
||||
!data.body.candidate.sdpMid ||
|
||||
!data.body.candidate.sdpMLineIndex
|
||||
) {
|
||||
console.warn('候选缺失要素', data);
|
||||
if(!this.taoyao.checkCandidate(data.body.candidate)) {
|
||||
console.debug('候选缺失要素', data);
|
||||
return;
|
||||
}
|
||||
let candidate = new RTCIceCandidate(data.body.candidate);
|
||||
this.taoyao.remoteMediaChannel.addIceCandidate(candidate);
|
||||
console.debug('Set ICE Candidate', this.taoyao.remoteMediaChannel);
|
||||
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) {
|
||||
this.taoyao
|
||||
.mediaSubscribe(data.body.sn);
|
||||
this.taoyao.mediaSubscribe(data.body.sn);
|
||||
}
|
||||
};
|
||||
/** 终端 */
|
||||
function TaoyaoClient(
|
||||
sn
|
||||
sn,
|
||||
audioEnabled,
|
||||
videoEnabled
|
||||
) {
|
||||
/** 终端标识 */
|
||||
this.sn = sn;
|
||||
@@ -391,12 +423,15 @@ function TaoyaoClient(
|
||||
this.video = null;
|
||||
/** 媒体信息 */
|
||||
this.stream = null;
|
||||
this.audioTrack = null;
|
||||
this.videoTrack = null;
|
||||
/** 媒体状态 */
|
||||
this.audioTrack = [];
|
||||
this.videoTrack = [];
|
||||
/** 媒体状态:是否含有 */
|
||||
this.audioStatus = false;
|
||||
this.videoStatus = false;
|
||||
this.recordStatus = false;
|
||||
/** 媒体状态:是否播放 */
|
||||
this.audioEnabled = audioEnabled == undefined ? true : audioEnabled;
|
||||
this.videoEnabled = videoEnabled == undefined ? true : videoEnabled;
|
||||
/** 重置 */
|
||||
this.reset = function() {
|
||||
}
|
||||
@@ -406,80 +441,80 @@ function TaoyaoClient(
|
||||
return this;
|
||||
};
|
||||
/** 重新加载 */
|
||||
this.load = function() {
|
||||
this.video.load();
|
||||
this.load = async function() {
|
||||
await this.video.load();
|
||||
return this;
|
||||
}
|
||||
/** 暂停视频 */
|
||||
this.pause = function() {
|
||||
this.video.pause();
|
||||
this.pause = async function() {
|
||||
await this.video.pause();
|
||||
return this;
|
||||
};
|
||||
/** 关闭视频 */
|
||||
this.close = function() {
|
||||
this.video.close();
|
||||
this.close = async function() {
|
||||
await this.video.close();
|
||||
return this;
|
||||
};
|
||||
/** 设置视频对象 */
|
||||
this.buildVideo = async function(videoId, stream, track) {
|
||||
if(!this.video) {
|
||||
/** 设置媒体 */
|
||||
this.buildStream = async function(videoId, stream, track) {
|
||||
if(!this.video && videoId) {
|
||||
this.video = document.getElementById(videoId);
|
||||
}
|
||||
await this.buildStream(stream, track);
|
||||
return this;
|
||||
};
|
||||
/** 设置媒体流 */
|
||||
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.video) {
|
||||
throw new Error('视频对象无效:' + videoId);
|
||||
}
|
||||
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;
|
||||
};
|
||||
/** 设置音频流 */
|
||||
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(
|
||||
webSocket
|
||||
webSocket,
|
||||
localClientAudioEnabled,
|
||||
localClientVideoEnabled
|
||||
) {
|
||||
/** WebRTC配置 */
|
||||
this.webrtc = null;
|
||||
@@ -497,6 +532,9 @@ function Taoyao(
|
||||
/** 本地终端 */
|
||||
this.localClient = null;
|
||||
this.localMediaChannel = null;
|
||||
/** 本地媒体状态 */
|
||||
this.localClientAudioEnabled = localClientAudioEnabled == undefined ? false : localClientAudioEnabled;
|
||||
this.localClientVideoEnabled = localClientVideoEnabled == undefined ? true : localClientVideoEnabled;
|
||||
/** 远程终端 */
|
||||
this.remoteClient = [];
|
||||
this.remoteMediaChannel = null;
|
||||
@@ -505,7 +543,7 @@ function Taoyao(
|
||||
/** 媒体配置 */
|
||||
this.configMedia = function(audio = {}, video = {}) {
|
||||
this.audioConfig = {...this.audioConfig, ...audio};
|
||||
this.videoCofnig = {...this.videoCofnig, ...video};
|
||||
this.videoConfig = {...this.videoConfig, ...video};
|
||||
console.debug('终端媒体配置', this.audioConfig, this.videoConfig);
|
||||
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);
|
||||
if(array.length <= 0) {
|
||||
return null;
|
||||
let remote = 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() {
|
||||
// TODO:释放资源
|
||||
@@ -605,16 +647,12 @@ function Taoyao(
|
||||
this.buildMediaChannel = async function(localVideoId, stream) {
|
||||
let self = this;
|
||||
// 本地视频
|
||||
this.localClient = new TaoyaoClient(signalConfig.sn);
|
||||
await this.localClient.buildVideo(localVideoId, stream);
|
||||
this.localClient = new TaoyaoClient(signalConfig.sn, this.localClientAudioEnabled, this.localClientVideoEnabled);
|
||||
await this.localClient.buildStream(localVideoId, stream);
|
||||
// 本地通道
|
||||
this.localMediaChannel = new RTCPeerConnection(defaultRPCConfig);
|
||||
if(this.localClient.audioTrack) {
|
||||
this.localClient.audioTrack.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.localClient.audioTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
||||
this.localClient.videoTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
||||
this.localMediaChannel.ontrack = function(e) {
|
||||
console.debug('Local Media Track', e);
|
||||
};
|
||||
@@ -633,12 +671,18 @@ function Taoyao(
|
||||
}
|
||||
};
|
||||
this.localMediaChannel.onicecandidate = function(e) {
|
||||
let sns = self.remoteClient.filter(v => v.video === null).map(v => v.sn);
|
||||
console.debug('Local ICE Candidate', sns, e);
|
||||
// TODO:判断给谁
|
||||
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(
|
||||
signalProtocol.media.candidate,
|
||||
{
|
||||
sns: sns,
|
||||
to: to,
|
||||
type: 'local',
|
||||
candidate: e.candidate
|
||||
}
|
||||
));
|
||||
@@ -649,7 +693,7 @@ function Taoyao(
|
||||
console.debug('Remote Media Track', e);
|
||||
// TODO:匹配
|
||||
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) {
|
||||
channel.onopen = function() {
|
||||
@@ -666,12 +710,18 @@ function Taoyao(
|
||||
}
|
||||
};
|
||||
this.remoteMediaChannel.onicecandidate = function(e) {
|
||||
let sns = self.remoteClient.filter(v => v.video === null).map(v => v.sn);
|
||||
console.debug('Remote ICE Candidate', sns, e);
|
||||
// TODO:判断给谁
|
||||
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(
|
||||
signalProtocol.media.candidate,
|
||||
{
|
||||
sns: sns,
|
||||
to: to,
|
||||
type: 'remote',
|
||||
candidate: e.candidate
|
||||
}
|
||||
));
|
||||
@@ -679,30 +729,30 @@ function Taoyao(
|
||||
console.debug('打开媒体通道', this.localMediaChannel, this.remoteMediaChannel);
|
||||
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) {
|
||||
let self = this;
|
||||
let remote = self.remoteClientFilter(sn);
|
||||
if(remote) {
|
||||
remote.reset();
|
||||
} else {
|
||||
remote = new TaoyaoClient(sn);
|
||||
this.remoteClient.push(remote);
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
self.remoteClientFilter(sn, true);
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalProtocol.media.subscribe,
|
||||
{
|
||||
to: sn
|
||||
}
|
||||
), callback);
|
||||
};
|
||||
/** 会议信令 */
|
||||
this.meetingCreate = function(callback) {
|
||||
|
||||
@@ -73,10 +73,11 @@
|
||||
methods: {
|
||||
// 信令回调:返回true表示已经处理
|
||||
callback: function(data) {
|
||||
let self = this;
|
||||
switch(data.header.pid) {
|
||||
case signalProtocol.client.heartbeat:
|
||||
// 心跳
|
||||
return true;
|
||||
case signalProtocol.client.config:
|
||||
// 如果需要下发配置生效需要在此打开媒体通道
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
@@ -373,10 +373,6 @@
|
||||
|
||||
取消订阅终端媒体流(终端取消拉流)
|
||||
|
||||
### 候选信令(5004)
|
||||
|
||||
IceCandidate
|
||||
|
||||
### 暂停信令(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
|
||||
public boolean timeout(long timeout) {
|
||||
return !this.authorized && System.currentTimeMillis() - this.time > timeout;
|
||||
return System.currentTimeMillis() - this.time > timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ClientSessionManager {
|
||||
|
||||
@Scheduled(cron = "${taoyao.scheduled.session:0 * * * * ?}")
|
||||
public void scheduled() {
|
||||
this.closeTimeoutSession();
|
||||
this.closeTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +65,9 @@ public class ClientSessionManager {
|
||||
* @param 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());
|
||||
v.push(message);
|
||||
});
|
||||
@@ -77,7 +79,7 @@ public class ClientSessionManager {
|
||||
* @param message 消息
|
||||
*/
|
||||
public void broadcast(Message message) {
|
||||
this.sessions.forEach(v -> {
|
||||
this.sessions().forEach(v -> {
|
||||
message.getHeader().setSn(v.sn());
|
||||
v.push(message);
|
||||
});
|
||||
@@ -90,7 +92,9 @@ public class ClientSessionManager {
|
||||
* @param 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());
|
||||
v.push(message);
|
||||
});
|
||||
@@ -102,7 +106,7 @@ public class ClientSessionManager {
|
||||
* @return 终端会话
|
||||
*/
|
||||
public ClientSession session(String sn) {
|
||||
return this.sessions.stream()
|
||||
return this.sessions().stream()
|
||||
.filter(v -> StringUtils.equals(sn, v.sn()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
@@ -122,14 +126,18 @@ public class ClientSessionManager {
|
||||
* @return 所有终端会话
|
||||
*/
|
||||
public List<ClientSession> sessions() {
|
||||
return this.sessions;
|
||||
return this.sessions.stream()
|
||||
.filter(ClientSession::authorized)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 所有终端状态
|
||||
*/
|
||||
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("定时关闭超时会话");
|
||||
this.sessions.stream()
|
||||
.filter(v -> !v.authorized())
|
||||
.filter(v -> v.timeout(this.taoyaoProperties.getTimeout()))
|
||||
.forEach(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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
@@ -26,10 +25,10 @@ public class MediaCandidateEvent extends ApplicationEventAdapter {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 终端列表
|
||||
* @return 接收终端标识
|
||||
*/
|
||||
public List<String> getSns() {
|
||||
return this.get("sns");
|
||||
public String getTo() {
|
||||
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() {
|
||||
return this.get("to");
|
||||
|
||||
@@ -25,7 +25,7 @@ public class MediaSubscribeEvent extends ApplicationEventAdapter {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 终端标识(订阅的谁)
|
||||
* @return 接收终端标识
|
||||
*/
|
||||
public String getTo() {
|
||||
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
|
||||
public class MediaCandidateProtocol extends ProtocolMapAdapter {
|
||||
|
||||
public static final Integer PID = 5004;
|
||||
public static final Integer PID = 5999;
|
||||
|
||||
public MediaCandidateProtocol() {
|
||||
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.Configuration;
|
||||
|
||||
import com.acgist.taoyao.webrtc.mesh.listener.MediaAnswerListener;
|
||||
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.MediaSubscribeListener;
|
||||
|
||||
@@ -30,6 +32,18 @@ public class MeshAutoConfiguration {
|
||||
return new MediaSubscribeListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MediaOfferListener mediaOfferListener() {
|
||||
return new MediaOfferListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MediaAnswerListener mediaAnswerListener() {
|
||||
return new MediaAnswerListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.signal.event.media.MediaCandidateEvent;
|
||||
import com.acgist.taoyao.signal.listener.MediaListenerAdapter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 候选监听
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Slf4j
|
||||
public class MediaCandidateListener extends MediaListenerAdapter<MediaCandidateEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(MediaCandidateEvent event) {
|
||||
final String sn = event.getSn();
|
||||
final List<String> sns = event.getSns();
|
||||
if(CollectionUtils.isEmpty(sns)) {
|
||||
final String to = event.getTo();
|
||||
if(sn.equals(to)) {
|
||||
log.debug("忽略候选消息(相同终端):{}-{}", sn, to);
|
||||
return;
|
||||
}
|
||||
final Message message = event.getMessage();
|
||||
final Map<String, Object> mergeBody = event.mergeBody();
|
||||
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.listener.MediaListenerAdapter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 发布监听
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Slf4j
|
||||
public class MediaPublishListener extends MediaListenerAdapter<MediaPublishEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(MediaPublishEvent event) {
|
||||
final String sn = event.getSn();
|
||||
final String to = event.getTo();
|
||||
if(sn.equals(to)) {
|
||||
log.debug("忽略发布消息(相同终端):{}-{}", sn, to);
|
||||
return;
|
||||
}
|
||||
final Message message = event.getMessage();
|
||||
final Map<String, Object> mergeBody = event.mergeBody();
|
||||
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.listener.MediaListenerAdapter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 订阅监听
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Slf4j
|
||||
public class MediaSubscribeListener extends MediaListenerAdapter<MediaSubscribeEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(MediaSubscribeEvent event) {
|
||||
final String sn = event.getSn();
|
||||
final String to = event.getTo();
|
||||
if(sn.equals(to)) {
|
||||
log.debug("忽略订阅消息(相同终端):{}-{}", sn, to);
|
||||
return;
|
||||
}
|
||||
final Message message = event.getMessage();
|
||||
final Map<String, Object> mergeBody = event.mergeBody();
|
||||
mergeBody.put("from", sn);
|
||||
|
||||
Reference in New Issue
Block a user