0

我正在尝试在 Agora 中切换麦克风和摄像头设备。我正在使用agora-rtc-sdk-ngagora-rtc-react库来实现这一点。我创建了一个自定义钩子 use-agora 来处理所有情况。即当远程用户加入或处理本地用户流等时。这是我的钩子实现:

import { useEffect, useState } from 'react'
import AgoraRTC, { ClientConfig, IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'
import { createClient, createMicrophoneAndCameraTracks } from 'agora-rtc-react'
import { useRecoilState } from 'recoil'
import config from '../../../config'
import { studioStore } from '../stores'

const videoConfig: ClientConfig = {
  mode: 'rtc',
  codec: 'vp8',
}

const { appId } = config.agora

const useClient = createClient(videoConfig)
const useMicrophoneAndCameraTracks = createMicrophoneAndCameraTracks()

export interface RTCUser extends IAgoraRTCRemoteUser {
  mediaStream?: MediaStream
}

export default function useAgora(
  channel: string,
  {
    onTokenWillExpire,
    onTokenDidExpire,
  }: { onTokenWillExpire: () => void; onTokenDidExpire: () => void }
) {
  const client = useClient()
  const [users, setUsers] = useState<RTCUser[]>([])
  const [stream, setStream] = useState<MediaStream>()
  const [studio, setStudio] = useRecoilState(studioStore)
  const [userAudios, setUserAudios] = useState<MediaStream[]>([])
  const [cameraDevice, setCameraDevice] = useState<MediaDeviceInfo>()
  const [microphoneDevice, setMicrophoneDevice] = useState<MediaDeviceInfo>()

  const { ready, tracks } = useMicrophoneAndCameraTracks()
  useEffect(() => {
    ;(async () => {
      init()
    })()
  }, [])

  useEffect(() => {
    if (!tracks) return
    setStream(
      new MediaStream([
        tracks[0].getMediaStreamTrack(),
        tracks[1].getMediaStreamTrack(),
      ])
    ) // I am doing setStream here. So whenever the track will update a new stream will be created and returned through the hook
  }, [tracks])

  useEffect(() => {
    if (!tracks) return
    ;(async () => {
      if (cameraDevice) {
        await tracks[1].setDevice(cameraDevice.deviceId) // I am setting the cameraDevice here
      }
    })()
  }, [cameraDevice])

  useEffect(() => {
    if (!tracks) return
    ;(async () => {
      if (microphoneDevice) {
        console.log('i came here', microphoneDevice) // This log is coming perfectly
        await tracks[0].setDevice(microphoneDevice.deviceId) // I am setting the Microphone device here
        console.log('Newmic', microphoneDevice) // This line never executes, because setDevice function is throwing timeout
      }
    })()
  }, [microphoneDevice])

  const getMediaDevices = async () => {
    const camDevices = await AgoraRTC.getCameras()
    setCameraDevice(camDevices[0])
    const audiDevices = await AgoraRTC.getMicrophones()
    setMicrophoneDevice(audiDevices[0])
  }

  const init = async () => {
    try {
      await getMediaDevices()
      client.on('user-published', async (user, mediaType) => {
        await client.subscribe(user, mediaType)
        const tracks: MediaStreamTrack[] = []
        if (user.audioTrack) tracks.push(user.audioTrack?.getMediaStreamTrack())
        if (user.videoTrack) tracks.push(user.videoTrack?.getMediaStreamTrack())
        if (mediaType === 'video') {
          setUsers((prevUsers) => {
            if (prevUsers.find((element) => element.uid === user.uid))
              return [...prevUsers]
            return [
              ...prevUsers,
              {
                ...user,
                mediaStream:
                  tracks && tracks.length > 0
                    ? // @ts-ignore
                      new MediaStream(tracks.filter((track) => !!track))
                    : undefined,
              },
            ]
          })
        }
        if (mediaType === 'audio') {
          user.audioTrack?.play()
          setUserAudios((prev) => [
            ...prev,
            new MediaStream([
              user.audioTrack?.getMediaStreamTrack() as MediaStreamTrack,
            ]),
          ])
        }
      })
      client.on('user-left', (user) => {
        setUsers((prevUsers) => {
          return prevUsers.filter((User) => User.uid !== user.uid)
        })
      })

      client.on('token-privilege-will-expire', () => {
        onTokenWillExpire()
      })

      client.on('token-privilege-did-expire', () => {
        onTokenDidExpire()
      })

      client.on('user-left', (user) => {
        setUsers((prevUsers) => {
          return prevUsers.filter((User) => User.uid !== user.uid)
        })
      })
    } catch (error) {
      console.log(error)
      throw error
    }
  }

  AgoraRTC.onCameraChanged = async (changedDevice) => {
    if (changedDevice.state === 'ACTIVE') {
      setCameraDevice(changedDevice.device)
      // Switch to an existing device when the current device is unplugged.
    } else if (changedDevice.device.label === tracks?.[0].getTrackLabel()) {
      const oldCameras = await AgoraRTC.getCameras()
      // eslint-disable-next-line @typescript-eslint/no-unused-expressions
      oldCameras[0] && setCameraDevice(oldCameras[0])
    }
  }

  AgoraRTC.onMicrophoneChanged = async (changedDevice) => {
    if (changedDevice.state === 'ACTIVE') {
      setMicrophoneDevice(changedDevice.device)
      // Switch to an existing device when the current device is unplugged.
    } else if (changedDevice.device.label === tracks?.[0].getTrackLabel()) {
      const oldMicrophones = await AgoraRTC.getMicrophones()
      // eslint-disable-next-line @typescript-eslint/no-unused-expressions
      oldMicrophones[0] && setMicrophoneDevice(oldMicrophones[0])
    }
  }

  const renewToken = async (token: string) => {
    client.renewToken(token)
  }

  const join = async (token: string, uid: string) => {
    try {
      if (!ready) throw new Error('Not ready')
      await client.join(appId, channel, token, uid)
      if (tracks) await client.publish(tracks)
    } catch (error) {
      console.error(error)
      throw error
    }
  }

  const mute = async (type: 'audio' | 'video') => {
    const { constraints } = studio
    if (type === 'audio') {
      const newValue = !constraints?.audio
      await tracks?.[0].setEnabled(newValue)
      setStudio((studio) => {
        return {
          ...studio,
          constraints: {
            ...studio.constraints,
            audio: newValue,
          },
        }
      })
    } else if (type === 'video') {
      const newValue = !constraints?.video
      await tracks?.[1].setEnabled(newValue)
      setStudio((studio) => {
        return {
          ...studio,
          constraints: {
            ...studio.constraints,
            video: newValue,
          },
        }
      })
    }
  }

  const getCameras = async () => {
    return AgoraRTC.getCameras()
  }

  const updateCameraDevices = (device: MediaDeviceInfo) => {
    setCameraDevice(device)
  }

  const getMicrophones = async () => {
    return AgoraRTC.getMicrophones()
  }

  const updateMicroPhoneDevices = (device: MediaDeviceInfo) => {
    setMicrophoneDevice(device)
  }

  const leave = async () => {
    try {
      tracks?.forEach((track) => track.stop())
      await client.leave()
    } catch (error) {
      console.error(error)
      throw error
    }
  }

  return {
    ready,
    users,
    join,
    leave,
    mute,
    tracks,
    stream,
    userAudios,
    renewToken,
    cameraDevice,
    microphoneDevice,
    getCameras,
    getMicrophones,
    updateCameraDevices,
    updateMicroPhoneDevices,
  }
}

现在从我调用的组件updateCameraDevicesupdateMicroPhoneDevices函数中更新输入设备。这是下拉代码:

<Select
            onChange={(device) => {
              if (!device) return
              const selectedCameraDevice = cameraDevices.find(
                (d) => d.deviceId === device.value
              )
              if (selectedCameraDevice)
                updateCameraDevices(selectedCameraDevice)
            }}
...
 <Select
            onChange={(device) => {
              if (!device) return
              const selectedMicrophoneDevice = microphoneDevices.find(
                (d) => d.deviceId === device.value
              )
              console.log('I set microphone', selectedMicrophoneDevice)
              if (selectedMicrophoneDevice)
                updateMicroPhoneDevices(selectedMicrophoneDevice)
            }}
...

现在,当我设置设备时,agora 会向我发送日志,表明新设备已设置。但一段时间后它显示 setDevice 超时。以下是日志:

I set microphone InputDeviceInfo {deviceId: '21b351d2e67c42c09478fd6578e971e6d4e43074346acd853785349bc536631b', kind: 'audioinput', label: 'Krisp Microphone (Krisp)', groupId: '348e200e095d9193419f72418cfdee7f94bc4cecea2e5ac686bf1238bdc8b561'}
use-agora.ts:67 i came here InputDeviceInfo {deviceId: '21b351d2e67c42c09478fd6578e971e6d4e43074346acd853785349bc536631b', kind: 'audioinput', label: 'Krisp Microphone (Krisp)', groupId: '348e200e095d9193419f72418cfdee7f94bc4cecea2e5ac686bf1238bdc8b561'}
AgoraRTC_N-production.js:395 17:02:32:956 Agora-SDK [INFO]: [getTrackId() {
          return this._ID;
        }] start set device to 21b351d2e67c42c09478fd6578e971e6d4e43074346acd853785349bc536631b
AgoraRTC_N-production.js:395 17:02:32:988 Agora-SDK [DEBUG]: [track-26722f69] GetUserMedia {"audio":{"deviceId":{"exact":"21b351d2e67c42c09478fd6578e971e6d4e43074346acd853785349bc536631b"}}}
AgoraRTC_N-production.js:395 17:02:33:50 Agora-SDK [DEBUG]: [track-track-26722f69] update player source track
AgoraRTC_N-production.js:395 17:03:02:143 Agora-SDK [DEBUG]: [client-2b69d] receive exception msg, code: 2001, msg: AUDIO_INPUT_LEVEL_TOO_LOW, uid: DtplUfgXdoN7ZpOMioMiH0i2opO2
AgoraRTC_N-production.js:395 17:03:07:143 Agora-SDK [DEBUG]: [client-2b69d] receive exception msg, code: 4001, msg: AUDIO_INPUT_LEVEL_TOO_LOW_RECOVER, uid: DtplUfgXdoN7ZpOMioMiH0i2opO2
AgoraRTC_N-production.js:395 17:03:32:956 Agora-SDK [DEBUG]: MicrophoneAudioTrack.setDevice timeout
AgoraRTC_N-production.js:395 17:04:07:142 Agora-SDK [DEBUG]: [client-2b69d] receive exception msg, code: 2001, msg: AUDIO_INPUT_LEVEL_TOO_LOW, uid: DtplUfgXdoN7ZpOMioMiH0i2opO2

现在我无法弄清楚为什么 setDevice 发送超时。我相信这是我的流没有更新的唯一原因。我在这里先向您的帮助表示感谢 :)

4

0 回答 0