Skip to content
This repository was archived by the owner on Dec 6, 2023. It is now read-only.

Commit dad54ff

Browse files
authored
Merge pull request #21 from AgoraIO-Community/nextjs
add nextjs example
2 parents 060c813 + 31850fd commit dad54ff

10 files changed

Lines changed: 20047 additions & 0 deletions

File tree

example/nextjs/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Agora RTC SDK React Wrapper NextJS Example
2+
3+
This is an example for [Agora RTC SDK React Wrapper](https://github.com/AgoraIO-Community/agora-rtc-react).
4+
5+
## How to use
6+
- Update your Agora App ID (& token) inside `./pages/Videocall.tsx`
7+
- Make sure you're running the bundler for the library in the parent directory
8+
- Execute `npm i && npm run dev`
9+
- Open `localhost:3000` in a modern browser
10+
11+
This project is created using `create-next-app`. The Videocall component is the same as the React only example, `index.tsx` contains the logic to handle SRR with the Agora SDK.
12+
13+
You can use the same dynamic import technique to use the Agora Web SDK in your nextjs project instead of this react wrapper.

example/nextjs/next-env.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/types/global" />
3+
/// <reference types="next/image-types/global" />
4+
5+
// NOTE: This file should not be edited
6+
// see https://nextjs.org/docs/basic-features/typescript for more information.

example/nextjs/next.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** @type {import('next').NextConfig} */
2+
module.exports = {
3+
reactStrictMode: true,
4+
}

example/nextjs/package-lock.json

Lines changed: 19622 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/nextjs/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "agora-nextjs-demo",
3+
"private": true,
4+
"scripts": {
5+
"dev": "next dev",
6+
"build": "next build",
7+
"start": "next start",
8+
"lint": "next lint"
9+
},
10+
"dependencies": {
11+
"agora-rtc-react": "file:../..",
12+
"next": "12.0.3",
13+
"react": "file:../../node_modules/react",
14+
"react-dom": "file:../../node_modules/react-dom"
15+
},
16+
"devDependencies": {
17+
"@types/node": "16.11.7",
18+
"@types/react": "17.0.34",
19+
"eslint": "7.32.0",
20+
"eslint-config-next": "12.0.3",
21+
"typescript": "4.4.4"
22+
}
23+
}

example/nextjs/pages/Videocall.tsx

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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;

example/nextjs/pages/_app.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import '../styles/globals.css'
2+
import type { AppProps } from 'next/app'
3+
4+
function MyApp({ Component, pageProps }: AppProps) {
5+
return <Component {...pageProps} />
6+
}
7+
8+
export default MyApp

example/nextjs/pages/index.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { NextPage } from 'next'
2+
import styles from '../styles/Home.module.css'
3+
import dynamic from 'next/dynamic'
4+
// Use next/dynamic to import the videocall component without ssr as the Agora SDK uses the window object
5+
// The Videocall component can use the Agora SDK like in any react app
6+
const App = dynamic(import('./Videocall'), { ssr: false });
7+
8+
const Home: NextPage = () => {
9+
return (
10+
<div>
11+
<App />
12+
</div>
13+
)
14+
}
15+
16+
export default Home

0 commit comments

Comments
 (0)