You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React集成Agora语音通话:接收方无法发送音频的问题排查求助

React集成Agora语音通话:接收方无法发送音频的问题排查求助

大家好,我现在在做一个React项目,用Agora实现语音通话功能,自己封装了useVoiceCall这个自定义Hook来管理通话逻辑。现在遇到一个头疼的问题:发起通话的用户麦克风正常,能正常发送音频;但接电话的用户虽然麦克风是激活且未静音状态,却没法把音频发回去。

问题细节

  • 两边都成功加入了同一个频道,也能正常听到对方的声音(audioTrack.play()是正常工作的);
  • 接收方的麦克风权限已获取,localTrack存在且isMuted状态为false;
  • 就是接收方的音频完全传不过去,发起方听不到接收方的声音。

自定义Hook useVoiceCall 代码

import { useState, useCallback, useRef } from "react";
import { AgoraAPP_ID } from "../../constants/data";
import { createAgoraClient, createMicrophoneTrack } from "../utils/rtcUtils";
import api from "../api";

const useVoiceCall = (currentUserId, conversationId) => {
  const [client, setClient] = useState(null);
  const [localTrack, setLocalTrack] = useState(null);
  const [remoteUsers, setRemoteUsers] = useState([]);
  const remoteUsersRef = useRef({});
  const [remoteUid, setRemoteUid] = useState("");
  const [isMuted, setIsMuted] = useState(false);
  const [isCallActive, setIsCallActive] = useState(false);
  const [incomingCall, setIncomingCall] = useState(false);
  const [callInfo, setCallInfo] = useState(null);

  const handleUserPublished = useCallback(
    async (clientInstance, user, mediaType) => {
      try {
        console.log("User published:", user.uid, "Type:", mediaType);
        await clientInstance.subscribe(user, mediaType);
        if (mediaType === "audio") {
          user.audioTrack?.play();
        }
        const uid = user.uid;
        const updatedUsers = { ...remoteUsersRef.current, [uid]: user };
        remoteUsersRef.current = updatedUsers;
        setRemoteUsers(Object.values(updatedUsers));
        setRemoteUid(uid);
        console.log("uid pub", uid);
      } catch (error) {
        console.error("Subscription failed:", error);
      }
    },
    []
  );

  const handleUserUnpublished = useCallback((user) => {
    const uid = user.uid;
    console.log("uid un pub", uid);
    delete remoteUsersRef.current[uid];
    setRemoteUsers(Object.values(remoteUsersRef.current));
  }, []);

  const createClient = useCallback(() => {
    const agoraClient = createAgoraClient();
    agoraClient.on("user-published", (user, mediaType) =>
      handleUserPublished(agoraClient, user, mediaType)
    );
    agoraClient.on("user-unpublished", handleUserUnpublished);
    setClient(agoraClient);
    return agoraClient;
  }, [handleUserPublished, handleUserUnpublished]);

  const joinVoiceChannel = useCallback(
    async ({ token, channel, uid }) => {
      const agoraClient = createClient();
      await agoraClient.join(AgoraAPP_ID, channel, token, uid);
      console.log("test Join");
      const micTrack = await createMicrophoneTrack();
      await micTrack.setEnabled(true);
      await agoraClient.publish([micTrack]);
      setLocalTrack(micTrack);
      setIsCallActive(true);
    },
    [createClient]
  );

  const initiateCall = useCallback(
    async (receiverId) => {
      const channelName = `private-call.${currentUserId}`;
      try {
        const { data } = await api.post("/user/chat/get-call-token", {
          receiver_id: receiverId,
          channel_name: channelName,
          uid: currentUserId,
          call_type: "voice",
          conversation_id: conversationId,
        });
        if (data?.data) {
          await joinVoiceChannel({
            token: data.data.token,
            channel: data.data.channel,
            uid: data.data.uid,
          });
          setCallInfo(data.data);
        }
      } catch (error) {
        console.error("Error initiating call:", error);
      }
    },
    [currentUserId, conversationId, joinVoiceChannel]
  );

  const acceptCall = useCallback(async () => {
    if (!incomingCall) return;
    const { token, channelName } = callInfo;
    setIncomingCall(false);
    await joinVoiceChannel({ token, channel: channelName, uid: currentUserId });
  }, [incomingCall, joinVoiceChannel, currentUserId, callInfo]);

  const rejectCall = useCallback(() => {
    setIncomingCall(false);
    setCallInfo(null);
  }, []);

  const toggleMute = useCallback(async () => {
    if (!localTrack) return;
    await localTrack.setEnabled(isMuted);
    setIsMuted((prev) => !prev);
  }, [localTrack, isMuted]);

  const leaveCall = useCallback(async () => {
    if (localTrack) {
      localTrack.stop();
      localTrack.close();
    }
    if (client) {
      await client.leave();
    }
    if (callInfo) {
      try {
        await api.post("/user/chat/end-call", {
          channel_name: `private-call.${currentUserId}`,
          conversation_id: callInfo.conversation_id,
        });
      } catch (error) {
        console.error("Failed to notify backend about ending call:", error);
      }
    }
    setClient(null);
    setLocalTrack(null);
    setRemoteUsers([]);
    remoteUsersRef.current = {};
    setIsMuted(false);
    setIsCallActive(false);
    setRemoteUid("");
    setCallInfo(null);
  }, [client, localTrack, currentUserId, callInfo]);

  return {
    initiateCall,
    acceptCall,
    rejectCall,
    leaveCall,
    toggleMute,
    isMuted,
    isCallActive,
    remoteUsers,
    incomingCall,
    setIncomingCall,
    callInfo,
    setCallInfo,
  };
};

export default useVoiceCall;

我的排查思路

  1. 检查了joinVoiceChannel方法,发起方和接收方的逻辑都是创建client、加入频道、创建麦克风轨道然后publish,看起来是一致的;
  2. 确认了接收方的localTrack存在,麦克风权限已获取,isMuted为false;
  3. 日志里能看到双方成功join频道,发起方的user-published事件能被接收方捕获,但接收方的user-published事件好像没被发起方接收到?

有没有哪位大佬遇到过类似的问题?或者能帮我看看代码里有没有哪里遗漏了?比如Agora客户端的配置、publish的时机、或者uid的处理有没有问题?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 14:09:29