15376779826 3 недель назад
Родитель
Сommit
464ce3e7cd
4 измененных файлов с 307 добавлено и 0 удалено
  1. 4 0
      src/constant/call.js
  2. 90 0
      src/im/eventListeners.js
  3. 38 0
      src/utils/openIM.js
  4. 175 0
      src/utils/trtc.js

+ 4 - 0
src/constant/call.js

@@ -0,0 +1,4 @@
+export const CALL_TYPE = {
+  AUDIO_CALL: 'audio',
+  VIDEO_CALL: 'video'
+}

+ 90 - 0
src/im/eventListeners.js

@@ -0,0 +1,90 @@
+import { ElMessage } from 'element-plus'
+import SDK from './index'
+import emitter from '@/utils/mitt'
+import store from '@/store'
+
+export const registerIMEvents = () => {
+  SDK.on('onConnectSuccess', () => {
+    console.log('[IM] 连接成功')
+  })
+
+  SDK.on('onConnectFailed', (err) => {
+    console.error('[IM] 连接失败:', err)
+  })
+
+  SDK.on('onKickedOffline', () => {
+  console.warn('[IM] 被踢下线')
+  ElMessage.warning('您已在其他设备登录,当前设备被踢下线')
+  store.commit('app/setLoginVisible', true) // 用于触发登录弹窗
+})
+
+SDK.on('onUserSigExpired', () => {
+    console.warn('[IM] Token 过期')
+    ElMessage.warning('登录已过期,请重新登录')
+    store.commit('app/setLoginVisible', true)
+  })
+
+  SDK.on('onSelfInfoUpdated', async () => {
+    console.log('[IM] 用户信息更新')
+    await store.dispatch('user/getSelfUserInfo')
+  })
+
+  SDK.on('onMsgReceived', (data) => {
+    console.log('[IM] 接收到消息:', data)
+    store.dispatch('message/handleNewMessages', data)
+  })
+
+  SDK.on('onConversationChanged', (data) => {
+    console.log('[IM] 会话变更:', data)
+    store.dispatch('conversation/updateConversations', data)
+  })
+
+  SDK.on('onNewConversation', (data) => {
+    console.log('[IM] 新会话:', data)
+    store.dispatch('conversation/addConversations', data)
+  })
+
+  SDK.on('onTotalUnreadMessageCountChanged', (unreadCount) => {
+    console.log('[IM] 未读总数变化:', unreadCount)
+    store.commit('conversation/setTotalUnreadCount', unreadCount)
+  })
+
+  SDK.on('onGroupInfoChanged', (groupList) => {
+    console.log('[IM] 群信息变更:', groupList)
+    store.dispatch('group/updateGroupInfo', groupList)
+  })
+
+  SDK.on('onGroupMemberAdded', (data) => {
+    store.dispatch('group/handleMemberAdded', data)
+  })
+
+  SDK.on('onGroupMemberDeleted', (data) => {
+    store.dispatch('group/handleMemberDeleted', data)
+  })
+
+  SDK.on('onGroupDismissed', (data) => {
+    store.dispatch('group/handleGroupDismissed', data)
+  })
+
+  SDK.on('onGroupRecycled', (data) => {
+    store.dispatch('group/handleGroupRecycled', data)
+  })
+
+  SDK.on('onGroupMemberInfoChanged', (data) => {
+    store.dispatch('group/handleMemberInfoChanged', data)
+  })
+
+  SDK.on('onRecvCustomMessage', (data) => {
+    console.log('[IM] 收到自定义消息:', data)
+    emitter.emit('custom-message', data)
+  })
+
+  SDK.on('onRecvCustomOnlineMessage', (data) => {
+    console.log('[IM] 收到在线自定义消息:', data)
+    emitter.emit('custom-online-message', data)
+  })
+}
+
+export const removeIMEvents = () => {
+  SDK.offAll()
+}

+ 38 - 0
src/utils/openIM.js

@@ -0,0 +1,38 @@
+import { getSDK, CbEvents } from '@openim/wasm-client-sdk';
+
+let sdkInstance = null;
+let cbEvents = null;
+
+export function getOpenIM() {
+  if (!sdkInstance) {
+    sdkInstance = getSDK(); // 只调用一次
+    console.log("OpenIM SDK 初始化完成");
+  }
+  return sdkInstance;
+}
+export const getCbEvents = () => {
+  if (!cbEvents) {
+    cbEvents = CbEvents;
+  }
+  return cbEvents;
+};
+
+// 设置全局监听(只需一次)
+/*export const setupListeners = (store) => {
+  if (isListenersSetup) return;
+
+  const im = getOpenIM();
+  im.on(CbEvents.OnConnectSuccess, () => {
+    store.commit('im/setReadyState', true);
+    console.log('[OpenIM] 连接成功');
+  });
+
+  im.on(CbEvents.OnConnectFailed, (error) => {
+    store.commit('im/setError', error.message);
+    console.error('[OpenIM] 连接失败:', error);
+  });
+
+  // 其他必要的事件监听...
+  isListenersSetup = true;
+  console.log('[OpenIM] 全局监听器已设置');
+};*/

+ 175 - 0
src/utils/trtc.js

