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