Build a Streaming Application with Face Filter on Android
Tags: agoraio, banuba, android, videocall
Published: 2020-07-29 00:24:24

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rgy116etf6o9jbgayxux.png)
Over the last decade, live streaming has gradually become one of the most popular ways of entertainment. More and more people are enjoying sharing their lives to the public through live streaming.

Nowadays, there are plenty of streaming application choices such as Twitch, Facebook Live, and Youtube Live; these apps provide many fun features such as face filter and voice changer. But have you ever thought about building a streaming application on your own? Are you hesitating because some of these features seem too fancy or complicated?

Luckily, using the [Agora Video SDK](https://docs.agora.io/en/Video/product_video?platform=All%20Platforms) and [Banuba Face AR SDK](https://www.banuba.com/) makes developing a streaming application with a face filter quick and easy. Today, I’ll show you how to do this on Android step by step.

#Prerequisites
1. A basic-to-intermediate understanding of Java and Android SDK
2. [Agora.io developer account](https://www.agora.io/en/blog/how-to-get-started-with-agora)
3. [Banuba Face AR demo app](https://drive.google.com/drive/folders/1Up10V5KjkTZYBODKkx7UdDSqYpEu33S-?_hsenc=p2ANqtz--RruVWo98u8Ifip-JQf4aHRHr8CSivJexqyw5TJ2mc_Wr0BDz2sjPmkBr359FLpQ1BEtmaoIChIDMNFQyFkhza1w69nA&_hsmi=75726806)
4. Android Studio and 2 Android devices

_Please Note: While no Java/Android knowledge is needed to follow along, certain basic concepts in Java/Android won’t be explained along the way._

#Overview
This guide will go over the steps for building a live streaming application on Android using the Agora SDK. This is a list of the core features that will be included in our app:

* Users (streams and audiences) can create and login into their account. User account information will be saved in Google Firebase Realtime Database.
* Users can set up virtual rooms to host live streams and become a streamer.
* Users can find all live streams and join the virtual room as an audience.
* Streamers can use the face filter feature to stream with a virtual mask or animation.
* Streamers can change their voice through a voice changer.
* Audiences in a virtual room can send text messages which can be seen by everyone in that room.
* Users can search for other users by their name and send private text messages to them.

_You can find this [demo app](https://drive.google.com/drive/folders/1o2IeRhJdylw9FQPNZZaf05CrTd2rmmpD) as a reference for this article._

#Download Banuba FaceAR Demo App
It is easier to build our application on top of the [Banuba FaceAR demo apps](https://drive.google.com/drive/folders/1Up10V5KjkTZYBODKkx7UdDSqYpEu33S-?_hsenc=p2ANqtz--RruVWo98u8Ifip-JQf4aHRHr8CSivJexqyw5TJ2mc_Wr0BDz2sjPmkBr359FLpQ1BEtmaoIChIDMNFQyFkhza1w69nA&_hsmi=75726806). The package contains a default [Banuba SDK Demo app](https://docs.banuba.com/docs/android/android_demo_app_ui) demonstrating major Banuba [SDK features](https://docs.banuba.com/docs/overview/sdk_features) and a [beautification example app](https://docs.banuba.com/docs/android/android_beauty_example_ui) with neural networks coloring feature. We are going to focus on the [Banuba SDK Demo app](https://docs.banuba.com/docs/android/android_demo_app_ui) for our project. Feel free to explore the beautification example app on your own.

Now, download the [Banuba FaceAR demo app](https://drive.google.com/drive/folders/1Up10V5KjkTZYBODKkx7UdDSqYpEu33S-?_hsenc=p2ANqtz--RruVWo98u8Ifip-JQf4aHRHr8CSivJexqyw5TJ2mc_Wr0BDz2sjPmkBr359FLpQ1BEtmaoIChIDMNFQyFkhza1w69nA&_hsmi=75726806) and open it in Android Studio. Follow the [online instruction](https://docs.banuba.com/docs/android/android_demo_app) to set up the project.

_You need to have a Banuba client token to run the project. Contact info@banuba.com for more information._

After you’re done, run the “app” project and you should be able to see something like this with a lot of cool face filter options:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mgyzptx35l85h8bk9swq.png)

This will be our streamer’s view when he starts a live stream. However, the Banuba demo only adds the face filters on the local camera view. If we want to send this to all of the audience, that is where [Agora Video SDK](https://docs.agora.io/en/Video/product_video?platform=All%20Platforms) comes very handy.

#Integrate Agora Video SDK
To integrate Agora SDK in your project, you need to add the following lines in the __/app/build.gradle__ file of the project.

```
//Agora RTC SDK for video call
implementation 'io.agora.rtc:full-sdk:3.0.1.1'

//Agora RTM SDK for chat messaging
implementation 'io.agora.rtm:rtm-sdk:1.2.2'
//Agora UIKit
implementation 'io.agora.uikit:agorauikit:2.0.1'
```

The first implementation is for the [Agora Video SDK](https://docs.agora.io/en/Video/product_video?platform=All%20Platforms). While the second one is for the [Agora Real-time Messaging SDK](https://docs.agora.io/en/Real-time-Messaging/product_rtm?platform=All%20Platforms). And the last one is for [Agora Android UIKit](https://github.com/AgoraIO-Community/Android-UIKit) which is a Agora Android UI wrapper. Using the UIKit, you just need a few lines of code to start a video call. And I will demonstrate that in the later section of this blog when we are dealing with the audience view.

###Add Project Permissions
The next thing we need to do is to add project permissions in the __/app/src/main/AndroidManifest.xml__ file for device access according to your needs:
```
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 
<!-- The Agora SDK requires Bluetooth permissions in case users are using Bluetooth devices. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
```

#Setup Google Firebase Database
Since our application allows users to search and chat with other users in the app, we need to use [Firebase Realtime Database](https://firebase.google.com/products/realtime-database) to save users’ account information. Here are the steps you need to connect your application to Firebase Realtime Database:

1. In your Android Studio, Click “__Tools__”, then select “__Firebase__”.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/r722jn2ithzqj2034jd2.png)
2. On the right hand side, you should see a Firebase assistant tab showing up. Find “__Realtime Database__” and click “__Save and retrieve Data__”. ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ki616wads6ndsobggbfx.png)
3. Then you should see the detailed page for Realtime Database. Click the “__Connect to Firebase__” button and “__Add the Realtime Database to your app__” button. Follow the on-screen instruction, if any.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cducp6joc3lfm7b6yh0n.png)
4. Now your application is connected to Firebase Realtime Database. The last thing you need to do is to go to your Firebase console and change the database rules. Remember to select the Realtime Database instead of the Cloud Firestore. Change the rules for read and write to “__true__” so that you can have access to the database.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q2t0qnd5d3a2fqf270hh.png)

#User Login
First, let’s build our application from the first page. Same as most of the streaming applications, our application needs a user login page. And that should look like this:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/x42juwd2p2tmgowid2g2.png)

When the user clicks the “__Next__” button, we need to get the user’s name and pass that to the next activity, __HomeActivity__. Here is the code for that.
```java
public void onLoginNextClick(View view) {
    EditText userNameEditText = findViewById(R.id.et_login_user_name);
    String userName = userNameEditText.getText().toString();

    if(userName == null || userName.equals("")) {
        Toast.makeText(this, "user name cannot be empty", Toast.LENGTH_SHORT).show();
    }else {
        Intent intent = new Intent(this, HomeActivity.class);
        intent.putExtra("userName", userName);
        startActivity(intent);
    }
}
```

#Building HomeActivity
In the __HomeActivity__, we will create a three-page [bottom navigation bar](https://material.io/develop/android/components/bottom-navigation) with a fragment connected to each page. If the user taps on the first bottom navigation menu, he will be directed to the __Live__ fragment. Similarly, the second menu will direct the user to the __Chat__ fragment and the last one is the __Other__ fragment.

In the __onCreate__ method of the __HomeActivity__, we will get the user name passed from the previous activity and save that user information on Firebase Database.
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);
    userName = getIntent().getStringExtra("userName");

    saveUserOnFireDatabase(userName);
}
private void saveUserOnFireDatabase(String userName) {
    mRef = FirebaseDatabase.getInstance().getReference("Users");
    mRef.push();
    mRef.child(userName).setValue(new DBUser(userName, false));
}
```

###Live Fragment
The Live fragment is to show all the live streamers’ names and allow other users to join the streamer’s virtual room by clicking a button.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dj8c673phnwms66wkhz9.png)

In the center of the fragment is a recycler view displaying all the live streamers. You can retrieve the streamer data from the Firebase database by registering a __ChildEventListener__ on the Firebase database reference.

```java
childEventListener = new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
        DBUser result = dataSnapshot.getValue(DBUser.class);
        if (!result.getName().equals(userName) && result.getStreaming() == true) {
            streamerList.add(result);
        }
    }

    ...
};

