下载 Agora Flutter SDK:https://docs.agora.io/cn/All/downloads?platform=Flutter
VS Code或其他你选择的IDE
访问网址 Agora.io,注册Agora开发者账户
flutter create agora_group_calling
dependencies:flutter:sdk: fluttercupertino_icons: ^1.0.0permission_handler: ^5.1.0+2agora_rtc_engine: ^3.2.1
在添加包的时候要注意缩进,因为如果缩进不对,可能会出错。
3. 在项目文件夹中,运行以下命令来安装所有的依赖项。
flutter pub get

import 'package:flutter/material.dart';import 'Screens/homepage.dart';void main() {runApp(MyApp());}class MyApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),home: MyHomePage(),);}}
创建我们的主页
继续创建我们的主页,我们将要求用户输入一个频道名。一个频道名是一个唯一的字符串,它将把具有相同频道名的人放在一个群组里调用。
import 'package:flutter/material.dart';import 'package:permission_handler/permission_handler.dart';import 'dart:async';import 'CallPage.dart';class MyHomePage extends StatefulWidget {@override_MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> {final myController = TextEditingController();bool _validateError = false;@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(centerTitle: true,title: Text('Agora Group Video Calling'),elevation: 0,),body: SafeArea(child: Center(child: SingleChildScrollView(clipBehavior: Clip.antiAliasWithSaveLayer,physics: BouncingScrollPhysics(),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Image(image: NetworkImage(://www.agora.io/en/wp-content/uploads/2019/07/agora-symbol-vertical.png'),height: MediaQuery.of(context).size.height * 0.17,),: EdgeInsets.only(top: 20)),Text(Group Video Call Demo',style: TextStyle(color: Colors.black,fontSize: 20,fontWeight: FontWeight.bold),),: EdgeInsets.symmetric(vertical: 20)),Container(width: MediaQuery.of(context).size.width * 0.8,child: TextFormField(controller: myController,decoration: InputDecoration(labelText: 'Channel Name',labelStyle: TextStyle(color: Colors.blue),hintText: 'test',hintStyle: TextStyle(color: Colors.black45),errorText:_validateError ? 'Channel name is mandatory' : null,border: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue),borderRadius: BorderRadius.circular(20),),),),),: EdgeInsets.symmetric(vertical: 30)),Container(width: MediaQuery.of(context).size.width * 0.25,child: MaterialButton(onPressed: onJoin,height: 40,color: Colors.blueAccent,child: Row(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text('Join',style: TextStyle(color: Colors.white),),Icon(Icons.arrow_forward,color: Colors.white,),],),),)],),),),),);
这样操作将创建一个类似于左图的用户界面,其中有一个频道名的输入栏和一个加入按钮。加入按钮会调用函数onJoin,它首先获取用户在通话过程中访问其摄像头和麦克风的权限。一旦用户授予这些权限,我们就进入下一个页面,CallPage.dart
为了要求用户访问摄像头和麦克风,我们使用了一个名为permission_handler.的包。这里我声明了一个名为_handleCameraAndMic(),的函数,我将在onJoin()函数中引用它 。
Future<void> _handleCameraAndMic(Permission permission) async {final status = await permission.request();print(status);}
Future<void> onJoin() async {setState(() {myController.text.isEmpty? _validateError = true: _validateError = false;});await _handleCameraAndMic(Permission.camera);await _handleCameraAndMic(Permission.microphone);Navigator.push(context,MaterialPageRoute(builder: (context) => CallPage(channelName: myController.text),));}
创建我们的呼叫页面
在我们开始使用CallPage.dart之前,让我们使用从Agora开发者账户中获得的App ID。(按照声网开发者注册使用指南的说明学习如何生成一个App ID。)导航到utils文件夹中的AppID.dart并创建一个名为appID的变量。
var appID = '<--- Enter your app id here --->'
import 'package:flutter/material.dart';import '../utils/AppID.dart';import 'package:agora_rtc_engine/rtc_engine.dart';import 'package:agora_rtc_engine/rtc_local_view.dart' as RtcLocalView;import 'package:agora_rtc_engine/rtc_remote_view.dart' as RtcRemoteView;
class CallPage extends StatefulWidget {final String channelName;const CallPage({Key key, this.channelName}) : super(key: key);_CallPageState createState() => _CallPageState();}
然后在CallPageState中声明一些变量,我们将在制作这个页面时使用这些变量。
_users是一个列表, 它包含了频道中所有用户的uid 。
_infoStrings包含了所有调用过程中发生的所有事件的日志。
muted 是一个布尔状态变量,用于静音或者取消静音。
_engine 是RtcEngine类的一个对象。
在dispose方法中,清除_users列表并销毁RtcEngine。
在initState()方法中,调用将在接下来的步骤中声明的initialize()函数。
class _CallPageState extends State<CallPage> {static final _users = <int>[];final _infoStrings = <String>[];bool muted = false;RtcEngine _engine;void dispose() {// clear users_users.clear();// destroy sdk_engine.leaveChannel();_engine.destroy();super.dispose();}void initState() {super.initState();// initialize agora sdkinitialize();}}
Future<void> initi alize() async {if (appID.isEmpty) {setState(() {_infoStrings.add('APP_ID missing, please provide your APP_ID in settings.dart',);_infoStrings.add('Agora Engine is not starting');});return;}await _initAgoraRtcEngine();_addAgoraEventHandlers();await _engine.joinChannel(null, widget.channelName, null, 0);}
Future<void> _initAgoraRtcEngine() async {_engine = await RtcEngine.create(appID);await _engine.enableVideo();}
_addAgoraEventHandlers()是一个处理所有主要回调函数的函数。所以我们从setEventHandler()开始,它监听引擎事件并接收相应RtcEngine的统计数据。
一些重要的回调包括
joinChannelSuccess()在本地用户加入指定频道时被触发。它返回频道名、用户的id和本地用户加入通道的时间(ms)。
leaveChannel()与之相反,因为它是在用户离开频道时触发的。每当用户离开频道时,它就会返回调用的统计信息。这些统计包括延迟、CPU使用量、持续时间等。
userJoined()是一个当远程用户加入一个特定频道时被触发的方法。一个成功的回调会返回远程用户的id和经过的时间。
userOffline() 与之相反,因为它发生在用户离开频道的时候。一个成功的回调会返回uid和离线的原因,包括退出、中断等。
firstRemoteVideoFrame()是一个当远程视频的第一个视频帧被渲染时被调用的方法。这可以帮助你返回uid、宽度、高度和经过的时间。
void _addAgoraEventHandlers() {_engine.setEventHandler(RtcEngineEventHandler(error: (code) {setState(() {final info = 'onError: $code';_infoStrings.add(info);});},joinChannelSuccess: (channel, uid, elapsed) {setState(() {final info = 'onJoinChannel: $channel, uid: $uid';_infoStrings.add(info);});},leaveChannel: (stats) {setState(() {_infoStrings.add('onLeaveChannel');_users.clear();});},userJoined: (uid, elapsed) {setState(() {final info = 'userJoined: $uid';_infoStrings.add(info);_users.add(uid);});},userOffline: (uid, reason) {setState(() {final info = 'userOffline: $uid , reason: $reason';_infoStrings.add(info);_users.remove(uid);});},firstRemoteVideoFrame: (uid, width, height, elapsed) {setState(() {final info = 'firstRemoteVideoFrame: $uid';_infoStrings.add(info);});},));
await _engine.joinChannel(null, 'channel-name', null, 0);
它需要四个参数才能成功运行
Token:它是一个可选的字段,在测试时可以为空,但在切换到生产环境时应该由Token服务器生成。
频道名称:它是一个字符串,让用户进入一个视频通话。
可选信息:这是一个可选字段,你可以通过它传递有关频道的其他信息。
uid:它是每个加入频道的用户的唯一ID。如果你在其中传递0或空值,那么Agora会自动为每个用户分配uid。
以上总结了制作这个视频调用应用程序所需的所有功能和方法。现在我们可以开始制作组件,它将成为我们应用程序的完整用户界面。
这里我声明了两个组件(_viewRows()和_toolbar()),这两个组件负责显示最多四个用户,并在底部添加了断开、静音和切换摄像头的按钮。
@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Agora Group Video Calling'),),backgroundColor: Colors.black,body: Center(child: Stack(children: <Widget>[_viewRows(),_toolbar(),],),),);}
我们从_viewRows()开始。因此需要知道用户和他们的uid来显示他们的视频。我们需要一个具有本地和远程用户的uid的通用列表。为了实现这一点,我们创建一个名为_getRendererViews()的组件,其中我们使用RtcLocalView和RtcRemoteView。
然后,我们只需使用名为_videoView()的组件来扩展视图 ,并使用_expandedVideoRow()组件将它们放在一行中。
/// Helper fun ction to get list of native viewsList<Widget> _getRenderViews() {final List<StatefulWidget> list = [];list.add(RtcLocalView.SurfaceView());_users.forEach((int uid) => list.add(RtcRemoteView.SurfaceView(uid: uid)));return list;}/// Video view wrapperWidget _videoView(view) {return Expanded(child: Container(child: view));}/// Video view row wrapperWidget _expandedVideoRow(List<Widget> views) {final wrappedViews = views.map<Widget>(_videoView).toList();return Expanded(child: Row(children: wrappedViews,),);}
Widget _viewRows() {final views = _getRenderViews();switch (views.length) {case 1:return Container(child: Column(children: <Widget>[_videoView(views[0])],));case 2:return Container(child: Column(children: <Widget>[_expandedVideoRow([views[0]]),_expandedVideoRow([views[1]])],));case 3:return Container(child: Column(children: <Widget>[2)),3))],));case 4:return Container(child: Column(children: <Widget>[2)),4))],));default:}return Container();}Widget _toolbar() {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 _toolbar() {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),)],),);}
这里我们声明了三个函数。
_onToggleMute()可以让你的视频流静音或者取消静音。这里,我们使用muteLocalAudioStream() 方法,它接受一个布尔值输入来使视频流静音或取消静音 。
void _onToggleMute() {setState(() {muted = !muted;});_engine.muteLocalAudioStream(muted);}
_onCallEnd()断开呼叫并将用户带回主页 。
void _onCallEnd(BuildContext context) {Navigator.pop(context);}
_onSwitchCamera()可以让你在前摄像头和后摄像头之间切换。在这里,我们使用switchCamera()方法,它可以帮助你实现所需的功能。
结论
现在你已经实现了Flutter多人视频通话APP,使用了Agora Flutter SDK,并实现了一些基本功能,如静音本地视频流、切换摄像头和断开通话。你可以在声网多人通话应用示例代码得到这个应用程序的完整代码:
https://github.com/Meherdeep/agora-group-calling
获取更多教程、Demo、技术帮助,请点击「阅读原文」访问声网开发者社区


