日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

flutter 如何判断在哪个页面_Agora 教程:构建你的第一个 Flutter 视频通话应用

發(fā)布時(shí)間:2024/9/27 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 flutter 如何判断在哪个页面_Agora 教程:构建你的第一个 Flutter 视频通话应用 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

作者:張乾澤,聲網(wǎng)Agora 工程師

我們基于 Agora SDK 封裝了?Agora Flutter SDK?,開發(fā)者可以在 Flutter 應(yīng)用中快速實(shí)現(xiàn)穩(wěn)定、可靠的實(shí)時(shí)音視頻通話、互動(dòng)直播應(yīng)用。本文將介紹如何使用 Agora Flutter SDK 快速構(gòu)建一個(gè)簡單的移動(dòng)跨平臺(tái)視頻通話應(yīng)用。

環(huán)境準(zhǔn)備

在 Flutter 中文網(wǎng)(flutterchina.club)上,關(guān)于搭建開放環(huán)境的教程已經(jīng)相對(duì)比較完善了,有關(guān) IDE 與環(huán)境配置的過程本文不再贅述,若 Flutter 安裝有問題,可以執(zhí)行 flutter doctor 做配置檢查。

本文使用 MacOS 下的 VS Code 作為主開發(fā)環(huán)境。

目標(biāo)

我們希望可以使用 Flutter+Agora Flutter SDK 實(shí)現(xiàn)一個(gè)簡單的視頻通話應(yīng)用,這個(gè)視頻通話應(yīng)用需要包含以下功能,

  • 加入通話房間

  • 視頻通話

  • 前后攝像頭切換

  • 本地靜音/取消靜音

聲網(wǎng)的視頻通話是按通話房間區(qū)分的,同一個(gè)通話房間內(nèi)的用戶都可以互通。為了方便區(qū)分,這個(gè)演示會(huì)需要一個(gè)簡單的表單頁面讓用戶提交選擇加入哪一個(gè)房間。同時(shí)一個(gè)房間內(nèi)可以容納最多 4 個(gè)用戶,當(dāng)用戶數(shù)不同時(shí)我們需要展示不同的布局。

想清楚了?動(dòng)手?jǐn)]代碼了。

項(xiàng)目創(chuàng)建

首先在 VS Code 選擇?查看->命令面板(或直接使用 cmd + shift + P)調(diào)出命令面板,輸入 flutter 后選擇?Flutter: New Project?創(chuàng)建一個(gè)新的 Flutter 項(xiàng)目,項(xiàng)目的名字為?agora_flutter_quickstart,隨后等待項(xiàng)目創(chuàng)建完成即可。

現(xiàn)在執(zhí)行?啟動(dòng)->啟動(dòng)調(diào)試(或 F5)即可看到一個(gè)最簡單的計(jì)數(shù) App。

看起來我們有了一個(gè)很好的開始

接下去我們需要對(duì)我們新建的項(xiàng)目做一下簡單的配置以使其可以引用和使用 Agora Flutter SDK。

打開項(xiàng)目根目錄下的 pubspec.yaml 文件,在?dependencies下添加?agora_rtc_engine:^0.9.0,

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

?# add agora rtc sdk

?agora_rtc_engine: ^0.9.0

dev_dependencies:

?flutter_test:

? ?sdk: flutter

保存后 VS Code 會(huì)自動(dòng)執(zhí)行?flutter packages get?更新依賴。

應(yīng)用首頁

在項(xiàng)目配置完成后,我們就可以開始開發(fā)了。首先我們需要?jiǎng)?chuàng)建一個(gè)頁面文件替換掉默認(rèn)示例代碼中的?MyHomePage?類。我們可以在?lib/src?下創(chuàng)建一個(gè)?pages目錄,并創(chuàng)建一個(gè)??index.dart?文件。

如果你已經(jīng)完成了官方教程 Write your first Flutter app,那么以下代碼對(duì)你來說就應(yīng)該不難理解。

class IndexPage extends StatefulWidget {

?@override

?State<StatefulWidget> createState() {

? ?return new IndexState();

?}

}

