Browse Source

feat: 单人音视频功能改造

master
xsx 2 years ago
parent
commit
bc0e3ffabb
  1. 24
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  2. 3
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  3. 1
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  4. 4
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  5. 82
      im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java
  6. 9
      im-platform/src/main/java/com/bx/implatform/session/WebrtcPrivateSession.java
  7. 6
      im-ui/src/api/enums.js
  8. 40
      im-ui/src/api/messageType.js
  9. 66
      im-ui/src/api/rtcPrivateApi.js
  10. 4
      im-ui/src/components/chat/ChatBox.vue
  11. 20
      im-ui/src/components/chat/ChatItem.vue
  12. 34
      im-ui/src/components/chat/ChatMessageItem.vue
  13. 10
      im-ui/src/components/rtc/RtcPrivateVideo.vue
  14. 2
      im-ui/src/main.js
  15. 16
      im-ui/src/store/chatStore.js
  16. 4
      im-ui/src/view/Chat.vue
  17. 25
      im-ui/src/view/Home.vue
  18. 16
      im-uniapp/App.vue
  19. 6
      im-uniapp/common/enums.js
  20. 40
      im-uniapp/common/messageType.js
  21. 19
      im-uniapp/components/chat-message-item/chat-message-item.vue
  22. 2
      im-uniapp/main.js
  23. 25
      im-uniapp/pages/chat/chat-box.vue
  24. 13
      im-uniapp/pages/chat/chat-private-video.vue
  25. 12
      im-uniapp/store/chatStore.js

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

@ -2,6 +2,17 @@ package com.bx.implatform.enums;
import lombok.AllArgsConstructor;
/**
* 0-9: 真正的消息需要存储到数据库
* 10-19: 状态类消息: 撤回已读回执
* 20-29: 提示类消息: 在会话中间显示的提示
* 30-39: UI交互类消息: 显示加载状态等
* 40-49: 操作交互类消息: 语音通话视频通话消息等
* 100-199: 单人语音通话rtc信令
* 200-299: 多人语音通话rtc信令
*
*/
@AllArgsConstructor
public enum MessageType {
@ -25,6 +36,7 @@ public enum MessageType {
* 视频
*/
VIDEO(4, "视频"),
/**
* 撤回
*/
@ -46,11 +58,19 @@ public enum MessageType {
* 文字提示
*/
TIP_TEXT(21,"文字提示"),
/**
* 消息加载标记
*/
LOADDING(30,"加载中"),
LOADING(30,"加载中"),
/**
* 语音通话提示
*/
ACT_RT_VOICE(40,"语音通话"),
/**
* 视频通话提示
*/
ACT_RT_VIDEO(41,"视频通话"),
RTC_CALL_VOICE(100, "语音呼叫"),
RTC_CALL_VIDEO(101, "视频呼叫"),

3
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java

@ -345,7 +345,6 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(GroupMessage::getGroupId, groupId).gt(GroupMessage::getSendTime, member.getCreatedTime())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code()).orderByDesc(GroupMessage::getId).last("limit " + stIdx + "," + size);
List<GroupMessage> messages = this.list(wrapper);
List<GroupMessageVO> messageInfos =
messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList());
@ -369,7 +368,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
private void sendLoadingMessage(Boolean isLoadding){
UserSession session = SessionContext.getSession();
GroupMessageVO msgInfo = new GroupMessageVO();
msgInfo.setType(MessageType.LOADDING.code());
msgInfo.setType(MessageType.LOADING.code());
msgInfo.setContent(isLoadding.toString());
IMGroupMessage sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));

1
im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java

@ -50,6 +50,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
private final IFriendService friendsService;
private final IMClient imClient;
private final RedisTemplate<String, Object> redisTemplate;
@Override
public GroupVO createGroup(GroupVO vo) {
UserSession session = SessionContext.getSession();

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

@ -9,7 +9,6 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.dto.PrivateMessageDTO;
@ -26,7 +25,6 @@ import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.util.SensitiveFilterUtil;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -245,7 +243,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
private void sendLoadingMessage(Boolean isLoadding){
UserSession session = SessionContext.getSession();
PrivateMessageVO msgInfo = new PrivateMessageVO();
msgInfo.setType(MessageType.LOADDING.code());
msgInfo.setType(MessageType.LOADING.code());
msgInfo.setContent(isLoadding.toString());
IMPrivateMessage sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));

82
im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java

