Browse Source

feat:移动端支持音视频通话

master
xsx 2 years ago
parent
commit
862c6f2b53
  1. 6
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java
  2. 9
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  3. 4
      im-platform/src/main/java/com/bx/implatform/service/IWebrtcService.java
  4. 8
      im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcServiceImpl.java
  5. BIN
      im-ui/src/assets/audio/call.wav
  6. 22
      im-uniapp/App.vue
  7. 3
      im-uniapp/common/enums.js
  8. 13
      im-uniapp/hybrid/html/index.html
  9. 7
      im-uniapp/pages.json
  10. 51
      im-uniapp/pages/chat/chat-box.vue
  11. 6
      im-uniapp/static/icon/iconfont.css
  12. BIN
      im-uniapp/static/icon/iconfont.ttf

6
im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java

@ -21,8 +21,8 @@ public class WebrtcController {
@ApiOperation(httpMethod = "POST", value = "呼叫视频通话")
@PostMapping("/call")
public Result call(@RequestParam Long uid, @RequestBody String offer) {
webrtcService.call(uid, offer);
public Result call(@RequestParam Long uid, @RequestParam(defaultValue = "video") String mode, @RequestBody String offer) {
webrtcService.call(uid, mode, offer);
return ResultUtils.success();
}
@ -65,7 +65,7 @@ public class WebrtcController {
@PostMapping("/candidate")
@ApiOperation(httpMethod = "POST", value = "同步candidate")
public Result forwardCandidate(@RequestParam Long uid, @RequestBody String candidate) {
public Result candidate(@RequestParam Long uid, @RequestBody String candidate) {
webrtcService.candidate(uid, candidate);
return ResultUtils.success();
}

9
im-platform/src/main/java/com/bx/implatform/enums/MessageType.java

@ -53,10 +53,15 @@ public enum MessageType {
* 消息加载标记
*/
LOADDING(30,"加载中"),
/**
* 语音呼叫
*/
RTC_CALL_VOICE(100, "语音呼叫"),
/**
* 呼叫
* 视频呼叫
*/
RTC_CALL(101, "呼叫"),
RTC_CALL_VIDEO(101, "视频呼叫"),
/**
* 接受
*/

4
im-platform/src/main/java/com/bx/implatform/service/IWebrtcService.java

@ -12,9 +12,9 @@ import java.util.List;
*/
public interface IWebrtcService {
void call(Long uid, String offer);
void call(Long uid, String mode,String offer);
void accept(Long uid, @RequestBody String answer);
void accept(Long uid, String answer);
void reject(Long uid);

8
im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcServiceImpl.java

@ -33,7 +33,7 @@ public class WebrtcServiceImpl implements IWebrtcService {
private final ICEServerConfig iceServerConfig;
@Override
public void call(Long uid, String offer) {
public void call(Long uid, String mode, String offer) {
UserSession session = SessionContext.getSession();
if (!imClient.isOnline(uid)) {
throw new GlobalException("对方目前不在线");
@ -46,7 +46,8 @@ public class WebrtcServiceImpl implements IWebrtcService {
redisTemplate.opsForValue().set(key, webrtcSession, 12, TimeUnit.HOURS);
// 向对方所有终端发起呼叫
PrivateMessageVO messageInfo = new PrivateMessageVO();
messageInfo.setType(MessageType.RTC_CALL.code());
MessageType messageType = mode.equals("video") ? MessageType.RTC_CALL_VIDEO : MessageType.RTC_CALL_VOICE;
messageInfo.setType(messageType.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
messageInfo.setContent(offer);
@ -146,6 +147,7 @@ public class WebrtcServiceImpl implements IWebrtcService {
messageInfo.setType(MessageType.RTC_FAILED.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
messageInfo.setContent(reason);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
@ -215,7 +217,7 @@ public class WebrtcServiceImpl implements IWebrtcService {
private WebrtcSession getWebrtcSession(Long userId, Long uid) {
String key = getSessionKey(userId, uid);
WebrtcSession webrtcSession = (WebrtcSession) redisTemplate.opsForValue().get(key);
WebrtcSession webrtcSession = (WebrtcSession)redisTemplate.opsForValue().get(key);
if (webrtcSession == null) {
throw new GlobalException("视频通话已结束");
}

BIN
im-ui/src/assets/audio/call.wav

Binary file not shown.

22
im-uniapp/App.vue

@ -104,8 +104,26 @@
},
insertPrivateMessage(friend, msg) {
// webrtc
if (msg.type >= enums.MESSAGE_TYPE.RTC_CALL &&
msg.type <= enums.MESSAGE_TYPE.RTC_CANDIDATE) {}
if (msg.type >= enums.MESSAGE_TYPE.RTC_CALL_VOICE &&
msg.type <= enums.MESSAGE_TYPE.RTC_CANDIDATE) {
//
if(msg.type == enums.MESSAGE_TYPE.RTC_CALL_VOICE
|| msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO){
let mode = msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO? "video":"voice";
let pages = getCurrentPages();
let curPage = pages[pages.length-1].route;
if(curPage != "pages/chat/chat-video"){
const friendInfo = encodeURIComponent(JSON.stringify(friend));
uni.navigateTo({
url: `/pages/chat/chat-video?mode=${mode}&friend=${friendInfo}&isHost=false`
})
}
}
setTimeout(() => {
uni.$emit('WS_RTC',msg);
},500)
return;
}
let chatInfo = {
type: 'PRIVATE',

3
im-uniapp/common/enums.js

@ -11,7 +11,8 @@ const MESSAGE_TYPE = {
TIP_TIME:20,
TIP_TEXT:21,
LOADDING:30,
RTC_CALL: 101,
RTC_CALL_VOICE: 100,
RTC_CALL_VIDEO: 101,
RTC_ACCEPT: 102,
RTC_REJECT: 103,
RTC_CANCEL: 104,

13
im-uniapp/hybrid/html/index.html

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="favicon.ico">
<title>视频通话</title>
</head>
<body>
<div style="padding-top:10px; text-align: center;font-size: 16px;">音视频通话为付费功能,有需要请联系作者...</div>
</body>
</html>

7
im-uniapp/pages.json

@ -1,5 +1,5 @@
{
"lazyCodeLoading":"requiredComponents",
"pages": [{
"path": "pages/login/login"
}, {
@ -16,6 +16,8 @@
"path": "pages/common/user-info"
}, {
"path": "pages/chat/chat-box"
},{
"path": "pages/chat/chat-video"
}, {
"path": "pages/friend/friend-add"
}, {
@ -30,7 +32,8 @@
"path": "pages/mine/mine-edit"
},{
"path": "pages/mine/mine-password"
}],
}
],
"globalStyle": {
"navigationBarTitleText": "盒子IM",
"navigationBarTextStyle": "black",

51
im-uniapp/pages/chat/chat-box.vue

@ -30,9 +30,9 @@
<view class="send-bar">
<view class="send-text">
<textarea class="send-text-area" v-model="sendText" auto-height :show-confirm-bar="false"
:placeholder="isReceipt?'[回执消息]':''"
:adjust-position="false" @confirm="sendTextMessage()" @keyboardheightchange="onKeyboardheightchange"
@input="onTextInput" confirm-type="send" confirm-hold :hold-keyboard="true"></textarea>
:placeholder="isReceipt?'[回执消息]':''" :adjust-position="false" @confirm="sendTextMessage()"
@keyboardheightchange="onKeyboardheightchange" @input="onTextInput" confirm-type="send" confirm-hold
:hold-keyboard="true"></textarea>
</view>
<view v-if="chat.type=='GROUP'" class="iconfont icon-at" @click="openAtBox()"></view>
<view class="iconfont icon-icon_emoji" @click="switchChatTabBox('emo',true)"></view>
@ -71,14 +71,18 @@
<view class="chat-tools-item" @click="showTip()">
<view class="tool-icon iconfont icon-microphone"></view>
<view class="tool-name">语音输入</view>
</view>
</view>
<view v-if="chat.type == 'GROUP'" class="chat-tools-item" @click="switchReceipt()">
<view class="tool-icon iconfont icon-receipt" :class="isReceipt?'active':''"></view>
<view class="tool-name">回执消息</view>
</view>
<view class="chat-tools-item" @click="showTip()">
<view class="chat-tools-item" @click="onVideoCall()">
<view class="tool-icon iconfont icon-video"></view>
<view class="tool-name">视频通话</view>
</view>
<view class="chat-tools-item" @click="onVoiceCall()">
<view class="tool-icon iconfont icon-call"></view>
<view class="tool-name">呼叫</view>
<view class="tool-name">语音通话</view>
</view>
</view>
<scroll-view v-if="chatTabBox==='emo'" class="chat-emotion" scroll-y="true">
@ -116,17 +120,28 @@
},
methods: {
showTip() {
uni.showToast({
title: "暂未支持...",
icon: "none"
})
},
moveChatToTop(){
onVideoCall(){
const friendInfo = encodeURIComponent(JSON.stringify(this.friend));
uni.navigateTo({
url: `/pages/chat/chat-video?mode=video&friend=${friendInfo}&isHost=true`
})
},
onVoiceCall(){
const friendInfo = encodeURIComponent(JSON.stringify(this.friend));
uni.navigateTo({
url: `/pages/chat/chat-video?mode=voice&friend=${friendInfo}&isHost=true`
})
},
moveChatToTop() {
let chatIdx = this.$store.getters.findChatIdx(this.chat);
this.$store.commit("moveTop",chatIdx);
this.$store.commit("moveTop", chatIdx);
},
switchReceipt(){
switchReceipt() {
this.isReceipt = !this.isReceipt;
},
openAtBox() {
@ -164,12 +179,12 @@
icon: "none"
});
}
let receiptText = this.isReceipt? "【回执消息】":"";
let receiptText = this.isReceipt ? "【回执消息】" : "";
let atText = this.createAtText();
let msgInfo = {
content: receiptText + this.sendText + atText,
atUserIds: this.atUserIds,
receipt : this.isReceipt,
receipt: this.isReceipt,
type: 0
}
// id
@ -185,7 +200,7 @@
msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
msgInfo.readedCount = 0,
msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND;
msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND;
this.$store.commit("insertMessage", msgInfo);
//
this.moveChatToTop();
@ -727,7 +742,7 @@
.chat-tools {
display: flex;
flex-wrap: wrap;
.chat-tools-item {
width: 140rpx;
padding: 16rpx;
@ -736,12 +751,12 @@
align-items: center;
.tool-icon {
padding: 18rpx;
font-size: 80rpx;
padding: 28rpx;
font-size: 60rpx;
background-color: white;
border-radius: 20%;
&.active{
&.active {
background-color: #ddd;
}
}

6
im-uniapp/static/icon/iconfont.css

@ -1,6 +1,6 @@
@font-face {
font-family: "iconfont"; /* Project id 4272106 */
src: url('iconfont.ttf?t=1706027587101') format('truetype');
src: url('iconfont.ttf?t=1710059877142') format('truetype');
}
.iconfont {
@ -11,6 +11,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-video:before {
content: "\e685";
}
.icon-receipt:before {
content: "\e61a";
}

BIN
im-uniapp/static/icon/iconfont.ttf

Binary file not shown.
Loading…
Cancel
Save