使用Agora RTC + React时远程流显示异常问题求助
Hey there, I see the issue you're facing—when a second user joins the channel, they can't see the first user's stream, even though the first user can see theirs. Let's break down why this happens and fix it step by step.
Root Cause
Your current code only handles the user-published event, which triggers when a user publishes their stream after you've joined the channel. But when the second user joins, the first user already published their stream before the second user connected. That means the second user's client never gets the user-published event for the first user, so it never subscribes to or renders their stream.
Additionally, your code uses ReactDOM.render() to add remote stream containers, which replaces the entire content of the remote-stream div every time—so even if you had multiple users, only the last one would show up.
Solution
We need to make two key changes:
- After joining the channel, manually subscribe to all existing remote users who've already published their streams.
- Append new remote stream containers instead of replacing the entire
remote-streamdiv.
Here's the revised code with fixes marked:
import React, { useEffect, useState, useRef } from "react"; import ReactDOM from "react-dom"; import "./App.css"; import { options, rtc } from "./constants"; import AgoraRTC from "agora-rtc-sdk-ng"; function App() { const [joined, setJoined] = useState(false); const channelRef = useRef(""); const remoteRef = useRef(null); // Updated to use ref directly async function handleSubmit(e) { try { if (channelRef.current.value === "") { return console.log("Please Enter Channel Name"); } setJoined(true); rtc.client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" }); const uid = await rtc.client.join( options.appId, channelRef.current.value, options.token, null ); // Create and play local tracks rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack(); rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack(); rtc.localVideoTrack.play("local-stream"); // Handle newly published streams (existing code) rtc.client.on("user-published", async (user, mediaType) => { await rtc.client.subscribe(user); console.log("subscribe success"); renderRemoteUserStream(user, mediaType); }); rtc.client.on("user-unpublished", (user) => { const playerContainer = document.getElementById(user.uid); playerContainer?.remove(); }); // --- NEW: Subscribe to existing remote users after joining --- const existingRemoteUsers = rtc.client.remoteUsers; for (const user of existingRemoteUsers) { // Check if the user has already published streams if (user.hasPublished("video") || user.hasPublished("all")) { await rtc.client.subscribe(user); renderRemoteUserStream(user, "video"); } if (user.hasPublished("audio") || user.hasPublished("all")) { await rtc.client.subscribe(user); user.audioTrack.play(); } } // Publish local tracks await rtc.client.publish([rtc.localAudioTrack, rtc.localVideoTrack]); console.log("publish success!"); } catch (error) { console.error(error); } } // --- NEW: Helper function to render remote stream without overwriting --- function renderRemoteUserStream(user, mediaType) { if (mediaType === "video" || mediaType === "all") { const playerContainer = document.createElement("div"); playerContainer.id = user.uid; playerContainer.className = "stream"; // Append instead of replacing remoteRef.current.appendChild(playerContainer); user.videoTrack.play(user.uid); } if (mediaType === "audio" || mediaType === "all") { user.audioTrack.play(); } } async function handleLeave() { try { const localContainer = document.getElementById("local-stream"); rtc.localAudioTrack.close(); rtc.localVideoTrack.close(); setJoined(false); localContainer.textContent = ""; rtc.client.remoteUsers.forEach((user) => { const playerContainer = document.getElementById(user.uid); playerContainer?.remove(); }); await rtc.client.leave(); } catch (err) { console.error(err); } } return ( <> <div className="container"> <input type="text" ref={channelRef} id="channel" placeholder="Enter Channel name" /> <input type="submit" value="Join" onClick={handleSubmit} disabled={joined} /> <input type="button" value="Leave" onClick={handleLeave} disabled={!joined} /> </div> {joined ? ( <> <div id="local-stream" className="stream local-stream"></div> <div id="remote-stream" ref={remoteRef} className="stream remote-stream" ></div> </> ) : null} </> ); } export default App;
Key Changes Explained
- Subscribe to Existing Users: After joining the channel, we loop through
rtc.client.remoteUsers(users already in the channel) and subscribe to their streams manually. This ensures new users see everyone who was already present. - Append Remote Stream Containers: Instead of using
ReactDOM.render()which replaces content, we create a div element and append it to theremote-streamcontainer using the ref. This keeps all remote streams visible. - Helper Function: We extracted the stream rendering logic into
renderRemoteUserStreamto avoid code duplication between handling existing users and new published events.
Now when a second user joins, they'll automatically subscribe to and see the first user's stream, and vice versa for any subsequent users.
内容的提问来源于stack exchange,提问作者Ankit Salvi