class IndexState extends State<IndexPage> {

?@override

?Widget build(BuildContext context) {

? ? ?// UI

?}

?onJoin() {

? ? ?//TODO

?}

}

現(xiàn)在我們需要開始在?build?方法中構(gòu)造首頁的 UI。

按上圖分解 UI 后,我們可以將我們的首頁代碼修改如下

@override

Widget build(BuildContext context) {

return Scaffold(

? ?appBar: AppBar(

? ? ?title: Text('Agora Flutter QuickStart'),

? ?),

? ?body: Center(

? ? ?child: Container(

? ? ? ? ?padding: EdgeInsets.symmetric(horizontal: 20),

? ? ? ? ?height: 400,

? ? ? ? ?child: Column(

? ? ? ? ? ?children: <Widget>[

? ? ? ? ? ? ?Row(children: <Widget>[]),

? ? ? ? ? ? ?Row(children: <Widget>[

? ? ? ? ? ? ? ?Expanded(

? ? ? ? ? ? ? ? ? ?child: TextField(

? ? ? ? ? ? ? ? ?decoration: InputDecoration(

? ? ? ? ? ? ? ? ? ? ?border: UnderlineInputBorder(

? ? ? ? ? ? ? ? ? ? ? ? ?borderSide: BorderSide(width: 1)),

? ? ? ? ? ? ? ? ? ? ?hintText: 'Channel name'),

? ? ? ? ? ? ? ?))

? ? ? ? ? ? ?]),

? ? ? ? ? ? ?Padding(

? ? ? ? ? ? ? ? ?padding: EdgeInsets.symmetric(vertical: 20),

? ? ? ? ? ? ? ? ?child: Row(

? ? ? ? ? ? ? ? ? ?children: <Widget>[

? ? ? ? ? ? ? ? ? ? ?Expanded(

? ? ? ? ? ? ? ? ? ? ? ?child: RaisedButton(

? ? ? ? ? ? ? ? ? ? ? ? ?onPressed: () => onJoin(),

? ? ? ? ? ? ? ? ? ? ? ? ?child: Text("Join"),

? ? ? ? ? ? ? ? ? ? ? ? ?color: Colors.blueAccent,

? ? ? ? ? ? ? ? ? ? ? ? ?textColor: Colors.white,

? ? ? ? ? ? ? ? ? ? ? ?),

? ? ? ? ? ? ? ? ? ? ?)

? ? ? ? ? ? ? ? ? ?],

? ? ? ? ? ? ? ? ?))

? ? ? ? ? ?],

? ? ? ? ?)),

? ?));

}

執(zhí)行 F5 啟動(dòng)查看,應(yīng)該可以看到下圖

看起來不錯(cuò)!但也只是看起來不錯(cuò)。我們的UI現(xiàn)在只能看,還不能交互。我們希望可以基于現(xiàn)在的 UI 實(shí)現(xiàn)以下功能,

1. 為 Join 按鈕添加回調(diào)導(dǎo)航到通話頁面

2. 對(duì)頻道名做檢查,若嘗試加入頻道時(shí)頻道名為空,則在 TextField 上提示錯(cuò)誤

TextField 輸入校驗(yàn)

TextField 自身提供了一個(gè)?decoration屬性,我們可以提供一個(gè)?InputDecoration的對(duì)象來標(biāo)識(shí) TextField 的裝飾樣式。?InputDecoration里的?errorText屬性非常適合在我們這里被拿來使用, 同時(shí)我們利用?TextEditingController對(duì)象來記錄 TextField 的值,以判斷當(dāng)前是否應(yīng)該顯示錯(cuò)誤。因此經(jīng)過簡單的修改后,我們的 TextField 代碼就變成了這樣:

? ?final _channelController = TextEditingController();

? ?/// if channel textfield is validated to have error

? ?bool _validateError = false;

? ?@override

? ?void dispose() {

? ? ? ?// dispose input controller

? ? ? ?_channelController.dispose();

? ? ? ?super.dispose();

? ?}

? ?@override

