flutter学习笔记(利用声网的flutter插件实现视频通话)

效果展示 实现细节包括:视频的呼叫和接听,来电时的响铃,画面切换等。先看效果图,相关视频界面截图如下:
flutter学习笔记(利用声网的flutter插件实现视频通话)
文章图片
有人来电时的界面 flutter学习笔记(利用声网的flutter插件实现视频通话)
文章图片
呼叫其他人时的界面 flutter学习笔记(利用声网的flutter插件实现视频通话)
文章图片
视频接通时的界面 flutter学习笔记(利用声网的flutter插件实现视频通话)
文章图片
点击小窗口切换画面时的界面 准备工作 ● 创建声网账户,并获取App ID,?官网
● 用到的声网Flutter插件有两个:
1> agora_rtc_engine(https://github.com/AgoraIO/Flutter-RTM)
主要实现视频通话部分的插件,点击官方demo查看代码示例。
2>agora_rtm(https://github.com/AgoraIO/Flutter-RTM)
主要实现呼叫的信令系统插件,点击官方demo可查看代码示例。
功能细节:登录、收发消息
代码部分 配置

  • 在项目根目录下的 pubspec.yaml 文件中添加插件:
dependencies: flutter: sdk: flutter# The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 agora_rtc_engine: 0.9.4 agora_rtm: 0.9.3

  • android-app-build.gradle 中添加
android { .. defaultConfig { .. ndk { abiFilters 'armeabi-v7a' } .. } .. }

  • androidAndroidManifest.xml 文件中添加权限:
.. ..

  • iosinfo.plist 文件中添加
Privacy - Microphone Usage Description, and add a note in the Value column. Privacy - Camera Usage Description, and add a note in the Value column.

视频相关
  1. 初始化
AgoraRtmClient _client; /// 收到通话请求时的响铃页面 VideoAnswerPage answer; /// 声网RTM初始化、注册接收 Future initAgoraRtm() async { // 初始化 _client =await AgoraUtils.getAgoraRtmClient(); // 设置消息接收器 _client.onMessageReceived = (AgoraRtmMessage message, String peerId) { if(!EmptyUtil.textIsEmpty(message.text)){ // 收到视频请求,消息内容定义为: “CALLVIDEO,视频通道id”(请求者id接收者id) if(message.text.contains(AgoraUtils.getAgoraMsgType(1)) && message.text.contains(",")){ try{ String _channelName =message.text.split(",")[1]; // 收到通话请求时的响铃页面 answer = new VideoAnswerPage(_channelName,peerId,_client); // 跳到响铃页面 Navigator.push(c, new MaterialPageRoute( builder: (BuildContext context) { return answer; })); }catch(e){ print(e.toString()); } // 收到消息:视频请求者取消了通话 }else if(message.text.contains(AgoraUtils.getAgoraMsgType(2)) ){ if(answer != null){ answer.videoAnswerState.isClosedByOne = true; answer.videoAnswerState.onCallEnd(c); } // 对方拒绝了通话请求 }else if(message.text.contains(AgoraUtils.getAgoraMsgType(3)) ){ if(AgoraUtils.videoCallState != null){ AgoraUtils.videoCallState.isClosedByOne = true; AgoraUtils.videoCallState.onCallEnd(c); } } } }; _client.onConnectionStateChanged = (int state, int reason) { // _log('Connection state changed: ' +state.toString() +', reason: ' + reason.toString()); if (state == 5) { _client.logout(); } }; }

  1. 登录
    自定义user id 提交登录
/// 声网登录 void _toggleLogin() async { if (!_isLogin) { // 获取输入框的user id(英文 || 数字) String userId = _userNameController.text; if (userId.isEmpty) { Fluttertoast.showToast(msg: "Please input your user id to login"); return; } if(_client == null){ return; } try { await _client.login(null, userId); setState(() { _isLogin = true; }); // User user = new User(); user.agoraId = userId; // 保存用户信息 ConstantObject.mUser = user; } catch (errorCode) { print(errorCode); } } }

  1. 请求视频通话
class AgoraCustomPage extends StatefulWidget { @override createState() => new AgoraCustomState(); }class AgoraCustomState extends State { TextEditingController _friendController = new TextEditingController(); TextEditingController _groupController = new TextEditingController(); String _channelName = "zhijie"; AgoraRtmClient _client; @override void initState() { super.initState(); initAgoraRtm(); }@override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("声网"), ), body: buildStartPage(), ); } Widget buildStartPage(){ return SingleChildScrollView( child: ConstrainedBox(// 添加额外为限制条件到child,如最小/大宽度、高度。。。 constraints: BoxConstraints( minHeight: 120.0, ), child: Column(children: [ Container( width: double.infinity, margin:const EdgeInsets.fromLTRB(20,30,20,0), child: TextField( controller: _friendController, autofocus: false, decoration: InputDecoration( icon: Icon(Icons.person), labelText: '请输入好友id', helperText: '请正确输入好友的id', ), ) ), Container( width: double.infinity, height: 50, margin:const EdgeInsets.fromLTRB(20,30,20,0), child: RaisedButton( onPressed: (){ clickFriendVideo(); }, // 文本内容 child: Text("和好友视频通话"), // 按钮颜色 color: ThemeColors.colorTheme, )) ]),), ); } void clickFriendVideo(){ if(EmptyUtil.textIsEmpty(_friendController.text)){ Fluttertoast.showToast(msg: "Friend id cannot be empty"); }else{ _callVideo(_friendController.text); } } // 发送视频通话请求 Future _callVideo(String peerId) async { if(_client != null){ // 查看对方是否在线 bool online =await AgoraUtils.queryPeerOnlineStatus(_client, peerId); if(online){ try{ // 自定义视频通道(这里采取 自己的agoraid+对方的id), _channelName = ConstantObject.getUser().agoraId+peerId; // 发送消息 : “CALLVIDEO,_channelName” String msg = AgoraUtils.getAgoraMsgType(1)+","+_channelName; await _client.sendMessageToPeer(peerId, AgoraRtmMessage(msg)); // 跳到拨打视频的等待接听页面 Navigator.push(context, new MaterialPageRoute( builder: (BuildContext context) { return new VideoCallPage(_channelName,peerId); })); }catch(e){ print(e.toString()); } }else{ Fluttertoast.showToast(msg: "The friend is offline"); } } } /// 获取AgoraRtmClient Future initAgoraRtm() async { _client =await AgoraUtils.getAgoraRtmClient(); } }

  1. 拨打视频的等待接听页面(及通话页面)
class VideoCallPage extends StatefulWidget { /// 视频通道 final String channelName; /// 好友的 agora Id final String firendName; VideoCallPage(this.channelName, this.firendName); /*/// Creates a call page with given channel name. const VideoCallPage({Key key, this.channelName, this.firendName}) : super(key: key); */@override createState() => new VideoCallState(); }class VideoCallState extends State { /// 和android本地交互的通道 static const _methodChannel1 = const MethodChannel(MethodChannelUtils.channelMedia); static final _sessions = List(); final _infoStrings = []; BuildContext mcontext; AgoraRtmClient _client; /// 声网上获取的App ID var APP_ID = APPApiKey.Agora_app_id; bool muted = false; /// 视频是否成功接通 bool videoSuccess = false; /// 发出视频请求但未接通时,自己取消通话 bool isClosedByOne = false; /// 主窗口展示自己? /// true 展示自己false 展示好友 bool mainWindowShowOneself = true; /// 计时的数值 int _count = 0; Timer _timer; @override void dispose() { // clean up native views & destroy sdk _sessions.forEach((session) { AgoraRtcEngine.removeNativeView(session.viewId); }); _sessions.clear(); AgoraRtcEngine.leaveChannel(); // 停止播放响铃 stopPlay(); if(!isClosedByOne && !videoSuccess){ /// 请求视频对方还未接听时,自己先取消,则需要通知对方,我已取消 _initSendMessage(); } stopTimer(); AgoraUtils.clearVideoCallState(); super.dispose(); }@override void initState() { super.initState(); // initialize third.agora sdk initialize(); // 初始化视频SDK startPlay(); // 开始播放响铃 }void initialize() { if (APP_ID.isEmpty) { setState(() { _infoStrings .add("APP_ID missing, please provide your APP_ID in settings.dart"); _infoStrings.add("Agora Engine is not starting"); }); return; }_initAgoraRTM(); // 信令系统 _initAgoraRtcEngine(); // 视频通话 _addAgoraEventHandlers(); // use _addRenderView everytime a native video view is needed _addRenderView(0, (viewId) { AgoraRtcEngine.setupLocalVideo(viewId, VideoRenderMode.Hidden); AgoraRtcEngine.startPreview(); // state can access widget directly // 加入视频通话(或者可以考虑在对方接听后发个消息通知请求方,请求方再加入视频,可以节省点视频分钟数) AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0); }); } /// 获取 AgoraRtmClient Future _initAgoraRTM() async{ _client =await AgoraUtils.getAgoraRtmClient(); AgoraUtils.videoCallState = this; } /// 发送消息通知对方取消通话 Future _initSendMessage() async{ String msg = AgoraUtils.getAgoraMsgType(2); await _client.sendMessageToPeer(widget.firendName, AgoraRtmMessage(msg)); } /// Create third.agora sdk instance and initialze Future _initAgoraRtcEngine() async { AgoraRtcEngine.create(APP_ID); AgoraRtcEngine.enableVideo(); }/// Add third.agora event handlers void _addAgoraEventHandlers() { AgoraRtcEngine.onError = (int code) { setState(() { String info = 'onError: ' + code.toString(); _infoStrings.add(info); }); }; /// 成功加入某次视频的回调 AgoraRtcEngine.onJoinChannelSuccess = (String channel, int uid, int elapsed) { setState(() { String info = 'onJoinChannel: ' + channel + ', uid: ' + uid.toString(); _infoStrings.add(info); }); }; AgoraRtcEngine.onLeaveChannel = () { setState(() { _infoStrings.add('onLeaveChannel'); }); }; /// 有其他用户(好友)成功加入到视频中的回调 AgoraRtcEngine.onUserJoined = (int uid, int elapsed) { setState(() { String info = 'userJoined: ' + uid.toString(); //setState(() { videoSuccess = true; }); _infoStrings.add(info); videoSuccess = true; // 成功开始视频通话 stopPlay(); // 停止播放响铃 startTimer(); // 开始通话计时 _addRenderView(uid, (viewId) { AgoraRtcEngine.setupRemoteVideo(viewId, VideoRenderMode.Hidden, uid); }); }); }; /// 好友退出通话 AgoraRtcEngine.onUserOffline = (int uid, int reason) { setState(() { String info = 'userOffline: ' + uid.toString(); _infoStrings.add(info); onCallEnd(mcontext); // 自己也退出 _removeRenderView(uid); }); }; AgoraRtcEngine.onFirstRemoteVideoFrame = (int uid, int width, int height, int elapsed) { setState(() { String info = 'firstRemoteVideo: ' + uid.toString() + ' ' + width.toString() + 'x' + height.toString(); _infoStrings.add(info); }); }; }/// Create a native view and add a new video session object /// The native viewId can be used to set up local/remote view void _addRenderView(int uid, Function(int viewId) finished) { Widget view = AgoraRtcEngine.createNativeView(uid, (viewId) { setState(() { _getVideoSession(uid).viewId = viewId; if (finished != null) { finished(viewId); } }); }); VideoSession session = VideoSession(uid, view); _sessions.add(session); }/// Remove a native view and remove an existing video session object void _removeRenderView(int uid) { VideoSession session = _getVideoSession(uid); if (session != null) { _sessions.remove(session); } AgoraRtcEngine.removeNativeView(session.viewId); }/// Helper function to filter video session with uid VideoSession _getVideoSession(int uid) { return _sessions.firstWhere((session) { return session.uid == uid; }); }/// Helper function to get list of native views List _getRenderViews() { return _sessions.map((session) => session.view).toList(); }/// Video view wrapper /// Expanded组件必须用在Row、Column、Flex内,并且从Expanded到封装它的Row、Column、Flex的路径必须只包括StatelessWidgets或StatefulWidgets组件(不能是其他类型的组件,像RenderObjectWidget,它是渲染对象,不再改变尺寸了,因此Expanded不能放进RenderObjectWidget)。 Widget _videoView(view) { return Expanded(child: Container(child: view)); }/// Video layout wrapper Widget _viewRows() { //List views = _getRenderViews(); List views = new List(); views.addAll(_getRenderViews()); return _mainWindow(views); }/// 主窗口视图 Widget _mainWindow(List views){ return GestureDetector( child: Container( child: Column( children: [ mainWindowShowOneself ? _videoView(views[0]) : _videoView(views[1]) ], )), ); } /// 右上角小窗口视图 Widget _smallWindow() { //List views = _getRenderViews(); List views = new List(); if(!videoSuccess ){ return _emptyView(); }else { views.addAll(_getRenderViews()); if( mainWindowShowOneself ){ if(!EmptyUtil.listIsEmpty(views) && views.length > 1){ return_smallVideoView(views[1]); }else{ return _emptyView(); } }else { if(!EmptyUtil.listIsEmpty(views)){ return _smallVideoView(views[0]); }else{ return _emptyView(); } } } } /// 右上角小窗口视图 Widget _smallVideoView(Widget view){ return GestureDetector( onTap: updateDoubleWindow, onDoubleTap: updateDoubleWindow, child: Align( alignment: Alignment.topRight, child: Container( width: 80.0, height: 130.0, margin: EdgeInsets.all(20), color: ThemeColors.colorWhite, child:Stack(children: [ Column( children: [ _videoView(view) ], ), Container( width: double.infinity, height: double.infinity, color: ThemeColors.transparent, child: Text(""), ) ],) ), ), ); } /// 未接通视频前的一层透明遮罩 Widget _mask(){ return Container( child:Offstage( offstage: videoSuccess, child: Container( width: double.infinity, height: double.infinity, color: ThemeColors.transparent1, ), ) , ); } /// 响铃时的dialog Widget _ProgressDialog() { return Offstage( offstage: videoSuccess, child:Container( height: 25.0, color: ThemeColors.transparent, margin: EdgeInsets.only(top:110), alignment: Alignment.topCenter, child: SpinKitWave(color: ThemeColors.colorTheme), ) , ); } /// 视频界面底部的工具栏(静音、挂断、摄像头切换) Widget _toolbar() { return Container( alignment: Alignment.bottomCenter, padding: EdgeInsets.symmetric(vertical: 48), child:Container( height: 100.0, child: Column(children: [ Offstage( offstage: !videoSuccess,//true -显示 child: Container( height: 20.0, margin: EdgeInsets.only(bottom:10.0), child: Text(DateTimeUtil.getHMmmss_Seconds(_count), style: TextStyle( color: ThemeColors.colorWhite, fontSize: 16, )), ), ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ RawMaterialButton( onPressed: () => _onToggleMute(), child: new Icon( muted ? Icons.mic : Icons.mic_off, color: muted ? Colors.white : ThemeColors.colorTheme, size: 20.0, ), shape: new CircleBorder(), elevation: 2.0, fillColor: muted ? ThemeColors.colorTheme : Colors.white, padding: const EdgeInsets.all(12.0), ), RawMaterialButton( onPressed: () => onCallEnd(context), child: new Icon( Icons.call_end, color: Colors.white, size: 35.0, ), shape: new CircleBorder(), elevation: 2.0, fillColor: Colors.redAccent, padding: const EdgeInsets.all(15.0), ), RawMaterialButton( onPressed: () => _onSwitchCamera(), child: new Icon( Icons.switch_camera, color: ThemeColors.colorTheme, size: 20.0, ), shape: new CircleBorder(), elevation: 2.0, fillColor: Colors.white, padding: const EdgeInsets.all(12.0), ) ], ), ],), ) , ); }/// 好友的信息视图(名称) Widget _friendInfo() { return Container( height: 50.0, alignment: Alignment.topCenter, margin: EdgeInsets.only(top:60), child: Offstage( offstage: videoSuccess, child:Text( widget.firendName, style: TextStyle( color: ThemeColors.colorWhite, fontSize: 26, ), ), ) ); } /// 退出通话 void onCallEnd(BuildContext context) { Navigator.pop(context); }void _onToggleMute() { setState(() { muted = !muted; }); AgoraRtcEngine.muteLocalAudioStream(muted); } /// 切换摄像头 void _onSwitchCamera() { AgoraRtcEngine.switchCamera(); } /// 开始播放自定义的响铃文件 void startPlay(){ _methodChannel1.invokeListMethod(MethodChannelUtils.methodStartMedia); } /// 停止播放响铃 void stopPlay(){ _methodChannel1.invokeListMethod(MethodChannelUtils.methodStopMedia); } /// 更换主窗口和小窗口的画面 void updateDoubleWindow(){ setState(() { mainWindowShowOneself = !mainWindowShowOneself; }); } /// 开始计时 void startTimer() { const oneSec = const Duration(seconds: 1); var callback = (timer) => { setState(() { _count++; // 秒数+1 }) }; _timer = Timer.periodic(oneSec, callback); } /// 停止计时 void stopTimer(){ if(_timer != null){ _timer.cancel(); } } @override Widget build(BuildContext context) { mcontext = context; return Scaffold( appBar: AppBar( title: Text('Agora Flutter'), ), backgroundColor: Colors.black, body: Center( child: Stack( children: [_viewRows(),_smallWindow(),_mask(),_ProgressDialog(), _toolbar(),_friendInfo()],//_panel(), ))); } Widget _emptyView(){ return Container( width: 1.0, height: 1.0, ); } }

  1. 等待接听视频的页面(及通话页面)
class VideoAnswerPage extends StatefulWidget { /// non-modifiable channel name of the page final String channelName; final String firendName; VideoAnswerState videoAnswerState; AgoraRtmClient _client; /// 接听视频邀请 /// 参数(视频通道id,好友名称) VideoAnswerPage(this.channelName, this.firendName,this._client); @override VideoAnswerState createState() { videoAnswerState = new VideoAnswerState(); return videoAnswerState; } }class VideoAnswerState extends State { static const _methodChannel1 = const MethodChannel(MethodChannelUtils.channelMedia); static final _sessions = List(); final _infoStrings = []; BuildContext mcontext; var APP_ID = APPApiKey.Agora_app_id; bool muted = false; /// 视频是否成功接通 bool videoSuccess = false; /// 拒绝通话 bool isClosedByOne = false; /// 主窗口展示自己? /// true 自己false 好友 bool mainWindowShowOneself = true; int _count = 0; Timer _timer; @override void dispose() { // clean up native views & destroy sdk _sessions.forEach((session) { AgoraRtcEngine.removeNativeView(session.viewId); }); _sessions.clear(); AgoraRtcEngine.leaveChannel(); stopPlay(); if(!isClosedByOne && !videoSuccess){ /// 视频没有接通前自己挂断,则需要通知对方,我已拒绝 _initSendMessage(); } stopTimer(); super.dispose(); }@override void initState() { super.initState(); // initialize third.agora sdk initialize(); startPlay(); } void startPlay(){ _methodChannel1.invokeListMethod(MethodChannelUtils.methodStartMedia); } void stopPlay(){ _methodChannel1.invokeListMethod(MethodChannelUtils.methodStopMedia); } void initialize() { if (APP_ID.isEmpty) { setState(() { _infoStrings .add("APP_ID missing, please provide your APP_ID in settings.dart"); _infoStrings.add("Agora Engine is not starting"); }); return; }_initAgoraRtcEngine(); _addAgoraEventHandlers(); // use _addRenderView everytime a native video view is needed _addRenderView(0, (viewId) { AgoraRtcEngine.setupLocalVideo(viewId, VideoRenderMode.Hidden); AgoraRtcEngine.startPreview(); // state can access widget directly // AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0); // 修改为点击接听按钮后再接通 }); } Future _initSendMessage() async{ /*-------收到消息--------*/ try { String msg = AgoraUtils.getAgoraMsgType(3); await widget._client.sendMessageToPeer(widget.firendName, AgoraRtmMessage(msg)); } catch (e) { print(e); } } /// Create third.agora sdk instance and initialze Future _initAgoraRtcEngine() async { AgoraRtcEngine.create(APP_ID); AgoraRtcEngine.enableVideo(); }/// Add third.agora event handlers void _addAgoraEventHandlers() { AgoraRtcEngine.onError = (int code) { setState(() { String info = 'onError: ' + code.toString(); _infoStrings.add(info); }); }; /// 成功加入某次视频的回调 AgoraRtcEngine.onJoinChannelSuccess = (String channel, int uid, int elapsed) { setState(() { String info = 'onJoinChannel: ' + channel + ', uid: ' + uid.toString(); _infoStrings.add(info); }); }; AgoraRtcEngine.onLeaveChannel = () { setState(() { _infoStrings.add('onLeaveChannel'); }); }; /// 有其他用户加入到视频中的回调 AgoraRtcEngine.onUserJoined = (int uid, int elapsed) { setState(() { String info = 'userJoined: ' + uid.toString(); //setState(() { videoSuccess = true; }); _infoStrings.add(info); videoSuccess = true; startTimer(); _addRenderView(uid, (viewId) { AgoraRtcEngine.setupRemoteVideo(viewId, VideoRenderMode.Hidden, uid); }); }); }; AgoraRtcEngine.onUserOffline = (int uid, int reason) { setState(() { String info = 'userOffline: ' + uid.toString(); _infoStrings.add(info); onCallEnd(mcontext); _removeRenderView(uid); }); }; AgoraRtcEngine.onFirstRemoteVideoFrame = (int uid, int width, int height, int elapsed) { setState(() { String info = 'firstRemoteVideo: ' + uid.toString() + ' ' + width.toString() + 'x' + height.toString(); _infoStrings.add(info); }); }; }/// Create a native view and add a new video session object /// The native viewId can be used to set up local/remote view void _addRenderView(int uid, Function(int viewId) finished) { Widget view = AgoraRtcEngine.createNativeView(uid, (viewId) { setState(() { _getVideoSession(uid).viewId = viewId; if (finished != null) { finished(viewId); } }); }); VideoSession session = VideoSession(uid, view); _sessions.add(session); }/// Remove a native view and remove an existing video session object void _removeRenderView(int uid) { VideoSession session = _getVideoSession(uid); if (session != null) { _sessions.remove(session); } AgoraRtcEngine.removeNativeView(session.viewId); }/// Helper function to filter video session with uid VideoSession _getVideoSession(int uid) { return _sessions.firstWhere((session) { return session.uid == uid; }); }/// Helper function to get list of native views List _getRenderViews() { return _sessions.map((session) => session.view).toList(); }/// Video view wrapper /// Expanded组件必须用在Row、Column、Flex内,并且从Expanded到封装它的Row、Column、Flex的路径必须只包括StatelessWidgets或StatefulWidgets组件(不能是其他类型的组件,像RenderObjectWidget,它是渲染对象,不再改变尺寸了,因此Expanded不能放进RenderObjectWidget)。 Widget _videoView(view) { return Expanded(child: Container(child: view)); }/// Video layout wrapper Widget _viewRows() { //List views = _getRenderViews(); List views = new List(); views.addAll(_getRenderViews()); return _mainWindow(views); } /// 主窗口视图 Widget _mainWindow(List views){ return GestureDetector( child: Container( child: Column( children: [ mainWindowShowOneself ? _videoView(views[0]) : _videoView(views[1]) ], )), ); } /// 右上角小窗口视图 Widget _smallWindow() { //List views = _getRenderViews(); List views = new List(); if(!videoSuccess ){ return _emptyView(); }else { views.addAll(_getRenderViews()); if( mainWindowShowOneself ){ if(!EmptyUtil.listIsEmpty(views) && views.length > 1){ return_smallVideoView(views[1]); }else{ return _emptyView(); } }else { if(!EmptyUtil.listIsEmpty(views)){ return _smallVideoView(views[0]); }else{ return _emptyView(); } } } } Widget _smallVideoView(Widget view){ return GestureDetector( onTap: updateDoubleWindow, onDoubleTap: updateDoubleWindow, child: Align( alignment: Alignment.topRight, child: Container( width: 80.0, height: 130.0, margin: EdgeInsets.all(20), color: ThemeColors.colorWhite, child:Stack(children: [ Column( children: [ _videoView(view) ], ), Container( width: double.infinity, height: double.infinity, color: ThemeColors.transparent, child: Text(" 视图 ", style: TextStyle( color: ThemeColors.transparent, fontSize: 20.0, ), ), ) ],) ), ), ); } /// 未接通视频前的一层遮罩 Widget _mask(){ return Container( child:Offstage( offstage: videoSuccess, child: Container( width: double.infinity, height: double.infinity, color: ThemeColors.transparent1, ), ) , ); }/// Toolbar layout Widget _toolbar() { if(videoSuccess){ return _answerSuccessToolbar(); }else{ return _waitAnswerToolbar(); } } /// 通话时的工具栏 Widget _answerSuccessToolbar(){ return Container( alignment: Alignment.bottomCenter, padding: EdgeInsets.symmetric(vertical: 48),child:Container( height: 100.0, child: Column(children: [ Container( height: 20.0, margin: EdgeInsets.only(bottom:10.0), child: Text(DateTimeUtil.getHMmmss_Seconds(_count), style: TextStyle( color: ThemeColors.colorWhite, fontSize: 16, )), ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ RawMaterialButton( onPressed: () => _onToggleMute(), child: new Icon( muted ? Icons.mic : Icons.mic_off, color: muted ? Colors.white : ThemeColors.colorTheme, size: 20.0, ), shape: new CircleBorder(), elevation: 2.0, fillColor: muted ? ThemeColors.colorTheme : Colors.white, padding: const EdgeInsets.all(12.0), ), RawMaterialButton( onPressed: () => onCallEnd(context), child: new Icon( Icons.call_end, color: Colors.white, size: 35.0, ), shape: new CircleBorder(), elevation: 2.0, fillColor: Colors.redAccent, padding: const EdgeInsets.all(15.0), ), RawMaterialButton( onPressed: () => _onSwitchCamera(), child: new Icon( Icons.switch_camera, color: ThemeColors.colorTheme, size: 20.0, ), shape: new CircleBorder(), elevation: 2.0, fillColor: Colors.white, padding: const EdgeInsets.all(12.0), ) ], ), ],), ) , ); } /// 响铃时的工具栏 Widget _waitAnswerToolbar(){ returnContainer( alignment: Alignment.bottomCenter, padding: EdgeInsets.symmetric(vertical: 48), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ RawMaterialButton( onPressed: () => _onCancelAnswer(context), child: new Icon( Icons.call_end, color: Colors.white, size: 35.0, ), shape: new CircleBorder(), elevation: 2.0, fillColor: Colors.redAccent, padding: const EdgeInsets.all(15.0), ), RawMaterialButton( onPressed: () => _onAnswerVideo(), child: new Icon( Icons.call_received, color: Colors.white, size: 35.0, ), shape: new CircleBorder(), elevation: 2.0, fillColor: Colors.green, padding: const EdgeInsets.all(12.0), ) ], ), ); } /// 好友的信息视图 Widget _friendInfo() { return Container( alignment: Alignment.topLeft, margin: EdgeInsets.all(15), child: Offstage( offstage: videoSuccess, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.firendName, textAlign: TextAlign.left, style: TextStyle( color: ThemeColors.colorWhite, fontSize: 25, ), ), Text( "邀请你进行视频通话", textAlign: TextAlign.start, style: TextStyle( color: ThemeColors.colorWhite, fontSize: 12, height: 1.5, ), ), ],) ) ); } void _onToggleMute() { setState(() { muted = !muted; }); AgoraRtcEngine.muteLocalAudioStream(muted); } /// 切换摄像头 void _onSwitchCamera() { AgoraRtcEngine.switchCamera(); } /// 当点击接受应答 void _onAnswerVideo() { try { stopPlay(); AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0); } catch (e) { print(e); } } /// 当点击取消应答 void _onCancelAnswer(BuildContext context) { Navigator.pop(context); } /// 退出视频页面,停止视频 void onCallEnd(BuildContext context) { Navigator.pop(context); } void updateDoubleWindow(){ setState(() { mainWindowShowOneself = !mainWindowShowOneself; }); } /// 开始计时 void startTimer() { const oneSec = const Duration(seconds: 1); var callback = (timer) => { setState(() { _count++; // 秒数+1 }) }; _timer = Timer.periodic(oneSec, callback); } /// 停止计时 void stopTimer(){ if(_timer != null){ _timer.cancel(); } } @override Widget build(BuildContext context) { mcontext = context; return Scaffold( appBar: AppBar( title: Text('Agora Flutter QuickStart'), ), backgroundColor: Colors.black, body: Center( child: Stack( children: [_viewRows(),_smallWindow(),_mask(), _toolbar(),_friendInfo()],//_panel(), ))); } Widget _emptyView(){ return Container( width: 1.0, height: 1.0, ); } }

  1. Agora的工具类
class AgoraUtils{static AgoraRtmClient _client; static VideoCallState _videoCallState; /// Agora 初始化 static Future getAgoraRtmClient() async { if(_client == null){ _client = await AgoraRtmClient.createInstance(APPApiKey.Agora_app_id); } return _client; }/// 查询用户是否在线 ///true-在线, false-离线 static Future queryPeerOnlineStatus(AgoraRtmClient _client, String peerUid) async { if(EmptyUtil.textIsEmpty(peerUid)){ return false; }else{ try { Map result = await _client.queryPeersOnlineStatus([peerUid]); return result[peerUid]; } catch (errorCode) { return false; } } } /// 获取声网的消息类型 /// 1-请求视频通话 /// 2-取消请求通话 /// 3-拒绝通话请求 static String getAgoraMsgType(int type){ switch(type){ case 1: return "CALLVIDEO"; case 2: return "CANCEL_VIDEO"; case 3: return "REFUSE_VIDEO"; default: return ""; }} /// 视频请求 static set videoCallState(VideoCallState value) { _videoCallState = value; } /// 视频请求 static VideoCallState get videoCallState => _videoCallState; static clearVideoCallState(){ _videoCallState= null; } }

  1. Android 本地播放响铃的相关代码
MediaPlayer mediaPlayer; privatefloat BEEP_VOLUME = 9.10f; MediaPlayer.OnCompletionListener beepListener; private void startPlayBell(){ if(beepListener == null){ beepListener = new MediaPlayer.OnCompletionListener() { // 声音 public void onCompletion(MediaPlayer mediaPlayer) { mediaPlayer.seekTo(0); } }; } mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setOnCompletionListener(beepListener); mediaPlayer.setLooping(true); AssetFileDescriptor file = getResources().openRawResourceFd(R.raw.wechat_video); try { mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); file.close(); mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME); mediaPlayer.prepare(); } catch (IOException e) { mediaPlayer = null; } // } if(mediaPlayer != null){ mediaPlayer.start(); } } private void stopPlayBell(){ if(mediaPlayer != null){ mediaPlayer.stop(); mediaPlayer.release(); } }@Override protected void onDestroy() { super.onDestroy(); if(mediaPlayer != null){ mediaPlayer.release(); } }

其中 R.raw.wechat_video 是我找的音频文件,类似微信视频来电时的响铃

flutter学习笔记(利用声网的flutter插件实现视频通话)
文章图片
图片.png
最后 【flutter学习笔记(利用声网的flutter插件实现视频通话)】GitHub地址(https://github.com/Lightforest/FlutterVideo)
---end---

    推荐阅读