@ -3,15 +3,18 @@ package com.bx.implatform.service.impl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.config.ICEServer;
import com.bx.implatform.config.WebrtcConfig;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType;
import com.bx.implatform.enums.WebrtcMode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.service.IPrivateMessageService;
import com.bx.implatform.service.IWebrtcPrivateService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.session.WebrtcPrivateSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.util.UserStateUtils;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.RequiredArgsConstructor;
@ -21,7 +24,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Collections;
import java.util.List;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Slf4j
@ -31,22 +34,28 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
private final IMClient imClient;
private final RedisTemplate<String, Object> redisTemplate;
private final WebrtcConfig iceServerConfig;
private final IPrivateMessageService privateMessageService;
private final UserStateUtils userStateUtils;
@Override
public void call(Long uid, String mode, String offer) {
UserSession session = SessionContext.getSession();
// 创建webrtc会话
WebrtcPrivateSession webrtcSession = new WebrtcPrivateSession();
webrtcSession.setCallerId(session.getUserId());
webrtcSession.setCallerTerminal(session.getTerminal());
webrtcSession.setAcceptorId(uid);
webrtcSession.setMode(mode);
// 校验
if (!imClient.isOnline(uid)) {
this.sendActMessage(webrtcSession,MessageStatus.UNSEND,"未接通");
throw new GlobalException("对方目前不在线");
}
if(userStateUtils.isBusy(uid)){
if (userStateUtils.isBusy(uid)) {
this.sendActMessage(webrtcSession,MessageStatus.UNSEND,"未接通");
throw new GlobalException("对方正忙");
}
// 创建webrtc会话
WebrtcPrivateSession webrtcSession = new WebrtcPrivateSession();
webrtcSession.setCallerId(session.getUserId());
webrtcSession.setCallerTerminal(session.getTerminal());
// 保存rtc session
String key = getWebRtcSessionKey(session.getUserId(), uid);
redisTemplate.opsForValue().set(key, webrtcSession, 60, TimeUnit.SECONDS);
// 设置用户忙线状态
@ -54,7 +63,8 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
userStateUtils.setBusy(session.getUserId());
// 向对方所有终端发起呼叫
PrivateMessageVO messageInfo = new PrivateMessageVO();
MessageType messageType = mode.equals("video") ? MessageType.RTC_CALL_VIDEO : MessageType.RTC_CALL_VOICE;
MessageType messageType =
mode.equals(WebrtcMode.VIDEO.getValue()) ? MessageType.RTC_CALL_VIDEO : MessageType.RTC_CALL_VOICE;
messageInfo.setType(messageType.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
@ -78,6 +88,7 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
// 更新接受者信息
webrtcSession.setAcceptorId(session.getUserId());
webrtcSession.setAcceptorTerminal(session.getTerminal());
webrtcSession.setChatTimeStamp(System.currentTimeMillis());
String key = getWebRtcSessionKey(session.getUserId(), uid);
redisTemplate.opsForValue().set(key, webrtcSession, 60, TimeUnit.SECONDS);
// 向发起人推送接受通话信令
@ -123,11 +134,15 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
sendMessage.setRecvTerminals(Collections.singletonList(webrtcSession.getCallerTerminal()));
sendMessage.setData(messageInfo);
imClient.sendPrivateMessage(sendMessage);
// 生成通话消息
sendActMessage(webrtcSession, MessageStatus.READED,"已拒绝");
}
@Override
public void cancel(Long uid) {
UserSession session = SessionContext.getSession();
// 查询webrtc会话
WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid);
// 删除会话信息
removeWebrtcSession(session.getUserId(), uid);
// 设置用户空闲状态
@ -147,6 +162,8 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
sendMessage.setData(messageInfo);
// 通知对方取消会话
imClient.sendPrivateMessage(sendMessage);
// 生成通话消息
sendActMessage(webrtcSession, MessageStatus.UNSEND,"已取消");
}
@Override
@ -175,7 +192,8 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
sendMessage.setData(messageInfo);
// 通知对方取消会话
imClient.sendPrivateMessage(sendMessage);
// 生成消息
sendActMessage(webrtcSession, MessageStatus.READED,"未接通");
}
@Override
@ -204,6 +222,8 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
sendMessage.setData(messageInfo);
// 通知对方取消会话
imClient.sendPrivateMessage(sendMessage);
// 生成通话消息
sendActMessage(webrtcSession, MessageStatus.READED,"通话时长 " + chatTimeText(webrtcSession));
}
@Override
@ -234,7 +254,7 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
UserSession session = SessionContext.getSession();
// 会话续命
String key = getWebRtcSessionKey(session.getUserId(), uid);
redisTemplate.expire(key,60,TimeUnit.SECONDS);
redisTemplate.expire(key, 60, TimeUnit.SECONDS);
// 用户状态续命
userStateUtils.expire(session.getUserId());
}
@ -266,4 +286,42 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService {
return webrtcSession.getAcceptorTerminal();
}
private void sendActMessage(WebrtcPrivateSession rtcSession, MessageStatus status,String content) {
// 保存消息
PrivateMessage msg = new PrivateMessage();
msg.setSendId(rtcSession.getCallerId());
msg.setRecvId(rtcSession.getAcceptorId());
msg.setContent(content);
msg.setSendTime(new Date());
msg.setStatus(status.code());
MessageType type = rtcSession.getMode().equals(WebrtcMode.VIDEO.getValue()) ? MessageType.ACT_RT_VIDEO
: MessageType.ACT_RT_VOICE;
msg.setType(type.code());
privateMessageService.save(msg);
// 推给发起人
PrivateMessageVO messageInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(rtcSession.getCallerId(), rtcSession.getCallerTerminal()));
sendMessage.setRecvId(rtcSession.getCallerId());
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
sendMessage.setData(messageInfo);
imClient.sendPrivateMessage(sendMessage);
// 推给接听方
sendMessage.setRecvId(rtcSession.getAcceptorId());
imClient.sendPrivateMessage(sendMessage);
}
private String chatTimeText(WebrtcPrivateSession rtcSession) {
long chatTime = (System.currentTimeMillis() - rtcSession.getChatTimeStamp())/1000;
int min = Math.abs((int)chatTime / 60);
int sec = Math.abs((int)chatTime % 60);
String strTime = min < 10 ? "0" : "";
strTime += min;
strTime += ":";
strTime += sec < 10 ? "0" : "";
strTime += sec;
return strTime;
}
}