? ? Widget build(BuildContext context) {

? ? ? ?...

? ? ? ?TextField(

? ? ? ? ?controller: _channelController,

? ? ? ? ?decoration: InputDecoration(

? ? ? ? ? ? ?errorText: _validateError

? ? ? ? ? ? ? ? ?? "Channel name is mandatory"

? ? ? ? ? ? ? ? ?: null,

? ? ? ? ? ? ?border: UnderlineInputBorder(

? ? ? ? ? ? ? ? ?borderSide: BorderSide(width: 1)),

? ? ? ? ? ? ?hintText: 'Channel name'),

? ? ? ?))

? ? ? ?...

? ?}

? ?onJoin() {

? ? ? ?// update input validation

? ? ? ?setState(() {

? ? ? ? ?_channelController.text.isEmpty

? ? ? ? ? ? ?? _validateError = true

? ? ? ? ? ? ?: _validateError = false;

? ? ? ?});

? ?}

在點(diǎn)擊加入頻道按鈕的時(shí)候回觸發(fā)?onJoin回調(diào),回調(diào)中會(huì)先通過?setState更新 TextField 的狀態(tài)以做組件重繪。

注意: 不要忘了 override?dispose方法在這個(gè)組件的生命周期結(jié)束時(shí)釋放?_controller。

前往通話頁面

到這里我們的首頁基本就算完成了,最后我們?cè)?onJoin中創(chuàng)建?MaterialPageRoute將用戶導(dǎo)航到通話頁面,在這里我們將獲取的頻道名作為通話頁面構(gòu)造函數(shù)的參數(shù)傳遞到下一個(gè)頁面?CallPage。

import './call.dart';

class IndexState extends State<IndexPage> {

? ?...

? ?onJoin() {

? ? ? ?// update input validation

? ? ? ?setState(() {

? ? ? ? ?_channelController.text.isEmpty

? ? ? ? ? ? ?? _validateError = true

? ? ? ? ? ? ?: _validateError = false;

? ? ? ?});

? ? ? ?if (_channelController.text.isNotEmpty) {

? ? ? ? ?// push video page with given channel name

? ? ? ? ?Navigator.push(

? ? ? ? ? ? ?context,

? ? ? ? ? ? ?MaterialPageRoute(

? ? ? ? ? ? ? ? ?builder: (context) => new CallPage(

? ? ? ? ? ? ? ? ? ? ? ?channelName: _channelController.text,

? ? ? ? ? ? ? ? ? ? ?)));

? ?}

}

通話頁面

同樣在?/lib/src/pages目錄下,我們需要新建一個(gè)?call.dart文件,在這個(gè)文件里我們會(huì)實(shí)現(xiàn)我們最重要的實(shí)時(shí)視頻通話邏輯。首先還是需要?jiǎng)?chuàng)建我們的?CallPage類。如果你還記得我們?cè)?IndexPage的實(shí)現(xiàn),?CallPage會(huì)需要在構(gòu)造函數(shù)中帶入一個(gè)參數(shù)作為頻道名。

class CallPage extends StatefulWidget {

? ?/// non-modifiable channel name of the page

? ?final String channelName;

? ?/// Creates a call page with given channel name.

? ?const CallPage({Key key, this.channelName}) : super(key: key);

? ?@override

? ?_CallPageState createState() {

? ? ? ?return new _CallPageState();

? ?}

}

class _CallPageState extends State<CallPage> {

? ?@override

? ?Widget build(BuildContext context) {

? ? ? ?return Scaffold(

? ? ? ? ? ?appBar: AppBar(

? ? ? ? ? ? ?title: Text(widget.channelName),

? ? ? ? ? ?),

? ? ? ? ? ?backgroundColor: Colors.black,

? ? ? ? ? ?body: Center(

? ? ? ? ? ? ? ?child: Stack(

? ? ? ? ? ? ?children: <Widget>[],

? ? ? ? ? ?)));

? ?}

}

這里需要注意的是,我們并不需要把參數(shù)在創(chuàng)建?state實(shí)例的時(shí)候傳入,?state可以直接訪問?widget.channelName獲取到組件的屬性。

引入聲網(wǎng)SDK

因?yàn)槲覀冊(cè)谧铋_始已經(jīng)在?pubspec.yaml中添加了?agora_rtc_engine的依賴,因此我們現(xiàn)在可以直接通過以下方式引入聲網(wǎng) SDK。

