├── .github
└── workflows
│ └── gradle.yml
├── .gitignore
├── .idea
├── codeStyles
│ └── Project.xml
├── compiler.xml
├── encodings.xml
├── jarRepositories.xml
├── misc.xml
└── runConfigurations.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── android
│ │ └── myagoraapplication
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── android
│ │ │ └── myagoraapplication
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── audio_toggle_active_btn.png
│ │ ├── audio_toggle_btn.png
│ │ ├── end_call.png
│ │ ├── ic_launcher_background.xml
│ │ ├── join_call.png
│ │ ├── video_disabled.png
│ │ ├── video_toggle_active_btn.png
│ │ └── video_toggle_btn.png
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── android
│ └── myagoraapplication
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Gradle
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3 |
4 | name: Java CI with Gradle
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up JDK 1.8
20 | uses: actions/setup-java@v1
21 | with:
22 | java-version: 1.8
23 | - name: Grant execute permission for gradlew
24 | run: chmod +x gradlew
25 | - name: Build with Gradle
26 | run: ./gradlew build
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 | *.aab
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 |
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Proguard folder generated by Eclipse
25 | proguard/
26 |
27 | # Log Files
28 | *.log
29 |
30 | # Android Studio Navigation editor temp files
31 | .navigation/
32 |
33 | # Android Studio captures folder
34 | captures/
35 |
36 | # IntelliJ
37 | *.iml
38 | .idea/workspace.xml
39 | .idea/tasks.xml
40 | .idea/gradle.xml
41 | .idea/assetWizardSettings.xml
42 | .idea/dictionaries
43 | .idea/libraries
44 | .idea/caches
45 | # Android Studio 3 in .gitignore file.
46 | .idea/caches/build_file_checksums.ser
47 | .idea/modules.xml
48 |
49 | # Keystore files
50 | # Uncomment the following lines if you do not want to check your keystore files in.
51 | #*.jks
52 | #*.keystore
53 |
54 | # External native build folder generated in Android Studio 2.2 and later
55 | .externalNativeBuild
56 |
57 | # Google Services (e.g. APIs or Firebase)
58 | # google-services.json
59 |
60 | # Freeline
61 | freeline.py
62 | freeline/
63 | freeline_project_description.json
64 |
65 | # fastlane
66 | fastlane/report.xml
67 | fastlane/Preview.html
68 | fastlane/screenshots
69 | fastlane/test_output
70 | fastlane/readme.md
71 |
72 | # Version control
73 | vcs.xml
74 |
75 | # lint
76 | lint/intermediates/
77 | lint/generated/
78 | lint/outputs/
79 | lint/tmp/
80 | # lint/reports/
81 |
82 | .DS_Store
83 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
27 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How to: Build a Video Chat App on Android
2 | 
3 | In this repo, we’ll build a basic video chat app in 10 easy steps, using the [Agora.io Video SDK](https://docs.agora.io/en/Video/product_video?platform=All%20Platforms) for Android.
4 |
5 | ## Prerequisites ##
6 | * [Android Studio](https://developer.android.com/studio)
7 | * [Basic knowledge of Java and the Android SDK](https://developer.android.com/training/basics/firstapp)
8 | * An Agora Developer Account (see: How To Get Started with Agora)[https://www.agora.io/en/blog/how-to-get-started-with-agora?utm_source=devto&utm_medium=blog&&utm_campaign=How_To_Build_a_Video_Chat_App_on_Android]
9 |
10 | ## Step 1: Agora.io Account ##
11 | Once you finish the sign-up process, you will be redirected to the Dashboard. Open the Projects tab on the left-hand nav to see your default project’s App ID.
12 |
13 | 
14 |
15 | ## Step 2: Create an Android App ##
16 | Within Android Studio, create a new Single Activity app.
17 |
18 | 
19 |
20 | 
21 |
22 | ## Step 3: Integrate the Agora SDK ##
23 | There are two ways to add the Agora Video SDK into your project. You can use [JCenter](https://mvnrepository.com/repos/jcenter) or you can manually add the SDK. For this project we'll add the project using JCenter.
24 |
25 | Add the following line in your project level `build.gradle`:
26 | ```
27 | allprojects {
28 | repositories {
29 | ...
30 | maven { url 'https://www.jitpack.io' }
31 | ...
32 | }
33 | }
34 | ```
35 |
36 | Add the following line in the `/app/build.gradle` file of your project:
37 | ```
38 | dependencies {
39 | ...
40 | //Agora RTC SDK for video call
41 | implementation 'com.github.agorabuilder:native-full-sdk:3.4.1'
42 | }
43 | ```
44 | 
45 |
46 | ## Set up Agora APP ID ###
47 | Next, its time to add your Agora.io App ID (see Step-1) to the Android project’s `Strings.xml` _(app/src/main/res/values/Strings.xml)_.
48 | ```XML
49 |
50 | Agora-Android-Video-Tutorial
51 | <#YOUR APP ID#>
52 |
53 | ```
54 | 
55 |
56 | The next step is to add the appropriate permissions within `Manifest.xml`
57 | ```XML
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | ```
67 |
68 | The final step is to prevent obfuscation of the Agora classes, while this might sound complex it’s really simple. In the `proguard-rules.pro` file, add:
69 | ```
70 | -keep class io.agora.**{*;}
71 | ```
72 |
73 | 
74 |
75 | > NOTE: Ensure that the Android NDK plugin is installed and setup for this project
76 |
77 | ## Step 4: Setup views ##
78 | Now that we have the Agora.io SDK integrated, let’s set up our UI. I will breeze through this portion as we will be using standard UI elements.
79 |
80 | 
81 |
82 | In the example, I chose to use `ImageView` instead of `Button` for the various UI elements. Either works, the important part is to note that there are functions that we link to using the `onClick` property.
83 |
84 | ## Step 5: Checking Permissions ##
85 | I know what you must be thinking… “didn’t we already set up the Permissions?” Earlier we let the applications Manifest know which permissions our app plans to use, but we still have to explicitly request the user grant these permissions. Don’t worry this is the final step in getting the boilerplate project running and it’s painless.
86 |
87 | First, let's declare which permissions we want to request.
88 | ```Java
89 | // Permissions
90 | private static final int PERMISSION_REQ_ID = 22;
91 | private static final String[] REQUESTED_PERMISSIONS = {Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA};
92 | ```
93 |
94 | Next, we set up a couple of functions to help us. First we'll add a method that will request permissions for a given permission string and code.
95 | ```Java
96 | public boolean checkSelfPermission(String permission, int requestCode) {
97 | if (ContextCompat.checkSelfPermission(this,
98 | permission)
99 | != PackageManager.PERMISSION_GRANTED) {
100 |
101 | ActivityCompat.requestPermissions(this,
102 | REQUESTED_PERMISSIONS,
103 | requestCode);
104 | return false;
105 | }
106 | return true;
107 | }
108 | ```
109 |
110 | Next, we have a callback method that will get called after the user has responded to the permissions request prompt.
111 |
112 | ```Java
113 | @Override
114 | public void onRequestPermissionsResult(int requestCode,
115 | @NonNull String permissions[], @NonNull int[] grantResults) {
116 | Log.i(LOG_TAG, "onRequestPermissionsResult " + grantResults[0] + " " + requestCode);
117 |
118 | switch (requestCode) {
119 | case PERMISSION_REQ_ID: {
120 | if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) {
121 | Log.i(LOG_TAG, "Need permissions " + Manifest.permission.RECORD_AUDIO + "/" + Manifest.permission.CAMERA);
122 | break;
123 | }
124 | // if permission granted, initialize the engine
125 | initAgoraEngine();
126 | break;
127 | }
128 | }
129 | }
130 | ```
131 |
132 | Last, within our Class’s `onCreate` we check if our permissions have been granted and if not the above methods will handle the requests.
133 |
134 | 
135 |
136 | ## Step 6: Initializing the Agora.io SDK ##
137 | Now that we have our view, we are ready to initialize the Agora.io SDK, set up the user profile and set the video quality settings.
138 |
139 | In the previous step, you may have noticed there are a couple places that I call `initAgoraEngine()`. Before we can dive into the initialization we need to make sure that our Activity has access to an instance of the Agora.io `RtcEngine`.
140 |
141 | Within our `MainActivity` Class, we need to declare a Class property to store our instance of RtcEngine.
142 | ```Java
143 | private RtcEngine mRtcEngine;
144 | ```
145 | Now its time to initialize! After all the boilerplate setup we are finally at the step where we can start playing with the Agora.io engine!
146 |
147 | Go ahead and declare your `initAgoraEngine` method within your class. Within this function, we will create a new instance of the `RtcEngine` using the baseContext, the Agora `AppID` _(declared above)_, and an instance of the `RtcEngineEventHandler` _(we’ll get into this a little later)_.
148 | ```Java
149 | private void initAgoraEngine() {
150 | try {
151 | mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler);
152 | } catch (Exception e) {
153 | Log.e(LOG_TAG, Log.getStackTraceString(e));
154 |
155 | throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
156 | }
157 | setupSession();
158 | }
159 | ```
160 |
161 | Once we have our new instance it’s time to set up our user’s session. Here we can set the Channel Profile to Communication, as this is a video chat and not a broadcast. This is also where we configure our video encoder settings.
162 |
163 | ```Java
164 | private void setupSession() {
165 | mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_COMMUNICATION);
166 |
167 | mRtcEngine.enableVideo();
168 |
169 | mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(VideoEncoderConfiguration.VD_1920x1080, VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_30,
170 | VideoEncoderConfiguration.STANDARD_BITRATE,
171 | VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
172 | }
173 | ```
174 |
175 | ## Step 7: Connecting the Video Streams ##
176 | Before we can join a view call we need to be able to present the local video stream to the user via the UI elements we setup earlier _(Step 4)_.
177 |
178 | In the first line, we get a reference for the UI element will act as our parent view for our video stream. The second step is to use the `RtcEngine` to create a [`SurfaceView`](https://developer.android.com/reference/android/view/SurfaceView) that will render the stream from the front camera, we also set the new `VideoSurface` to render on top of its parent view. The next step is to add the `VideoSurface` as a subview of the UI element. Lastly, we pass the `VideoSurface` to the engine as part of a `VideoCanvas` object. We leave the `uid` parameter blank so the SDK can handle creating a dynamic id for each user.
179 |
180 | ```Java
181 | private void setupLocalVideoFeed() {
182 | FrameLayout videoContainer = findViewById(R.id.floating_video_container);
183 | SurfaceView videoSurface = RtcEngine.CreateRendererView(getBaseContext());
184 | videoSurface.setZOrderMediaOverlay(true);
185 | videoContainer.addView(videoSurface);
186 | mRtcEngine.setupLocalVideo(new VideoCanvas(videoSurface, VideoCanvas.RENDER_MODE_FIT, 0));
187 | }
188 | ```
189 |
190 | Now, that we have our local video feed setup we need to use a similar function to connect our remote video stream.
191 | ```Java
192 | private void setupRemoteVideoStream(int uid) {
193 | FrameLayout videoContainer = findViewById(R.id.bg_video_container);
194 | SurfaceView videoSurface = RtcEngine.CreateRendererView(getBaseContext());
195 | videoContainer.addView(videoSurface);
196 | mRtcEngine.setupRemoteVideo(new VideoCanvas(videoSurface, VideoCanvas.RENDER_MODE_FIT, uid));
197 | mRtcEngine.setRemoteSubscribeFallbackOption(io.agora.rtc.Constants.STREAM_FALLBACK_OPTION_AUDIO_ONLY);
198 | }
199 | ```
200 |
201 | The main difference with the remote video from the local, is the user `id` parameter that gets passed to the engine as part of the `VideoCanvas` object that gets passed to the engine. The last line sets the fall back option in case the video degrades the engine will revert to audio only.
202 |
203 | 
204 |
205 |
206 | ## Step 8: Setup the SDK Event Handler ##
207 | Earlier, I made a reference to the `RtcEngineEventHandler`, and now it’s time to declare it as a property of our `MainActivity` Class. The engine will call these methods from the `RtcEngineEventHandler`.
208 |
209 | ```Java
210 | // Handle SDK Events
211 | private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
212 | @Override
213 | public void onUserJoined(final int uid, int elapsed) {
214 | runOnUiThread(new Runnable() {
215 | @Override
216 | public void run() {
217 | // set first remote user to the main bg video container
218 | setupRemoteVideoStream(uid);
219 | }
220 | });
221 | }
222 |
223 | // remote user has left channel
224 | @Override
225 | public void onUserOffline(int uid, int reason) { // Tutorial Step 7
226 | runOnUiThread(new Runnable() {
227 | @Override
228 | public void run() {
229 | onRemoteUserLeft();
230 | }
231 | });
232 | }
233 |
234 | // remote user has toggled their video
235 | @Override
236 | public void onRemoteVideoStateChanged(final int uid, final int state, int reason, int elapsed) {
237 | runOnUiThread(new Runnable() {
238 | @Override
239 | public void run() {
240 | onRemoteUserVideoToggle(uid, state);
241 | }
242 | });
243 | }
244 | };
245 | ```
246 |
247 | Each event triggers some fairly straight forward functions, including one we wrote in the previous step. In the interest of keeping this brief, I will provide the code below but I won’t give an in-depth breakdown.
248 |
249 | ```Java
250 | private void onRemoteUserVideoToggle(int uid, int state) {
251 | FrameLayout videoContainer = findViewById(R.id.bg_video_container);
252 |
253 | SurfaceView videoSurface = (SurfaceView) videoContainer.getChildAt(0);
254 | videoSurface.setVisibility(state == 0 ? View.GONE : View.VISIBLE);
255 |
256 | // add an icon to let the other user know remote video has been disabled
257 | if(state == 0){
258 | ImageView noCamera = new ImageView(this);
259 | noCamera.setImageResource(R.drawable.video_disabled);
260 | videoContainer.addView(noCamera);
261 | } else {
262 | ImageView noCamera = (ImageView) videoContainer.getChildAt(1);
263 | if(noCamera != null) {
264 | videoContainer.removeView(noCamera);
265 | }
266 | }
267 | }
268 |
269 | private void onRemoteUserLeft() {
270 | removeVideo(R.id.bg_video_container);
271 | }
272 |
273 | private void removeVideo(int containerID) {
274 | FrameLayout videoContainer = findViewById(containerID);
275 | videoContainer.removeAllViews();
276 | }
277 | ```
278 |
279 | ## Step 9: Joining and Leaving Channels ##
280 | I know what you’re thinking, STEP 9 ?!! Don’t sweat it the next two steps are really simple. Let’s start by joining a call…
281 |
282 | Below you can see from the first line, Agora SDK makes it simple, the engine calls joinChannel, passing in the channel name followed by the call to set up our local video stream. _(Step 7)_
283 |
284 | ```Java
285 | public void onjoinChannelClicked(View view) {
286 | mRtcEngine.joinChannel(null, "test-channel", "Extra Optional Data", 0);
287 | setupLocalVideoFeed();
288 | findViewById(R.id.joinBtn).setVisibility(View.GONE); // set the join button hidden
289 | findViewById(R.id.audioBtn).setVisibility(View.VISIBLE); // set the audio button hidden
290 | findViewById(R.id.leaveBtn).setVisibility(View.VISIBLE); // set the leave button hidden
291 | findViewById(R.id.videoBtn).setVisibility(View.VISIBLE); // set the video button hidden
292 | }
293 | ```
294 |
295 | Leaving the channel is even simpler, the engine calls leaveChannel. Above you’ll notice there are a few lines to remove the video stream subviews from each UI element.
296 | ```Java
297 | public void onLeaveChannelClicked(View view) {
298 | leaveChannel();
299 | removeVideo(R.id.floating_video_container);
300 | removeVideo(R.id.bg_video_container);
301 | findViewById(R.id.joinBtn).setVisibility(View.VISIBLE); // set the join button visible
302 | findViewById(R.id.audioBtn).setVisibility(View.GONE); // set the audio button hidden
303 | findViewById(R.id.leaveBtn).setVisibility(View.GONE); // set the leave button hidden
304 | findViewById(R.id.videoBtn).setVisibility(View.GONE); // set the video button hidden
305 | }
306 |
307 | private void leaveChannel() {
308 | mRtcEngine.leaveChannel();
309 | }
310 |
311 | private void removeVideo(int containerID) {
312 | FrameLayout videoContainer = findViewById(containerID);
313 | videoContainer.removeAllViews();
314 | }
315 | ```
316 |
317 | ## Step 10: Adding UI Functionality ##
318 | The last remaining parts are related to connecting the UI elements for toggling the microphone and video stream on the local device. Let’s start with the audio toggle.
319 |
320 | First, we get the reference to our button, and then check if it has been toggled on/off using `isSelected()`. Once we have updated the UI element state, we pass the button’s updated state to the engine.
321 | ```Java
322 | public void onAudioMuteClicked(View view) {
323 | ImageView btn = (ImageView) view;
324 | if (btn.isSelected()) {
325 | btn.setSelected(false);
326 | btn.setImageResource(R.drawable.audio_toggle_btn);
327 | } else {
328 | btn.setSelected(true);
329 | btn.setImageResource(R.drawable.audio_toggle_active_btn);
330 | }
331 |
332 | mRtcEngine.muteLocalAudioStream(btn.isSelected());
333 | }
334 | ```
335 |
336 | Moving on to the video toggle, as with the audio toggle we check/update the button’s state using `isSelected()` and then pass that to the engine. To give a better visual representation of the video being muted, we hide/show the `VideoSurface`.
337 |
338 | ```Java
339 | public void onVideoMuteClicked(View view) {
340 | ImageView btn = (ImageView) view;
341 | if (btn.isSelected()) {
342 | btn.setSelected(false);
343 | btn.setImageResource(R.drawable.video_toggle_btn);
344 | } else {
345 | btn.setSelected(true);
346 | btn.setImageResource(R.drawable.video_toggle_active_btn);
347 | }
348 |
349 | mRtcEngine.muteLocalVideoStream(btn.isSelected());
350 |
351 | FrameLayout container = findViewById(R.id.floating_video_container);
352 | container.setVisibility(btn.isSelected() ? View.GONE : View.VISIBLE);
353 | SurfaceView videoSurface = (SurfaceView) container.getChildAt(0);
354 | videoSurface.setZOrderMediaOverlay(!btn.isSelected());
355 | videoSurface.setVisibility(btn.isSelected() ? View.GONE : View.VISIBLE);
356 | }
357 | ```
358 |
359 | 
360 | I hope you enjoyed reading along and working together on creating a 1-to-1 Video Chat Android app using the [Agora.io Video SDK](https://docs.agora.io/en/Video/product_video?platform=All%20Platforms).
361 |
362 | =======
363 | This repo is part of a tutorial posted on Medium. For an in depth explanation of how to build your own version please read:
364 | https://medium.com/agora-io/1-to-1-video-chat-app-on-android-using-agora-io-3c2fe6c42fb4
365 |
366 | ***Requirements:***
367 | - Agora.io Developer Account (https://dashboard.agora.io)
368 | - Agora Video SDK for Android - available in the Agora.io Developer Center (https://docs.agora.io/en/Agora%20Platform/downloads)
369 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.example.android.myagoraapplication"
7 | minSdkVersion 16
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 |
20 | sourceSets {
21 | main {
22 |
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation 'com.github.agorabuilder:native-full-sdk:3.4.1'
29 | implementation 'com.android.support:appcompat-v7:28.0.0'
30 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
31 | testImplementation 'junit:junit:4.12'
32 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
34 | }
35 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 |
24 | -keep class io.agora.**{*;}
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/android/myagoraapplication/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.example.android.myagoraapplication;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.example.android.myagoraapplication", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/myagoraapplication/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.android.myagoraapplication;
2 |
3 | import android.Manifest;
4 | import android.content.pm.PackageManager;
5 | import android.graphics.PorterDuff;
6 | import android.support.annotation.NonNull;
7 | import android.support.v4.app.ActivityCompat;
8 | import android.support.v4.content.ContextCompat;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.os.Bundle;
11 | import android.util.Log;
12 | import android.view.SurfaceView;
13 | import android.view.View;
14 | import android.widget.FrameLayout;
15 | import android.widget.Button;
16 | import android.widget.ImageView;
17 | import android.widget.Toast;
18 |
19 | import io.agora.rtc.Constants;
20 | import io.agora.rtc.IRtcEngineEventHandler;
21 | import io.agora.rtc.RtcEngine;
22 | import io.agora.rtc.video.VideoEncoderConfiguration;
23 | import io.agora.rtc.video.VideoCanvas;
24 |
25 | public class MainActivity extends AppCompatActivity {
26 |
27 | private RtcEngine mRtcEngine;
28 |
29 | // Permissions
30 | private static final int PERMISSION_REQ_ID = 22;
31 | private static final String[] REQUESTED_PERMISSIONS = {Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA};
32 |
33 | private static final String LOG_TAG = MainActivity.class.getSimpleName();
34 |
35 | // Handle SDK Events
36 | private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
37 | @Override
38 | public void onUserJoined(final int uid, int elapsed) {
39 | runOnUiThread(new Runnable() {
40 | @Override
41 | public void run() {
42 | // set first remote user to the main bg video container
43 | setupRemoteVideoStream(uid);
44 | }
45 | });
46 | }
47 |
48 | // remote user has left channel
49 | @Override
50 | public void onUserOffline(int uid, int reason) { // Tutorial Step 7
51 | runOnUiThread(new Runnable() {
52 | @Override
53 | public void run() {
54 | onRemoteUserLeft();
55 | }
56 | });
57 | }
58 |
59 | // remote user has toggled their video
60 | @Override
61 | public void onRemoteVideoStateChanged(final int uid, final int state, int reason, int elapsed) {
62 | runOnUiThread(new Runnable() {
63 | @Override
64 | public void run() {
65 | onRemoteUserVideoToggle(uid, state);
66 | }
67 | });
68 | }
69 | };
70 |
71 | @Override
72 | protected void onCreate(Bundle savedInstanceState) {
73 | super.onCreate(savedInstanceState);
74 | setContentView(R.layout.activity_main);
75 |
76 | if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
77 | checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
78 | initAgoraEngine();
79 | }
80 |
81 | findViewById(R.id.audioBtn).setVisibility(View.GONE); // set the audio button hidden
82 | findViewById(R.id.leaveBtn).setVisibility(View.GONE); // set the leave button hidden
83 | findViewById(R.id.videoBtn).setVisibility(View.GONE); // set the video button hidden
84 | }
85 |
86 | private void initAgoraEngine() {
87 | try {
88 | mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler);
89 | } catch (Exception e) {
90 | Log.e(LOG_TAG, Log.getStackTraceString(e));
91 |
92 | throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
93 | }
94 | setupSession();
95 | }
96 |
97 | private void setupSession() {
98 | mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_COMMUNICATION);
99 |
100 | mRtcEngine.enableVideo();
101 |
102 | mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(VideoEncoderConfiguration.VD_640x480, VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_30,
103 | VideoEncoderConfiguration.STANDARD_BITRATE,
104 | VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
105 | }
106 |
107 | private void setupLocalVideoFeed() {
108 |
109 | // setup the container for the local user
110 | FrameLayout videoContainer = findViewById(R.id.floating_video_container);
111 | SurfaceView videoSurface = RtcEngine.CreateRendererView(getBaseContext());
112 | videoSurface.setZOrderMediaOverlay(true);
113 | videoContainer.addView(videoSurface);
114 | mRtcEngine.setupLocalVideo(new VideoCanvas(videoSurface, VideoCanvas.RENDER_MODE_FIT, 0));
115 | }
116 |
117 | private void setupRemoteVideoStream(int uid) {
118 | // setup ui element for the remote stream
119 | FrameLayout videoContainer = findViewById(R.id.bg_video_container);
120 | // ignore any new streams that join the session
121 | if (videoContainer.getChildCount() >= 1) {
122 | return;
123 | }
124 |
125 | SurfaceView videoSurface = RtcEngine.CreateRendererView(getBaseContext());
126 | videoContainer.addView(videoSurface);
127 | mRtcEngine.setupRemoteVideo(new VideoCanvas(videoSurface, VideoCanvas.RENDER_MODE_FIT, uid));
128 | mRtcEngine.setRemoteSubscribeFallbackOption(io.agora.rtc.Constants.STREAM_FALLBACK_OPTION_AUDIO_ONLY);
129 |
130 | }
131 |
132 | public void onAudioMuteClicked(View view) {
133 | ImageView btn = (ImageView) view;
134 | if (btn.isSelected()) {
135 | btn.setSelected(false);
136 | btn.setImageResource(R.drawable.audio_toggle_btn);
137 | } else {
138 | btn.setSelected(true);
139 | btn.setImageResource(R.drawable.audio_toggle_active_btn);
140 | }
141 |
142 | mRtcEngine.muteLocalAudioStream(btn.isSelected());
143 | }
144 |
145 | public void onVideoMuteClicked(View view) {
146 | ImageView btn = (ImageView) view;
147 | if (btn.isSelected()) {
148 | btn.setSelected(false);
149 | btn.setImageResource(R.drawable.video_toggle_btn);
150 | } else {
151 | btn.setSelected(true);
152 | btn.setImageResource(R.drawable.video_toggle_active_btn);
153 | }
154 |
155 | mRtcEngine.muteLocalVideoStream(btn.isSelected());
156 |
157 | FrameLayout container = findViewById(R.id.floating_video_container);
158 | container.setVisibility(btn.isSelected() ? View.GONE : View.VISIBLE);
159 | SurfaceView videoSurface = (SurfaceView) container.getChildAt(0);
160 | videoSurface.setZOrderMediaOverlay(!btn.isSelected());
161 | videoSurface.setVisibility(btn.isSelected() ? View.GONE : View.VISIBLE);
162 | }
163 |
164 | // join the channel when user clicks UI button
165 | public void onjoinChannelClicked(View view) {
166 | mRtcEngine.joinChannel(null, "test-channel", "Extra Optional Data", 0); // if you do not specify the uid, Agora will assign one.
167 | setupLocalVideoFeed();
168 | findViewById(R.id.joinBtn).setVisibility(View.GONE); // set the join button hidden
169 | findViewById(R.id.audioBtn).setVisibility(View.VISIBLE); // set the audio button hidden
170 | findViewById(R.id.leaveBtn).setVisibility(View.VISIBLE); // set the leave button hidden
171 | findViewById(R.id.videoBtn).setVisibility(View.VISIBLE); // set the video button hidden
172 | }
173 |
174 | public void onLeaveChannelClicked(View view) {
175 | leaveChannel();
176 | removeVideo(R.id.floating_video_container);
177 | removeVideo(R.id.bg_video_container);
178 | findViewById(R.id.joinBtn).setVisibility(View.VISIBLE); // set the join button visible
179 | findViewById(R.id.audioBtn).setVisibility(View.GONE); // set the audio button hidden
180 | findViewById(R.id.leaveBtn).setVisibility(View.GONE); // set the leave button hidden
181 | findViewById(R.id.videoBtn).setVisibility(View.GONE); // set the video button hidden
182 | }
183 |
184 | private void leaveChannel() {
185 | mRtcEngine.leaveChannel();
186 | }
187 |
188 | private void removeVideo(int containerID) {
189 | FrameLayout videoContainer = findViewById(containerID);
190 | videoContainer.removeAllViews();
191 | }
192 |
193 | private void onRemoteUserVideoToggle(int uid, int state) {
194 | FrameLayout videoContainer = findViewById(R.id.bg_video_container);
195 |
196 | SurfaceView videoSurface = (SurfaceView) videoContainer.getChildAt(0);
197 | videoSurface.setVisibility(state == 0 ? View.GONE : View.VISIBLE);
198 |
199 | // add an icon to let the other user know remote video has been disabled
200 | if(state == 0){
201 | ImageView noCamera = new ImageView(this);
202 | noCamera.setImageResource(R.drawable.video_disabled);
203 | videoContainer.addView(noCamera);
204 | } else {
205 | ImageView noCamera = (ImageView) videoContainer.getChildAt(1);
206 | if(noCamera != null) {
207 | videoContainer.removeView(noCamera);
208 | }
209 | }
210 | }
211 |
212 | private void onRemoteUserLeft() {
213 | removeVideo(R.id.bg_video_container);
214 | }
215 |
216 |
217 |
218 | public boolean checkSelfPermission(String permission, int requestCode) {
219 | Log.i(LOG_TAG, "checkSelfPermission " + permission + " " + requestCode);
220 | if (ContextCompat.checkSelfPermission(this,
221 | permission)
222 | != PackageManager.PERMISSION_GRANTED) {
223 |
224 | ActivityCompat.requestPermissions(this,
225 | REQUESTED_PERMISSIONS,
226 | requestCode);
227 | return false;
228 | }
229 | return true;
230 | }
231 |
232 |
233 | @Override
234 | public void onRequestPermissionsResult(int requestCode,
235 | @NonNull String permissions[], @NonNull int[] grantResults) {
236 | Log.i(LOG_TAG, "onRequestPermissionsResult " + grantResults[0] + " " + requestCode);
237 |
238 | switch (requestCode) {
239 | case PERMISSION_REQ_ID: {
240 | if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) {
241 | Log.i(LOG_TAG, "Need permissions " + Manifest.permission.RECORD_AUDIO + "/" + Manifest.permission.CAMERA);
242 | break;
243 | }
244 |
245 | initAgoraEngine();
246 | break;
247 | }
248 | }
249 | }
250 |
251 | @Override
252 | protected void onDestroy() {
253 | super.onDestroy();
254 |
255 | leaveChannel();
256 | RtcEngine.destroy();
257 | mRtcEngine = null;
258 | }
259 |
260 | public final void showLongToast(final String msg) {
261 | this.runOnUiThread(new Runnable() {
262 | @Override
263 | public void run() {
264 | Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
265 | }
266 | });
267 | }
268 |
269 |
270 | }
271 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/audio_toggle_active_btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/drawable/audio_toggle_active_btn.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/audio_toggle_btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/drawable/audio_toggle_btn.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/end_call.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/drawable/end_call.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/join_call.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/drawable/join_call.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/video_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/drawable/video_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/video_toggle_active_btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/drawable/video_toggle_active_btn.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/video_toggle_btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/drawable/video_toggle_btn.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
20 |
21 |
35 |
36 |
48 |
49 |
55 |
56 |
64 |
65 |
73 |
74 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Agora - 1:1 Video Call
3 | AGORA_KEY
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/android/myagoraapplication/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.android.myagoraapplication;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.3'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | maven { url 'https://www.jitpack.io' }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/digitallysavvy/android-video-chat-demo/090fa8a4b630e7be635556fc3af98b69a1388327/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jun 03 12:07:43 EDT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------