英文:
How to add a remote users with Agora handleUserPublished event
问题
我正在尝试实现一个视频通话房间,用户可以使用Agora和React共享其音频和视频。实际上,我有一个可用的示例,但只使用原生JavaScript而没有React。
我尝试做的是每次运行handleUserPublished
时都要存储已发布的远程用户。
问题在于,当用户发布时,如果他同时共享音频和视频轨道,那么事件会触发两次,因为每个轨道即audio
和video
都会导致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();
}, []);
正如您在上面的代码中所看到的,我创建了两种不同的状态,newRemoteAudio
和newRemoteVideo
,这是因为根据mediaType
的不同,我选择用接收到的user
来更新哪个状态。
请注意,当mediaType === "video"
时,user
与mediaType === "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) => {
let isUserDataExistsInPrev = // return 'true' if user data exists in 'prev', 'false' otherwise
if(isUserDataExistsInPrev){
return prev
}else{
let dataElement = // create 'dataElement' from 'user'
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) => 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论