From 7fe3babab01787209726debfdb2355eb1e3a1f4d Mon Sep 17 00:00:00 2001 From: acgist <289547414@qq.com> Date: Mon, 28 Nov 2022 19:33:17 +0800 Subject: [PATCH] [*] mesh --- docs/FFmpeg.md | 92 ----- pom.xml | 44 --- .../boot/config/MediaAudioProperties.java | 16 +- .../boot/config/MediaVideoProperties.java | 2 +- taoyao-media/pom.xml | 14 - .../com/acgist/taoyao/meeting/Meeting.java | 10 + .../acgist/taoyao/meeting/MeetingManager.java | 3 +- .../listener/MeetingEnterListener.java | 1 + .../src/main/resources/application.yml | 6 +- .../resources/static/javascript/taoyao.js | 314 ++++++++++-------- .../src/main/resources/static/meeting.html | 7 +- taoyao-signal/README.md | 16 +- .../signal/client/ClientSessionAdapter.java | 2 +- .../signal/client/ClientSessionManager.java | 25 +- .../signal/event/media/MediaAnswerEvent.java | 34 ++ .../event/media/MediaCandidateEvent.java | 7 +- .../signal/event/media/MediaOfferEvent.java | 34 ++ .../signal/event/media/MediaPublishEvent.java | 2 +- .../event/media/MediaSubscribeEvent.java | 2 +- .../protocol/media/MediaAnswerProtocol.java | 30 ++ .../media/MediaCandidateProtocol.java | 2 +- .../protocol/media/MediaOfferProtocol.java | 30 ++ .../mesh/config/MeshAutoConfiguration.java | 14 + .../mesh/listener/MediaAnswerListener.java | 33 ++ .../mesh/listener/MediaCandidateListener.java | 13 +- .../mesh/listener/MediaOfferListener.java | 33 ++ .../mesh/listener/MediaPublishListener.java | 7 + .../mesh/listener/MediaSubscribeListener.java | 7 + 28 files changed, 474 insertions(+), 326 deletions(-) delete mode 100644 docs/FFmpeg.md create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaAnswerEvent.java create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaOfferEvent.java create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaAnswerProtocol.java create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaOfferProtocol.java create mode 100644 taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaAnswerListener.java create mode 100644 taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaOfferListener.java diff --git a/docs/FFmpeg.md b/docs/FFmpeg.md deleted file mode 100644 index 525add6..0000000 --- a/docs/FFmpeg.md +++ /dev/null @@ -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 -``` diff --git a/pom.xml b/pom.xml index ddbc793..723b035 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,6 @@ 17 1.5.8 - 5.1.2 1.18.24 6.18.0 2.0.0-RC1 @@ -189,49 +188,6 @@ kurento-client ${kurento.version} - - - org.bytedeco - ffmpeg-platform - ${ffmpeg.version}-${javacv.version} - - - org.bytedeco - ffmpeg - - - org.bytedeco - javacpp - - - - - org.bytedeco - ffmpeg - ${ffmpeg.version}-${javacv.version} - ${javacv.os.version} - - - org.bytedeco - javacpp - ${javacv.version} - ${javacv.os.version} - org.apache.commons diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaAudioProperties.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaAudioProperties.java index 646a747..ffb7099 100644 --- a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaAudioProperties.java +++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaAudioProperties.java @@ -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; } diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaVideoProperties.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaVideoProperties.java index 7f9c6ac..b010440 100644 --- a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaVideoProperties.java +++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaVideoProperties.java @@ -54,7 +54,7 @@ public class MediaVideoProperties { * 帧率(流畅) */ @Schema(title = "帧率", description = "帧率影响流程", example = "20|24|30|60") - private Integer framerate; + private Integer frameRate; /** * 分辨率(画面大小) */ diff --git a/taoyao-media/pom.xml b/taoyao-media/pom.xml index b077856..0445f84 100644 --- a/taoyao-media/pom.xml +++ b/taoyao-media/pom.xml @@ -37,20 +37,6 @@ com.acgist taoyao-webrtc-kurento - - org.bytedeco - ffmpeg-platform - - - org.bytedeco - ffmpeg - ${javacv.os.version} - - - org.bytedeco - javacpp - ${javacv.os.version} - \ No newline at end of file diff --git a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/Meeting.java b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/Meeting.java index 3dde72a..5a397d9 100644 --- a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/Meeting.java +++ b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/Meeting.java @@ -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; /** * 终端会话标识列表 */ diff --git a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/MeetingManager.java b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/MeetingManager.java index 233c5e3..4e4acee 100644 --- a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/MeetingManager.java +++ b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/MeetingManager.java @@ -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); diff --git a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/listener/MeetingEnterListener.java b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/listener/MeetingEnterListener.java index 03946b2..18266b1 100644 --- a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/listener/MeetingEnterListener.java +++ b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/listener/MeetingEnterListener.java @@ -32,6 +32,7 @@ public class MeetingEnterListener extends MeetingListenerAdapter !sn.equals(v)) .forEach(v -> this.clientSessionManager.unicast(v, message)); diff --git a/taoyao-server/src/main/resources/application.yml b/taoyao-server/src/main/resources/application.yml index 21fbb85..46f146c 100644 --- a/taoyao-server/src/main/resources/application.yml +++ b/taoyao-server/src/main/resources/application.yml @@ -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配置 diff --git a/taoyao-server/src/main/resources/static/javascript/taoyao.js b/taoyao-server/src/main/resources/static/javascript/taoyao.js index f5a22a4..44cef8d 100644 --- a/taoyao-server/src/main/resources/static/javascript/taoyao.js +++ b/taoyao-server/src/main/resources/static/javascript/taoyao.js @@ -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) { diff --git a/taoyao-server/src/main/resources/static/meeting.html b/taoyao-server/src/main/resources/static/meeting.html index 90eb4e9..b42237f 100644 --- a/taoyao-server/src/main/resources/static/meeting.html +++ b/taoyao-server/src/main/resources/static/meeting.html @@ -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; }, diff --git a/taoyao-signal/README.md b/taoyao-signal/README.md index 903061c..b262493 100644 --- a/taoyao-signal/README.md +++ b/taoyao-signal/README.md @@ -373,10 +373,6 @@ 取消订阅终端媒体流(终端取消拉流) -### 候选信令(5004) - -IceCandidate - ### 暂停信令(5004) 终端->服务端 @@ -397,6 +393,18 @@ MCU/SFU模式有效 配置订阅媒体:码率、帧率、分辨率等等 +### Offer信令(5997) + +Offer + +### Answer信令(5998) + +Answer + +### 候选信令(5999) + +IceCandidate + ## 测试 ``` diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientSessionAdapter.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientSessionAdapter.java index d2a4c27..a851ff4 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientSessionAdapter.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientSessionAdapter.java @@ -49,7 +49,7 @@ public abstract class ClientSessionAdapter implements C @Override public boolean timeout(long timeout) { - return !this.authorized && System.currentTimeMillis() - this.time > timeout; + return System.currentTimeMillis() - this.time > timeout; } @Override diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientSessionManager.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientSessionManager.java index 7818349..c14c15f 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientSessionManager.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientSessionManager.java @@ -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 sessions() { - return this.sessions; + return this.sessions.stream() + .filter(ClientSession::authorized) + .toList(); } /** * @return 所有终端状态 */ public List 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); diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaAnswerEvent.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaAnswerEvent.java new file mode 100644 index 0000000..96429d9 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaAnswerEvent.java @@ -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"); + } + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaCandidateEvent.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaCandidateEvent.java index f1fa5b7..6d3a1b3 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaCandidateEvent.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaCandidateEvent.java @@ -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 getSns() { - return this.get("sns"); + public String getTo() { + return this.get("to"); } } diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaOfferEvent.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaOfferEvent.java new file mode 100644 index 0000000..e746320 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaOfferEvent.java @@ -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"); + } + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaPublishEvent.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaPublishEvent.java index 75c3189..415cc3d 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaPublishEvent.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaPublishEvent.java @@ -25,7 +25,7 @@ public class MediaPublishEvent extends ApplicationEventAdapter { } /** - * @return 终端标识(发布给谁) + * @return 接收终端标识 */ public String getTo() { return this.get("to"); diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaSubscribeEvent.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaSubscribeEvent.java index 75afee1..24e832b 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaSubscribeEvent.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/media/MediaSubscribeEvent.java @@ -25,7 +25,7 @@ public class MediaSubscribeEvent extends ApplicationEventAdapter { } /** - * @return 终端标识(订阅的谁) + * @return 接收终端标识 */ public String getTo() { return this.get("to"); diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaAnswerProtocol.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaAnswerProtocol.java new file mode 100644 index 0000000..a07d997 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaAnswerProtocol.java @@ -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)); + } + +} \ No newline at end of file diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaCandidateProtocol.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaCandidateProtocol.java index d06a1e8..fb00a02 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaCandidateProtocol.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaCandidateProtocol.java @@ -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, "候选信令"); diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaOfferProtocol.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaOfferProtocol.java new file mode 100644 index 0000000..c5c7f53 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaOfferProtocol.java @@ -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)); + } + +} \ No newline at end of file diff --git a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/config/MeshAutoConfiguration.java b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/config/MeshAutoConfiguration.java index 8281c6e..0d5ca0f 100644 --- a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/config/MeshAutoConfiguration.java +++ b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/config/MeshAutoConfiguration.java @@ -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() { diff --git a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaAnswerListener.java b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaAnswerListener.java new file mode 100644 index 0000000..7c55013 --- /dev/null +++ b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaAnswerListener.java @@ -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 { + + @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 mergeBody = event.mergeBody(); + mergeBody.put("from", sn); + this.clientSessionManager.unicast(to, message); + } + +} diff --git a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaCandidateListener.java b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaCandidateListener.java index 35dc916..19368f3 100644 --- a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaCandidateListener.java +++ b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaCandidateListener.java @@ -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 { @Override public void onApplicationEvent(MediaCandidateEvent event) { final String sn = event.getSn(); - final List 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 mergeBody = event.mergeBody(); mergeBody.put("from", sn); - sns.forEach(to -> this.clientSessionManager.unicast(to, message)); + this.clientSessionManager.unicast(to, message); } } diff --git a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaOfferListener.java b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaOfferListener.java new file mode 100644 index 0000000..2ab7053 --- /dev/null +++ b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaOfferListener.java @@ -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 { + + @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 mergeBody = event.mergeBody(); + mergeBody.put("from", sn); + this.clientSessionManager.unicast(to, message); + } + +} diff --git a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaPublishListener.java b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaPublishListener.java index 2033b55..7363389 100644 --- a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaPublishListener.java +++ b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaPublishListener.java @@ -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 { @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 mergeBody = event.mergeBody(); mergeBody.put("from", sn); diff --git a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaSubscribeListener.java b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaSubscribeListener.java index f8a9ed4..5112f60 100644 --- a/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaSubscribeListener.java +++ b/taoyao-webrtc/taoyao-webrtc-mesh/src/main/java/com/acgist/taoyao/webrtc/mesh/listener/MediaSubscribeListener.java @@ -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 { @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 mergeBody = event.mergeBody(); mergeBody.put("from", sn);