Flutter 中的 Agora - 多次导航到视频聊天屏幕使本地视频永远加载
Agora in Flutter- navigating to the video chat screen more than one time keeps local video loading forever
我正在使用 Agora for a one-to-one video chat purpose in Flutter。
User1有一个应用要上线,user2有另一个应用要上线。两人上网后,就可以进行视频聊天了。 这两个应用程序的代码库几乎相似。
我有一个屏幕或 activity(例如屏幕 1),其中在点击按钮(例如按钮 1)时会显示一个警告对话框。点击警告对话框中的 Continue
按钮,对话框消失,用户被带到进行视频聊天的屏幕(比如屏幕 2)。但是在成功进入视频聊天屏幕后,如果用户点击移动设备上的后退按钮,则 s/he 将被带到屏幕 1,并且在点击按钮 1 后,如果用户点击 Continue
按钮在弹出的警告对话框中,用户再次被带到屏幕 2,但这次本地视频(即用户使用应用程序的视频)一直在加载。显然我希望本地视频像第一次一样加载。
我要把我的代码放在这里,这样你就可以很容易地运行。
以下代码适用于用户 1。对于 user2,应用程序中没有警告框。来自 user1 的相同代码用于 user2 应用程序,不同之处在于 remoteUid
的值对于 user2 设置为 2,而该值对于 user1 设置为 1。这些只是标识 2 个用户的两个值。
用户 1:
main.dart:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'livesession1to1.dart';
void main() {
runApp(MessagingExampleApp());
}
class NavigationService {
static GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
}
/// Entry point for the example application.
class MessagingExampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Messaging Example App',
navigatorKey: NavigationService.navigatorKey, // set property
theme: ThemeData.dark(),
routes: {
'/': (context) => Application(),
'/liveSession1to1': (context) =>LiveSession1to1(),
},
);
}
}
int _messageCount = 0;
/// The API endpoint here accepts a raw FCM payload for demonstration purposes.
String constructFCMPayload(String? token, String server_key) {
_messageCount++;
return jsonEncode({
'token': token,
'to':token,
'data': {
'via': 'FlutterFire Cloud Messaging!!!',
'count': _messageCount.toString(),
},
'notification': {
'title': 'Hello FlutterFire!',
'body': 'This notification (#$_messageCount) was created via FCM! =============',
},
"delay_while_idle" : false,
"priority" : "high",
"content_available" : true
});
}
/// Renders the example application.
class Application extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
String? _token;
@override
void initState() {
super.initState();
}
showAlertDialog() {
BuildContext context=NavigationService.navigatorKey.currentContext!;
// set up the buttons
Widget cancelButton = TextButton(
child: Text("Cancel"),
onPressed: () {},
);
Widget continueButton = TextButton(
child: Text("Continue"),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context).pushNamed('/liveSession1to1');
},
);
Timer? timer = Timer(Duration(milliseconds: 5000), (){
Navigator.of(context, rootNavigator: true).pop();
});
showDialog(
context: context,
builder: (BuildContext builderContext) {
return AlertDialog(
backgroundColor: Colors.black26,
title: Text('One to one live session'),
content: SingleChildScrollView(
child: Text('Do you want to connect for a live session ?'),
),
actions: [
cancelButton,
continueButton,
],
);
}
).then((value){
// dispose the timer in case something else has triggered the dismiss.
timer?.cancel();
timer = null;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My App'),
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: showAlertDialog,
backgroundColor: Colors.white,
child: const Icon(Icons.send),
),
),
body: SingleChildScrollView(
child: Text(
'Trigger Alert'
),
),
);
}
}
livesession1to1.dart:
import 'dart:async';
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;
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
// const appId = "<-- Insert App Id -->";
// const token = "<-- Insert Token -->";
const appId = "......";// Put Agora App ID from Agora site here
const token = "....";// Put token ( temporary token avilable from Agora site)
void main() => runApp(MaterialApp(home: LiveSession1to1()));
class LiveSession1to1 extends StatefulWidget {
@override
_LiveSession1to1State createState() => _LiveSession1to1State();
}
class _LiveSession1to1State extends State<LiveSession1to1> {
int _remoteUid=1;
bool _localUserJoined = false;
late RtcEngine _engine;
@override
void initState() {
super.initState();
setState(() {});
initAgora();
}
Future<void> initAgora() async {
// retrieve permissions
await [Permission.microphone, Permission.camera].request();
// Create RTC client instance
RtcEngineContext context = RtcEngineContext(appId);
_engine = await RtcEngine.createWithContext(context);
await _engine.enableVideo();
_engine.setEventHandler(
RtcEngineEventHandler(
joinChannelSuccess: (String channel, int uid, int elapsed) {
print("local user $uid joined");
setState(() {
_localUserJoined = true;
});
},
userJoined: (int uid, int elapsed) {
print("remote user $uid joined");
setState(() {
_remoteUid = uid;
});
},
userOffline: (int uid, UserOfflineReason reason) {
print("remote user $uid left channel");
setState(() {
// _remoteUid = null;
_remoteUid = 0;
});
},
),
);
try {
await _engine.joinChannel(token, "InstaClass", null, 0);
} catch (e) {
print("error with agora = ");
print("$e");
print("error printeddddd");
}
}
// Create UI with local view and remote view
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Video Call'),
),
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.topLeft,
child: Container(
width: 100,
height: 150,
child: Center(
child: _localUserJoined
? RtcLocalView.SurfaceView()
: CircularProgressIndicator(),
),
),
),
],
),
);
}
// Display remote user's video
Widget _remoteVideo() {
if (_remoteUid != 0) {
return RtcRemoteView.SurfaceView(
uid: _remoteUid,
channelId: "InstaClass",
);
}else {
print("'Please wait for remote user to join',");
return Text(
'Please wait for remote user to join',
textAlign: TextAlign.center,
);
}
}
}
用户 2:
main.dart:
import 'dart:async';
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;
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
const appId = "....."; // Same as user1 app
const token = "....."; // same as user1 app
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// int? _remoteUid=1;
int _remoteUid=2;
bool _localUserJoined = false;
late RtcEngine _engine;
@override
void initState() {
super.initState();
initAgora();
}
Future<void> initAgora() async {
// retrieve permissions
await [Permission.microphone, Permission.camera].request();
//create the engine
_engine = await RtcEngine.create(appId);
await _engine.enableVideo();
_engine.setEventHandler(
RtcEngineEventHandler(
joinChannelSuccess: (String channel, int uid, int elapsed) {
print("local user $uid joined");
setState(() {
_localUserJoined = true;
});
},
userJoined: (int uid, int elapsed) {
print("remote user $uid joined");
setState(() {
_remoteUid = uid;
});
},
userOffline: (int uid, UserOfflineReason reason) {
print("remote user $uid left channel");
setState(() {
// _remoteUid = null;
_remoteUid = 0;
});
},
),
);
// await _engine.joinChannel(token, "test", null, 0);
await _engine.joinChannel(token, "InstaClass", null, 0);
}
// Create UI with local view and remote view
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Video Call'),
),
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.topLeft,
child: Container(
width: 100,
height: 150,
child: Center(
child: _localUserJoined
? RtcLocalView.SurfaceView()
: CircularProgressIndicator(),
),
),
),
],
),
);
}
// Display remote user's video
Widget _remoteVideo() {
/*if (_remoteUid != null) {
return RtcRemoteView.SurfaceView(uid: _remoteUid!);
}*/
if (_remoteUid != 0) {
return RtcRemoteView.SurfaceView(
uid: _remoteUid,
channelId: "InstaClass",
);
}else {
return Text(
'Please wait for remote user to join',
textAlign: TextAlign.center,
);
}
}
}
为了获取应用程序 ID 和令牌,请登录 Agora 站点。登录后,转到 'Project Management' 部分查看已在此处创建的项目。在 Functions
列下,单击密钥符号,您将被带到一个可以生成临时令牌的页面。在该页面上,为频道名称输入值 'InstaClass',因为我在我的代码中使用了这个名称。
如何让视频聊天第一次顺畅后顺畅?
我认为问题在于,当您按下 back
按钮时,您只是被带到上一个屏幕,而呼叫 session
并未结束。您可以按 leaving
后退按钮尝试频道,例如:
_engine.leaveChannel();
End Call
按钮示例
ElevatedButton(
onPressed: () {
_rtcEngine.leaveChannel();
Navigator.pop(context);
},
style: ButtonStyle(
shape: MaterialStateProperty.all(CircleBorder()),
backgroundColor: MaterialStateProperty.all(Colors.red),
padding: MaterialStateProperty.all(
EdgeInsets.fromLTRB(15, 15, 15, 12)),
),
child: Icon(
Icons.phone,
size: 30,
),
)
Back Button
使用 WillPopScope
覆盖
return WillPopScope(
onWillPop: () async {
_rtcEngine.leaveChannel();
return true;
},
child: Scaffold(
body: Container(),
),
);
我正在使用 Agora for a one-to-one video chat purpose in Flutter。 User1有一个应用要上线,user2有另一个应用要上线。两人上网后,就可以进行视频聊天了。 这两个应用程序的代码库几乎相似。
我有一个屏幕或 activity(例如屏幕 1),其中在点击按钮(例如按钮 1)时会显示一个警告对话框。点击警告对话框中的 Continue
按钮,对话框消失,用户被带到进行视频聊天的屏幕(比如屏幕 2)。但是在成功进入视频聊天屏幕后,如果用户点击移动设备上的后退按钮,则 s/he 将被带到屏幕 1,并且在点击按钮 1 后,如果用户点击 Continue
按钮在弹出的警告对话框中,用户再次被带到屏幕 2,但这次本地视频(即用户使用应用程序的视频)一直在加载。显然我希望本地视频像第一次一样加载。
我要把我的代码放在这里,这样你就可以很容易地运行。
以下代码适用于用户 1。对于 user2,应用程序中没有警告框。来自 user1 的相同代码用于 user2 应用程序,不同之处在于 remoteUid
的值对于 user2 设置为 2,而该值对于 user1 设置为 1。这些只是标识 2 个用户的两个值。
用户 1:
main.dart:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'livesession1to1.dart';
void main() {
runApp(MessagingExampleApp());
}
class NavigationService {
static GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
}
/// Entry point for the example application.
class MessagingExampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Messaging Example App',
navigatorKey: NavigationService.navigatorKey, // set property
theme: ThemeData.dark(),
routes: {
'/': (context) => Application(),
'/liveSession1to1': (context) =>LiveSession1to1(),
},
);
}
}
int _messageCount = 0;
/// The API endpoint here accepts a raw FCM payload for demonstration purposes.
String constructFCMPayload(String? token, String server_key) {
_messageCount++;
return jsonEncode({
'token': token,
'to':token,
'data': {
'via': 'FlutterFire Cloud Messaging!!!',
'count': _messageCount.toString(),
},
'notification': {
'title': 'Hello FlutterFire!',
'body': 'This notification (#$_messageCount) was created via FCM! =============',
},
"delay_while_idle" : false,
"priority" : "high",
"content_available" : true
});
}
/// Renders the example application.
class Application extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
String? _token;
@override
void initState() {
super.initState();
}
showAlertDialog() {
BuildContext context=NavigationService.navigatorKey.currentContext!;
// set up the buttons
Widget cancelButton = TextButton(
child: Text("Cancel"),
onPressed: () {},
);
Widget continueButton = TextButton(
child: Text("Continue"),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context).pushNamed('/liveSession1to1');
},
);
Timer? timer = Timer(Duration(milliseconds: 5000), (){
Navigator.of(context, rootNavigator: true).pop();
});
showDialog(
context: context,
builder: (BuildContext builderContext) {
return AlertDialog(
backgroundColor: Colors.black26,
title: Text('One to one live session'),
content: SingleChildScrollView(
child: Text('Do you want to connect for a live session ?'),
),
actions: [
cancelButton,
continueButton,
],
);
}
).then((value){
// dispose the timer in case something else has triggered the dismiss.
timer?.cancel();
timer = null;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My App'),
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: showAlertDialog,
backgroundColor: Colors.white,
child: const Icon(Icons.send),
),
),
body: SingleChildScrollView(
child: Text(
'Trigger Alert'
),
),
);
}
}
livesession1to1.dart:
import 'dart:async';
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;
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
// const appId = "<-- Insert App Id -->";
// const token = "<-- Insert Token -->";
const appId = "......";// Put Agora App ID from Agora site here
const token = "....";// Put token ( temporary token avilable from Agora site)
void main() => runApp(MaterialApp(home: LiveSession1to1()));
class LiveSession1to1 extends StatefulWidget {
@override
_LiveSession1to1State createState() => _LiveSession1to1State();
}
class _LiveSession1to1State extends State<LiveSession1to1> {
int _remoteUid=1;
bool _localUserJoined = false;
late RtcEngine _engine;
@override
void initState() {
super.initState();
setState(() {});
initAgora();
}
Future<void> initAgora() async {
// retrieve permissions
await [Permission.microphone, Permission.camera].request();
// Create RTC client instance
RtcEngineContext context = RtcEngineContext(appId);
_engine = await RtcEngine.createWithContext(context);
await _engine.enableVideo();
_engine.setEventHandler(
RtcEngineEventHandler(
joinChannelSuccess: (String channel, int uid, int elapsed) {
print("local user $uid joined");
setState(() {
_localUserJoined = true;
});
},
userJoined: (int uid, int elapsed) {
print("remote user $uid joined");
setState(() {
_remoteUid = uid;
});
},
userOffline: (int uid, UserOfflineReason reason) {
print("remote user $uid left channel");
setState(() {
// _remoteUid = null;
_remoteUid = 0;
});
},
),
);
try {
await _engine.joinChannel(token, "InstaClass", null, 0);
} catch (e) {
print("error with agora = ");
print("$e");
print("error printeddddd");
}
}
// Create UI with local view and remote view
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Video Call'),
),
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.topLeft,
child: Container(
width: 100,
height: 150,
child: Center(
child: _localUserJoined
? RtcLocalView.SurfaceView()
: CircularProgressIndicator(),
),
),
),
],
),
);
}
// Display remote user's video
Widget _remoteVideo() {
if (_remoteUid != 0) {
return RtcRemoteView.SurfaceView(
uid: _remoteUid,
channelId: "InstaClass",
);
}else {
print("'Please wait for remote user to join',");
return Text(
'Please wait for remote user to join',
textAlign: TextAlign.center,
);
}
}
}
用户 2:
main.dart:
import 'dart:async';
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;
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
const appId = "....."; // Same as user1 app
const token = "....."; // same as user1 app
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// int? _remoteUid=1;
int _remoteUid=2;
bool _localUserJoined = false;
late RtcEngine _engine;
@override
void initState() {
super.initState();
initAgora();
}
Future<void> initAgora() async {
// retrieve permissions
await [Permission.microphone, Permission.camera].request();
//create the engine
_engine = await RtcEngine.create(appId);
await _engine.enableVideo();
_engine.setEventHandler(
RtcEngineEventHandler(
joinChannelSuccess: (String channel, int uid, int elapsed) {
print("local user $uid joined");
setState(() {
_localUserJoined = true;
});
},
userJoined: (int uid, int elapsed) {
print("remote user $uid joined");
setState(() {
_remoteUid = uid;
});
},
userOffline: (int uid, UserOfflineReason reason) {
print("remote user $uid left channel");
setState(() {
// _remoteUid = null;
_remoteUid = 0;
});
},
),
);
// await _engine.joinChannel(token, "test", null, 0);
await _engine.joinChannel(token, "InstaClass", null, 0);
}
// Create UI with local view and remote view
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Video Call'),
),
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.topLeft,
child: Container(
width: 100,
height: 150,
child: Center(
child: _localUserJoined
? RtcLocalView.SurfaceView()
: CircularProgressIndicator(),
),
),
),
],
),
);
}
// Display remote user's video
Widget _remoteVideo() {
/*if (_remoteUid != null) {
return RtcRemoteView.SurfaceView(uid: _remoteUid!);
}*/
if (_remoteUid != 0) {
return RtcRemoteView.SurfaceView(
uid: _remoteUid,
channelId: "InstaClass",
);
}else {
return Text(
'Please wait for remote user to join',
textAlign: TextAlign.center,
);
}
}
}
为了获取应用程序 ID 和令牌,请登录 Agora 站点。登录后,转到 'Project Management' 部分查看已在此处创建的项目。在 Functions
列下,单击密钥符号,您将被带到一个可以生成临时令牌的页面。在该页面上,为频道名称输入值 'InstaClass',因为我在我的代码中使用了这个名称。
如何让视频聊天第一次顺畅后顺畅?
我认为问题在于,当您按下 back
按钮时,您只是被带到上一个屏幕,而呼叫 session
并未结束。您可以按 leaving
后退按钮尝试频道,例如:
_engine.leaveChannel();
End Call
按钮示例
ElevatedButton(
onPressed: () {
_rtcEngine.leaveChannel();
Navigator.pop(context);
},
style: ButtonStyle(
shape: MaterialStateProperty.all(CircleBorder()),
backgroundColor: MaterialStateProperty.all(Colors.red),
padding: MaterialStateProperty.all(
EdgeInsets.fromLTRB(15, 15, 15, 12)),
),
child: Icon(
Icons.phone,
size: 30,
),
)
Back Button
使用 WillPopScope
return WillPopScope(
onWillPop: () async {
_rtcEngine.leaveChannel();
return true;
},
child: Scaffold(
body: Container(),
),
);