今天用聲網提供的Flutter插件聲網Agora來簡單實現體驗音視頻功能。首先前往聲網官網看看大體介紹:java
由於我是用Flutter來實現,所以聲網插件應該在pub.dev/packages/上,搜索Agore,能夠看到:ios
pubspec.yaml
文件下添加依賴:
首頁佈局很簡單,就兩個按鈕,分別是語音通話和視頻通話,先上草圖:git
Center
,孩子是
Row
,
Row
裏分別是左右排列的
RaisedButton
按鈕,代碼具體以下:
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,//主軸空白區域均分
children: <Widget>[
//左邊的按鈕
RaisedButton(
padding: EdgeInsets.all(0),
//點擊事件
onPressed: () {
//去往語音頁面
onAudio();
},
child: Container(
height: 120,
width: 120,
//裝飾
decoration: BoxDecoration(
//漸變色
gradient: const LinearGradient( colors: [Colors.blueAccent, Colors.lightBlueAccent], ), //圓角12度 borderRadius: BorderRadius.circular(12.0)), child: Text( "語音通話", style: TextStyle(color: Colors.white, fontSize: 18.0), ), //文字居中 alignment: Alignment.center, ), shape: new RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), ), //右邊的按鈕 RaisedButton( padding: EdgeInsets.all(0), onPressed: () {
//去往視頻頁面
onVideo();
},
child: Container(
height: 120,
width: 120,
//裝飾--->漸變
decoration: BoxDecoration(
gradient: const LinearGradient( colors: [Colors.blueAccent, Colors.lightBlueAccent], ), //圓角12度 borderRadius: BorderRadius.circular(12.0)), child: Text( "視頻通話", style: TextStyle(color: Colors.white, fontSize: 18.0), ), //文字居中 alignment: Alignment.center, ), shape: new RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), ), ], ), ), );
}
複製代碼
效果以下: github
onAudio()
onAudio() async {
SimplePermissions.requestPermission(Permission.RecordAudio)
.then((status_first) {
if (status_first == PermissionStatus.denied) {
//若是拒絕
Toast.show("此功能須要授予錄音權限", context,
duration: Toast.LENGTH_SHORT, gravity: Toast.CENTER);
} else if (status_first == PermissionStatus.authorized) {
//若是受權贊成 跳轉到語音頁面
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => new AudioCallPage(
//頻道寫死,爲了方便體驗
channelName: "122343",
),
),
);
}
});
}
複製代碼
語音只授予錄音權限便可。服務器
onVideo()
視頻須要授予的權限多了相機權限而兒:onVideo() async {
SimplePermissions.requestPermission(Permission.Camera).then((status_first) {
if (status_first == PermissionStatus.denied) {
//若是拒絕
Toast.show("此功能須要授予相機權限", context,
duration: Toast.LENGTH_SHORT, gravity: Toast.CENTER);
} else if (status_first == PermissionStatus.authorized) {
//若是贊成
SimplePermissions.requestPermission(Permission.RecordAudio)
.then((status_second) {
if (status_second == PermissionStatus.denied) {
//若是拒絕
Toast.show("此功能須要授予錄音權限", context,
duration: Toast.LENGTH_SHORT, gravity: Toast.CENTER);
} else if (status_second == PermissionStatus.authorized) {
//若是受權贊成
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => new VideoCallPage(
//視頻房間頻道號寫死,爲了方便體驗
channelName: "122343",
),
),
);
}
});
}
});
}
複製代碼
這樣首頁算完成了。微信
這裏我只作了一對一語音通話的界面效果,也能夠實現多人通話,只是把界面樣式改爲本身喜歡的樣式便可。app
一對一通話的界面相似微信語音通話界面同樣,屏幕中間是對方頭像(這裏我只顯示對方用戶ID),底部是菜單欄:是否靜音,掛斷,是否外放,草圖以下:async
Stack
層疊控件+
Positioned
來定位:
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text(widget.channelName),
),
//背景黑色
backgroundColor: Colors.black,
body: new Center(
child: Stack(
children: <Widget>[_viewAudio(), _bottomToolBar()],
),
),
);
}
複製代碼
實現語音主要五個步驟,分別是:ide
初始化引擎只有一句代碼:工具
//初始化引擎
AgoraRtcEngine.create(agore_appId);
複製代碼
進去源碼發現:
/// Creates an RtcEngine instance.
///
/// The Agora SDK only supports one RtcEngine instance at a time, therefore the app should create one RtcEngine object only.
/// Only users with the same App ID can join the same channel and call each other.
//在RtcEngine SDK的應用程序應該只建立一個RtcEngine實例
static Future<void> create(String appid) async {
_addMethodCallHandler();
return await _channel.invokeMethod('create', {'appId': appid});
}
複製代碼
發現裏面還調用例_addMethodCallHandler
方法,忘看看裏面:
// CallHandler
static void _addMethodCallHandler() {
_channel.setMethodCallHandler((MethodCall call) {
Map values = call.arguments;
switch (call.method) {
// Core Events
case 'onWarning':
if (onWarning != null) {
onWarning(values['warn']);
}
break;
case 'onError':
if (onError != null) {
onError(values['err']);
}
break;
case 'onJoinChannelSuccess':
if (onJoinChannelSuccess != null) {
onJoinChannelSuccess(
values['channel'], values['uid'], values['elapsed']);
}
break;
case 'onRejoinChannelSuccess':
if (onRejoinChannelSuccess != null) {
onRejoinChannelSuccess(
values['channel'], values['uid'], values['elapsed']);
}
break;
......
}
}
}
複製代碼
能夠看到主要是特定觸發條件的回調,如:SDK錯誤,是否成功建立頻道,是否離開頻道等,那麼如今能夠知道AgoraRtcEngine.create(agore_appId)
這行代碼是初始化引擎和實現某些狀態下的監聽回調。
啓用音頻模塊:
//設置視頻爲可用 啓用音頻模塊
AgoraRtcEngine.enableAudio();
複製代碼
看官方文檔介紹:
當初始化完引擎和啓用音頻模塊後,下面進行建立房間:
//建立渲染視圖
void _createRendererView(int uid) {
//增長音頻會話對象 爲了音頻佈局須要(經過uid和容器信息)
//加入頻道 第一個參數是 token 第二個是頻道id 第三個參數 頻道信息 通常爲空 第四個 用戶id
setState(() {
AgoraRtcEngine.joinChannel(null, widget.channelName, null, uid);
});
VideoUserSession videoUserSession = VideoUserSession(uid);
_userSessions.add(videoUserSession);
print("集合大小"+_userSessions.length.toString());
}
複製代碼
主要看AgoraRtcEngine.joinChannel(null, widget.channelName, null, uid);
這個方法:
VideoUserSession
類來管理用戶信息,經過集合
List<VideoUserSession>
來存放當前在房間的人數,目的就是爲了佈局方便。
當若是有用戶新加入進來,或者用戶離開又或者是掉線,咱們能不能知道呢?答案是確定的:
//設置事件監聽
void setAgoreEventListener() {
//成功加入房間
AgoraRtcEngine.onJoinChannelSuccess =
(String channel, int uid, int elapsed) {
print("成功加入房間,頻道號:${channel}+uid+${uid}");
};
//監聽是否有新用戶加入
AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {
print("新用戶所加入的id爲:$uid");
setState(() {
//更新UI佈局
_createRendererView(uid);
self_uid = uid;
});
};
//監聽用戶是否離開這個房間
AgoraRtcEngine.onUserOffline = (int uid, int reason) {
print("用戶離開的id爲:$uid");
setState(() {
//移除用戶 更新UI佈局
_removeRenderView(uid);
});
};
//監聽用戶是否離開這個頻道
AgoraRtcEngine.onLeaveChannel = () {
print("用戶離開");
};
}
複製代碼
下面簡單實現屏幕中間的UI實現,我這邊只作了一對一通話,也就是中間只顯示對方的用戶id,若是多人通話,也能夠根據List<VideoUserSession>
的數量依次顯示。
//音頻佈局視圖佈局
Widget _viewAudio() {
//先獲取音頻人數
List<int> views = _getRenderViews();
switch (views.length) {
//只有一個用戶(即本身)
case 1:
return Center(
child: Container(
child: Text("用戶1"),
),
);
//兩個用戶
case 2:
return Positioned(//在中間顯示對方id
top: 180,
left: 30,
right: 30,
child: Container(
height: 260,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
alignment: Alignment.center,
width: 140,
height: 140,
color: Colors.red,
child: Text("對方用戶uid:\n${self_uid}",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
);
default:
}
return new Container();
}
複製代碼
上面主要是根據List<VideoUserSession>
集合本身控制語音經過頁面。
若是用戶退出本界面或者掛斷,必須調用AgoraRtcEngine.leaveChannel();
:
//本頁面即將銷燬
@override
void dispose() {
//把集合清掉
_userSessions.clear();
AgoraRtcEngine.leaveChannel();
//sdk資源釋放
AgoraRtcEngine.destroy();
super.dispose();
}
複製代碼
當有用戶離開了這個房間後,會回調AgoraRtcEngine.onUserOffline
這個方法,文檔也有說明:
//移除對應的用戶界面 而且移除用戶會話對象
void _removeRenderView(int uid) {
//先從會話對象根據uid來清除
VideoUserSession videoUserSession = _getVideoUidSession(uid);
if (videoUserSession != null) {
_userSessions.remove(videoUserSession);
}
}
複製代碼
是否靜音是經過AgoraRtcEngine.muteLocalAudioStream(muted);
方法來實現:
//開關本地音頻發送
void _isMute() {
setState(() {
muted = !muted;
});
// true:麥克風靜音 false:取消靜音(默認)
AgoraRtcEngine.muteLocalAudioStream(muted);
}
複製代碼
//是否開啓揚聲器
void _isSpeakPhone() {
setState(() {
speakPhone = !speakPhone;
});
AgoraRtcEngine.setEnableSpeakerphone(speakPhone);
}
複製代碼
這裏視頻支持多人視頻,工具欄也和語音同樣,也是在底部,當和一對一對方視頻通話時,屏幕分爲兩部分,上面是本身,下面是對方的視頻,其餘邏輯和語音基本一致,實現視頻主要有四個步驟:
啓用視頻模塊主要也是一句代碼AgoraRtcEngine.enableVideo();
,看文檔說明:
建立視頻播放插件:
//建立渲染視圖
void _createDrawView(int uid,Function(int viewId) successCreate){
//該方法建立視頻渲染視圖 而且添加新的視頻會話對象,這個渲染視圖能用在本地/遠端流 這裏須要更新
//Agora SDK 在 App 提供的 View 上進行渲染。
Widget view = AgoraRtcEngine.createNativeView(uid, (viewId){
setState(() {
_getVideoUidSession(uid).viewId = viewId;
if(successCreate != null){
successCreate(viewId);
}
});
});
//增長視頻會話對象 爲了視頻須要(經過uid和容器信息)
VideoUserSession videoUserSession = VideoUserSession(uid, view: view);
_userSessions.add(videoUserSession);
}
複製代碼
也是經過集合來存放管理會話對象信息,就是爲了方便視頻佈局。
//設置本地視圖。 該方法設置本地視圖。App 經過調用此接口綁定本地視頻流的顯示視圖 (View),並設置視頻顯示模式。
// 在 App 開發中,一般在初始化後調用該方法進行本地視頻設置,而後再加入頻道。退出頻道後,綁定仍然有效,若是須要解除綁定,能夠指定空 (null) View 調用
//該方法設置本地視頻顯示模式。App 能夠屢次調用此方法更改顯示模式。
//RENDER_MODE_HIDDEN(1):優先保證視窗被填滿。視頻尺寸等比縮放,直至整個視窗被視頻填滿。若是視頻長寬與顯示窗口不一樣,多出的視頻將被截掉
AgoraRtcEngine.setupLocalVideo(viewId, VideoRenderMode.Hidden);
複製代碼
而且制定視頻渲染模式。
setupLocalVideo
和
enableVideo
。
當一切準備就緒後就要加入視頻房間,加入視頻房間和加入語音房間是同樣的:
//加入頻道 第一個參數是 token 第二個是頻道id 第三個參數 頻道信息 通常爲空 第四個 用戶id
AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0);
複製代碼
設置事件監聽視頻和語音最大一點不同就是,多了設置遠程用戶的視頻視圖,這個方法主要是此方法將遠程用戶綁定到視頻顯示窗口(爲指定的遠程用戶設置視圖uid)。
//設置事件監聽
void setAgoreEventListener(){
//成功加入房間
AgoraRtcEngine.onJoinChannelSuccess = (String channel,int uid,int elapsed){
print("成功加入房間,頻道號:$channel");
};
//監聽是否有新用戶加入
AgoraRtcEngine.onUserJoined = (int uid,int elapsed){
print("新用戶所加入的id爲:$uid");
setState(() {
_createDrawView(uid, (viewId){
//設置遠程用戶的視頻視圖
AgoraRtcEngine.setupRemoteVideo(viewId, VideoRenderMode.Hidden, uid);
});
});
};
//監聽用戶是否離開這個房間
AgoraRtcEngine.onUserOffline = (int uid,int reason){
print("用戶離開的id爲:$uid");
setState(() {
_removeRenderView(uid);
});
};
//監聽用戶是否離開這個頻道
AgoraRtcEngine.onLeaveChannel = (){
print("用戶離開");
};
}
複製代碼
這裏要分狀況,1-5各用戶的狀況:
//視頻視圖佈局
Widget _videoLayout(){
//先獲取視頻試圖個數
List<Widget> views = _getRenderViews();
switch(views.length){
//只有一個用戶的時候 整個屏幕
case 1:
return new Container(
child: new Column(
children: <Widget>[
_videoView(views[0])
],
),
);
//兩個用戶的時候 上下佈局 本身在上面 對方在下面
case 2:
return new Container(
child: new Column(
children: <Widget>[
_createVideoRow([views[0]]),
_createVideoRow([views[1]]),
],
),
);
//三個用戶
case 3:
return new Container(
child: new Column(
children: <Widget>[
//截取0-2 不包括2 上面一列兩個 下面一個
_createVideoRow(views.sublist(0, 2)),
//截取2 -3 不包括3
_createVideoRow(views.sublist(2, 3))
],
),
);
//四個用戶
case 4:
return new Container(
child: new Column(
children: <Widget>[
//截取0-2 不包括2 也就是0,1 上面 下面各兩個用戶
_createVideoRow(views.sublist(0, 2)),
//截取2-4 不包括4 也就是 3,4
_createVideoRow(views.sublist(2, 4))
],
),
);
default:
}
return new Container();
}
複製代碼
最核心的就是,有用戶退出和加入就要更新UI視圖。