mRef.orderByChild("name").addChildEventListener(childEventListener);
```

On the side of the streamer’s name, there is a button for users to join that virtual room. And that will direct users to the __Audience__ activity using an Intent.
```java
private void joinStream(String streamerName) {
    Intent intent = new Intent(getActivity(), AudienceActivity.class);
    intent.putExtra("streamerName", streamerName);
    intent.putExtra("userName", userName);
    startActivity(intent);
}
```

When the user clicks the “GO LIVE” button on the bottom, he will be directed to the __MainActivity__ using an Intent. The __MainActivity__ is the activity to enable the Banuba face filter features which we have discussed above.

```java
@Override
public void onClick(View v) {
    Intent intent = new Intent(getActivity(), MainActivity.class);
    intent.putExtra("userName", userName);
    startActivity(intent);
}
```

###Chat Fragment
The __Chat__ fragment is the place for users to search for other users and send private text messages to them. So you will build your UI like this:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/slppeknf0fk8kxiq4xpq.png)

#Send Local Camera View to Remote Users
Let’s open the __MainActivity__ class. From the previous steps, we know this activity is for adding the face filter features on the local camera views. Our job here is to send these local camera views with face filters out to the remote users.

###Initialize RtcEngine and Join Channel
Inside the __onCreate__ callback, get the username passed from the home activity and initialize the Agora RtcEngine.

```java
userName = getIntent().getStringExtra("userName");