@@ -0,0 +1,175 @@
+import {
+  Room,
+  RoomEvent,
+  createLocalTracks,
+  createLocalAudioTrack,
+  createLocalVideoTrack,
+} from 'livekit-client'
+
+class Trtc {
+  constructor() {
+    this.room = null
+    this.localTracks = []
+  }
+
+  async joinRoom(token, url, userId, onRemoteTrack) {
+    try {
+      if (this.room) {
+        this.leaveRoom()
+      }
+
+      this.room = new Room()
+
+      // 挂载远端轨道(带 DOM 等待机制)
+      const attachRemoteTrack = (track, participant) => {
+        const id = 'video-' + participant.identity
+        const maxRetry = 20
+        let retry = 0
+
+        const tryAttach = () => {
+          const container = document.getElementById(id)
+          if (container) {
+            console.log(`📺 正在挂载视频到容器: ${id}`)
+            container.innerHTML = ''
+            const element = track.attach()
+            element.style.width = '100%'
+            element.style.height = '100%'
+            element.style.objectFit = 'cover'
+            container.appendChild(element)
+          } else if (retry < maxRetry) {
+            retry++
+            setTimeout(tryAttach, 200)
+          } else {
+            console.warn(`❌ 超时未找到远端容器: ${id}`)
+          }
+        }
+
+        tryAttach()
+      }
+
+      // 监听远端轨道
+      this.room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
+        console.log('📡 TrackSubscribed:', track.kind, participant.identity)
+        if (track.kind === 'video' || track.kind === 'audio') {
+          attachRemoteTrack(track, participant)
+          if (typeof onRemoteTrack === 'function') {
+            onRemoteTrack(track, participant)
+          }
+        }
+      })
+
+      // 检查设备
+      this.localTracks = []
+      const devices = await navigator.mediaDevices.enumerateDevices()
+      const hasMic = devices.some(d => d.kind === 'audioinput')
+      const hasCam = devices.some(d => d.kind === 'videoinput')
+
+      if (hasMic || hasCam) {
+        this.localTracks = await createLocalTracks({
+          audio: hasMic,
+          video: hasCam,
+        })
+        console.log('🎥 本地轨道列表:', this.localTracks)
+      } else {
+        console.warn('未检测到摄像头或麦克风,将以静默方式加入房间')
+      }
+
+      console.log('🟢 发起连接到 LiveKit ...', url)
+
+      // 监听连接状态
+      const connectedPromise = new Promise((resolve, reject) => {
+        const timeout = setTimeout(() => {
+          reject(new Error('连接 LiveKit 超时'))
+        }, 15000)
+
+        this.room.once(RoomEvent.Connected, () => {
+          clearTimeout(timeout)
+          console.log('✅ 成功连接到 LiveKit 房间')
+          resolve()
+
+          // ✅ 主动挂载已有轨道
+          if (this.room.participants && typeof this.room.participants.values === 'function') {
+            for (const participant of this.room.participants.values()) {
+              participant.tracks.forEach(publication => {
+                if (publication.isSubscribed && publication.track) {
+                  console.log('🟡 主动挂载已有远端轨道:', publication.track.kind)
+                  attachRemoteTrack(publication.track, participant)
+                  if (typeof onRemoteTrack === 'function') {
+                    onRemoteTrack(publication.track, participant)
+                  }
+                }
+              })
+            }
+          }
+        })
+
+        this.room.once(RoomEvent.ConnectionError, (err) => {
+          clearTimeout(timeout)
+          console.error('❌ LiveKit 连接错误:', err)
+          reject(err)
+        })
+      })
+
+      this.room.on(RoomEvent.Disconnected, () => {
+        console.warn('⚠️ 与 LiveKit 断开连接')
+      })
+
+      // 发起连接
+      await this.room.connect(url, token, {
+        autoSubscribe: true,
+      })
+
+      await connectedPromise
+
+      // 发布本地轨道
+      for (const track of this.localTracks) {
+        console.log('📤 正在发布轨道:', track.kind)
+        await this.room.localParticipant.publishTrack(track)
+      }
+
+      return this.room
+    } catch (err) {
+      console.error('🚨 joinRoom 出错:', err)
+      throw err
+    }
+  }
+
+  getLocalVideoTrack() {
+    return this.localTracks.find(t => t.kind === 'video') || null
+  }
+
+  async leaveRoom() {
+    if (this.room) {
+      console.log("🔴 Trtc 离开房间");
+
+      // 停止并释放本地轨道
+      this.localTracks.forEach(track => {
+        try {
+          track.stop();
+          track.detach();
+        } catch (e) {
+          console.warn("❌ 轨道释放失败:", e);
+        }
+      });
+
+      try {
+        await this.room.disconnect(); // ✅ 等待断开完成
+      } catch (e) {
+        console.error("❌ 房间断开异常:", e);
+      }
+
+      this.room = null;
+      this.localTracks = [];
+    } else {
+      console.warn("⚠️ leaveRoom 时 room 为 null");
+    }
+  }
+
+
+}
+
+export {
+  createLocalAudioTrack,
+  createLocalVideoTrack,
+}
+export default new Trtc()