网页访问 Agora.io,注册一个Agora开发者账户。
下载 Flutter SDK:https://docs.agora.io/cn/All/downloads
已安装 VS Code 或 Android Studio
对 Flutter 开发的基本了解
flutter create agora_multi_channel_demo
dependencies:flutter:sdk: fluttercupertino_icons: ^1.0.0agora_rtc_engine: ^3.2.1permission_handler: ^5.1.0+2
flutter pub get
import 'package:agora_multichannel_video/pages/lobby_page.dart';import 'package:flutter/material.dart';import 'package:permission_handler/permission_handler.dart';class LoginPage extends StatefulWidget {_LoginPageState createState() => _LoginPageState();}class _LoginPageState extends State<LoginPage> {final rteChannelNameController = TextEditingController();final rtcChannelNameController = TextEditingController();bool _validateError = false;Widget build(BuildContext context) {return Scaffold(appBar: AppBar(centerTitle: true,title: Text('Agora Multi-Channel Demo'),elevation: 0,),body: SafeArea(child: SingleChildScrollView(clipBehavior: Clip.antiAliasWithSaveLayer,physics: BouncingScrollPhysics(),child: Column(crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[SizedBox(height: MediaQuery.of(context).size.height * 0.12,),Center(child: Image(image: NetworkImage('https://www.agora.io/en/wp-content/uploads/2019/06/agoralightblue-1.png'),height: MediaQuery.of(context).size.height * 0.17,),),SizedBox(height: MediaQuery.of(context).size.height * 0.1,),Container(width: MediaQuery.of(context).size.width * 0.8,child: TextFormField(controller: rteChannelNameController,decoration: InputDecoration(labelText: 'Broadcast channel Name',labelStyle: TextStyle(color: Colors.black54),errorText:_validateError ? 'Channel name is mandatory' : null,border: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue, width: 2),borderRadius: BorderRadius.circular(20),),enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.black, width: 2),borderRadius: BorderRadius.circular(20),),focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue, width: 2),borderRadius: BorderRadius.circular(20),),),),),SizedBox(height: MediaQuery.of(context).size.height * 0.03,),Container(width: MediaQuery.of(context).size.width * 0.8,child: TextFormField(controller: rtcChannelNameController,decoration: InputDecoration(labelText: 'RTC channel Name',labelStyle: TextStyle(color: Colors.black54),errorText:_validateError ? 'RTC Channel name is mandatory' : null,border: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue, width: 2),borderRadius: BorderRadius.circular(20),),enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.black, width: 2),borderRadius: BorderRadius.circular(20),),focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue, width: 2),borderRadius: BorderRadius.circular(20),),),),),SizedBox(height: MediaQuery.of(context).size.height * 0.05),Container(width: MediaQuery.of(context).size.width * 0.35,child: MaterialButton(onPressed: onJoin,color: Colors.blueAccent,child: Padding(padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.01,vertical: MediaQuery.of(context).size.height * 0.02),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: <Widget>[Text('Join',style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),),Icon(Icons.arrow_forward,color: Colors.white,),],),),),)],),),),);}Future<void> onJoin() async {setState(() {rteChannelNameController.text.isEmpty &&rtcChannelNameController.text.isEmpty? _validateError = true: _validateError = false;});await _handleCameraAndMic(Permission.camera);await _handleCameraAndMic(Permission.microphone);Navigator.push(context,MaterialPageRoute(builder: (context) => LobbyPage(rtcChannelName: rtcChannelNameController.text,rteChannelName: rteChannelNameController.text,),),);}Future<void> _handleCameraAndMic(Permission permission) async {final status = await permission.request();print(status);}}
const appID = '<---Enter your App ID here--->';
_engine = await RtcEngine.create(appID);await _engine.enableVideo();await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting);_addAgoraEventHandlers();await _engine.joinChannel(null, widget.rteChannelName, null, 0);
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);});},));}
_channel = await RtcChannel.create(widget.rtcChannelName);_addRtcChannelEventHandlers();await _engine.setClientRole(ClientRole.Broadcaster);await _channel.joinChannel(null, null, 0, ChannelMediaOptions(true, true));await _channel.publish();
void _addRtcChannelEventHandlers() {_channel.setEventHandler(RtcChannelEventHandler(error: (code) {setState(() {_infoStrings.add('Rtc Channel onError: $code');});},joinChannelSuccess: (channel, uid, elapsed) {setState(() {final info = 'Rtc Channel onJoinChannel: $channel, uid: $uid';_infoStrings.add(info);});},leaveChannel: (stats) {setState(() {_infoStrings.add('Rtc Channel onLeaveChannel');_users2.clear();});},userJoined: (uid, elapsed) {setState(() {final info = 'Rtc Channel userJoined: $uid';_infoStrings.add(info);_users2.add(uid);});},userOffline: (uid, reason) {setState(() {final info = 'Rtc Channel userOffline: $uid , reason: $reason';_infoStrings.add(info);_users2.remove(uid);});},));}
List<Widget> _getRenderViews() {final List<StatefulWidget> list = [];list.add(RtcLocalView.SurfaceView());return list;}Widget _videoView(view) {return Expanded(child: Container(child: view));}Widget _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>[_expandedVideoRow(views.sublist(0, 2)),_expandedVideoRow(views.sublist(2, 3))],));case 4:return Container(child: Column(children: <Widget>[_expandedVideoRow(views.sublist(0, 2)),_expandedVideoRow(views.sublist(2, 4))],));default:}return Container();}
List<Widget> _getRenderRtcChannelViews() {final List<StatefulWidget> list = [];_users2.forEach((int uid) => list.add(RtcRemoteView.SurfaceView(uid: uid,channelId: widget.rtcChannelName,renderMode: VideoRenderMode.FILL,),),);return list;}Widget _viewRtcRows() {final views = _getRenderRtcChannelViews();if (views.length > 0) {print("NUMBER OF VIEWS : ${views.length}");return ListView.builder(scrollDirection: Axis.horizontal,itemCount: views.length,itemBuilder: (BuildContext context, int index) {return Align(alignment: Alignment.bottomCenter,child: Container(height: 200,width: MediaQuery.of(context).size.width * 0.25,child: _videoView(views[index])),);},);} else {return Align(alignment: Alignment.bottomCenter,child: Container(),);}}
Widg et build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Lobby'),),body: Stack(children: <Widget>[_viewRows(),_viewRtcRows(),_panel()],),);}
Widget _panel() {return Container(padding: const EdgeInsets.symmetric(vertical: 48),alignment: Alignment.topLeft,child: FractionallySizedBox(heightFactor: 0.5,child: Container(padding: const EdgeInsets.symmetric(vertical: 48),child: ListView.builder(reverse: true,itemCount: _infoStrings.length,itemBuilder: (BuildContext context, int index) {if (_infoStrings.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(_infoStrings[index],style: TextStyle(color: Colors.blueGrey),),),)],),);},),),),);}
@overridevoid dispose() {// clear users_users.clear();_users2.clear();// leave channel_engine.leaveChannel();_engine.destroy();_channel.unpublish();_channel .leaveChannel();_channel.destroy();super.dispose();}
flutter run
获取更多教程、Demo、技术帮助,请点击「阅读原文」访问声网开发者社区