try {
    mRtcEngine = RtcEngine.create(getBaseContext(), getString(appID), mRtcEventHandler);
} catch (Exception e) {
    Log.e(TAG, Log.getStackTraceString(e));
    throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}

mRtcEngine.enableVideo();
```

Here, you need to put the [__appID__](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#a-nameappidaapp-id) as a parameter when initialize the RtcEngine. In order to get the [_appID_](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#a-nameappidaapp-id) in the parameter, follow these steps:
1. [Create an Agora project](https://docs.agora.io/en/Agora%20Platform/manage_projects?platform=All%20Platforms) in the Agora Console.
2. Click the [Project Management](https://sso.agora.io/login/?response_type=code&client_id=59T2cPE9by201sbXpExrCEVNgIi0su1h&redirect_uri=https%3A%2F%2Fconsole.agora.io%2Fapi%2Fv2%2Foauth) tab on the left navigation panel.
3. Click “Create” and follow the on-screen instructions to set the project name, choose an authentication mechanism, and click “Submit”.
4. On the Project Management page, find the App ID of your project.

The __mRtcEventHandler__ is a handler to manage different events occurring with the RtcEngine.
Then we need to call [__setExternalVideoSource__](https://docs.agora.io/en/Video/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a2d9966c52798ab62ed941fa865e926cd) to switch Agora video input to an external video source which is the camera frame from Banuba SDK.

```java
mRtcEngine.setExternalVideoSource(true, false, true);
```

After that we are ready to join the channel by calling __joinChannel()__ on the RtcEngine instance. The __channelName__ is the user name we get from the previous activity. In this case, each streamer will start a video call channel with his entered user name when he login.

```java
mRtcEngine.joinChannel(token, channelName, "Extra Info", 0);
```
_Please Note: The token in the parameter can be set to null. You can get more information about token [here](https://docs.agora.io/en/faqs/token_usage_generation_description#function-of-a-token)._

###Push External Video Frame
We have already let the Agora RtcEngine know that we will use the external video source as the video input. However, we haven’t provided the video frame yet.

First, call __startForwardingFrames()__ on BanubaSDKManager instance and that will trigger the __onFrameRendered()__ callback.
```java
mSdkManager.startForwardingFrames();
```
Now, in the __onFrameRendered()__ callback, we can get the camera frame data which contains the local camera view and the AR Face Filter view. Then create an __AgoraVideoFrame__ instance and pass this information in. Call [__pushExternalVideoFrame()__](https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a6e7327f4449800a2c2ddc200eb2c0386) on the Agora RtcEngine instance and pass the __AgoraVideoFrame__ instance in the parameter.
```java
@Override
public void onFrameRendered(@NonNull Data data, int width, int height) {
    byte[] arr = new byte[data.data.remaining()];
    data.data.get(arr);

    AgoraVideoFrame agoraVideoFrame = new AgoraVideoFrame();
    agoraVideoFrame.buf = arr;
    agoraVideoFrame.stride = width;
    agoraVideoFrame.height = height;
    agoraVideoFrame.format = AgoraVideoFrame.FORMAT_RGBA;
    agoraVideoFrame.timeStamp = System.currentTimeMillis();
    mRtcEngine.pushExternalVideoFrame(agoraVideoFrame);
    data.close();
}
```

Now these cool face filters and animations can be sent to the remote users who are in the same Rtc channel.

_You can test it by using [Agora Web Showcase](https://webdemo.agora.io/agora-web-showcase/examples/Agora-Web-Tutorial-1to1-Web/). Enter your appID, token and channel name and you should be able to see the video._

###Add the Voice Changer
To make the streaming experience more interesting, we can add a voice changer in the application. Create a button in the __MainActivity__ and set the onClick method for that. When the user clicks that, we will call __setLocalVoiceChanger__ to set the voice changer.

```java
public void onVoiceChangerClick(View view) {
    mRtcEngine.setLocalVoiceChanger(VOICE_CHANGER_BABYGIRL);
    Toast.makeText(this, "Voice Changer ON", Toast.LENGTH_SHORT).show();
}
```
There are many different voice changers you can choose. Here I used the baby girl’s voice. For more voice changer options, check the [__setLocalVoiceChanger__](https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#ade6883c7878b7a596d5b2563462597dd) api.

#Set Up Audience View
Setting up the audience view is easier. Let’s go to the Audience activity and use the [__Agora UIKit__](https://github.com/samyak-jain/uikitv2).

To use the UIKit, simply call this in the __onCreate()__ method.

```java
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    AgoraRTC.instance().bootstrap(this, appID, channelName);
    setContentView(R.layout.group_template);
}
```
Here, the __appId__ is the same __appID__ we entered in the __MainActivity__ and the channelName is the streamer’s name we passed from the __LiveFragment__.

_For more information, visit [__UIKit Github__](https://github.com/samyak-jain/uikitv2)._

Just 2 lines of code, you are good to start a video call!

However, in our case, we need to add some customizations to make it to the audience view. First, we don’t want the audience to send the video and audio streams to the channel.

```java
AgoraRTC.instance().muteLocalVideoStream(true);
AgoraRTC.instance().muteLocalAudioStream(true);
```
Also, we want the remote view (streamer view) always on the main screen. To do this, we need to register the __IRtcEnginEventHandler__ instance.
```java
IRtcEngineEventHandler eventHandler = new IRtcEngineEventHandler() {

    @Override
    public void onRemoteVideoStateChanged(final int uid, int state, int reason, int elapsed) {
        super.onRemoteVideoStateChanged(uid, state, reason, elapsed);

        if (state == REMOTE_VIDEO_STATE_STARTING) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    agoraView.setUID(uid);
                }
            });
        }
    }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    agoraView = findViewById(R.id.max_view);
    AgoraRTC.instance().registerListener(eventHandler);
    ...
}
```
The last customization we need to do is to add a button click listener on the end call button to redirect the user to the previous activity when the user clicks the end call button.

```java
endCallButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        finish();
    }
});

