Satyam Parasa is a web and mobile application developer from India. He loves learning new Technology. The tech enthusiasm led him to make a blog on Flutter named flutterant.com.
Since the beginning of the pandemic, live video streaming is a buzzword everywhere you turn. While many businesses have faced losses or decelerating growth, the media industry has seen exponential growth. In fact, it has become a major platform for content sharing, especially for entertainment, education, and information technology. Live streaming use cases include:
- Live conferencing
- Online Q&A sessions
- Company and industry conferences
Although many companies provide streaming services, Agora stands out because it enables people to easily develop their own live streaming applications with less code.
In this article, we are going to build a live streaming application using the Agora Flutter SDK with an add-on feature that can switch the client role in live streaming. The user can join as a host using the Agora RTM SDK.
- Basic knowledge of Flutter
- Any IDE (for example, Android Studio or Visual Studio Code)
- Agora Flutter SDK
- Agora Flutter RTM SDK
- An Agora developer account (see How to Get Started with Agora)
- Create a Flutter project in your favorite IDE, and remove the boilerplate code.
- Add the following required dependencies in the
pubspec.yamlfile, and install those dependencies by running
3. Create the file structure in the lib folder:
Building the Home Page
Let’s create a dart file with the name
home_page.dart. This page will take the username, channel name, and role (broadcaster or audience) of the client.
Whenever the user clicks the join button, the app will ask for the camera and mic permissions from the user. If permissions are granted, then the collected data will be passed to
onJoin() method will get triggered whenever the user clicks the join button on the home page. It validates the user inputs and calls the
_handleCameraAndMic(..) method to get the camera and mic permissions. After getting the required permissions, the collected data will be passed to
Here we used the permission handler plug-in to request the permissions and check their status:
Home Page Output:
Building the Live Stream Page
Create a dart file with the name
live_stream_page.dart. On this page, we have to create two interfaces: the broadcaster view and the audience view.
In the broadcaster view, we can show the number of members in the channel along with co-hosts in a horizontal row, the local user view, and interactive buttons.
In the audience view, we will show two interactive buttons. One is to leave the call, and the other is to join as host (the user can join as host by clicking this button):
Let’s have a look at the above code for understanding the variable uses.
Our LiveStreamPage constructor holds the values of the channel name, the username, and the role of the user, which come from the home page:
_usersis to maintain the data of the users who joined the channel.
_isMicMutedis to give the Boolean status of whether or not the mic is muted.
_isVideoMutedis to give the Boolean status of whether or not the video is muted.
_isLocalUserJoinedreturns a Boolean value indicating whether or not a local user has joined the channel.
_isCamSwitchis used to determine whether or not the status of the came has changed.
_rtcEngineis a reference to the RtcEngine class.
_rtmClientis a reference to the AgoraRtmClient class.
_rtmChanneis a reference to the AgoraRtmChannel class.
channelCountis to maintain the count of the members in the specific channel.
As we know, the
initState() method is called only once when the stateful widget is added to the widget tree. Here, we initialize the Agora RTM by calling the
initRTM() method, we create the object of our AgoraRtmClient with the App ID. That object will allow the user to log in to the RTM system by using the
login() method, which has two parameters: token and userId. Here, we are using user name as user ID. We are developing this app for testing, so we can keep null as a token:
onMessageReceived()occurs when the local user receives a peer-to-peer message. It returns a message and a peer ID.
onConnectionStateChanged()occurs when the connection state changes between the SDK and the Agora RTM system. It returns the connection state and the reason.
- We are calling
_createRtmChannel()method to create and join to a specific channel. After that, we’ll call the
initRTC()method to initialize Agora RTC.
If we look at the code above, we create an object for AgoraRtmChannel by calling
createChannel() which uses a unique channel ID. It cannot be empty:
onMemberJoinedtriggers when the user joins the channel. It returns AgoraRtmMember, which includes the user ID and the channel ID.
onMemberLeftoccurs when the user leaves the channel. It returns AgoraRtmMember, which includes the user ID and the channel ID.
onMessageReceivedinitiates a new channel message is received. It returns AgoraRtmMessage and AgoraRtmMember.
onAttributesUpdatedtriggers when channel attributes are updated. It returns a list of Agora RTM channel attributes.
As we discussed earlier, the
channelCount variable maintains the count of the users present in the channel. We can get the channel members by calling the
For our project, we’ve now built up an Agora RTM. Let’s add Agora RTC:
If we look at the above code, the
initRTC() method is responsible for calling the following methods:
_initAgoraEngine() is responsible for initializing the Rtc Engine.
joinChannel() is responsible for joining into a specific channel.
_addAgoraEventHandlers() is responsible for Event handling.
_initAgoraRtcEngine() method, the AgoraRtc Engine will be initialized, and our app will connect to the Agora’ engine by the App ID that we got from the Agora developer console (this App ID is kept in the utils.dart file). The
enableVideo() method is to enable the video mode. The channel profile is set to LiveBroadcasting. We can set the client roles based on the user’s input with the
_addAgoraEventHandler() is responsible for the RtcEngine event callback methods, which set the event handler by calling
setEventHandler(). After setting the engine event handler, we can listen to engine events and receive the statistics of the corresponding RtcEngine.
The major callback methods include:
joinChannelSuccess()triggers when the local user joins a specific channel. This method returns the channel name, uid for the user, and the time elapsed (in ms) for the local user to join the specific channel.
leaveChannel()initiates whenever the user leaves the channel. It returns the RtcStats, which includes call duration, number of bytes transmitted and received, latency, and so on.
userJoined()triggers when a remote user joins a specific channel. It returns the uid for the user and the time elapsed(in ms) for the remote user to join a channel.
userOffline()occurs when the remote user leaves the channel. Itt returns the uid of the user and the reason why the user went offline.
clientRoleChanged()triggers whenever the user switches the role in live streaming (for example, from host to audience member or from audience member to host). It returns the client’s old role and the new role.
We have integrated both Agora RTC and Agora RTM into our project. Now let’s work on the UI part.
In our project, we need to build a live streaming app where the audience can have an option to join as a host by clicking the button (the audience role is to be changed to the broadcaster role). For this requirement, we need to build two interfaces. One is for the broadcaster view, and the other is for the audience view, as the user wishes.
Building the Broadcaster View
widget.isBroadcaster variable states the Boolean status of the client role. If it is true, we need to toggle the broadcaster view:
In ListView widget, I am declaring a few widgets, such as
description View() and
Let’s start with
appBarView(). This widget is for showing the channel name and the number of users present in the channel at the top of the screen:
getChannelCount() method is used to update the channel count value.
coHostsView() widget is used to show the views of the co-hosts in a horizontal manner:
localHostView() widget used to show the local host view by
toolBar() widget has four interactive buttons, such as mute mic, end call, switch camera, and mute video:
_onToggleMute is used to toggle the local audio stream by calling the
_rtcEngin.muteLocalAudioStream() method, which takes a Boolean value:
_onCallEnd is used to disconnect the call and navigate back to the home page:
_onToggleLocalVideoMute() is used to toggle the local video stream by calling the
_rtcEngine.muteLocalVideoStream() method, which takes a Boolean value:
_onSwitchCamera() is used to toggle between the front and back cameras by calling the
Broadcaster View UI:
The screenshot shows the UI of the broadcaster view. At the top of the screen are the channel name and the channel members/ Horizontal grids are used to show the co-host’s video. The box is for the local video view, with brief descriptive text. Interactive buttons are at the bottom of the screen.
Building the Audience View
widget.isBroadcaster variable states the Boolean status of the client role. If it is false, we need to show the audience view:
In the Stack widget, I am declaring
_viewRows() for displaying broadcasters and
audienceToolbar() for displaying interactive buttons at the bottom of the screen
Let’s start with the
_viewRows() widget, which displays the list of broadcasters based on the
_users data in a grid manner:
audienceToolBar() widget has two interactive buttons, such as Call End and Join as Host:
_onCallEnd()method is used to disconnect the call and navigate back to the home page.
joinAsHost()method is responsible for calling the
_toChangeRole()method to change from the audience role to the broadcaster role:
The output image shows the audience view, which has a Call End button and a Join as a Host button.
The flow of API callback methods for this app is shown in the image below:
Whenever an audience member clicks the Join as Host button, the
_toChangeRole() method will be called. This method changes the client role to broadcaster by calling
After successfully changing the user role, an audience member receives the
clientRoleChanged callback method and becomes a co-host who can stream video and audio.
clientRoleChanged callback, we set the
isBroadcaster value to true for changing the audience view to the Broadcaster view so that the new co-host can have control over the interaction buttons.
We need to update the channel attributes by notifying all channel members of the role change by calling
addOrUpdateChannelAttributes(), which will take channel attributes as input:
And finally, the
onAttributesUpdated() callback method triggers at the host side. It returns the attributes of the channel. This callback is enabled only when the user updates the channel attributes and
setEnableNotificationToChannelMembers as true:
Once we are done with coding, we can test the app on the device. For that, we need to run the project in our IDE:
We have successfully implemented live streaming in our project with the feature that enables the audience members to join as host thanks to the integration with the Agora RTC and Agora RTM SDKs.
Click this link for the complete source code.
For more about the Agora Flutter SDK, see the developer’s guide here.
For more about the methods discussed in the article, by click this link.
I also invite you to join Agora Developer Slack community.
Want to build Real-Time Engagement apps?
If you have questions, please call us at 408-879-5885. We’d be happy to help you add voice or video chat, streaming and messaging into your apps.