import { useWebRTCStore } from "@/stores/modules/webrtcStore"; import * as MsgType from "@/common/constant/msgType"; import * as Constant from "@/common/constant/Constant"; import { setupNotifications, soundVoice } from "@/utils/notifications.js"; import { useWebSocketStore } from "@/stores/modules/webSocketStore"; import { useWalletStore } from "@/stores/modules/walletStore.js"; const IM_PATH = import.meta.env.VITE_IM_PATH_FIlE; // 格式化扩展消息提取 const formatMessageExt = (message, ext) => { const isExt = ext && typeof ext == 'object'; if(isExt){ return { ...message, content:ext.content, msgId: ext.msgId, // fromUuids: ext.fromUuid, cc : ext.cc, // @群成员 0:全部 id : ext.id, // 真实消息id quote : ext.quote, //引用的消息id isTemp : ext.isTemp, //消息阅后即焚 sessionId: ext.sessionId, //会话ID sessionUuid: ext.sessionUuid, //会话UUID } } return message } // 格式化头像地址 const formatAvatarUrl = (url) => url && /^https?:\/\//.test(url) ? url: (IM_PATH + (url || '')) // 消息通知 const notifications = (state)=>{ // 检测是否开启免打扰 if (!state.toUserInfo.slience) { setupNotifications(1) } } /** * 发送消息处理 * @param {{}} message * @param {useWebSocketStore} wsStore */ export const setMessageHook = (message, wsStore) => { const walletStore = useWalletStore(); console.log('发送消息',message) // 尝试解析扩展消息JOSON let msg; try{ msg = JSON.parse(message.content); if (msg && typeof msg !== 'object') { msg = '' } }catch(e){} // 处理好友系统消息 wsStore.funApprovalFriend(message) // 过滤发送的系统消息 if (MsgType.MSG_SYSTEM_GROUP.includes(message.messageType)) { return; } // 处理个人消息撤回 if (message.messageType === MsgType.MESSAGE_REVOKE) { // console.log('消息撤回',message) // 更新消息 if (msg && msg.isTemp) { wsStore.modifyMessageId({isTemp: true}, msg, message.to) } else { wsStore.deleteMessage(message, msg.id, message.to); } // 更新会话列表 if(msg.isTemp){ wsStore.updateSessionNewMessage({...message, ...msg, content:'[消息已销毁]'}, message.to); } else { wsStore.pushMessage({ ...message, align: 'right', fromAvatar: message.avatar, toUuid: message.to, fromUuid: message.from, content: `${message.from == walletStore.account ? '你' : (message.fromUsername || '对方')}撤回了一条消息`, }, message.to); } } // 语音和视频挂断/拒接消息处理 if (Constant.MSG_AUDIO_GROUP.includes(message.contentType)) { let sender = wsStore.toUserAudioInfo.sender || { uuid: walletStore.account, nickname: walletStore.username, avatar: walletStore.avatar, } wsStore.pushMessage({ ...message, align: sender.uuid == message.from ? 'right' : 'left', fromAvatar: message.avatar, toUuid: message.to, fromUuid: message.from, content: message.contentType === Constant.REJECT_AUDIO_ONLINE || message.contentType === Constant.REJECT_VIDEO_ONLINE ? '[对方拒绝]' : '[通话结束]', sender, }, message.to); } // 文本消息 if (message.contentType == MsgType.MSG_TYPE.TEXT) { msg.isTemp=false; wsStore.pushMessage(formatMessageExt({ ...message, align: 'right', fromAvatar: message.avatar, toUuid:message.to, fromUuid:message.from, }, msg), message.to); } // 音频消息 if (message.contentType === MsgType.MSG_TYPE.AUDIO) { msg.isTemp = false; const blob = new Blob([message.file], { type: message.fileSuffix }); const url = URL.createObjectURL(blob); wsStore.pushMessage(formatMessageExt({ ...message, toUuid: message.to, fromUuid: message.from, align: 'right', fromAvatar: message.avatar, localUrl: url, }, msg), message.to); } // 图片 if (message.contentType === MsgType.MSG_TYPE.IMAGE) { const blob = new Blob([message.file], { type: message.fileSuffix }); const url = URL.createObjectURL(blob); wsStore.pushMessage(formatMessageExt({ ...message, toUuid: message.to, fromUuid: message.from, align: 'right', fromAvatar: message.avatar, localUrl: url, }, msg), message.to); } // 视频消息 if (message.contentType === MsgType.MSG_TYPE.VIDEO) { const blob = new Blob([message.file], { type: message.fileSuffix }); const url = URL.createObjectURL(blob); wsStore.pushMessage(formatMessageExt({ ...message, toUuid: message.to, fromUuid: message.from, align: 'right', fromAvatar: message.avatar, localUrl: url, }, msg), message.to); } }; /** * 接收到消息处理 * @param {{}} payload * @param {useWebSocketStore} wsStore * @returns */ export const handleMessageHook = async (payload, wsStore) => { // console.log('接收消息', payload); const walletStore = useWalletStore(); const message = {...payload}; // 过滤欢迎消息和心跳 if (message.type === MsgType.MESSAGE_HEAT_BEAT || message.from == 'System'){ return; } console.log('接收消息', message); // if (!message.contentType && !message.messageType && !message.type) return; // 尝试解析扩展消息JOSON let msg; try{ msg = JSON.parse(message.content); if (msg && typeof msg !== 'object') { msg = '' } }catch(e){} // 处理消息回执, 更新消息id if (message.messageType === MsgType.MESSAGE_RECEIPT) { // 检测是否在群内或是好友 wsStore.modifyMessageId(message, { id: msg.id, uniqueId: msg.uniqueId, msgId: msg.msgId }, message.from) return; } // 消息撤回/阅后即焚(个人和群) if (message.messageType === MsgType.MESSAGE_REVOKE || message.messageType === MsgType.MESSAGE_REVOKE_GROUP) { // 消息送达回执发送 wsStore.systemReceiptMessage(message, msg) // 更新消息 if (msg && msg.isTemp) { wsStore.modifyMessageId({isTemp: true}, msg,message.from) } else { wsStore.deleteMessage(message, msg.id); } // 更新会话列表 if (msg.isTemp) { wsStore.updateSessionNewMessage({...message, ...msg, content: '[消息已销毁]'}, message.from); } else { wsStore.pushMessage({ ...message, toUuid: wsStore.toUserInfo.type == 'user' ? message.to : message.from, fromUuid: wsStore.toUserInfo.type == 'user' ? message.from : message.to, // fromUuids: msg?.fromUuid, align: 'left', content: `${(msg.fromUsername || '对方')}撤回了一条消息`, fromAvatar: message.avatar, }); } } // 接收好友系统消息 if(MsgType.MSG_TYPE_FRIEND.includes(message.messageType)){ // 消息送达回执发送 wsStore.systemReceiptMessage(message, msg) // 好友系统功能消息 wsStore.listenFriendSystem(message); return; } // 接收群系统消息 if (MsgType.MSG_TYPE_GROUP.includes(message.messageType)) { // 消息送达回执发送 wsStore.systemReceiptMessage(message, msg) //群系统功能消息 wsStore.listenGroupSystemMessage(message, msg); // 群系统消息转换 let newContent = msg?msg.content:message.content; let contentType = MsgType.MSG_TYPE.NOTICE; // 处理群通知 if (message.messageType === MsgType.MESSAGE_NOTICE_GROUP) { newContent = `管理员修改了群${msg.notice?'公告':'名称'}`; // 更新群通知 if(msg.name){ message.nickname = msg.name; } if(msg.notice){ message.notice = msg.notice; } } // 如果是@群成员 if(message.messageType == MsgType.MESSAGE_CC_GROUP){ contentType = MsgType.MSG_TYPE.TEXT } // 如果是修改群个人昵称直接返回 if (message.messageType == MsgType.MESSAGE_NICKNAME_GROUP) return; wsStore.pushMessage({ ...message, align: 'left', content: newContent, contentType, messageType: MsgType.MESSAGE_TYPE_GROUP, fromAvatar: message.avatar, toUuid: wsStore.toUserInfo.type == 'user' ? message.to : message.from, fromUuid: wsStore.toUserInfo.type == 'user' ? message.from : message.to, // fromUuids: msg?.fromUuid, }); notifications(wsStore); return; } // 语音和视频挂断/拒接消息处理 if (Constant.MSG_AUDIO_GROUP.includes(message.contentType)) { // 消息送达回执发送 wsStore.systemReceiptMessage(message, msg) let sender = wsStore.toUserAudioInfo.sender || { uuid: walletStore.account, nickname: walletStore.username, avatar: walletStore.avatar, } // 语音消息 wsStore.pushMessage({ ...message, align: sender.uuid != message.from?'right':'left', toUuid: wsStore.toUserAudioInfo.type == 'user' ? message.to : message.from, fromUuid: wsStore.toUserInfo.type == 'user' ? message.from : msg.fromUuid, // fromUuids: msg?.fromUuid, content: message.contentType === Constant.REJECT_AUDIO_ONLINE || message.contentType === Constant.REJECT_VIDEO_ONLINE ? '[对方拒绝]' : '[通话结束]', sender, fromAvatar: message.avatar, }); } // 文本消息 if (message.contentType === MsgType.MSG_TYPE.TEXT) { // 消息送达回执发送 wsStore.systemReceiptMessage(message, msg) wsStore.pushMessage(formatMessageExt({ ...message, toUuid: wsStore.toUserInfo.type == 'user' ? message.to : message.from, fromUuid: wsStore.toUserInfo.type == 'user' ? message.from : msg.fromUuid, // fromUuids: msg?.fromUuid, align: 'left', fromAvatar: message.avatar, }, msg)); notifications(wsStore); return } // 音频消息 if (message.contentType === MsgType.MSG_TYPE.AUDIO) { // 消息送达回执发送 wsStore.systemReceiptMessage(message, msg) const audioBlob = new Blob([message.file], { type: `audio/${message.fileSuffix}`, }); // 生成可播放的 ObjectURL const audioUrl = URL.createObjectURL(audioBlob); wsStore.pushMessage(formatMessageExt({ ...message, file: audioUrl, toUuid: wsStore.toUserInfo.type == 'user' ? message.to : message.from, fromUuid: wsStore.toUserInfo.type == 'user' ? message.from : msg.fromUuid, // fromUuids: msg?.fromUuid, align: 'left', fromAvatar: message.avatar, }, msg)); notifications(wsStore); return } // 图片 if (message.contentType === MsgType.MSG_TYPE.IMAGE) { // 消息送达回执发送 wsStore.systemReceiptMessage(message, msg) const blob = new Blob([message.file], { type: message.fileSuffix }); const url = URL.createObjectURL(blob); wsStore.pushMessage(formatMessageExt({ ...message, file: url, toUuid: wsStore.toUserInfo.type == 'user' ? message.to : message.from, fromUuid: wsStore.toUserInfo.type == 'user' ? message.from : msg.fromUuid, // fromUuids: msg?.fromUuid, align: 'left', fromAvatar: message.avatar, }, msg)); notifications(wsStore); return } // 视频 if (message.contentType === MsgType.MSG_TYPE.VIDEO) { // 消息送达回执发送 wsStore.systemReceiptMessage(message, msg) const blob = new Blob([message.file], { type: message.fileSuffix }); const url = URL.createObjectURL(blob); wsStore.pushMessage(formatMessageExt({ ...message, file: url, toUuid: wsStore.toUserInfo.type == 'user' ? message.to : message.from, fromUuid: wsStore.toUserInfo.type == 'user' ? message.from : msg.fromUuid, // fromUuids: msg?.fromUuid, align: 'left', fromAvatar: message.avatar, }, msg)); notifications(wsStore); return } // 音频在线:被呼叫 if ( message.contentType === Constant.DIAL_AUDIO_ONLINE || message.contentType === Constant.DIAL_VIDEO_ONLINE ) { console.log("收到播号") // 接收请求者信息,更新当前会话 wsStore.toUserAudioInfo = { uuid: message.from, type: "user", // 接收到来自谁的拨号请求信息 toUuid: message.from, toAvatar: message.avatar, toUsername: message.fromUsername, // 自己的信息 fromUuid: message.to, fromAvatar: walletStore.avatar, fromUsername: wsStore.toUserInfo.nickname, // 发送者信息 sender: { uuid: message.from, avatar: message.avatar, nickname: message.fromUsername, }, }; // wsStore.toUserInfo = { // ...wsStore.toUserInfo, // uuid: message.from, // type: "user", // unReadNum: 1, // sender: { // uuid: message.from, // avatar: message.avatar, // nickname: message.fromUsername, // }, // }; // 调起通话界面 const rtcStore = useWebRTCStore(); rtcStore.streamType = message.contentType == Constant.DIAL_AUDIO_ONLINE ? 'audio' :'video' rtcStore.isCaller = false rtcStore.imSate = { ...rtcStore.imSate, videoCallModal: true, callName: message.fromUsername, fromUserUuid: message.from, callAvatar: message.avatar }; // 播放铃声 soundVoice.play() return } // 音频在线:挂断 if ( message.contentType === Constant.CANCELL_AUDIO_ONLINE || message.contentType === Constant.CANCELL_VIDEO_ONLINE ) { console.log("音频在线:挂断") const rtcStore = useWebRTCStore(); // 关闭通话界面 rtcStore.imSate.videoCallModal = false; // 停止铃声 soundVoice.stop() // 清理通话p2p配置 rtcStore.cleanup() return; } // 音频在线:拒接 if ( message.contentType === Constant.REJECT_AUDIO_ONLINE || message.contentType === Constant.REJECT_VIDEO_ONLINE ) { console.log("音频在线:拒接") // 停止铃声 soundVoice.stop() const rtcStore = useWebRTCStore(); // 关闭通话界面 rtcStore.imSate.videoCallModal = false; // 清理通话p2p配置 rtcStore.cleanup() return; } // 音频在线:接听 if ( message.contentType === Constant.ACCEPT_VIDEO_ONLINE || message.contentType === Constant.ACCEPT_AUDIO_ONLINE ) { console.log("音频在线:接听", message.contentType) // 停止铃声 soundVoice.stop() const video = message.contentType == Constant.ACCEPT_VIDEO_ONLINE // 初始化通话p2p配置 startAudioOnline(video, wsStore); return; } // 音频通话交换信息 if (message.type === Constant.MESSAGE_TRANS_TYPE) { if ( message.contentType >= Constant.DIAL_MEDIA_START && message.contentType <= Constant.DIAL_MEDIA_END ) { console.log("音频通话") // 媒体通话处理 // dealMediaCall(message); return; } // 停止铃声 soundVoice.stop() const rtcStore = useWebRTCStore(); const { type, sdp, iceCandidate } = JSON.parse(message.content); // 接收answer:设置对端sdp if (type === "answer") { console.log('关联远程RTC') // 关联远程RTC标识到本地RTC const answerSdp = new RTCSessionDescription({ type, sdp }); await rtcStore.setRemoteDescription(answerSdp); } // 处理 ICE 候选(统一处理offer_ice和answer_ice) if (type.endsWith("_ice")) { console.log("添加ICE", type, iceCandidate) const candidate = new RTCIceCandidate(iceCandidate); await rtcStore .addIceCandidate(candidate) .catch((error) => console.error("添加ICE候选失败:", error)); // return; } // 响应对端offer if (type === "offer") { // 检查媒体权限是否开启 // let preview = null; console.log("被呼叫者收到offer") // 被呼叫收到offer, 需要创建RTC, 并开启音视频数据流 // console.log("message.contentType=", message.contentType) let video = false; // 视频电话 if (message.contentType === Constant.VIDEO_ONLINE) { // preview = document.getElementById("localVideoReceiver"); video = true; // // 屏幕共享 } // 创建RTC if (!rtcStore.peerConnection) { rtcStore.initConnection(false, video); // false表示是Answer方 } // 音频电话 if (message.contentType === Constant.AUDIO_ONLINE) { // preview = document.getElementById("audioPhone"); // 音频电话 } // 获取音视频流数据 // navigator.mediaDevices // .getUserMedia({ audio: true, video: video }) rtcStore.getUserMedia({ audio: true, video: video }) .then(async (stream) => { console.log('关联数据流和远程RTC') // 关联数据流到本地RTC rtcStore.addLocalStream(stream); // 关联远程RTC标识到本地RTC const offer = new RTCSessionDescription({ type, sdp }); return await rtcStore.setRemoteDescription(offer); }) // 创建回应offer .then(() => rtcStore.createAnswer()) .then((answer) => { console.log('发送answer给呼叫者', answer) // 发送给被呼叫者(拨打者) wsStore.sendMessage({ content: JSON.stringify(answer), type: Constant.MESSAGE_TRANS_TYPE, messageType: message.contentType, fromUsername: wsStore.toUserAudioInfo.fromUsername, avatar: wsStore.toUserAudioInfo.fromAvatar, to: wsStore.toUserAudioInfo.toUuid, from: wsStore.toUserAudioInfo.fromUuid, }); }) .catch((error) => { console.error("回应呼叫失败:", error); }); } } }; // 消息回调 const messageCallback = (contentType, state)=>{ state.sendMessage({ contentType, // 消息内容类型 content: "callback", // type: Constant.MESSAGE_TRANS_TYPE, }); } // 创建呼叫:开启语音电话 const startAudioOnline = (video, state) => { const rtcStore = useWebRTCStore(); const wsStore = useWebSocketStore(); // 初始化webrtc连接 rtcStore.initConnection(true, video); // 获取音视频数据流 // navigator.mediaDevices // .getUserMedia({ audio: true, video: video }) rtcStore.getUserMedia({ audio: true, video: video }) .then((stream) => { console.log('关联数据流和创建offer') // 关联数据流到本地RTC rtcStore.addLocalStream(stream); // 创建RTC offer return rtcStore.createOffer(); }) .then((offer) => { console.log('caller', offer) // 发送offer给被呼叫者(接听者) state.sendMessage({ contentType: video? Constant.VIDEO_ONLINE: Constant.AUDIO_ONLINE, // 消息内容类型 content: JSON.stringify(offer), type: Constant.MESSAGE_TRANS_TYPE, fromUsername: wsStore.toUserAudioInfo.fromUsername, avatar: wsStore.toUserAudioInfo.fromAvatar, to: wsStore.toUserAudioInfo.toUuid, from: wsStore.toUserAudioInfo.fromUuid, }); }) .catch((error) => { console.error("发起呼叫失败:", error); }); };