import 'package:agora_rtc_engine/agora_rtc_engine.dart';

引入后即可以使用創(chuàng)建聲網(wǎng)媒體引擎實(shí)例。在使用聲網(wǎng) SDK 進(jìn)行視頻通話之前,我們需要進(jìn)行以下初始化工作。初始化工作應(yīng)該在整個(gè)頁面生命周期中只做一次,因此這里我們需要 override?initState方法,在這個(gè)方法里做好初始化。

class _CallPageState extends State<CallPage> {

? ?@override

? ?void initState() {

? ? ? ?super.initState();

? ? ? ?initialize();

? ?}

? ?void initialize() {

? ? ? ?_initAgoraRtcEngine();

? ? ? ?_addAgoraEventHandlers();

? ?}

? ?/// Create agora sdk instance and initialze

? ?void _initAgoraRtcEngine() {

? ? ? ?AgoraRtcEngine.create(APP_ID);

? ? ? ?AgoraRtcEngine.enableVideo();

? ?}

? ?/// Add agora event handlers

? void _addAgoraEventHandlers() {

? ?AgoraRtcEngine.onError = (int code) {

? ? ?// sdk error

? ?};

? ?AgoraRtcEngine.onJoinChannelSuccess =

? ? ? ?(String channel, int uid, int elapsed) {

? ? ?// join channel success

? ?};

? ?AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {

? ? ?// there's a new user joining this channel

? ?};

? ?AgoraRtcEngine.onUserOffline = (int uid, int reason) {

? ? ?// there's an existing user leaving this channel

? ?};

?}

}

注意:?有關(guān)如何獲取聲網(wǎng) APP_ID,請(qǐng)?jiān)L問 docs.gora.io 參閱官方文檔。

在以上的代碼中我們主要?jiǎng)?chuàng)建了聲網(wǎng)的媒體 SDK 實(shí)例并監(jiān)聽了關(guān)鍵事件,接下去我們會(huì)開始做視頻流的處理。

在一般的視頻通話中,對(duì)于本地設(shè)備來說一共會(huì)有兩種視頻流,本地流與遠(yuǎn)端流 - 前者需要通過本地?cái)z像頭采集渲染并發(fā)送出去,后者需要接收遠(yuǎn)端流的數(shù)據(jù)后渲染。現(xiàn)在我們需要?jiǎng)討B(tài)地將最多4人的視頻流渲染到通話頁面。

我們會(huì)以大致這樣的結(jié)構(gòu)渲染通話頁面。

這里和首頁不同的是,放置通話操作按鈕的工具欄是覆蓋在視頻上的,因此這里我們會(huì)使用?Stack組件來放置層疊組件。

為了更好地區(qū)分 UI 構(gòu)建,我們將視頻構(gòu)建與工具欄構(gòu)建分為兩個(gè)方法。

本地流創(chuàng)建與渲染

要渲染本地流,需要在初始化 SDK 完成后創(chuàng)建一個(gè)供視頻流渲染的容器,然后通過 SDK 將本地流渲染到對(duì)應(yīng)的容器上。聲網(wǎng) SDK 提供了?createNativeView的方法以創(chuàng)建容器,在獲取到容器并且成功渲染到容器視圖上后,我們就可以利用SDK加入頻道與其他客戶端互通了。

? ?void initialize() {

? ? ? ?_initAgoraRtcEngine();

? ? ? ?_addAgoraEventHandlers();

? ? ? ?// use _addRenderView everytime a native video view is needed

? ? ? ?_addRenderView(0, (viewId) {

? ? ? ? ? ?// local view setup & preview

? ? ? ? ? ?AgoraRtcEngine.setupLocalVideo(viewId, 1);

? ? ? ? ? ?AgoraRtcEngine.startPreview();

? ? ? ? ? ?// state can access widget directly

? ? ? ? ? ?AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0);

? ? ? ?});

? ?}

? ?/// 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);

? ?}

注意:?代碼最后利用 uid 與容器信息創(chuàng)建了一個(gè)?VideoSession對(duì)象并添加到?_sessions中,這主要是為了視頻布局需要,這塊稍后會(huì)詳細(xì)觸及。

