diff --git a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java index 9aa66d2..460dfa7 100644 --- a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java +++ b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java @@ -4,7 +4,7 @@ import com.bx.imclient.listener.MessageListenerMulticaster; import com.bx.imcommon.contant.RedisKey; import com.bx.imcommon.enums.IMCmdType; import com.bx.imcommon.enums.IMListenerType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.GroupMessageInfo; import com.bx.imcommon.model.IMRecvInfo; import com.bx.imcommon.model.PrivateMessageInfo; @@ -54,8 +54,7 @@ public class IMSender { SendResult result = new SendResult(); result.setMessageInfo(messageInfo); result.setRecvId(recvId); - result.setStatus(IMSendStatus.FAIL); - result.setFailReason("用户不在线"); + result.setCode(IMSendCode.NOT_ONLINE); listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, result); } } @@ -103,8 +102,7 @@ public class IMSender { SendResult result = new SendResult(); result.setMessageInfo(messageInfo); result.setRecvId(id); - result.setStatus(IMSendStatus.FAIL); - result.setFailReason("用户不在线"); + result.setCode(IMSendCode.NOT_ONLINE); listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE,result); } } diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMSendStatus.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java similarity index 61% rename from im-commom/src/main/java/com/bx/imcommon/enums/IMSendStatus.java rename to im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java index cf82384..62ad254 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMSendStatus.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java @@ -1,16 +1,18 @@ package com.bx.imcommon.enums; -public enum IMSendStatus { +public enum IMSendCode { SUCCESS(0,"发送成功"), - FAIL(1,"发送失败"); + NOT_ONLINE(1,"对方当前不在线"), + NOT_FIND_CHANNEL(2,"未找到对方的channel"), + UNKONW_ERROR(9999,"未知异常"); private int code; private String desc; // 构造方法 - IMSendStatus(int code, String desc) { + IMSendCode(int code, String desc) { this.code = code; this.desc = desc; } diff --git a/im-commom/src/main/java/com/bx/imcommon/model/SendResult.java b/im-commom/src/main/java/com/bx/imcommon/model/SendResult.java index 66ea316..2a65b82 100644 --- a/im-commom/src/main/java/com/bx/imcommon/model/SendResult.java +++ b/im-commom/src/main/java/com/bx/imcommon/model/SendResult.java @@ -1,6 +1,6 @@ package com.bx.imcommon.model; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import lombok.Data; @Data @@ -14,12 +14,7 @@ public class SendResult { /* * 发送状态 */ - private IMSendStatus status; - - /* - * 失败原因 - */ - private String failReason=""; + private IMSendCode code; /* * 消息体(透传) diff --git a/im-platform/src/main/java/com/bx/implatform/config/ICEServer.java b/im-platform/src/main/java/com/bx/implatform/config/ICEServer.java new file mode 100644 index 0000000..d29c182 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/ICEServer.java @@ -0,0 +1,19 @@ +package com.bx.implatform.config; + + +import lombok.Data; + + +@Data +public class ICEServer { + + + private String urls; + + + private String username; + + + private String credential; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/ICEServerConfig.java b/im-platform/src/main/java/com/bx/implatform/config/ICEServerConfig.java new file mode 100644 index 0000000..43b1b52 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/ICEServerConfig.java @@ -0,0 +1,17 @@ +package com.bx.implatform.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Component +@ConfigurationProperties(prefix="webrtc") +public class ICEServerConfig { + + private List iceServers = new ArrayList<>(); + +} diff --git a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java new file mode 100644 index 0000000..b1fccd4 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java @@ -0,0 +1,126 @@ +package com.bx.implatform.controller; + + +import com.bx.imclient.IMClient; +import com.bx.imcommon.model.PrivateMessageInfo; +import com.bx.implatform.config.ICEServerConfig; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.session.SessionContext; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@Api(tags = "webrtc视频单人通话") +@RestController +@RequestMapping("/webrtc/private") +public class WebrtcController { + + @Autowired + private IMClient imClient; + + @Autowired + private ICEServerConfig iceServerConfig; + + @ApiOperation(httpMethod = "POST", value = "呼叫视频通话") + @PostMapping("/call") + public Result call(@RequestParam Long uid, @RequestBody String offer) { + Long userId = SessionContext.getSession().getId(); + + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_CALL.code()); + message.setRecvId(uid); + message.setSendId(userId); + message.setContent(offer); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @ApiOperation(httpMethod = "POST", value = "接受视频通话") + @PostMapping("/accept") + public Result accept(@RequestParam Long uid,@RequestBody String answer) { + Long userId = SessionContext.getSession().getId(); + + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_ACCEPT.code()); + message.setRecvId(uid); + message.setSendId(userId); + message.setContent(answer); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + + @ApiOperation(httpMethod = "POST", value = "拒绝视频通话") + @PostMapping("/reject") + public Result reject(@RequestParam Long uid) { + Long userId = SessionContext.getSession().getId(); + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_REJECT.code()); + message.setRecvId(uid); + message.setSendId(userId); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @ApiOperation(httpMethod = "POST", value = "取消呼叫") + @PostMapping("/cancel") + public Result cancel(@RequestParam Long uid) { + Long userId = SessionContext.getSession().getId(); + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_CANCEL.code()); + message.setRecvId(uid); + message.setSendId(userId); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @ApiOperation(httpMethod = "POST", value = "呼叫失败") + @PostMapping("/failed") + public Result failed(@RequestParam Long uid,@RequestParam String reason) { + Long userId = SessionContext.getSession().getId(); + + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_FAILED.code()); + message.setRecvId(uid); + message.setSendId(userId); + message.setContent(reason); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @ApiOperation(httpMethod = "POST", value = "挂断") + @PostMapping("/handup") + public Result leave(@RequestParam Long uid) { + Long userId = SessionContext.getSession().getId(); + + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_HANDUP.code()); + message.setRecvId(uid); + message.setSendId(userId); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + + @PostMapping("/candidate") + @ApiOperation(httpMethod = "POST", value = "同步candidate") + public Result candidate(@RequestParam Long uid,@RequestBody String candidate ) { + Long userId = SessionContext.getSession().getId(); + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_CANDIDATE.code()); + message.setRecvId(uid); + message.setSendId(userId); + message.setContent(candidate); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @GetMapping("/iceservers") + @ApiOperation(httpMethod = "GET", value = "获取iceservers") + public Result iceservers() { + return ResultUtils.success(iceServerConfig.getIceServers()); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java index f6a0622..cc7a07c 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java @@ -7,7 +7,15 @@ public enum MessageType { FILE(1,"文件"), IMAGE(2,"图片"), VIDEO(3,"视频"), - TIP(10,"系统提示"); + TIP(10,"系统提示"), + + RTC_CALL(101,"呼叫"), + RTC_ACCEPT(102,"接受"), + RTC_REJECT(103, "拒绝"), + RTC_CANCEL(104,"取消呼叫"), + RTC_FAILED(105,"呼叫失败"), + RTC_HANDUP(106,"挂断"), + RTC_CANDIDATE(107,"同步candidate"); private Integer code; diff --git a/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java index 3a8a745..1258781 100644 --- a/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java +++ b/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java @@ -3,7 +3,7 @@ package com.bx.implatform.listener; import com.bx.imclient.annotation.IMListener; import com.bx.imclient.listener.MessageListener; import com.bx.imcommon.enums.IMListenerType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.GroupMessageInfo; import com.bx.imcommon.model.SendResult; import com.bx.implatform.contant.RedisKey; @@ -29,7 +29,7 @@ public class GroupMessageListener implements MessageListener { } // 保存该用户已拉取的最大消息id - if(result.getStatus().equals(IMSendStatus.SUCCESS)) { + if(result.getCode().equals(IMSendCode.SUCCESS)) { String key = RedisKey.IM_GROUP_READED_POSITION + messageInfo.getGroupId() + ":" + result.getRecvId(); redisTemplate.opsForValue().set(key, messageInfo.getId()); } diff --git a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java index 636ee4e..b7fad41 100644 --- a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java +++ b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java @@ -1,10 +1,11 @@ package com.bx.implatform.listener; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.bx.imclient.IMClient; import com.bx.imclient.annotation.IMListener; import com.bx.imclient.listener.MessageListener; import com.bx.imcommon.enums.IMListenerType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.PrivateMessageInfo; import com.bx.imcommon.model.SendResult; import com.bx.implatform.entity.PrivateMessage; @@ -14,6 +15,8 @@ import com.bx.implatform.service.IPrivateMessageService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import java.util.Date; + @Slf4j @IMListener(type = IMListenerType.PRIVATE_MESSAGE) @@ -22,15 +25,33 @@ public class PrivateMessageListener implements MessageListener { @Autowired private IPrivateMessageService privateMessageService; + @Autowired + private IMClient imClient; + @Override public void process(SendResult result){ PrivateMessageInfo messageInfo = (PrivateMessageInfo) result.getMessageInfo(); - if(messageInfo.getType().equals(MessageType.TIP)){ - // 提示类数据不记录 + // 提示类数据不记录 + if(messageInfo.getType().equals(MessageType.TIP.code())){ + return; } + // 视频通话信令不记录 + if(messageInfo.getType() >= MessageType.RTC_CALL.code() && messageInfo.getType()< MessageType.RTC_CANDIDATE.code()){ + // 通知用户呼叫失败了 + if(messageInfo.getType() == MessageType.RTC_CALL.code() + && !result.getCode().equals(IMSendCode.SUCCESS)){ + PrivateMessageInfo sendMessage = new PrivateMessageInfo(); + sendMessage.setRecvId(messageInfo.getSendId()); + sendMessage.setSendId(messageInfo.getRecvId()); + sendMessage.setType(MessageType.RTC_FAILED.code()); + sendMessage.setContent(result.getCode().description()); + sendMessage.setSendTime(new Date()); + imClient.sendPrivateMessage(sendMessage.getRecvId(),sendMessage); + } + } // 更新消息状态 - if(result.getStatus().equals(IMSendStatus.SUCCESS)){ + if(result.getCode().equals(IMSendCode.SUCCESS)){ UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.lambda().eq(PrivateMessage::getId,messageInfo.getId()) .eq(PrivateMessage::getStatus, MessageStatus.UNREAD.code()) diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java index 2b5769c..d24c9df 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java @@ -74,6 +74,8 @@ public class GroupMessageServiceImpl extends ServiceImpluserId!=id).collect(Collectors.toList()); // 群发 GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class); imClient.sendGroupMessage(userIds,msgInfo); @@ -112,6 +114,8 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId()); + // 不用发给自己 + userIds = userIds.stream().filter(uid->userId.equals(uid)).collect(Collectors.toList()); GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class); msgInfo.setType(MessageType.TIP.code()); String content = String.format("'%s'撤回了一条消息",member.getAliasName()); @@ -140,6 +144,7 @@ public class GroupMessageServiceImpl extends ServiceImpl wrapper = new QueryWrapper(); wrapper.lambda().eq(GroupMessage::getGroupId,member.getGroupId()) .gt(GroupMessage::getSendTime,member.getCreatedTime()) + .ne(GroupMessage::getSendId, userId) .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()); if(maxReadedId!=null){ wrapper.lambda().gt(GroupMessage::getId,maxReadedId); @@ -198,4 +203,5 @@ public class GroupMessageServiceImpl extends ServiceImpl imServers; + /*** + * 判断服务器是否就绪 + * + * @return + **/ + public boolean isReady(){ + for(IMServer imServer:imServers){ + if(!imServer.isReady()){ + return false; + } + } + return true; + } + @Override public void run(String... args) throws Exception { // 初始化SERVER_ID diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java index b506551..3b589b9 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java @@ -2,7 +2,7 @@ package com.bx.imserver.netty.processor; import com.bx.imcommon.contant.RedisKey; import com.bx.imcommon.enums.IMCmdType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.GroupMessageInfo; import com.bx.imcommon.model.IMRecvInfo; import com.bx.imcommon.model.IMSendInfo; @@ -34,28 +34,25 @@ public class GroupMessageProcessor extends MessageProcessor { ctx.channel().attr(attr).set(0L); // 在redis上记录每个user的channelId,15秒没有心跳,则自动过期 String key = RedisKey.IM_USER_SERVER_ID+loginInfo.getUserId(); - redisTemplate.opsForValue().set(key, IMServerMap.serverId, Constant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS); + redisTemplate.opsForValue().set(key, IMServerGroup.serverId, Constant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS); // 响应ws IMSendInfo sendInfo = new IMSendInfo(); sendInfo.setCmd(IMCmdType.LOGIN.code()); diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java index d40eb0e..659412c 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java @@ -2,7 +2,7 @@ package com.bx.imserver.netty.processor; import com.bx.imcommon.contant.RedisKey; import com.bx.imcommon.enums.IMCmdType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.IMRecvInfo; import com.bx.imcommon.model.IMSendInfo; import com.bx.imcommon.model.PrivateMessageInfo; @@ -38,7 +38,7 @@ public class PrivateMessageProcessor extends MessageProcessor { hasLogin = false; websock = new WebSocket(wsurl); websock.onmessage = function(e) { - let msg = JSON.parse(e.data) - if (msg.cmd == 0) { + let sendInfo = JSON.parse(e.data) + if (sendInfo.cmd == 0) { hasLogin = true; heartCheck.start() console.log('WebSocket登录成功') // 登录成功才算连接完成 openCallBack && openCallBack(); } - else if(msg.cmd==1){ + else if(sendInfo.cmd==1){ // 重新开启心跳定时 heartCheck.reset(); } else { // 其他消息转发出去 - messageCallBack && messageCallBack(JSON.parse(e.data)) + messageCallBack && messageCallBack(sendInfo.cmd,sendInfo.data) } } websock.onclose = function(e) { diff --git a/im-ui/src/assets/audio/call.wav b/im-ui/src/assets/audio/call.wav new file mode 100644 index 0000000..754e33c Binary files /dev/null and b/im-ui/src/assets/audio/call.wav differ diff --git a/im-ui/src/assets/audio/tip.wav b/im-ui/src/assets/audio/tip.wav new file mode 100644 index 0000000..adc45dd Binary files /dev/null and b/im-ui/src/assets/audio/tip.wav differ diff --git a/im-ui/src/assets/iconfont/iconfont.css b/im-ui/src/assets/iconfont/iconfont.css index c76ce8c..0b6840e 100644 --- a/im-ui/src/assets/iconfont/iconfont.css +++ b/im-ui/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { - font-family: "iconfont"; /* Project id 3776657 */ - src: url('iconfont.woff2?t=1668665799410') format('woff2'), - url('iconfont.woff?t=1668665799410') format('woff'), - url('iconfont.ttf?t=1668665799410') format('truetype'); + font-family: "iconfont"; /* Project id 3791506 */ + src: url('iconfont.woff2?t=1669336625993') format('woff2'), + url('iconfont.woff?t=1669336625993') format('woff'), + url('iconfont.ttf?t=1669336625993') format('truetype'); } .iconfont { @@ -41,3 +41,11 @@ content: "\e953"; } +.icon-phone-reject:before { + content: "\e605"; +} + +.icon-phone-accept:before { + content: "\e8be"; +} + diff --git a/im-ui/src/assets/iconfont/iconfont.ttf b/im-ui/src/assets/iconfont/iconfont.ttf index 9a4d5b0..9450138 100644 Binary files a/im-ui/src/assets/iconfont/iconfont.ttf and b/im-ui/src/assets/iconfont/iconfont.ttf differ diff --git a/im-ui/src/assets/iconfont/iconfont.woff b/im-ui/src/assets/iconfont/iconfont.woff index cbd67e3..70cb168 100644 Binary files a/im-ui/src/assets/iconfont/iconfont.woff and b/im-ui/src/assets/iconfont/iconfont.woff differ diff --git a/im-ui/src/assets/iconfont/iconfont.woff2 b/im-ui/src/assets/iconfont/iconfont.woff2 index 94a9411..c788739 100644 Binary files a/im-ui/src/assets/iconfont/iconfont.woff2 and b/im-ui/src/assets/iconfont/iconfont.woff2 differ diff --git a/im-ui/src/components/chat/ChatBox.vue b/im-ui/src/components/chat/ChatBox.vue index 8228bce..98a881f 100644 --- a/im-ui/src/components/chat/ChatBox.vue +++ b/im-ui/src/components/chat/ChatBox.vue @@ -2,8 +2,7 @@ {{title}} - + @@ -12,11 +11,9 @@
  • - - + +
@@ -26,24 +23,24 @@
-
- +
+
+
- +
发送
@@ -57,25 +54,22 @@
- +
diff --git a/im-ui/src/components/chat/ChatVideoAcceptor.vue b/im-ui/src/components/chat/ChatVideoAcceptor.vue new file mode 100644 index 0000000..77c97e3 --- /dev/null +++ b/im-ui/src/components/chat/ChatVideoAcceptor.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/im-ui/src/components/chat/ChatVoice.vue b/im-ui/src/components/chat/ChatVoice.vue index 223eb49..48bdc2c 100644 --- a/im-ui/src/components/chat/ChatVoice.vue +++ b/im-ui/src/components/chat/ChatVoice.vue @@ -19,7 +19,6 @@ 重新录音 立即发送 - @@ -61,7 +60,8 @@ this.state = 'RUNNING'; this.stateTip = "正在录音..."; }).catch(error => { - this.$message.error("录音失败"); + console.log(error); + this.$message.error(error); console.log(error); }); diff --git a/im-ui/src/main.js b/im-ui/src/main.js index 5fb1b03..530df50 100644 --- a/im-ui/src/main.js +++ b/im-ui/src/main.js @@ -9,7 +9,8 @@ import * as socketApi from './api/wssocket'; import emotion from './api/emotion.js'; import element from './api/element.js'; import store from './store'; - +import * as enums from './api/enums.js'; +import './utils/directive/dialogDrag'; Vue.use(ElementUI); @@ -18,7 +19,7 @@ Vue.prototype.$wsApi = socketApi; Vue.prototype.$http = httpRequest // http请求方法 Vue.prototype.$emo = emotion; // emo表情 Vue.prototype.$elm = element; // 元素操作 - +Vue.prototype.$enums = enums; // 枚举 Vue.config.productionTip = false; new Vue({ diff --git a/im-ui/src/store/uiStore.js b/im-ui/src/store/uiStore.js index 351f6f4..1fd5d5a 100644 --- a/im-ui/src/store/uiStore.js +++ b/im-ui/src/store/uiStore.js @@ -11,6 +11,17 @@ export default { fullImage: { // 全屏大图 show: false, url: "" + }, + chatPrivateVideo:{ // 私人视频聊天 + show: false, + master: false, // 是否房主 + friend:{}, + offer:{} // 对方发起带过过来的sdp信息 + }, + videoAcceptor:{ // 视频呼叫选择 + show: false, + + friend:{} } }, @@ -35,6 +46,23 @@ export default { }, closeFullImageBox(state){ state.fullImage.show = false; + }, + showChatPrivateVideoBox(state,info){ + state.chatPrivateVideo.show = true; + state.chatPrivateVideo.friend = info.friend; + state.chatPrivateVideo.master = info.master; + state.chatPrivateVideo.offer = info.offer; + }, + closeChatPrivateVideoBox(state){ + state.chatPrivateVideo.show = false; + }, + showVideoAcceptorBox(state,friend){ + state.videoAcceptor.show = true; + state.videoAcceptor.friend = friend; + + }, + closeVideoAcceptorBox(state){ + state.videoAcceptor.show = false; } - }, + } } \ No newline at end of file diff --git a/im-ui/src/store/userStore.js b/im-ui/src/store/userStore.js index 4ad45bd..478ce8f 100644 --- a/im-ui/src/store/userStore.js +++ b/im-ui/src/store/userStore.js @@ -1,7 +1,12 @@ +import {USER_STATE} from "../api/enums.js" + export default { state: { - userInfo: {} + userInfo: { + + }, + state: USER_STATE.FREE }, mutations: { @@ -12,7 +17,10 @@ export default { this.commit("resetChatStore"); } state.userInfo = userInfo; - } + }, + setUserState(state, userState) { + state.state = userState; + }, } } \ No newline at end of file diff --git a/im-ui/src/utils/directive/dialogDrag.js b/im-ui/src/utils/directive/dialogDrag.js new file mode 100644 index 0000000..5a35d2c --- /dev/null +++ b/im-ui/src/utils/directive/dialogDrag.js @@ -0,0 +1,72 @@ +import Vue from 'vue' +  +// v-dialogDrag: 弹窗拖拽 +Vue.directive('dialogDrag', { +  bind (el, binding, vnode, oldVnode) { +    const dialogHeaderEl = el.querySelector('.el-dialog__header') +    const dragDom = el.querySelector('.el-dialog') +    dialogHeaderEl.style.cursor = 'move' +  +    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); +    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null) +  +    dialogHeaderEl.onmousedown = (e) => { +      // 鼠标按下,计算当前元素距离可视区的距离 +      const disX = e.clientX - dialogHeaderEl.offsetLeft +      const disY = e.clientY - dialogHeaderEl.offsetTop +      const screenWidth = document.body.clientWidth; // body当前宽度 +      const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取) +      const dragDomWidth = dragDom.offsetWidth; // 对话框宽度 +      const dragDomheight = dragDom.offsetHeight; // 对话框高度 +      const minDragDomLeft = dragDom.offsetLeft; +      const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth; +      const minDragDomTop = dragDom.offsetTop; +      const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight; +  +      // 获取到的值带px 正则匹配替换 +      let styL, styT +  +      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px +      if (sty.left.includes('%')) { +        styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100) +        styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100) +      } else { +        styL = +sty.left.replace(/\px/g, '') +        styT = +sty.top.replace(/\px/g, '') +      } +  +      document.onmousemove = function (e) { +        // 获取body的页面可视宽高 +        // var clientHeight = document.documentElement.clientHeight || document.body.clientHeight +        // var clientWidth = document.documentElement.clientWidth || document.body.clientWidth +  +        // 通过事件委托,计算移动的距离 +        var l = e.clientX - disX +        var t = e.clientY - disY +  +        // 边界处理 +        if (-l > minDragDomLeft) { +          l = -minDragDomLeft; +        } else if (l > maxDragDomLeft) { +          l = maxDragDomLeft; +        } +        if (-t > minDragDomTop) { +          t = -minDragDomTop; +        } else if (t > maxDragDomTop) { +          t = maxDragDomTop; +        } +        // 移动当前元素 +        dragDom.style.left = `${l + styL}px` +        dragDom.style.top = `${t + styT}px` +  +        // 将此时的位置传出去 +        // binding.value({x:e.pageX,y:e.pageY}) +      } +  +      document.onmouseup = function (e) { +        document.onmousemove = null +        document.onmouseup = null +      } +    } +  } +}) diff --git a/im-ui/src/view/Home.vue b/im-ui/src/view/Home.vue index 4bc2a62..a3b8b89 100644 --- a/im-ui/src/view/Home.vue +++ b/im-ui/src/view/Home.vue @@ -38,6 +38,17 @@ + + + + @@ -46,42 +57,49 @@ import Setting from '../components/setting/Setting.vue'; import UserInfo from '../components/common/UserInfo.vue'; import FullImage from '../components/common/FullImage.vue'; - + import ChatPrivateVideo from '../components/chat/ChatPrivateVideo.vue'; + import ChatVideoAcceptor from '../components/chat/ChatVideoAcceptor.vue'; + + export default { components: { HeadImage, Setting, UserInfo, - FullImage + FullImage, + ChatPrivateVideo, + ChatVideoAcceptor }, data() { return { - showSettingDialog: false + showSettingDialog: false, + } }, methods: { init(userInfo) { this.$store.commit("setUserInfo", userInfo); + this.$store.commit("setUserState", this.$enums.USER_STATE.FREE); this.$store.commit("initStore"); this.$wsApi.createWebSocket(process.env.VUE_APP_WS_URL, userInfo.id); this.$wsApi.onopen(() => { this.pullUnreadMessage(); }); - this.$wsApi.onmessage((e) => { - if (e.cmd == 2) { + this.$wsApi.onmessage((cmd,msgInfo) => { + if (cmd == 2) { // 异地登录,强制下线 this.$message.error("您已在其他地方登陆,将被强制下线"); setTimeout(() => { location.href = "/"; }, 1000) - } else if (e.cmd == 3) { + } else if (cmd == 3) { // 插入私聊消息 - this.handlePrivateMessage(e.data); - } else if (e.cmd == 4) { + this.handlePrivateMessage(msgInfo); + } else if (cmd == 4) { // 插入群聊消息 - this.handleGroupMessage(e.data); - } + this.handleGroupMessage(msgInfo); + } }) }, pullUnreadMessage() { @@ -113,6 +131,21 @@ }) }, insertPrivateMessage(friend, msg) { + // webrtc 信令 + if(msg.type >= this.$enums.MESSAGE_TYPE.RTC_CALL + && msg.type <= this.$enums.MESSAGE_TYPE.RTC_CANDIDATE){ + // 呼叫 + console.log(msg) + if(msg.type == this.$enums.MESSAGE_TYPE.RTC_CALL + || msg.type == this.$enums.MESSAGE_TYPE.RTC_CANCEL){ + this.$store.commit("showVideoAcceptorBox",friend); + this.$refs.videoAcceptor.handleMessage(msg) + }else { + this.$refs.privateVideo.handleMessage(msg) + } + return ; + } + let chatInfo = { type: 'PRIVATE', targetId: friend.id, @@ -123,6 +156,8 @@ this.$store.commit("openChat", chatInfo); // 插入消息 this.$store.commit("insertMessage", msg); + // 播放提示音 + this.playAudioTip(); }, handleGroupMessage(msg) { // 群聊缓存存在,直接插入群聊消息 @@ -151,6 +186,8 @@ this.$store.commit("openChat", chatInfo); // 插入消息 this.$store.commit("insertMessage", msg); + // 播放提示音 + this.playAudioTip(); }, handleExit() { this.$http({ @@ -161,6 +198,12 @@ location.href = "/"; }) }, + playAudioTip(){ + let audio = new Audio(); + let url = require(`@/assets/audio/tip.wav`); + audio.src = url; + audio.play(); + }, showSetting() { this.showSettingDialog = true; },