Agora Flutter Video Calling Example

In this article, we are going to integrate video calling functionality in mobile applications using agora flutter sdk. We are using agora_rtc_engine package to make video call in flutter apps.

Nowadays video call is important part of our daily life. In this example, we are connecting two mobile devices using agora flutter sdk though channel. We can also switch camera and mute/unmute mic in this flutter video calling app.

First we need to create an account on Agora website. Then we need to create project after registration process. You can create new project using dashboard page. After that you need to copy APP ID to connect our app with agora sdk.

 
agora flutter
agora flutter
 
 

Add required dependencies in your pubspec.yaml file

agora_rtc_engine: ^3.1.3
permission_handler: ^5.0.1
 
 

Make changes for Android Platform

FIrst enable AndoidX in gradle.properties file in android studio. We need to declare below permissions in AndroidManifest.xml file to make video call.

<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” />
<uses-permission android:name=”android.permission.BLUETOOTH” />
 
 

Make Changes for iOS Platform

for iOS platform, we need to add camera and microphone usage description in Info.plist file under iOS -> Runner folder.

 
 

Create Join Channel UI for Agora Flutter

final channelController = TextEditingController();
bool validateError = false;
ClientRole clientRole = ClientRole.Broadcaster;

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
height: 400,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: channelController,
decoration: InputDecoration(
hintText: ‘Enter Channel Name’,
errorText: validateError ? ‘Channel name is required’ : null,
border: UnderlineInputBorder(
borderSide: BorderSide(width: 1),
),
),
))
],
),
Column(
children: [
ListTile(
title: Text(ClientRole.Broadcaster.toString()),
leading: Radio(
value: ClientRole.Broadcaster,
groupValue: clientRole,
onChanged: (ClientRole value) {
setState(() {
clientRole = value;
});
},
),
),
ListTile(
title: Text(ClientRole.Audience.toString()),
leading: Radio(
value: ClientRole.Audience,
groupValue: clientRole,
onChanged: (ClientRole value) {
setState(() {
clientRole = value;
});
},
),
)
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
onPressed: onJoinChannel,
child: Text(‘Join Channel’),
color: Colors.blueAccent,
textColor: Colors.white,
),
)
],
),
)
],
),
),
),
);
}

 
 

Join Agora Flutter Channel for Flutter Chat App

Future<void> onJoinChannel() async {
setState(() {
channelController.text.isEmpty
? validateError = true
: validateError = false;
});
if (channelController.text.isNotEmpty) {
await handlePermissions(Permission.camera);
await handlePermissions(Permission.microphone);
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoCallingScreen(
channelName: channelController.text,
clientRole: clientRole,
),
),
);
}
}

Future<void> handlePermissions(Permission permission) async {
final status = await permission.request();
print(status);
}

@override
void dispose() {
channelController.dispose();
super.dispose();
}

 
 

Pass Flutter Channel Name to Video Call Screen

class VideoCallingScreen extends StatefulWidget {

final String channelName;
final ClientRole clientRole;
const VideoCallingScreen({Key key, this.channelName, this.clientRole}) : super(key: key);

@override
_VideoCallingScreenState createState() => _VideoCallingScreenState();
}

 
 

Initialize Agora Flutter SDK

Here you add your APP ID from your agora account. You can also generate temporary token from your agora account

final APP_ID = ‘YOUR_APP_ID’;
final Token = ‘YOUR_TOKEN’;
final listUsers = <int>[];
final strInfo = <String>[];
bool muted = false;
RtcEngine rtcEngine;

@override
void initState() {
super.initState();
initializeAgoraSDK();
}

Future<void> initializeAgoraSDK() async {
if (APP_ID.isEmpty) {
setState(() {
strInfo.add(
‘Please add your APP_ID’,
);
});
return;
}

await initializeAgoraEngine();
addAgoraEventHandlers();
await rtcEngine.enableWebSdkInteroperability(true);
VideoEncoderConfiguration encoderConfiguration = VideoEncoderConfiguration();
encoderConfiguration.dimensions = VideoDimensions(1920, 1080);
await rtcEngine.setVideoEncoderConfiguration(encoderConfiguration);
await rtcEngine.joinChannel(Token, widget.channelName, null, 0);
}

Future<void> initializeAgoraEngine() async {
rtcEngine = await RtcEngine.create(APP_ID);
await rtcEngine.enableVideo();
await rtcEngine.setChannelProfile(ChannelProfile.LiveBroadcasting);
await rtcEngine.setClientRole(widget.clientRole);
}