9
im-platform/src/main/java/com/bx/implatform/session/WebrtcPrivateSession.java

@ -27,4 +27,13 @@ public class WebrtcPrivateSession {
* 接受者终端类型
*/
private Integer acceptorTerminal;
/**
* 通话模式
*/
private String mode;
/**
* 开始聊天时间戳
*/
private Long chatTimeStamp;
}

6
im-ui/src/api/enums.js

@ -4,14 +4,14 @@ const MESSAGE_TYPE = {
FILE: 2,
AUDIO: 3,
VIDEO: 4,
RT_VOICE: 5,
RT_VIDEO: 6,
RECALL: 10,
READED: 11,
RECEIPT: 12,
TIP_TIME: 20,
TIP_TEXT: 21,
LOADDING: 30,
LOADING: 30,
ACT_RT_VOICE: 40,
ACT_RT_VIDEO: 41,
RTC_CALL_VOICE: 100,
RTC_CALL_VIDEO: 101,
RTC_ACCEPT: 102,

40
im-ui/src/api/messageType.js

@ -0,0 +1,40 @@
// 是否普通消息
let isNormal = function(type){
return type>=0 && type < 10;
}
// 是否状态消息
let isStatus = function(type){
return type>=10 && type < 20;
}
// 是否提示消息
let isTip = function(type){
return type>=20 && type < 30;
}
// 操作交互类消息
let isAction = function(type){
return type>=40 && type < 50;
}
// 单人通话信令
let isRtcPrivate = function(type){
return type>=100 && type < 300;
}
// 多人通话信令
let isRtcGroup = function(type){
return type>=200 && type < 400;
}
export {
isNormal,
isStatus,
isTip,
isAction,
isRtcPrivate,
isRtcGroup
}

66
im-ui/src/api/rtcPrivateApi.js

@ -0,0 +1,66 @@
import http from './httpRequest.js'
class RtcPrivateApi {
}
RtcPrivateApi.prototype.call = function(uid, mode, offer) {
return http({
url: `/webrtc/private/call?uid=${uid}&mode=${mode}`,
method: 'post',
data: JSON.stringify(offer)
})
}
RtcPrivateApi.prototype.accept = function(uid, answer) {
return http({
url: `/webrtc/private/accept?uid=${uid}`,
method: 'post',
data: JSON.stringify(answer)
})
}
RtcPrivateApi.prototype.handup = function(uid) {
return http({
url: `/webrtc/private/handup?uid=${uid}`,
method: 'post'
})
}
RtcPrivateApi.prototype.cancel = function(uid) {
return http({
url: `/webrtc/private/cancel?uid=${uid}`,
method: 'post'
})
}
RtcPrivateApi.prototype.reject = function(uid) {
return http({
url: `/webrtc/private/reject?uid=${uid}`,
method: 'post'
})
}
RtcPrivateApi.prototype.failed = function(uid, reason) {
return http({
url: `/webrtc/private/failed?uid=${uid}&reason=${reason}`,
method: 'post'
})
}
RtcPrivateApi.prototype.sendCandidate = function(uid, candidate) {
return http({
url: `/webrtc/private/candidate?uid=${uid}`,
method: 'post',
data: JSON.stringify(candidate)
});
}
RtcPrivateApi.prototype.heartbeat = function(uid) {
return http({
url: `/webrtc/private/heartbeat?uid=${uid}`,
method: 'post'
})
}
export default RtcPrivateApi;

4
im-ui/src/components/chat/ChatBox.vue

@ -157,9 +157,9 @@
this.$refs.atBox.close();
},
onCall(type) {
if (type == this.$enums.MESSAGE_TYPE.RT_VOICE) {
if (type == this.$enums.MESSAGE_TYPE.ACT_RT_VOICE) {
this.showPrivateVideo('voice');
} else if (type == this.$enums.MESSAGE_TYPE.RT_VIDEO) {
} else if (type == this.$enums.MESSAGE_TYPE.ACT_RT_VIDEO) {
this.showPrivateVideo('video');
}
},

20
im-ui/src/components/chat/ChatItem.vue

@ -12,7 +12,7 @@
</div>
<div class="chat-content">
<div class="chat-at-text">{{atText}}</div>
<div class="chat-send-name" v-show="chat.sendNickName">{{chat.sendNickName+':&nbsp;'}}</div>
<div class="chat-send-name" v-show="isShowSendName">{{chat.sendNickName+':&nbsp;'}}</div>
<div class="chat-content-text" v-html="$emo.transform(chat.lastContent)"></div>
</div>
</div>
@ -76,6 +76,18 @@
}
},
computed: {
isShowSendName() {
if (!this.chat.sendNickName) {
return false;
}
let size = this.chat.messages.length;
if (size == 0) {
return false;
}
//
let lastMsg = this.chat.messages[size - 1];
return this.$msgType.isNormal(lastMsg.type)
},
showTime() {
return this.$date.toTimeText(this.chat.lastSendTime, true)
},
@ -147,6 +159,7 @@
display: flex;
line-height: 25px;
height: 25px;
.chat-name-text {
flex: 1;
font-size: 15px;
@ -156,7 +169,7 @@
}
.chat-time-text{
.chat-time-text {
font-size: 13px;
text-align: right;
color: #888888;
@ -175,7 +188,7 @@
font-size: 12px;
}
.chat-send-name{
.chat-send-name {
font-size: 13px;
}
@ -186,6 +199,7 @@
overflow: hidden;
text-overflow: ellipsis;
font-size: 13px;
img {
width: 20px !important;
height: 20px !important;

34
im-ui/src/components/chat/ChatMessageItem.vue

@ -1,14 +1,13 @@
<template>
<div class="chat-msg-item">
<div class="chat-msg-tip"
v-show="msgInfo.type == $enums.MESSAGE_TYPE.RECALL || msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">
v-if="msgInfo.type == $enums.MESSAGE_TYPE.RECALL || msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">
{{ msgInfo.content }}
</div>
<div class="chat-msg-tip" v-show="msgInfo.type == $enums.MESSAGE_TYPE.TIP_TIME">
<div class="chat-msg-tip" v-if="msgInfo.type == $enums.MESSAGE_TYPE.TIP_TIME">
{{ $date.toTimeText(msgInfo.sendTime) }}
</div>
<div class="chat-msg-normal" v-show="msgInfo.type >= 0 && msgInfo.type < 10" :class="{ 'chat-msg-mine': mine }">
<div class="chat-msg-normal" v-if="isNormal" :class="{ 'chat-msg-mine': mine }">
<div class="head-image">
<head-image :name="showName" :size="40" :url="headImage" :id="msgInfo.sendId"></head-image>
</div>
@ -28,7 +27,7 @@
<div class="img-load-box" v-loading="loading" element-loading-text="上传中.."
element-loading-background="rgba(0, 0, 0, 0.4)">
<img class="send-image" :src="JSON.parse(msgInfo.content).thumbUrl"
@click="showFullImageBox()" loading="lazy"/>
@click="showFullImageBox()" loading="lazy" />
</div>
<span title="发送失败" v-show="loadFail" @click="onSendFail"
class="send-fail el-icon-warning"></span>
@ -51,14 +50,14 @@
<div class="chat-msg-voice" v-if="msgInfo.type == $enums.MESSAGE_TYPE.AUDIO" @click="onPlayVoice()">
<audio controls :src="JSON.parse(msgInfo.content).url"></audio>
</div>
<div class="chat-realtime chat-msg-text" v-if="isRealtime">
<span v-if="msgInfo.type==$enums.MESSAGE_TYPE.RT_VOICE" title="重新呼叫"
@click="$emit('call')" class="iconfont icon-chat-voice"></span>
<span v-if="msgInfo.type==$enums.MESSAGE_TYPE.RT_VIDEO" title="重新呼叫"
@click="$emit('call')" class="iconfont icon-chat-video"></span>
<div class="chat-action chat-msg-text" v-if="isAction">
<span v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VOICE" title="重新呼叫" @click="$emit('call')"
class="iconfont icon-chat-voice"></span>
<span v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VIDEO" title="重新呼叫" @click="$emit('call')"
class="iconfont icon-chat-video"></span>
<span>{{msgInfo.content}}</span>
</div>
<div class="chat-msg-status" v-if="!isRealtime">
<div class="chat-msg-status" v-if="!isAction">
<span class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status == $enums.MESSAGE_STATUS.READED">已读</span>
<span class="chat-unread" v-show="msgInfo.selfSend && !msgInfo.groupId
@ -201,9 +200,12 @@
}
return items;
},
isRealtime() {
return this.msgInfo.type == this.$enums.MESSAGE_TYPE.RT_VOICE ||
this.msgInfo.type == this.$enums.MESSAGE_TYPE.RT_VIDEO
isAction(){
return this.$msgType.isAction(this.msgInfo.type);
},
isNormal() {
const type = this.msgInfo.type;
return this.$msgType.isNormal(type) || this.$msgType.isAction(type)
}
}
}
@ -369,7 +371,7 @@
}
}
.chat-realtime {
.chat-action {
display: flex;
align-items: center;
@ -458,7 +460,7 @@
flex-direction: row-reverse;
}
.chat-realtime {
.chat-action {
flex-direction: row-reverse;
.iconfont {

10
im-ui/src/components/rtc/RtcPrivateVideo.vue

@ -4,8 +4,8 @@
:visible.sync="showRoom" width="50%" height="70%" :before-close="onQuit">
<div class="rtc-private-video">
<div v-show="isVideo" class="rtc-video-box">
<div class="rtc-video-friend" v-loading="!isChating"
element-loading-spinner="el-icon-loading" >
<div class="rtc-video-friend" v-loading="!isChating" element-loading-text="等待对方接听..."
element-loading-background="rgba(0, 0, 0, 0.3)" >
<head-image class="friend-head-image" :id="friend.id" :size="80" :name="friend.nickName"
:url="friend.headImage">
</head-image>
@ -23,10 +23,8 @@
</head-image>
</div>
<div class="rtc-control-bar">
<div v-show="isWaiting" title="取消呼叫" class="icon iconfont icon-phone-reject reject"
style="color: red;" @click="onCancel()"></div>
<div v-show="isChating" title="挂断" class="icon iconfont icon-phone-reject reject"
style="color: red;" @click="onHandup()"></div>
<div title="取消" class="icon iconfont icon-phone-reject reject"
style="color: red;" @click="onQuit()"></div>
</div>
</div>
</el-dialog>

2
im-ui/src/main.js

@ -6,6 +6,7 @@ import 'element-ui/lib/theme-chalk/index.css';
import './assets/iconfont/iconfont.css';
import httpRequest from './api/httpRequest';
import * as socketApi from './api/wssocket';
import * as messageType from './api/messageType';
import emotion from './api/emotion.js';
import element from './api/element.js';
import store from './store';
@ -16,6 +17,7 @@ import './utils/directive/dialogDrag';
Vue.use(ElementUI);
// 挂载全局
Vue.prototype.$wsApi = socketApi;
Vue.prototype.$msgType = messageType
Vue.prototype.$date = date;
Vue.prototype.$http = httpRequest // http请求方法
Vue.prototype.$emo = emotion; // emo表情

16
im-ui/src/store/chatStore.js

@ -142,11 +142,13 @@ export default {
chat.lastContent = "[文件]";
} else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
chat.lastContent = "[语音]";
} else if (msgInfo.type == MESSAGE_TYPE.TEXT || msgInfo.type == MESSAGE_TYPE.RECALL) {
} else if (msgInfo.type == MESSAGE_TYPE.TEXT
|| msgInfo.type == MESSAGE_TYPE.RECALL
|| msgInfo.type == MESSAGE_TYPE.TIP_TEXT ) {
chat.lastContent = msgInfo.content;
} else if (msgInfo.type == MESSAGE_TYPE.RT_VOICE) {
} else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) {
chat.lastContent = "[语音通话]";
} else if (msgInfo.type == MESSAGE_TYPE.RT_VIDEO) {
} else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) {
chat.lastContent = "[视频通话]";
}
chat.lastSendTime = msgInfo.sendTime;
@ -239,14 +241,14 @@ export default {
this.commit("saveToStorage");
},
loadingPrivateMsg(state, loadding) {
state.loadingPrivateMsg = loadding;
loadingPrivateMsg(state, loading) {
state.loadingPrivateMsg = loading;
if (!state.loadingPrivateMsg && !state.loadingGroupMsg) {
this.commit("sort")
}
},
loadingGroupMsg(state, loadding) {
state.loadingGroupMsg = loadding;
loadingGroupMsg(state, loading) {
state.loadingGroupMsg = loading;
if (!state.loadingPrivateMsg && !state.loadingGroupMsg) {
this.commit("sort")
}

4
im-ui/src/view/Chat.vue

@ -6,7 +6,7 @@
<i class="el-icon-search el-input__icon" slot="prefix"> </i>
</el-input>
</div>
<div class="chat-list-loadding" v-if="loading" v-loading="true" element-loading-text="消息接收中..."
<div class="chat-list-loading" v-if="loading" v-loading="true" element-loading-text="消息接收中..."
element-loading-spinner="el-icon-loading" element-loading-background="#eee">
<div class="chat-loading-box"></div>
</div>
@ -85,7 +85,7 @@
}
.chat-list-loadding{
.chat-list-loading{
height: 50px;
background-color: #eee;

25
im-ui/src/view/Home.vue

@ -41,7 +41,7 @@
<full-image :visible="uiStore.fullImage.show" :url="uiStore.fullImage.url"
@close="$store.commit('closeFullImageBox')"></full-image>
<rtc-private-video ref="rtcPrivateVideo"></rtc-private-video>
<rtc-group-video ref="rtcGroupVideo" ></rtc-group-video>
<rtc-group-video ref="rtcGroupVideo"></rtc-group-video>
</el-container>
</template>
@ -72,11 +72,11 @@
},
methods: {
init() {
this.$eventBus.$on('openPrivateVideo', (rctInfo)=>{
this.$eventBus.$on('openPrivateVideo', (rctInfo) => {
//
this.$refs.rtcPrivateVideo.open(rctInfo);
});
this.$eventBus.$on('openGroupVideo', (rctInfo)=>{
this.$eventBus.$on('openGroupVideo', (rctInfo) => {
//
this.$refs.rtcGroupVideo.open(rctInfo);
});
@ -136,7 +136,7 @@
},
handlePrivateMessage(msg) {
//
if (msg.type == this.$enums.MESSAGE_TYPE.LOADDING) {
if (msg.type == this.$enums.MESSAGE_TYPE.LOADING) {
this.$store.commit("loadingPrivateMsg", JSON.parse(msg.content))
return;
}
@ -158,7 +158,7 @@
//
msg.selfSend = msg.sendId == this.$store.state.userStore.userInfo.id;
// webrtc
if (msg.type >= 100 && msg.type <= 199) {
if (this.$msgType.isRtcPrivate(msg.type)) {
this.$refs.rtcPrivateVideo.onRTCMessage(msg)
return;
}
@ -181,14 +181,14 @@
//
this.$store.commit("insertMessage", msg);
//
if (!msg.selfSend && msg.type < 10
&& msg.status != this.$enums.MESSAGE_STATUS.READED) {
if (!msg.selfSend && this.$msgType.isNormal(msg.type) &&
msg.status != this.$enums.MESSAGE_STATUS.READED) {
this.playAudioTip();
}
},
handleGroupMessage(msg) {
//
if (msg.type == this.$enums.MESSAGE_TYPE.LOADDING) {
if (msg.type == this.$enums.MESSAGE_TYPE.LOADING) {
this.$store.commit("loadingGroupMsg", JSON.parse(msg.content))
return;
}
@ -217,8 +217,8 @@
//
msg.selfSend = msg.sendId == this.$store.state.userStore.userInfo.id;
//
if (msg.type >= 200 && msg.type <= 299) {
this.$nextTick(()=>{
if (this.$msgType.isRtcGroup(msg.type)) {
this.$nextTick(() => {
this.$refs.rtcGroupVideo.onRTCMessage(msg);
})
return;
@ -241,8 +241,8 @@
//
this.$store.commit("insertMessage", msg);
//
if (!msg.selfSend && msg.type < 10
&& msg.status != this.$enums.MESSAGE_STATUS.READED) {
if (!msg.selfSend && msg.type <= this.$enums.MESSAGE_TYPE.VIDEO &&
msg.status != this.$enums.MESSAGE_STATUS.READED) {
this.playAudioTip();
}
},
@ -346,6 +346,7 @@
background-color: #19082f !important;
padding: 0 !important;
text-align: center;
.link {
text-decoration: none;

16
im-uniapp/App.vue

@ -81,7 +81,7 @@
},
handlePrivateMessage(msg) {
//
if (msg.type == enums.MESSAGE_TYPE.LOADDING) {
if (msg.type == enums.MESSAGE_TYPE.LOADING) {
store.commit("loadingPrivateMsg", JSON.parse(msg.content))
return;
}
@ -109,12 +109,13 @@
},
insertPrivateMessage(friend, msg) {
//
if (msg.type >= 100 && msg.type <= 199) {
if (this.$msgType.isRtcPrivate(msg.type)) {
// #ifdef MP-WEIXIN
//
return;
// #endif
//
let delayTime = 100;
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";
@ -125,11 +126,12 @@
uni.navigateTo({
url: `/pages/chat/chat-private-video?mode=${mode}&friend=${friendInfo}&isHost=false`
})
delayTime = 500;
}
}
setTimeout(() => {
uni.$emit('WS_RTC_PRIVATE',msg);
},500)
},delayTime)
return;
}
let chatInfo = {
@ -143,12 +145,12 @@
//
store.commit("insertMessage", msg);
//
!msg.selfSend && this.playAudioTip();
this.playAudioTip();
},
handleGroupMessage(msg) {
//
if (msg.type == enums.MESSAGE_TYPE.LOADDING) {
if (msg.type == enums.MESSAGE_TYPE.LOADING) {
store.commit("loadingGroupMsg",JSON.parse(msg.content))
return;
}
@ -184,7 +186,7 @@
},
insertGroupMessage(group, msg) {
//
if (msg.type >= 200 && msg.type <= 299) {
if (this.$msgType.isRtcGroup(msg.type)) {
// #ifdef MP-WEIXIN
//
return;
@ -223,7 +225,7 @@
//
store.commit("insertMessage", msg);
//
!msg.selfSend && this.playAudioTip();
this.playAudioTip();
},
loadFriendInfo(id) {
return new Promise((resolve, reject) => {

6
im-uniapp/common/enums.js

@ -5,14 +5,14 @@ const MESSAGE_TYPE = {
FILE:2,
AUDIO:3,
VIDEO:4,
RT_VOICE:5,
RT_VIDEO:6,
RECALL:10,
READED:11,
RECEIPT:12,
TIP_TIME:20,
TIP_TEXT:21,
LOADDING:30,
LOADING:30,
ACT_RT_VOICE:40,
ACT_RT_VIDEO:41,
RTC_CALL_VOICE: 100,
RTC_CALL_VIDEO: 101,
RTC_ACCEPT: 102,

40
im-uniapp/common/messageType.js

@ -0,0 +1,40 @@
// 是否普通消息
let isNormal = function(type){
return type>=0 && type < 10;
}
// 是否状态消息
let isStatus = function(type){
return type>=10 && type < 20;
}
// 是否提示消息
let isTip = function(type){
return type>=20 && type < 30;
}
// 操作交互类消息
let isAction = function(type){
return type>=40 && type < 50;
}
// 单人通话信令
let isRtcPrivate = function(type){
return type>=100 && type < 300;
}
// 多人通话信令
let isRtcGroup = function(type){
return type>=200 && type < 400;
}
export {
isNormal,
isStatus,
isTip,
isAction,
isRtcPrivate,
isRtcGroup
}

19
im-uniapp/components/chat-message-item/chat-message-item.vue

@ -7,7 +7,7 @@
{{$date.toTimeText(msgInfo.sendTime)}}
</view>
<view class="chat-msg-normal" v-if="msgInfo.type>=0 && msgInfo.type<10"
<view class="chat-msg-normal" v-if="isNormal"
:class="{'chat-msg-mine':msgInfo.selfSend}">
<head-image class="avatar" @longpress.prevent="$emit('longPressHead')" :id="msgInfo.sendId" :url="headImage"
:name="showName" :size="80"></head-image>
@ -52,13 +52,13 @@
<text v-if="audioPlayState=='PAUSE'" class="iconfont icon-play"></text>
<text v-if="audioPlayState=='PLAYING'" class="iconfont icon-pause"></text>
</view>
<view class="chat-realtime chat-msg-text" v-if="isRTMessage"
<view class="chat-realtime chat-msg-text" v-if="isAction"
@click="$emit('call')" @longpress="onShowMenu($event)">
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.RT_VOICE" class="iconfont icon-chat-voice"></text>
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.RT_VIDEO" class="iconfont icon-chat-video"></text>
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VOICE" class="iconfont icon-chat-voice"></text>
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VIDEO" class="iconfont icon-chat-video"></text>
<text>{{msgInfo.content}}</text>
</view>
<view class="chat-msg-status" v-if="!isRTMessage">
<view class="chat-msg-status" v-if="!isAction">
<text class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status==$enums.MESSAGE_STATUS.READED">已读</text>
<text class="chat-unread" v-show="msgInfo.selfSend && !msgInfo.groupId
@ -225,9 +225,12 @@
}
return items;
},
isRTMessage() {
return this.msgInfo.type == this.$enums.MESSAGE_TYPE.RT_VOICE ||
this.msgInfo.type == this.$enums.MESSAGE_TYPE.RT_VIDEO
isAction(){
return this.$msgType.isAction(this.msgInfo.type);
},
isNormal() {
const type = this.msgInfo.type;
return this.$msgType.isNormal(type) || this.$msgType.isAction(type)
}
}

2
im-uniapp/main.js

@ -4,6 +4,7 @@ import emotion from './common/emotion.js';
import * as enums from './common/enums.js';
import * as date from './common/date';
import * as socketApi from './common/wssocket';
import * as messageType from './common/messageType';
import store from './store';
import { createSSRApp } from 'vue'
// #ifdef H5
@ -19,6 +20,7 @@ export function createApp() {
app.use(store);
app.config.globalProperties.$http = request;
app.config.globalProperties.$wsApi = socketApi;
app.config.globalProperties.$msgType = messageType;
app.config.globalProperties.$emo = emotion;
app.config.globalProperties.$enums = enums;
app.config.globalProperties.$date = date;

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

@ -8,7 +8,7 @@
<view class="chat-msg" @click="switchChatTabBox('none',true)">
<scroll-view class="scroll-box" scroll-y="true" upper-threshold="200" @scrolltoupper="onScrollToTop"
:scroll-into-view="'chat-item-'+scrollMsgIdx">
<view v-for="(msgInfo,idx) in chat.messages" :key="idx">
<view v-if="chat" v-for="(msgInfo,idx) in chat.messages" :key="idx">
<chat-message-item v-if="idx>=showMinIdx&&!msgInfo.delete" :headImage="headImage(msgInfo)"
@call="onRtCall(msgInfo)" :showName="showName(msgInfo)" @recall="onRecallMessage"
@delete="onDeleteMessage" @longPressHead="onLongPressHead(msgInfo)" @download="onDownloadFile"
@ -37,7 +37,7 @@
@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 v-if="chat && chat.type=='GROUP'" class="iconfont icon-at" @click="openAtBox()"></view>
<view class="iconfont icon-icon_emoji" @click="onShowEmoChatTab()"></view>
<view v-if="sendText==''" class="iconfont icon-add" @click="onShowToolsChatTab()">
</view>
@ -133,6 +133,7 @@
keyboardHeight: 322,
atUserIds: [],
recordText: "",
needScrollToBottom: false, //
showMinIdx: 0 // showMinIdx
}
},
@ -174,9 +175,9 @@
})
},
onRtCall(msgInfo) {
if (msgInfo.type == this.$enums.MESSAGE_TYPE.RT_VOICE) {
if (msgInfo.type == this.$enums.MESSAGE_TYPE.ACT_RT_VOICE) {
this.onPriviteVoice();
} else if (msgInfo.type == this.$enums.MESSAGE_TYPE.RT_VIDEO) {
} else if (msgInfo.type == this.$enums.MESSAGE_TYPE.ACT_RT_VIDEO) {
this.onPriviteVideo();
}
},
@ -692,7 +693,14 @@
messageSize: function(newSize, oldSize) {
//
if (newSize > oldSize) {
console.log("messageSize",newSize,oldSize)
let pages = getCurrentPages();
let curPage = pages[pages.length-1].route;
if(curPage == "pages/chat/chat-box"){
this.scrollToBottom();
}else {
this.needScrollToBottom = true;
}
}
},
unreadCount: {
@ -723,11 +731,16 @@
this.$store.commit("activeChat", options.chatIdx);
//
this.isReceipt = false;
//
this.scrollToBottom();
},
onUnload() {
this.$store.commit("activeChat", -1);
},
onShow(){
if(this.needScrollToBottom){
//
this.scrollToBottom();
this.needScrollToBottom = false;
}
}
}
</script>

13
im-uniapp/pages/chat/chat-private-video.vue

@ -20,16 +20,6 @@
onMessage(e) {
this.onWebviewMessage(e.detail.data[0]);
},
onInsertMessage(msgInfo){
let chat = {
type: 'PRIVATE',
targetId: this.friend.id,
showName: this.friend.nickName,
headImage: this.friend.headImage,
};
this.$store.commit("openChat",chat);
this.$store.commit("insertMessage", msgInfo);
},
onWebviewMessage(event) {
console.log("来自webview的消息:" + JSON.stringify(event))
switch (event.key) {
@ -39,9 +29,6 @@
case "WV_CLOSE":
uni.navigateBack();
break;
case "INSERT_MESSAGE":
this.onInsertMessage(event.data);
break;
}
},
sendMessageToWebView(key, message) {

12
im-uniapp/store/chatStore.js

@ -171,9 +171,9 @@ export default {
chat.lastContent = "[语音]";
} else if (msgInfo.type == MESSAGE_TYPE.TEXT || msgInfo.type == MESSAGE_TYPE.RECALL) {
chat.lastContent = msgInfo.content;
} else if (msgInfo.type == MESSAGE_TYPE.RT_VOICE) {
} else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) {
chat.lastContent = "[语音通话]";
} else if (msgInfo.type == MESSAGE_TYPE.RT_VIDEO) {
} else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) {
chat.lastContent = "[视频通话]";
}
chat.lastSendTime = msgInfo.sendTime;
@ -275,14 +275,14 @@ export default {
}
this.commit("saveToStorage");
},
loadingPrivateMsg(state, loadding) {
state.loadingPrivateMsg = loadding;
loadingPrivateMsg(state, loading) {
state.loadingPrivateMsg = loading;
if (!this.getters.isLoading()) {
this.commit("refreshChats")
}
},
loadingGroupMsg(state, loadding) {
state.loadingGroupMsg = loadding;
loadingGroupMsg(state, loading) {
state.loadingGroupMsg = loading;
if (!this.getters.isLoading()) {
this.commit("refreshChats")
}

Loading…
Cancel
Save