AgoraIO / Basic-Video-Call

Sample app to join/leave a channel, mute/unmute, enable/disable the video, and switch between front/rear cameras.
MIT License
695 stars 1.01k forks source link

Multiple remote users gets unpublished on unpublishing one remote user #359

Open ahmedbashir-veraio opened 1 year ago

ahmedbashir-veraio commented 1 year ago

This is my useEffect code. I have subscribed to the user-joined, user-unpublished events. When multiple remote users join the channel, their video gets published successfully. When any of the remote user leaves the channel, all remote users get unpublished somehow. useEffect(() => { let initClientCallbacks = () => { videoCallContext.client.on("user-published", async (remoteUser, mediaType) => { await videoCallContext.client.subscribe(remoteUser, mediaType); if (mediaType == "video") { console.log("subscribe video success"); // remoteUser.videoTrack.play(document.getElementById("subscriber")); setUsers((prevUsers) => { return [...prevUsers, remoteUser]; }); } if (mediaType == "audio") { console.log("subscribe audio success"); remoteUser.audioTrack.play(); } const p = document.getElementById('publisher'); p.style.width = '30%'; p.style.height = '30%'; p.style.left = '10px'; p.style.bottom = '10px'; })

        videoCallContext.client.on("user-unpublished", async (user, type) => {
            if (user.uid === leavingUserUidRef.current) {
                return;
            }

            console.log("unpublished", user, type);

            if (type === "audio") {
                user.audioTrack?.stop();
            }

            if (type === "video") {
                setUsers((prevUsers) => {
                    return prevUsers.filter((User) => User.uid !== user.uid);
                });
            }

            setInitiatePolling(true);
        });

        videoCallContext.client.on("user-left", (user) => {
            console.log("leaving", user);
            console.log("leaving id", user.uid);
            leavingUserUidRef.current = user.uid;
            setUsers((prevUsers) => {
                return prevUsers.filter((User) => User.uid !== user.uid);
            });
            setInitiatePolling(true);
        });

    }

    initClientCallbacks();

}, [appointmentInformation?.id]);
plutoless commented 1 year ago

looks like an application issue. could you pls help provide a minimal reproducible project?

ahmedbashir-veraio commented 1 year ago

Sure. Let me share with you the config and other details. I am using agora-rtc-sdk-ng 4.16.1 RTC Client configuration is mode: "rtc", codec: "vp8"

React component markup:

`<Draggable id="my-draggable-element" disabled={isExpanded === true ? true : false} position={controlledPosition} onDrag={handleDrag} bounds="body"> <div id="videos" className="no-select" style={{ visibility: videoOpenState === false ? "hidden" : "visible", // height: '75%', // width: '75%' }}>

                    {users.length > 0 &&
                        users.map((user) => {
                            if (user.videoTrack) {
                                return (
                                    <AgoraVideoPlayer className='vid' videoTrack={user.videoTrack} key={user.uid} />
                                );
                            } else return null;
                        })}

                    <div ref={publisherRef} id="publisher" className='video-publisher'></div>
                </div>
            </div>
        </Draggable>`

AgoraVideoPlayer Component

const AgoraVideoPlayer = props => {
    const vidDiv = useRef(null)
    const { videoTrack, config, ...other } = props
    useLayoutEffect(() => {
        if (vidDiv.current !== null) videoTrack.play(vidDiv.current, config)
        return () => {
            videoTrack.stop()
        }
    }, [videoTrack])

    return <div {...other} ref={vidDiv} />
}

Here's a complete minimal component

VideoCall.js

const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });

const VideoCall = () => {
  const [users, setUsers] = useState([]);

  //functions to get session id and token id from backend
....

  //After getting the session id and token id following function is called
  function join(apiKey, sessionId, token){
     client.join(apiKey, session, token)
     .then(()=>{ client.publish([localAudioTrack, localVideoTrack])}) //these tracks are available in ref
  }

//The events for remote users are registered in useEffect
  useEffect(()=>{
       client.on("user-published", async (remoteUser, mediaType) => {
                client.subscribe(remoteUser, mediaType);
                if (mediaType == "video") {
                    console.log("subscribe video success");
                    // remoteUser.videoTrack.play(document.getElementById("subscriber"));
                    setUsers((prevUsers) => {
                        return [...prevUsers, remoteUser];
                    });
                }
                if (mediaType == "audio") {
                    console.log("subscribe audio success");
                    remoteUser.audioTrack.play();
                }
     }

     client.on("user-unpublished", async (user, type) => {

                console.log("unpublished", user, type);

                if (type === "audio") {
                    user.audioTrack?.stop();
                }

                if (type === "video") {
                    setUsers((prevUsers) => {
                        return prevUsers.filter((User) => User.uid !== user.uid);
                    });
                }
            });

     client.on("user-left", (user) => {

                leavingUserUidRef.current = user.uid;
                setUsers((prevUsers) => {
                    return prevUsers.filter((User) => User.uid !== user.uid);
                });
            });

  });

return (
<div id="videos">
                    <div id="subscriber">

                        {users.length > 0 &&
                            users.map((user) => {
                                if (user.videoTrack) {
                                    return (
                                        <AgoraVideoPlayer className='vid' videoTrack={user.videoTrack} key={user.uid} />
                                    );
                                } else return null;
                            })}

                        <div ref={publisherRef} id="publisher" className='video-publisher'></div>
                    </div>
                </div>
)

}

Everything works fine while publishing. Suppose there are 4 users in a call. If any 1 remote user unpublishes the video or leaves the call, the unpublish event is triggered for the other users also with their unique uid.