如何使用Agora的handleUserPublished事件添加远程用户

huangapple go评论151阅读模式
英文:

How to add a remote users with Agora handleUserPublished event

问题

我正在尝试实现一个视频通话房间,用户可以使用Agora和React共享其音频和视频。实际上,我有一个可用的示例,但只使用原生JavaScript而没有React。

我尝试做的是每次运行handleUserPublished时都要存储已发布的远程用户。

问题在于,当用户发布时,如果他同时共享音频和视频轨道,那么事件会触发两次,因为每个轨道即audiovideo都会导致handleUserPublished运行。这是我的Room组件的代码:

  let handleUserPublished = async (user, mediaType) => {

    let dataElement = // 根据用户创建dataElement

    if (mediaType === "video") {    
      setNewRemoteVideo(user);
      setPlayers((prev) => [...prev, dataElement]); // 我不确定是在这里还是在另一种情况下放置这个,但我必须只更新'players'状态一次
    }
    if (mediaType === "audio") {
      setNewRemoteAudio(user);
      // 或者在这里放置 setPlayers((prev) => [...prev, dataElement])
    }
  };
  useEffect(() => {
    const joinRoomInit = async () => {
      // 创建Agora连接
      client.on("user-published", handleUserPublished);
    };
    joinRoomInit();
  }, []);

正如您在上面的代码中所看到的,我创建了两种不同的状态,newRemoteAudionewRemoteVideo,这是因为根据mediaType的不同,我选择用接收到的user来更新哪个状态。

请注意,当mediaType === "video"时,usermediaType === "audio"时的user是不同的,但第二个handleUserPublished事件传递的user对象足以存储,因为它包含了两种数据,但我永远无法猜测哪一个将是第二个,顺序不总是相同的,这就是为什么我将两个用户存储在不同状态中的原因,这在某种程度上有所帮助,但我想要一次性(第二次运行时)存储远程用户,对于players也是如此。

我的目标是在远程用户添加到另一个状态后将dataElement添加到players状态,因为在JSX中我正在返回players,但我在何时更新players状态方面遇到了困难,因为handleUserPublished事件运行两次,我不知道它们将发生的顺序,因此不知道何时确切地更新players状态,我希望在handleUserPublished第二次运行时执行此操作,这样我就不必分别设置音频数据和视频数据,只需一次(第二次运行)以及players状态。

我需要您的帮助,谢谢。

英文:

I am trying to implement a video call room where users can share their audio and video using Agora and React. In fact, I have a working example but only with native javascript without React.

what I try to do is each time the handleUserPublished is running to store the remote user who published.

The problem with that is when the user publishes, and if he is sharing both video and audio tracks then the event is triggered twice since each track i.e. audio and video causes handleUserPublished to run. this is my code for the Room component:

  let handleUserPublished = async (user, mediaType) => {

    let dataElement = // create dataElement based on user

    if (mediaType === "video") {    
      setNewRemoteVideo(user);
      setPlayers((prev) => [...prev, dataElement]); // I am not sure put this here or in the other case but I have to update 'players' state only one time
    }
    if (mediaType === "audio") {
      setNewRemoteAudio(user);
      // or put setPlayers((prev) => [...prev, dataElement]) here
    }
  };
  useEffect(() => {
    const joinRoomInit = async () => {
      // create Agora connection
      client.on("user-published", handleUserPublished);
    };
    joinRoomInit();
  }, []);

As you can see in the above code I create two different states, newRemoteAudio and newRemoteVideo this is because depending on mediaType I chose what state to update with the received user.

Note that user when mediaType === "video" is different from user when mediaType === "audio" however the user object coming with the second handleUserPublished event is enough to store because it contains both data but I can never guess which one is going to be the second one the order is not always the same that's why I store both users in different state so after that, I can merge them. This somehow helps but I would like to store the remote user once and for all (the second run), the same for players.

My goal is to add dataElement to players state once the remote user is added to the other state because in JSX I am returning players but I am struggling to do that since the event handleUserPublished runs twice and I don't know which order gonna happen so I don't know when exactly to update players state, I want to do it the second time handleUserPublished runs so I don't have to set audio data and video data separately but only once (the second run) and the same of players state.

I need your help, thank you

答案1

得分: 0

我曾经也处于你现在的位置,面对了相同的行为,但我记得无论是哪种 mediaType,都会返回相同的 user 事件。
无论如何,你仍然希望只处理一次事件。

行为

