zx 1 ay önce
ebeveyn
işleme
b92d2d7a91
4 değiştirilmiş dosya ile 203 ekleme ve 1 silme
  1. 3 1
      package.json
  2. 4 0
      src/constant/call.js
  3. 38 0
      src/utils/openIM.js
  4. 158 0
      src/utils/trtc.js

+ 3 - 1
package.json

@@ -13,7 +13,8 @@
     "build:prod-jzzx": "vue-cli-service build --mode prod-jzzx",
     "build:stage": "vue-cli-service build --mode staging",
     "preview": "node build/index.js --preview",
-    "lint": "eslint --ext .js,.vue src"
+    "lint": "eslint --ext .js,.vue src",
+    "postinstall": "patch-package"
   },
   "husky": {
     "hooks": {
@@ -55,6 +56,7 @@
     "livekit-client": "^2.15.4",
     "mta-h5-analysis": "^2.0.15",
     "nprogress": "0.2.0",
+    "patch-package": "^8.0.0",
     "quill": "1.3.7",
     "screenfull": "5.0.2",
     "sortablejs": "1.10.2",

+ 4 - 0
src/constant/call.js

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

+ 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] 全局监听器已设置');
+};*/

+ 158 - 0
src/utils/trtc.js

@@ -0,0 +1,158 @@
+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
+  }
+
+  leaveRoom() {
+    if (this.room) {
+      this.localTracks.forEach(track => {
+        track.stop()
+        track.detach()
+      })
+      this.room.disconnect()
+      this.room = null
+      this.localTracks = []
+    }
+  }
+}
+
+export {
+  createLocalAudioTrack,
+  createLocalVideoTrack,
+}
+export default new Trtc()