遠(yuǎn)端流監(jiān)聽與渲染

遠(yuǎn)端流的監(jiān)聽其實(shí)我們已經(jīng)在前面的初始化代碼中提及了,我們可以監(jiān)聽 SDK 提供的?onUserJoined與?onUserOffline回調(diào)來判斷是否有其他用戶進(jìn)出當(dāng)前頻道,若有新用戶加入頻道,就為他創(chuàng)建一個(gè)渲染容器并做對(duì)應(yīng)的渲染;若有用戶離開頻道,則去掉他的渲染容器。

? ?AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {

? ? ?setState(() {

? ? ? ?_addRenderView(uid, (viewId) {

? ? ? ? ?AgoraRtcEngine.setupRemoteVideo(viewId, 1, uid);

? ? ? ?});

? ? ?});

? ?};

? ?AgoraRtcEngine.onUserOffline = (int uid, int reason) {

? ? ?setState(() {

? ? ? ?_removeRenderView(uid);

? ? ?});

? ?};

? ?/// 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);

? ?}

注意:?_sessions的作用是在本地保存一份當(dāng)前頻道內(nèi)的視頻流列表信息。因此在用戶加入的時(shí)候,需要?jiǎng)?chuàng)建對(duì)應(yīng)的?VideoSession對(duì)象并添加到?sessions,在用戶離開的時(shí)候,則需要?jiǎng)h除對(duì)應(yīng)的?VideoSession實(shí)例。

視頻流布局

在有了?_sessions數(shù)組,且每一個(gè)本地/遠(yuǎn)端流都有了一個(gè)對(duì)應(yīng)的原生渲染容器后,我們就可以開始對(duì)視頻流進(jìn)行布局了。

? ?/// Helper function to get list of native views

? ?List<Widget> _getRenderViews() {

? ? ? ?return _sessions.map((session) => session.view).toList();

? ?}

? ?/// Video view wrapper

? ?Widget _videoView(view) {

? ? ? ?return Expanded(child: Container(child: view));

? ?}

? ?/// Video view row wrapper

? ?Widget _expandedVideoRow(List<Widget> views) {

? ? ? ?List<Widget> wrappedViews =

? ? ? ? ? ?views.map((Widget view) => _videoView(view)).toList();

? ? ? ?return Expanded(

? ? ? ? ? ?child: Row(

? ? ? ? ?children: wrappedViews,

? ?));

? ?}

? ?/// Video layout wrapper

? ?Widget _viewRows() {

? ? ? ?List<Widget> 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();

? ?}

工具欄(掛斷、靜音、切換攝像頭)

在實(shí)現(xiàn)完視頻流布局后,我們接下來實(shí)現(xiàn)視頻通話的操作工具欄。工具欄里有三個(gè)按鈕,分別對(duì)應(yīng)靜音、掛斷、切換攝像頭的順序。用簡單的?flexRow布局即可。

? ?/// Toolbar layout

? ?Widget _toolbar() {

? ? ? ?return Container(

? ? ? ? ?alignment: Alignment.bottomCenter,

? ? ? ? ?padding: EdgeInsets.symmetric(vertical: 48),

? ? ? ? ?child: Row(

? ? ? ? ? ?mainAxisAlignment: MainAxisAlignment.center,

? ? ? ? ? ?children: <Widget>[

? ? ? ? ? ? ?RawMaterialButton(

? ? ? ? ? ? ? ?onPressed: () => _onToggleMute(),

? ? ? ? ? ? ? ?child: new Icon(

? ? ? ? ? ? ? ? ?muted ? Icons.mic : Icons.mic_off,

? ? ? ? ? ? ? ? ?color: muted ? Colors.white : Colors.blueAccent,

? ? ? ? ? ? ? ? ?size: 20.0,

? ? ? ? ? ? ? ?),

? ? ? ? ? ? ? ?shape: new CircleBorder(),

? ? ? ? ? ? ? ?elevation: 2.0,

? ? ? ? ? ? ? ?fillColor: muted?Colors.blueAccent : 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: Colors.blueAccent,

? ? ? ? ? ? ? ? ?size: 20.0,

? ? ? ? ? ? ? ?),

? ? ? ? ? ? ? ?shape: new CircleBorder(),

? ? ? ? ? ? ? ?elevation: 2.0,

? ? ? ? ? ? ? ?fillColor: Colors.white,

? ? ? ? ? ? ? ?padding: const EdgeInsets.all(12.0),

? ? ? ? ? ? ?)

? ? ? ? ? ?],

? ? ? ? ?),

? ? ? ?);

