1+ // same file as the videocall example
2+ import React , { useEffect , useState } from "react" ;
3+ import {
4+ AgoraVideoPlayer ,
5+ createClient ,
6+ createMicrophoneAndCameraTracks ,
7+ ClientConfig ,
8+ IAgoraRTCRemoteUser ,
9+ ICameraVideoTrack ,
10+ IMicrophoneAudioTrack ,
11+ } from "agora-rtc-react" ;
12+
13+ const config : ClientConfig = {
14+ mode : "rtc" , codec : "vp8" ,
15+ } ;
16+
17+ const appId : string = "" ; //ENTER APP ID HERE
18+ const token : string | null = null ;
19+
20+ const App = ( ) => {
21+ const [ inCall , setInCall ] = useState ( false ) ;
22+ const [ channelName , setChannelName ] = useState ( "" ) ;
23+ return (
24+ < div >
25+ < h1 className = "heading" > Agora RTC NG SDK React Wrapper</ h1 >
26+ { inCall ? (
27+ < VideoCall setInCall = { setInCall } channelName = { channelName } />
28+ ) : (
29+ < ChannelForm setInCall = { setInCall } setChannelName = { setChannelName } />
30+ ) }
31+ </ div >
32+ ) ;
33+ } ;
34+
35+ // the create methods in the wrapper return a hook
36+ // the create method should be called outside the parent component
37+ // this hook can be used the get the client/stream in any component
38+ const useClient = createClient ( config ) ;
39+ const useMicrophoneAndCameraTracks = createMicrophoneAndCameraTracks ( ) ;
40+
41+ const VideoCall = ( props : {
42+ setInCall : React . Dispatch < React . SetStateAction < boolean > > ;
43+ channelName : string ;
44+ } ) => {
45+ const { setInCall, channelName } = props ;
46+ const [ users , setUsers ] = useState < IAgoraRTCRemoteUser [ ] > ( [ ] ) ;
47+ const [ start , setStart ] = useState < boolean > ( false ) ;
48+ // using the hook to get access to the client object
49+ const client = useClient ( ) ;
50+ // ready is a state variable, which returns true when the local tracks are initialized, untill then tracks variable is null
51+ const { ready, tracks } = useMicrophoneAndCameraTracks ( ) ;
52+
53+ useEffect ( ( ) => {
54+ // function to initialise the SDK
55+ let init = async ( name : string ) => {
56+ client . on ( "user-published" , async ( user , mediaType ) => {
57+ await client . subscribe ( user , mediaType ) ;
58+ console . log ( "subscribe success" ) ;
59+ if ( mediaType === "video" ) {
60+ setUsers ( ( prevUsers ) => {
61+ return [ ...prevUsers , user ] ;
62+ } ) ;
63+ }
64+ if ( mediaType === "audio" ) {
65+ user . audioTrack ?. play ( ) ;
66+ }
67+ } ) ;
68+
69+ client . on ( "user-unpublished" , ( user , type ) => {
70+ console . log ( "unpublished" , user , type ) ;
71+ if ( type === "audio" ) {
72+ user . audioTrack ?. stop ( ) ;
73+ }
74+ if ( type === "video" ) {
75+ setUsers ( ( prevUsers ) => {
76+ return prevUsers . filter ( ( User ) => User . uid !== user . uid ) ;
77+ } ) ;
78+ }
79+ } ) ;
80+
81+ client . on ( "user-left" , ( user ) => {
82+ console . log ( "leaving" , user ) ;
83+ setUsers ( ( prevUsers ) => {
84+ return prevUsers . filter ( ( User ) => User . uid !== user . uid ) ;
85+ } ) ;
86+ } ) ;
87+
88+ await client . join ( appId , name , token , null ) ;
89+ if ( tracks ) await client . publish ( [ tracks [ 0 ] , tracks [ 1 ] ] ) ;
90+ setStart ( true ) ;
91+
92+ } ;
93+
94+ if ( ready && tracks ) {
95+ console . log ( "init ready" ) ;
96+ init ( channelName ) ;
97+ }
98+
99+ } , [ channelName , client , ready , tracks ] ) ;
100+
101+
102+ return (
103+ < div className = "App" >
104+ { ready && tracks && (
105+ < Controls tracks = { tracks } setStart = { setStart } setInCall = { setInCall } />
106+ ) }
107+ { start && tracks && < Videos users = { users } tracks = { tracks } /> }
108+ </ div >
109+ ) ;
110+ } ;
111+
112+ const Videos = ( props : {
113+ users : IAgoraRTCRemoteUser [ ] ;
114+ tracks : [ IMicrophoneAudioTrack , ICameraVideoTrack ] ;
115+ } ) => {
116+ const { users, tracks } = props ;
117+
118+ return (
119+ < div >
120+ < div id = "videos" >
121+ { /* AgoraVideoPlayer component takes in the video track to render the stream,
122+ you can pass in other props that get passed to the rendered div */ }
123+ < AgoraVideoPlayer style = { { height : '95%' , width : '95%' } } className = 'vid' videoTrack = { tracks [ 1 ] } />
124+ { users . length > 0 &&
125+ users . map ( ( user ) => {
126+ if ( user . videoTrack ) {
127+ return (
128+ < AgoraVideoPlayer style = { { height : '95%' , width : '95%' } } className = 'vid' videoTrack = { user . videoTrack } key = { user . uid } />
129+ ) ;
130+ } else return null ;
131+ } ) }
132+ </ div >
133+ </ div >
134+ ) ;
135+ } ;
136+
137+ export const Controls = ( props : {
138+ tracks : [ IMicrophoneAudioTrack , ICameraVideoTrack ] ;
139+ setStart : React . Dispatch < React . SetStateAction < boolean > > ;
140+ setInCall : React . Dispatch < React . SetStateAction < boolean > > ;
141+ } ) => {
142+ const client = useClient ( ) ;
143+ const { tracks, setStart, setInCall } = props ;
144+ const [ trackState , setTrackState ] = useState ( { video : true , audio : true } ) ;
145+
146+ const mute = async ( type : "audio" | "video" ) => {
147+ if ( type === "audio" ) {
148+ await tracks [ 0 ] . setEnabled ( ! trackState . audio ) ;
149+ setTrackState ( ( ps ) => {
150+ return { ...ps , audio : ! ps . audio } ;
151+ } ) ;
152+ } else if ( type === "video" ) {
153+ await tracks [ 1 ] . setEnabled ( ! trackState . video ) ;
154+ setTrackState ( ( ps ) => {
155+ return { ...ps , video : ! ps . video } ;
156+ } ) ;
157+ }
158+ } ;
159+
160+ const leaveChannel = async ( ) => {
161+ await client . leave ( ) ;
162+ client . removeAllListeners ( ) ;
163+ // we close the tracks to perform cleanup
164+ tracks [ 0 ] . close ( ) ;
165+ tracks [ 1 ] . close ( ) ;
166+ setStart ( false ) ;
167+ setInCall ( false ) ;
168+ } ;
169+
170+ return (
171+ < div className = "controls" >
172+ < p className = { trackState . audio ? "on" : "" }
173+ onClick = { ( ) => mute ( "audio" ) } >
174+ { trackState . audio ? "MuteAudio" : "UnmuteAudio" }
175+ </ p >
176+ < p className = { trackState . video ? "on" : "" }
177+ onClick = { ( ) => mute ( "video" ) } >
178+ { trackState . video ? "MuteVideo" : "UnmuteVideo" }
179+ </ p >
180+ { < p onClick = { ( ) => leaveChannel ( ) } > Leave</ p > }
181+ </ div >
182+ ) ;
183+ } ;
184+
185+ const ChannelForm = ( props : {
186+ setInCall : React . Dispatch < React . SetStateAction < boolean > > ;
187+ setChannelName : React . Dispatch < React . SetStateAction < string > > ;
188+ } ) => {
189+ const { setInCall, setChannelName } = props ;
190+
191+ return (
192+ < form className = "join" >
193+ { appId === '' && < p style = { { color : 'red' } } > Please enter your Agora App ID in App.tsx and refresh the page</ p > }
194+ < input type = "text"
195+ placeholder = "Enter Channel Name"
196+ onChange = { ( e ) => setChannelName ( e . target . value ) }
197+ />
198+ < button onClick = { ( e ) => {
199+ e . preventDefault ( ) ;
200+ setInCall ( true ) ;
201+ } } >
202+ Join
203+ </ button >
204+ </ form >
205+ ) ;
206+ } ;
207+
208+ export default App ;
0 commit comments