在这里,你的问题是无法区分第一次运行 handleUserPublished 与第二次运行,因为当第二次运行时,第一次的更改尚未反映出来,因为组件还没有重新渲染,所以状态尚未更新。这是关于 setState 是异步的 的旧故事。

解决方案

使用一个 ref,你可以将 useRef hook 视为一个立即更新的 JavaScript 普通变量,它不会在组件渲染时初始化。
现在生活变得更容易了,思路是每次运行 handleUserPublished 时,都要检查用户是否存在于 remoteUsersRef.current 中。
如果是这种情况,你知道这是第二次运行 handleUserPublished,因此你可以执行必要的操作;如果不是,你只需将 user 添加到 remoteUsersRef.current 中。

注意: 即使接收到的 user 对象不共享相同的数据,正如你所说,它们应该共享相同的 ID,因为毕竟是同一用户。你可以根据 id 属性来检查是否存在。


值得知道

也许你不必使用 remoteUsers 的状态,也许只使用 remoteUsersRef 就足够了,这是我的做法。
我看到人们在需要重新渲染组件时无论何时都使用状态,而只要有一个 ref 就足够了。只有在必须重新渲染组件以反映 UI 更改时,才应将数据存储在状态中。


全局解决方案

注意: 也有可能 handleUserPublished 仅运行一次(如果用户仅发布音频轨道),因此实际上你必须每次都更新 players 状态,并每次都检查用户是否存在,然后只需返回相同的先前值到状态,否则将新数据添加到其中:

setPlayers((prev) => {
 let isUserDataExistsInPrev = // 如果用户数据存在于 'prev' 中,则返回 'true',否则返回 'false'
 if(isUserDataExistsInPrev){
   return prev
 }else{
   let dataElement = // 从 'user' 创建 'dataElement'
   return [...prev, dataElement]
 }
});

你还可以使用相同的逻辑处理 remoteUsersRef.current,每次都检查用户是否存在,然后不执行任何操作(或者如果确信第二个包含更多数据,则仅替换它)。


最后,我不得不承认

setPlayers((prev) => prev)

很丑陋,我知道,但在我们这种特殊情况下,我没有找到更好的方法,我认为没有绕过它的方法,也没有其他方法可以实时访问状态值,而不使用 setState 的函数形式 🤷‍♂️。不管怎样,这不是我们的话题。

英文:

I've been where you are right now, I faced the same behavior but from what I remember the same user is returned with the event no matter which mediaType it is.<br> anyways, you still want to handle the event only once

Behavior

Here your problem is that you cannot differentiate the first handleUserPublished run from the second run because when it runs the second time, changes made by the first one are not reflected yet, since the component didn't get the time to rerender so the state is not updated yet. the old story of setState is asynchronous.

Solution

Use a ref, you can think of useRef hook as a javascript regular variable that is updated immediately and does not get initiated when the component renders.<br>
Now life become easier, the idea is each time handleUserPublished runs you check if the user exists in remoteUsersRef.current.<br> If it is the case, you know it is the second time handleUserPublished is running therefore you do what you have to, and if it is not, you just add the user to remoteUsersRef.current

Note: even if the received user objects don't share the same data as you said, they should share the same id since it is the same user after all. You check the existence based on the id property.


Good to know

You might don't have to use a state for remoteUsers, maybe remoteUsersRef is enough to store remote users, that's what I did.<br>
I see people using state everywhere when a ref is enough. you want to store data in a state only when you have to rerender the component, and so reflect changes in the UI when this data changes.


Global solution

Note: it is also possible that handleUserPublished runs only once (if the user is only publishing audio tracks) so in fact you have to update players state each time and check each time if the user exists so then you just return the same previous value to the state otherwise you add the new data to it:

setPlayers((prev) =&gt; {
 let isUserDataExistsInPrev = // return &#39;true&#39; if user data exists in &#39;prev&#39;, &#39;false&#39; otherwise
 if(isUserDataExistsInPrev){
   return prev
 }else{
   let dataElement = // create &#39;dataElement&#39; from &#39;user&#39; 
   return [...prev, dataElement]
 }
});

You do also the same logic with remoteUsersRef.current, each time you check if the user exists so you do nothing (or just replace it with the new one if you are sure that the second one contains more data) otherwise you add it


Finally, I can't pass without admitting that

setPlayers((prev) =&gt; prev)

Is ugly, I know that, but in our particular case here, I didn't find better, I think there's no way around it, and there's no other way to access the state value in real time without using the functional form of setState 🤷‍♂️. anyways, this is not our topic.

huangapple
  • 本文由 发表于 2023年8月11日 04:58:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76879272.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定