void addAgoraEventHandlers() {
rtcEngine.setEventHandler(RtcEngineEventHandler(error: (code) {
setState(() {
final info = ‘onError: $code’;
strInfo.add(info);
});
}, joinChannelSuccess: (channel, uid, elapsed) {
setState(() {
final info = ‘onJoinChannel: $channel, uid: $uid’;
strInfo.add(info);
});
}, leaveChannel: (stats) {
setState(() {
strInfo.add(‘onLeaveChannel’);
listUsers.clear();
});
}, userJoined: (uid, elapsed) {
setState(() {
final info = ‘userJoined: $uid’;
strInfo.add(info);
listUsers.add(uid);
});
}, userOffline: (uid, elapsed) {
setState(() {
final info = ‘userOffline: $uid’;
strInfo.add(info);
listUsers.remove(uid);
});
}, firstRemoteVideoFrame: (uid, width, height, elapsed) {
setState(() {
final info = ‘firstRemoteVideo: $uid ${width}x $height’;
strInfo.add(info);
});
}));
}

 
 

Flutter Video Call using Agora SDK

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘Flutter Agora Video Call Sample’),
centerTitle: true,
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[
videoViewRowsWidget(),
panelWidget(),
toolbarWidget(),
],
),
),
);
}

@override
void dispose() {
listUsers.clear();
rtcEngine.leaveChannel();
rtcEngine.destroy();
super.dispose();
}

 
List<Widget> listRenderViews() {
final List<StatefulWidget> list = [];
if (widget.clientRole == ClientRole.Broadcaster) {
list.add(RtcLocalView.SurfaceView());
}
listUsers.forEach((int uid) => list.add(RtcRemoteView.SurfaceView(uid: uid)));
return list;
}

Widget videoViewWidget(view) {
return Expanded(child: Container(child: view));
}

Widget expandedVideoViewWidget(List<Widget> views) {
final wrappedViews = views.map<Widget>(videoViewWidget).toList();
return Expanded(
child: Row(
children: wrappedViews,
),
);
}

Widget videoViewRowsWidget() {
final views = listRenderViews();
switch (views.length) {
case 1:
return Container(
child: Column(
children: <Widget>[videoViewWidget(views[0])],
));
case 2:
return Container(
child: Column(
children: <Widget>[
expandedVideoViewWidget([views[0]]),
expandedVideoViewWidget([views[1]])
],
));
case 3:
return Container(
child: Column(
children: <Widget>[
expandedVideoViewWidget(views.sublist(0, 2)),
expandedVideoViewWidget(views.sublist(2, 3))
],
));
case 4:
return Container(
child: Column(
children: <Widget>[
expandedVideoViewWidget(views.sublist(0, 2)),
expandedVideoViewWidget(views.sublist(2, 4))
],
));
default:
}
return Container();
}

Widget toolbarWidget() {
if (widget.clientRole == ClientRole.Audience) return Container();
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 48),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawMaterialButton(
onPressed: onToggleMute,
child: Icon(
muted ? Icons.mic_off : Icons.mic,
color: muted ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: muted ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: () => onCallEnd(context),
child: Icon(
Icons.call_end,
color: Colors.white,
size: 35.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
),
RawMaterialButton(
onPressed: onSwitchCamera,
child: Icon(
Icons.switch_camera,
color: Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(12.0),
)
],
),
);
}

Widget panelWidget() {
return Container(
padding: const EdgeInsets.symmetric(vertical: 48),
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 48),
child: ListView.builder(
reverse: true,
itemCount: strInfo.length,
itemBuilder: (BuildContext context, int index) {
if (strInfo.isEmpty) {
return null;
}
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 10,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 2,
horizontal: 5,
),
decoration: BoxDecoration(
color: Colors.yellowAccent,
borderRadius: BorderRadius.circular(5),
),
child: Text(
strInfo[index],
style: TextStyle(color: Colors.blueGrey),
),
),
)
],
),
);
},
),
),
),
);
}

void onCallEnd(BuildContext context) {
Navigator.pop(context);
}

void onToggleMute() {
setState(() {
muted = !muted;
});
rtcEngine.muteLocalAudioStream(muted);
}

void onSwitchCamera() {
rtcEngine.switchCamera();
}

 
 
agora flutter
 
agora flutter
 

Leave a Reply