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:
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.
pubspec.yaml
file, and install those dependencies by running pub get
:3. Create the file structure in the lib folder:
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 live_stream_page.dart
:
The 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 live_stream_page.dart
:
Here we used the permission handler plug-in to request the permissions and check their status:
Home Page Output:
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:
_users
is to maintain the data of the users who joined the channel._isMicMuted
is to give the Boolean status of whether or not the mic is muted._isVideoMuted
is to give the Boolean status of whether or not the video is muted._isLocalUserJoined
returns a Boolean value indicating whether or not a local user has joined the channel._isCamSwitch
is used to determine whether or not the status of the came has changed._rtcEngine
is a reference to the RtcEngine class._rtmClient
is a reference to the AgoraRtmClient class._rtmChanne
is a reference to the AgoraRtmChannel class.channelCount
is 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()
:
In this 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._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:
onMemberJoined
triggers when the user joins the channel. It returns AgoraRtmMember, which includes the user ID and the channel ID.onMemberLeft
occurs when the user leaves the channel. It returns AgoraRtmMember, which includes the user ID and the channel ID.onMessageReceived
initiates a new channel message is received. It returns AgoraRtmMessage and AgoraRtmMember.onAttributesUpdated
triggers 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 _rtmChannel.getMembers()
method
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.
In the _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 setClientRole()
method:
_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.
The 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 appBarView()
, coHostsView()
, description View()
and localHostView()
.
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:
The getChannelCount()
method is used to update the channel count value.
The coHostsView()
widget is used to show the views of the co-hosts in a horizontal manner:
The localHostView()
widget used to show the local host view by RtcLocalView.SurfaceView()
:
The toolBar()
widget has four interactive buttons, such as mute mic, end call, switch camera, and mute video:
1. _onToggleMute
is used to toggle the local audio stream by calling the _rtcEngin.muteLocalAudioStream()
method, which takes a Boolean value:
2. _onCallEnd
is used to disconnect the call and navigate back to the home page:
3. _onToggleLocalVideoMute()
is used to toggle the local video stream by calling the _rtcEngine.muteLocalVideoStream()
method, which takes a Boolean value:
4. _onSwitchCamera()
is used to toggle between the front and back cameras by calling the _rtcEngine.switchCamera()
method:
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.
The 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:
The 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 _rtcEngine.setClientRole(Client.Broadcaster)
:
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.
In the 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:
Audience View output
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.