trtc.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import {
  2. Room,
  3. RoomEvent,
  4. createLocalTracks,
  5. createLocalAudioTrack,
  6. createLocalVideoTrack,
  7. } from 'livekit-client'
  8. console.log("Room 构造函数:", Room)
  9. class Trtc {
  10. constructor() {
  11. this.room = null
  12. this.localTracks = []
  13. }
  14. // 安全 attach 远端轨道
  15. attachTrack(track, participant) {
  16. const container = document.getElementById('video-' + participant.identity)
  17. if (!container) return
  18. // 已经 attach 过就跳过,避免闪屏
  19. if (track.attachedElements.length > 0) return
  20. const element = track.attach()
  21. element.style.width = '100%'
  22. element.style.height = '100%'
  23. element.style.objectFit = 'cover'
  24. container.appendChild(element)
  25. }
  26. async joinRoom(token, url, userId, onRemoteTrack) {
  27. try {
  28. // 如果已有房间,先退出
  29. if (this.room) {
  30. await this.leaveRoom()
  31. }
  32. this.room = new Room()
  33. // 监听远端轨道
  34. this.room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
  35. console.log('📡 TrackSubscribed:', track.kind, participant.identity)
  36. if (track.kind === 'video' || track.kind === 'audio') {
  37. this.attachTrack(track, participant)
  38. if (typeof onRemoteTrack === 'function') {
  39. onRemoteTrack(track, participant)
  40. }
  41. }
  42. })
  43. // 检查设备
  44. this.localTracks = []
  45. if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
  46. const devices = await navigator.mediaDevices.enumerateDevices()
  47. const hasMic = devices.some(d => d.kind === 'audioinput')
  48. const hasCam = devices.some(d => d.kind === 'videoinput')
  49. if (hasMic || hasCam) {
  50. this.localTracks = await createLocalTracks({
  51. audio: hasMic,
  52. video: hasCam,
  53. })
  54. console.log('本地轨道列表:', this.localTracks)
  55. } else {
  56. console.warn('未检测到摄像头或麦克风,将以静默方式加入房间')
  57. }
  58. } else {
  59. console.warn('当前环境不支持 mediaDevices,将以纯接收模式加入房间')
  60. }
  61. console.log('发起连接到 LiveKit ...', url)
  62. // 监听连接状态
  63. const connectedPromise = new Promise((resolve, reject) => {
  64. const timeout = setTimeout(() => {
  65. reject(new Error('连接 LiveKit 超时'))
  66. }, 15000)
  67. try {
  68. this.room.once(RoomEvent.Connected, () => {
  69. clearTimeout(timeout)
  70. console.log('成功连接到 LiveKit 房间')
  71. resolve()
  72. // 主动挂载已有轨道
  73. if (this.room.participants && typeof this.room.participants.values === 'function') {
  74. for (const participant of this.room.participants.values()) {
  75. participant.tracks.forEach(publication => {
  76. if (publication.isSubscribed && publication.track) {
  77. this.attachTrack(publication.track, participant)
  78. if (typeof onRemoteTrack === 'function') {
  79. onRemoteTrack(publication.track, participant)
  80. }
  81. }
  82. })
  83. }
  84. }
  85. })
  86. this.room.once(RoomEvent.ConnectionError, (err) => {
  87. clearTimeout(timeout)
  88. console.error('LiveKit 连接错误:', err)
  89. reject(err)
  90. })
  91. } catch (e) {
  92. console.error('LiveKit连接失败:', e)
  93. }
  94. })
  95. this.room.on(RoomEvent.Disconnected, () => {
  96. console.warn('与 LiveKit 断开连接')
  97. })
  98. // 发起连接
  99. await this.room.connect(url, token, {
  100. autoSubscribe: true,
  101. })
  102. await connectedPromise
  103. // 发布本地轨道
  104. for (const track of this.localTracks) {
  105. console.log('正在发布轨道:', track.kind)
  106. await this.room.localParticipant.publishTrack(track)
  107. }
  108. return this.room
  109. } catch (err) {
  110. console.error('joinRoom 出错:', err)
  111. throw err
  112. }
  113. }
  114. getLocalVideoTrack() {
  115. return this.localTracks.find(t => t.kind === 'video') || null
  116. }
  117. async leaveRoom() {
  118. if (this.room) {
  119. console.log("Trtc 离开房间")
  120. // 停止并释放本地轨道
  121. this.localTracks.forEach(track => {
  122. try {
  123. track.stop()
  124. track.detach()
  125. } catch (e) {
  126. console.warn("轨道释放失败:", e)
  127. }
  128. })
  129. try {
  130. await this.room.disconnect()
  131. } catch (e) {
  132. console.error("房间断开异常:", e)
  133. }
  134. this.room = null
  135. this.localTracks = []
  136. } else {
  137. this.room = null
  138. console.warn("离开房间 时 room 为 null")
  139. }
  140. }
  141. }
  142. export {
  143. createLocalAudioTrack,
  144. createLocalVideoTrack,
  145. }
  146. export default new Trtc()