```
Till this point, our streaming application is able to allow users to stream with face filters features on or watch the stream as an audience. Let’s make it more robust by adding the text messaging system using [Agora RTM SDK](https://docs.agora.io/en/Real-time-Messaging/product_rtm?platform=All%20Platforms).

#Text Messaging
Text messaging in a streaming application can be divided into two parts: channel messaging and private messaging. The first one sends messages to the channel and everyone in the channel can receive that message while the second one sends private messages from a peer to another peer.

###Channel Messaging
We want to allow all the audiences to send messages in the streamer’s room. To achieve that, we need to create a __RtmClient__ using Agora Messaging SDK.
```java
mRtmclient = RtmClient.createInstance(mContext, appID, new RtmClientListener() {
        @Override
        public void onConnectionStateChanged(int state, int reason){
            for (RtmClientListener listener : mListenerList) {
                listener.onConnectionStateChanged(state, reason);
            }
        }@Override
        public void onMessageReceived(RtmMessage rtmMessage, String peerId) { ... }
});
```
Here, we pass the same __appID__ as we used when we initialize the Agora Video engine.

In the __onCreateView__ method in the __LiveFragment__, we need to login __RtmClient__.

```java
mRtmClient.login(null, userName, new io.agora.rtm.ResultCallback<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        ...
    }
    @Override
    public void onFailure(ErrorInfo errorInfo) {
        ...
    }
});
```
We use the same __userName__ as the one we used to join the video call channel. In this case, users will join the RTM channel and RTC channel with the same channel name.

In the __AudienceActivity__ onCreate() method, we are going to call __createChannel()__ on the __mRtmClient__ we initialized earlier to create a RtmChannel instance.

```java
mRtmChannel = mRtmClient.createChannel(mChannelName, new MyChannelListener());
```

We need to define the __MyChannelListener__ which implements __RtmChannelListener__ with callback __onMessageReceived()__ to handle the logic when we receive messages sent from any member in the chat room.

```java
class MyChannelListener implements RtmChannelListener {
    @Override
    public void onMessageReceived(final RtmMessage message, final RtmChannelMember fromMember) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                String account = fromMember.getUserId();
                String msg = message.getText();
                MessageBean messageBean = new MessageBean(account, msg, false);
                messageBean.setBackground(getMessageColor(account));
                mMessageBeanList.add(messageBean);
                mMessageAdapter.notifyItemRangeChanged(mMessageBeanList.size(), 1);
                mRecyclerView.scrollToPosition(mMessageBeanList.size() - 1);
            }
        });
    }
    ...
}
```
Then join the channel by calling __join()__ on the RtmChannel instance.

```java
mRtmChannel.join(new ResultCallback<Void>() {
    @Override
    public void onSuccess(Void responseInfo) { ... }@Override
    public void onFailure(ErrorInfo errorInfo) { ... }
});
```
In the __onSuccess()__ callback, we can call __getMembers()__ on the RtmChannel instance to get the number of members in the chat room.

We also need to add some UI components to let users enter their text messages and see the chat history. The audience layout should be similar to this:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ah3bgzif9s2obheb3okh.png)
When a user clicks the “Send” button, we will send his message out to the channel by calling __sendMessage()__ method on the RtmChannel instance.

```java
private void sendChannelMessage(String content) {
    // step 1: create a message
    RtmMessage message = mRtmClient.createMessage();
    message.setText(content);
    // step 2: send message to channel
    mRtmChannel.sendMessage(message, new ResultCallback<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
            ...
        }

        @Override
        public void onFailure(ErrorInfo errorInfo) {
            ...
        }
    });
}
```

###Private Messaging
In the __ChatFragment__, users can find their friend by searching the friend’s name and send private text messages to them.

To search friends by names, you need to register the ChildEventListener and get data from Firebase Database.

```java
childEventListener = new ChildEventListener() {
  @Override
  public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
    DBUser result = dataSnapshot.getValue(DBUser.class);
    showFriendText.setText(result.getName());
    mRef.orderByChild("name").startAt(friendName).endAt(friendName + "\uf8ff").removeEventListener(childEventListener);
  }
  ...
};

