diff --git a/src/components/Account/agora/Agora.tsx b/src/components/Account/agora/Agora.tsx index f63be12..6be491d 100644 --- a/src/components/Account/agora/Agora.tsx +++ b/src/components/Account/agora/Agora.tsx @@ -24,7 +24,7 @@ export const Agora = ({ sessionId, secret, stopCalling, remoteUser }: AgoraProps useJoin( { - appid: 'ed90c9dc42634e5687d4e2e0766b363f', + appid: process.env.NEXT_PUBLIC_AGORA_APPID, channel: `${sessionId}-${secret}`, token: null, }, diff --git a/src/components/Account/agora/components/CameraVideoTrack.tsx b/src/components/Account/agora/components/CameraVideoTrack.tsx new file mode 100644 index 0000000..668f51b --- /dev/null +++ b/src/components/Account/agora/components/CameraVideoTrack.tsx @@ -0,0 +1,30 @@ +import { useEffect } from 'react'; +import { ICameraVideoTrack, LocalVideoTrack, LocalVideoTrackProps, MaybePromiseOrNull } from 'agora-rtc-react'; +import { useAwaited } from '../../../../utils/agora/tools'; + +interface CameraVideoTrackProps extends LocalVideoTrackProps { + /** + * A camera video track which can be created by `createCameraVideoTrack()`. + */ + readonly track?: MaybePromiseOrNull; + /** + * Device ID, which can be retrieved by calling `getDevices()`. + */ + readonly deviceId?: string; +} + +export const CameraVideoTrack = ({ + track: maybeTrack, + deviceId, + ...props +}: CameraVideoTrackProps) => { + const track = useAwaited(maybeTrack); + + useEffect(() => { + if (track && deviceId != null) { + track.setDevice(deviceId).catch(console.warn); + } + }, [deviceId, track]); + + return ; +}; diff --git a/src/components/Account/agora/components/LocalUser.tsx b/src/components/Account/agora/components/LocalUser.tsx new file mode 100644 index 0000000..5d5bf01 --- /dev/null +++ b/src/components/Account/agora/components/LocalUser.tsx @@ -0,0 +1,108 @@ +import { HTMLProps, ReactNode } from 'react'; +import { ICameraVideoTrack, IMicrophoneAudioTrack, MaybePromiseOrNull } from 'agora-rtc-react'; +import { UserCover } from '../components'; +import { MicrophoneAudioTrack } from './MicrophoneAudioTrack'; +import { CameraVideoTrack } from './CameraVideoTrack'; + +interface LocalUserProps extends HTMLProps { + /** + * Whether to turn on the local user's microphone. Default false. + */ + readonly micOn?: boolean; + /** + * Whether to turn on the local user's camera. Default false. + */ + readonly cameraOn?: boolean; + /** + * A microphone audio track which can be created by `createMicrophoneAudioTrack()`. + */ + readonly audioTrack?: MaybePromiseOrNull; + /** + * A camera video track which can be created by `createCameraVideoTrack()`. + */ + readonly videoTrack?: MaybePromiseOrNull; + /** + * Whether to play the local user's audio track. Default follows `micOn`. + */ + readonly playAudio?: boolean; + /** + * Whether to play the local user's video track. Default follows `cameraOn`. + */ + readonly playVideo?: boolean; + /** + * Device ID, which can be retrieved by calling `getDevices()`. + */ + readonly micDeviceId?: string; + /** + * Device ID, which can be retrieved by calling `getDevices()`. + */ + readonly cameraDeviceId?: string; + /** + * The volume. The value ranges from 0 (mute) to 1000 (maximum). A value of 100 is the current volume. + */ + readonly volume?: number; + /** + * Render cover image if playVideo is off. + */ + readonly cover?: string; + /** + * Children is rendered on top of the video canvas. + */ + readonly children?: ReactNode; +} + +/** + * Play/Stop local user camera and microphone track. + */ +export function LocalUser({ + micOn, + cameraOn, + audioTrack, + videoTrack, + playAudio = false, + playVideo, + micDeviceId, + cameraDeviceId, + volume, + cover, + children, + style, + ...props +}: LocalUserProps) { + playVideo = playVideo ?? !!cameraOn; + playAudio = playAudio ?? !!micOn; + return ( +
+ + + {cover && !cameraOn && } +
{children}
+
+ ); +}; diff --git a/src/components/Account/agora/components/LocalUserPanel.tsx b/src/components/Account/agora/components/LocalUserPanel.tsx index d76c908..87587bd 100644 --- a/src/components/Account/agora/components/LocalUserPanel.tsx +++ b/src/components/Account/agora/components/LocalUserPanel.tsx @@ -1,14 +1,14 @@ -import { LocalUser, useLocalMicrophoneTrack, useLocalCameraTrack, usePublish, useIsConnected } from 'agora-rtc-react'; -import { useState, useEffect } from 'react'; +import { useLocalMicrophoneTrack, useLocalCameraTrack, usePublish, useIsConnected } from 'agora-rtc-react'; import { UserOutlined } from '@ant-design/icons'; import { useLocalStorage } from '../../../../hooks/useLocalStorage'; import { AUTH_USER } from '../../../../constants/common'; +import { LocalUser } from './LocalUser'; type LocalUserPanelProps = { calling: boolean; micOn: boolean; cameraOn: boolean; -} +}; export const LocalUserPanel = ({ calling, @@ -18,26 +18,11 @@ export const LocalUserPanel = ({ const isConnected = useIsConnected(); const [userData] = useLocalStorage(AUTH_USER, ''); const { faceImageUrl: userImage = '' } = userData ? JSON.parse(userData) : {}; - - const [playVideo, setPlayVideo] = useState(false); - const [playAudio, setPlayAudio] = useState(false); - const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn); const { localCameraTrack } = useLocalCameraTrack(cameraOn); + usePublish([localMicrophoneTrack, localCameraTrack]); - useEffect(() => { - if (calling) { - setPlayVideo(cameraOn) - } - }, [cameraOn]); - - useEffect(() => { - if (calling) { - setPlayAudio(micOn) - } - }, [micOn]); - return calling && isConnected ? (
{!cameraOn && ( @@ -51,9 +36,6 @@ export const LocalUserPanel = ({ audioTrack={localMicrophoneTrack} cameraOn={cameraOn} micOn={micOn} - playAudio={playAudio} - playVideo={playVideo} - style={{ width: '100%', height: '100%' }} videoTrack={localCameraTrack} />
diff --git a/src/components/Account/agora/components/MicrophoneAudioTrack.tsx b/src/components/Account/agora/components/MicrophoneAudioTrack.tsx new file mode 100644 index 0000000..3876449 --- /dev/null +++ b/src/components/Account/agora/components/MicrophoneAudioTrack.tsx @@ -0,0 +1,32 @@ +import { ReactNode, useEffect } from 'react'; +import { IMicrophoneAudioTrack, LocalAudioTrack, LocalAudioTrackProps, MaybePromiseOrNull } from 'agora-rtc-react'; +import { useAwaited } from '../../../../utils/agora/tools'; + +interface MicrophoneAudioTrackProps extends LocalAudioTrackProps { + /** + * A microphone audio track which can be created by `createMicrophoneAudioTrack()`. + */ + readonly track?: MaybePromiseOrNull; + /** + * Device ID, which can be retrieved by calling `getDevices()`. + */ + readonly deviceId?: string; + + readonly children?: ReactNode; +} + +export const MicrophoneAudioTrack = ({ + track: maybeTrack, + deviceId, + ...props +}: MicrophoneAudioTrackProps) => { + const track = useAwaited(maybeTrack); + + useEffect(() => { + if (track && deviceId != null) { + track.setDevice(deviceId).catch(console.warn); + } + }, [deviceId, track]); + + return ; +}; diff --git a/src/components/Account/agora/components/RemoteVideoPlayer.tsx b/src/components/Account/agora/components/RemoteVideoPlayer.tsx index b5014f5..bb61164 100644 --- a/src/components/Account/agora/components/RemoteVideoPlayer.tsx +++ b/src/components/Account/agora/components/RemoteVideoPlayer.tsx @@ -31,39 +31,38 @@ export interface RemoteVideoPlayerProps extends HTMLProps { * An `IRemoteVideoTrack` can only be own by one `RemoteVideoPlayer`. */ export function RemoteVideoPlayer({ - track, - playVideo, - cover, - client, - style, - children, - ...props + track, + playVideo, + cover, + client, + style, + children, + ...props }: RemoteVideoPlayerProps) { - const resolvedClient = useRTCClient(client); - const hasVideo = resolvedClient.remoteUsers?.find( - user => user.uid === track?.getUserId(), - )?.hasVideo; - playVideo = playVideo ?? hasVideo; - return ( -
- - {cover && !playVideo && } -
{children}
-
- ); -} + const resolvedClient = useRTCClient(client); + const hasVideo = resolvedClient.remoteUsers?.find(user => user.uid === track?.getUserId())?.hasVideo; + playVideo = playVideo ?? hasVideo; + + return ( +
+ + {cover && !playVideo && } +
{children}
+
+ ); +}; diff --git a/src/components/Account/agora/index.tsx b/src/components/Account/agora/index.tsx index d4d23f9..cbca252 100644 --- a/src/components/Account/agora/index.tsx +++ b/src/components/Account/agora/index.tsx @@ -4,8 +4,6 @@ import AgoraRTC, { AgoraRTCProvider } from 'agora-rtc-react'; import { Session } from '../../../types/sessions'; import { Agora } from './Agora'; -AgoraRTC.setLogLevel(0); - export const AgoraClient = ({ session, stopCalling, isCoach }: { session?: Session, stopCalling: () => void, isCoach: boolean }) => { const remoteUser = isCoach ? (session?.clients?.length ? session?.clients[0] : undefined) : session?.coach;