? ?}

? ?void _onCallEnd(BuildContext context) {

? ? ? ?Navigator.pop(context);

? ?}

? ?void _onToggleMute() {

? ? ? ?setState(() {

? ? ? ? ?muted = !muted;

? ? ? ?});

? ? ? ?AgoraRtcEngine.muteLocalAudioStream(muted);

? ?}

? ?void _onSwitchCamera() {

? ? ? ?AgoraRtcEngine.switchCamera();

? ?}

最終整合

現(xiàn)在兩個(gè)部分的 UI 都完成了,我們接下去要將這兩個(gè)組件通過?Stack組裝起來。

? ?@override

? ?Widget build(BuildContext context) {

? ? ? ?return Scaffold(

? ? ? ? ? ?appBar: AppBar(

? ? ? ? ? ? ?title: Text(widget.channelName),

? ? ? ? ? ?),

? ? ? ? ? ?backgroundColor: Colors.black,

? ? ? ? ? ?body: Center(

? ? ? ? ? ? ? ?child: Stack(

? ? ? ? ? ? ?children: <Widget>[_viewRows(), _toolbar()],

? ? ? ? ? ?)));

清理

若只在當(dāng)前頁面使用聲網(wǎng) SDK,則需要在離開前調(diào)用?destroy接口將 SDK 實(shí)例銷毀。若需要跨頁面使用,則推薦將 SDK 實(shí)例做成單例以供不同頁面訪問。同時(shí)也要注意對(duì)原生渲染容器的釋放,可以至直接使用?removeNativeView方法釋放對(duì)應(yīng)的原生容器。

? ?@override

? ?void dispose() {

? ? ? ?// clean up native views & destroy sdk

? ? ? ?_sessions.forEach((session) {

? ? ? ? ?AgoraRtcEngine.removeNativeView(session.viewId);

? ? ? ?});

? ? ? ?_sessions.clear();

? ? ? ?AgoraRtcEngine.destroy();

? ? ? ?super.dispose();

? ?}

最終效果:

總結(jié)

Flutter 作為新生事物,難免還是有他不成熟的地方,但我們已經(jīng)從他現(xiàn)在的進(jìn)步上看到了巨大的潛力。從目前的體驗(yàn)來看,只要有充足的社區(qū)資源,在 Flutter 上開發(fā)跨平臺(tái)應(yīng)用還是比較舒服的。聲網(wǎng)提供的 Flutter SDK 基本已經(jīng)覆蓋了原生 SDK 提供的大部分方法,開發(fā)體驗(yàn)基本可以和原生 SDK 開發(fā)保持一致。這次也是基于學(xué)習(xí)的態(tài)度寫下了這篇文章,希望對(duì)于想要使用 Flutter 開發(fā) RTC 應(yīng)用的同學(xué)有所幫助。

文中講解的完整代碼及 Agora Flutter SDK 可在 Github 獲取。

Agora Flutter SDK:

https://github.com/AgoraIO/Flutter-SDK

Quickstart Demo:

https://github.com/AgoraIO-Community/Agora-Flutter-Quickstart

相關(guān)閱讀:

[1]?Agora Flutter SDK:一套代碼,實(shí)現(xiàn)雙端通話

[2] Flutter 的渲染邏輯,以及與 Native 通信

[3]?Flutter + WebRTC:Agora Flutter SDK 設(shè)計(jì)實(shí)踐

如開發(fā)中遇到問題,可訪問 RTC 開發(fā)者社區(qū)發(fā)帖提問

總結(jié)

以上是生活随笔為你收集整理的flutter 如何判断在哪个页面_Agora 教程:构建你的第一个 Flutter 视频通话应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。