mRef.orderByChild("name").startAt(friendName).endAt(friendName + "\uf8ff").addChildEventListener(childEventListener);
```

Sending the private chat message is very similar as sending the group message. There are a few differences you need to pay attention to:
1. You don’t need to create a RTM channel to send message.
2. You need to register a [__RtmClientListener__](https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_java/interfaceio_1_1agora_1_1rtm_1_1_rtm_client_listener.html) and receive messages from the __onMessageReceived__ callback.

```java
class MyRtmClientListener implements RtmClientListener {

    @Override
    public void onMessageReceived(final RtmMessage message, final String peerId) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                String content = message.getText();
                if (peerId.equals(mPeerId)) {
                    MessageBean messageBean = new MessageBean(peerId, content,false);
                    messageBean.setBackground(getMessageColor(peerId));
                    mMessageBeanList.add(messageBean);
                    mMessageAdapter.notifyItemRangeChanged(mMessageBeanList.size(), 1);
                    mRecyclerView.scrollToPosition(mMessageBeanList.size() - 1);
                } else {
                    MessageUtil.addMessageBean(peerId, content);
                }
            }
        });
    }

    ...
}
```
3.When you send the message out, call __sendMessageToPeer()__ instead of __sendMessage()__ which we used to send channel messages.

```java
mRtmClient.sendMessageToPeer(mPeerId, message, mChatManager.getSendMessageOptions(), new ResultCallback<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
       ...
    }

    @Override
    public void onFailure(ErrorInfo errorInfo) {
       ...
    }
});
```

#Build And Test on Device
Now let’s run our application!

In Android Studio, make sure your Android device is plugged in, and click __Run__.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xqh2vo0ttg9gjoxezivr.gif)

#Done!
Congratulations! You just built yourself a live streaming application with face filter features and chat messaging!

Thank you for following along. If you want to see more features such as virtual gifting and in app gaming, please leave a comment below! Here is the email address for any of the questions you might have: devrel@agora.io.