From 9ac5c36796054a456d5ef10061dc98bd4caa423e Mon Sep 17 00:00:00 2001 From: xsx <825657193@qq.com> Date: Sun, 16 Jun 2024 14:35:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=9A=E4=BA=BA=E9=9F=B3=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- im-ui/src/api/camera.js | 76 +++++++++ im-ui/src/api/eventBus.js | 3 + im-ui/src/api/rtcGroupApi.js | 156 +++++++++++++++++++ im-ui/src/api/webrtc.js | 117 ++++++++++++++ im-ui/src/components/rtc/RtcGroupJoin.vue | 116 ++++++++++++++ im-ui/src/store/configStore.js | 32 ++++ im-uniapp/hybrid/html/index.html | 1 - im-uniapp/hybrid/html/rtc-group/index.html | 13 ++ im-uniapp/hybrid/html/rtc-private/index.html | 13 ++ 9 files changed, 526 insertions(+), 1 deletion(-) create mode 100644 im-ui/src/api/camera.js create mode 100644 im-ui/src/api/eventBus.js create mode 100644 im-ui/src/api/rtcGroupApi.js create mode 100644 im-ui/src/api/webrtc.js create mode 100644 im-ui/src/components/rtc/RtcGroupJoin.vue create mode 100644 im-ui/src/store/configStore.js delete mode 100644 im-uniapp/hybrid/html/index.html create mode 100644 im-uniapp/hybrid/html/rtc-group/index.html create mode 100644 im-uniapp/hybrid/html/rtc-private/index.html diff --git a/im-ui/src/api/camera.js b/im-ui/src/api/camera.js new file mode 100644 index 0000000..be3a1ae --- /dev/null +++ b/im-ui/src/api/camera.js @@ -0,0 +1,76 @@ + +class ImCamera { + constructor() { + this.stream = null; + } +} + +ImCamera.prototype.isEnable = function() { + return !!navigator && !!navigator.mediaDevices && !!navigator.mediaDevices.getUserMedia; +} + +ImCamera.prototype.openVideo = function(isFacing) { + return new Promise((resolve, reject) => { + if(this.stream){ + this.close() + } + let facingMode = isFacing ? "user" : "environment"; + let constraints = { + video: { + facingMode: facingMode + }, + audio: { + echoCancellation: true, //音频开启回音消除 + noiseSuppression: true // 开启降噪 + } + } + console.log("getUserMedia") + navigator.mediaDevices.getUserMedia(constraints).then((stream) => { + console.log("摄像头打开") + this.stream = stream; + resolve(stream); + }).catch((e) => { + console.log(e) + console.log("摄像头未能正常打开") + reject({ + code: 0, + message: "摄像头未能正常打开" + }) + }) + }) +} + + +ImCamera.prototype.openAudio = function() { + return new Promise((resolve, reject) => { + let constraints = { + video: false, + audio: { + echoCancellation: true, //音频开启回音消除 + noiseSuppression: true // 开启降噪 + } + } + navigator.mediaDevices.getUserMedia(constraints).then((stream) => { + this.stream = stream; + resolve(stream); + }).catch(() => { + console.log("麦克风未能正常打开") + reject({ + code: 0, + message: "麦克风未能正常打开" + }) + }) + + }) +} + +ImCamera.prototype.close = function() { + // 停止流 + if (this.stream) { + this.stream.getTracks().forEach((track) => { + track.stop(); + }); + } +} + +export default ImCamera; \ No newline at end of file diff --git a/im-ui/src/api/eventBus.js b/im-ui/src/api/eventBus.js new file mode 100644 index 0000000..a72b416 --- /dev/null +++ b/im-ui/src/api/eventBus.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; + +export default new Vue(); \ No newline at end of file diff --git a/im-ui/src/api/rtcGroupApi.js b/im-ui/src/api/rtcGroupApi.js new file mode 100644 index 0000000..58e8409 --- /dev/null +++ b/im-ui/src/api/rtcGroupApi.js @@ -0,0 +1,156 @@ +import http from './httpRequest.js' + +class RtcGroupApi { + constructor() { + this.http = http; + } +} + + +RtcGroupApi.prototype.setup = function(groupId, userInfos) { + let formData = { + groupId, + userInfos + } + return this.http({ + url: '/webrtc/group/setup', + method: 'post', + data: formData + }) +} + +RtcGroupApi.prototype.accept = function(groupId) { + return this.http({ + url: '/webrtc/group/accept?groupId='+groupId, + method: 'post' + }) +} + +RtcGroupApi.prototype.reject = function(groupId) { + return this.http({ + url: '/webrtc/group/reject?groupId='+groupId, + method: 'post' + }) +} + +RtcGroupApi.prototype.failed = function(groupId,reason) { + let formData = { + groupId, + reason + } + return this.http({ + url: '/webrtc/group/failed', + method: 'post', + data: formData + }) +} + + +RtcGroupApi.prototype.join = function(groupId) { + return this.http({ + url: '/webrtc/group/join?groupId='+groupId, + method: 'post' + }) +} + +RtcGroupApi.prototype.invite = function(groupId, userInfos) { + let formData = { + groupId, + userInfos + } + return this.http({ + url: '/webrtc/group/invite', + method: 'post', + data: formData + }) +} + + +RtcGroupApi.prototype.offer = function(groupId, userId, offer) { + let formData = { + groupId, + userId, + offer + } + return this.http({ + url: '/webrtc/group/offer', + method: 'post', + data: formData + }) +} + +RtcGroupApi.prototype.answer = function(groupId, userId, answer) { + let formData = { + groupId, + userId, + answer + } + return this.http({ + url: '/webrtc/group/answer', + method: 'post', + data: formData + }) +} + +RtcGroupApi.prototype.quit = function(groupId) { + return this.http({ + url: '/webrtc/group/quit?groupId=' + groupId, + method: 'post' + }) +} + +RtcGroupApi.prototype.cancel = function(groupId) { + return this.http({ + url: '/webrtc/group/cancel?groupId=' + groupId, + method: 'post' + }) +} + +RtcGroupApi.prototype.candidate = function(groupId, userId, candidate) { + let formData = { + groupId, + userId, + candidate + } + return this.http({ + url: '/webrtc/group/candidate', + method: 'post', + data: formData + }) +} + +RtcGroupApi.prototype.device = function(groupId, isCamera, isMicroPhone) { + let formData = { + groupId, + isCamera, + isMicroPhone + } + return this.http({ + url: '/webrtc/group/device', + method: 'post', + data: formData + }) +} + + +RtcGroupApi.prototype.candidate = function(groupId, userId, candidate) { + let formData = { + groupId, + userId, + candidate + } + return this.http({ + url: '/webrtc/group/candidate', + method: 'post', + data: formData + }) +} + +RtcGroupApi.prototype.heartbeat = function(groupId) { + return this.http({ + url: '/webrtc/group/heartbeat?groupId=' + groupId, + method: 'post' + }) +} + +export default RtcGroupApi; \ No newline at end of file diff --git a/im-ui/src/api/webrtc.js b/im-ui/src/api/webrtc.js new file mode 100644 index 0000000..359118b --- /dev/null +++ b/im-ui/src/api/webrtc.js @@ -0,0 +1,117 @@ + +class ImWebRtc { + constructor() { + this.configuration = {} + this.stream = null; + } +} + +ImWebRtc.prototype.isEnable = function() { + window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window + .mozRTCPeerConnection; + window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window + .mozRTCSessionDescription; + window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window + .mozRTCIceCandidate; + return !!window.RTCPeerConnection; +} + +ImWebRtc.prototype.init = function(configuration) { + this.configuration = configuration; +} + +ImWebRtc.prototype.setupPeerConnection = function(callback) { + this.peerConnection = new RTCPeerConnection(this.configuration); + this.peerConnection.ontrack = (e) => { + // 对方的视频流 + callback(e.streams[0]); + }; +} + + +ImWebRtc.prototype.setStream = function(stream) { + if(this.stream){ + this.peerConnection.removeStream(this.stream) + } + stream.getTracks().forEach((track) => { + this.peerConnection.addTrack(track, stream); + }); + this.stream = stream; +} + + +ImWebRtc.prototype.onIcecandidate = function(callback) { + this.peerConnection.onicecandidate = (event) => { + // 追踪到候选信息 + if (event.candidate) { + callback(event.candidate) + } + } +} + +ImWebRtc.prototype.onStateChange = function(callback) { + // 监听连接状态 + this.peerConnection.oniceconnectionstatechange = (event) => { + let state = event.target.iceConnectionState; + console.log("ICE连接状态变化: : " + state) + callback(state) + }; +} + +ImWebRtc.prototype.createOffer = function() { + return new Promise((resolve, reject) => { + const offerParam = {}; + offerParam.offerToRecieveAudio = 1; + offerParam.offerToRecieveVideo = 1; + // 创建本地sdp信息 + this.peerConnection.createOffer(offerParam).then((offer) => { + // 设置本地sdp信息 + this.peerConnection.setLocalDescription(offer); + // 发起呼叫请求 + resolve(offer) + }).catch((e) => { + reject(e) + }) + }); +} + + +ImWebRtc.prototype.createAnswer = function(offer) { + return new Promise((resolve, reject) => { + // 设置远端的sdp + this.setRemoteDescription(offer); + // 创建本地dsp + const offerParam = {}; + offerParam.offerToRecieveAudio = 1; + offerParam.offerToRecieveVideo = 1; + this.peerConnection.createAnswer(offerParam).then((answer) => { + // 设置本地sdp信息 + this.peerConnection.setLocalDescription(answer); + // 接受呼叫请求 + resolve(answer) + }).catch((e) => { + reject(e) + }) + }); +} + +ImWebRtc.prototype.setRemoteDescription = function(offer) { + // 设置对方的sdp信息 + this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer)); +} + +ImWebRtc.prototype.addIceCandidate = function(candidate) { + // 添加对方的候选人信息 + this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); +} + +ImWebRtc.prototype.close = function(uid) { + // 关闭RTC连接 + if (this.peerConnection) { + this.peerConnection.close(); + this.peerConnection.onicecandidate = null; + this.peerConnection.onaddstream = null; + } +} + +export default ImWebRtc; \ No newline at end of file diff --git a/im-ui/src/components/rtc/RtcGroupJoin.vue b/im-ui/src/components/rtc/RtcGroupJoin.vue new file mode 100644 index 0000000..7d09b10 --- /dev/null +++ b/im-ui/src/components/rtc/RtcGroupJoin.vue @@ -0,0 +1,116 @@ + + + + + \ No newline at end of file diff --git a/im-ui/src/store/configStore.js b/im-ui/src/store/configStore.js new file mode 100644 index 0000000..6debf5a --- /dev/null +++ b/im-ui/src/store/configStore.js @@ -0,0 +1,32 @@ +import http from '../api/httpRequest.js' + +export default { + state: { + webrtc: {} + }, + mutations: { + setConfig(state, config) { + state.webrtc = config.webrtc; + }, + clear(state){ + state.webrtc = {}; + } + }, + actions:{ + loadConfig(context){ + return new Promise((resolve, reject) => { + http({ + url: '/system/config', + method: 'GET' + }).then((config) => { + console.log("系统配置",config) + context.commit("setConfig",config); + resolve(); + }).catch((res)=>{ + reject(res); + }); + }) + } + } + +} \ No newline at end of file diff --git a/im-uniapp/hybrid/html/index.html b/im-uniapp/hybrid/html/index.html deleted file mode 100644 index 05ff8e2..0000000 --- a/im-uniapp/hybrid/html/index.html +++ /dev/null @@ -1 +0,0 @@ -web
\ No newline at end of file diff --git a/im-uniapp/hybrid/html/rtc-group/index.html b/im-uniapp/hybrid/html/rtc-group/index.html new file mode 100644 index 0000000..be64c86 --- /dev/null +++ b/im-uniapp/hybrid/html/rtc-group/index.html @@ -0,0 +1,13 @@ + + + + + + + + 语音通话 + + +
音视频通话为付费功能,有需要请联系作者...
+ + \ No newline at end of file diff --git a/im-uniapp/hybrid/html/rtc-private/index.html b/im-uniapp/hybrid/html/rtc-private/index.html new file mode 100644 index 0000000..6733f82 --- /dev/null +++ b/im-uniapp/hybrid/html/rtc-private/index.html @@ -0,0 +1,13 @@ + + + + + + + + 视频通话 + + +
音视频通话为付费功能,有需要请联系作者...
+ + \ No newline at end of file