diff --git a/packages/agora-rtc-react/src/components/RemoteUser.css b/packages/agora-rtc-react/src/components/RemoteUser.css new file mode 100644 index 00000000..a1fce114 --- /dev/null +++ b/packages/agora-rtc-react/src/components/RemoteUser.css @@ -0,0 +1,11 @@ +.agora-rtc-remote-user { + width: 288px; + height: 216px; + position: relative; + overflow: hidden; +} + +.agora-rtc-remote-user-video { + width: 100%; + height: 100%; +} diff --git a/packages/agora-rtc-react/src/components/RemoteUser.tsx b/packages/agora-rtc-react/src/components/RemoteUser.tsx new file mode 100644 index 00000000..dbc58cd9 --- /dev/null +++ b/packages/agora-rtc-react/src/components/RemoteUser.tsx @@ -0,0 +1,94 @@ +import "./RemoteUser.css"; + +import type { IAgoraRTCClient, IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng"; +import type { HTMLProps, PropsWithChildren } from "react"; +import { useEffect } from "react"; + +import { forwardRef, useRef } from "react"; +import { RemoteAudioTrack } from "./RemoteAudioTrack"; +import { RemoteVideoTrack } from "./RemoteVideoTrack"; +import { useRTCClient } from "../hooks"; +import { useForceUpdate } from "../hooks/tools"; +import { useSafePromise } from "../utils"; + +export interface RemoteUserProps extends HTMLProps { + readonly client?: IAgoraRTCClient; + /** + * A remote user + */ + readonly user?: IAgoraRTCRemoteUser; + /** + * Whether to play the remote user's video track. + */ + readonly videoOn?: boolean; + /** + * Whether to play the remote user's audio track. + */ + readonly audioOn?: boolean; + /** + * Device ID, which can be retrieved by calling `getPlaybackDevices`. + * + * Changes of the ID will invoke `setPlaybackDevice` which sets the audio playback device, for example, the speaker. + * + * > `setPlaybackDevice` supports Chrome on desktop devices only. Other browsers throw a `NOT_SUPPORTED` error when calling the method. + */ + readonly playbackDeviceId?: string; + /** + * The volume. The value ranges from 0 (mute) to 100 (maximum). A value of 100 is the current volume. + */ + readonly volume?: number; +} + +export const RemoteUser = /* @__PURE__ */ forwardRef< + HTMLDivElement, + PropsWithChildren +>(function RemoteUser( + { user, videoOn, audioOn, playbackDeviceId, volume, children, className, ...props }, + ref, +) { + const client = useRTCClient(); + const subscribedRef = useRef(0b00); // 0b01: audio, 0b10: video, 0b11: both, 0: none + const forceUpdate = useForceUpdate(); + const sp = useSafePromise(); + + useEffect(() => { + if (user && videoOn && !(subscribedRef.current & 0b10)) { + subscribedRef.current |= 0b10; + sp(client.subscribe(user, "video")).then(forceUpdate).catch(console.error); + } + }, [client, user, videoOn, sp, forceUpdate]); + + useEffect(() => { + if (user && audioOn && !(subscribedRef.current & 0b01)) { + subscribedRef.current |= 0b01; + sp(client.subscribe(user, "audio")).then(forceUpdate).catch(console.error); + } + }, [client, user, audioOn, sp, forceUpdate]); + + useEffect( + () => () => { + if (user && subscribedRef.current) { + subscribedRef.current = 0b00; + client.unsubscribe(user).catch(console.error); + } + }, + [user, client], + ); + + return ( +
+ + + {children} +
+ ); +}); diff --git a/packages/agora-rtc-react/src/components/index.ts b/packages/agora-rtc-react/src/components/index.ts index 7ab5f72b..85f7cbef 100644 --- a/packages/agora-rtc-react/src/components/index.ts +++ b/packages/agora-rtc-react/src/components/index.ts @@ -4,3 +4,4 @@ export * from "./MicrophoneAudioTrack"; export * from "./CameraVideoTrack"; export * from "./RemoteVideoTrack"; export * from "./RemoteAudioTrack"; +export * from "./RemoteUser"; diff --git a/packages/agora-rtc-react/src/hooks/tools.ts b/packages/agora-rtc-react/src/hooks/tools.ts new file mode 100644 index 00000000..ea96ef5b --- /dev/null +++ b/packages/agora-rtc-react/src/hooks/tools.ts @@ -0,0 +1,6 @@ +import { useCallback, useState } from "react"; + +export function useForceUpdate() { + const [_, forceUpdate] = useState(0); + return useCallback(() => forceUpdate(n => (n + 1) | 0), []); +}