#android #flutter
Вопрос:
Я пытался реализовать голосовые вызовы с помощью Agora с помощью flutter, но столкнулся с проблемой, не имея возможности передавать голос. Если кто-нибудь реализовал это, пожалуйста, поделитесь кодом.
Вот мой код:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'package:hys/Calling/callingfeedback.dart';
import 'package:hys/database/crud.dart';
import 'package:hys/database/questionDB.dart';
import '../navBar.dart';
import '../utils/AppID.dart';
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:firebase_database/firebase_database.dart';
import 'package:readmore/readmore.dart';
import 'package:flutter_tex/flutter_tex.dart';
import 'package:flutter_placeholder_textlines/flutter_placeholder_textlines.dart';
//Audio call page
class AudioCallPage extends StatefulWidget {
final String channelName;
final String questioned;
final String userid;
const AudioCallPage({Key key, this.channelName, this.questionid, this.userid})
: super(key: key);
@override
_AudioCallPageState createState() => _AudioCallPageState();
}
class _AudioCallPageState extends State<AudioCallPage> {
static final _users = <int>[];
final _infoStrings = <String>[];
bool muted = false;
RtcEngine _engine;
DataSnapshot callData;
final databaseReference = FirebaseDatabase.instance.reference();
String _currentUserId = FirebaseAuth.instance.currentUser.uid;
DocumentSnapshot questionDetails;
QuerySnapshot callLogs;
CrudMethods crudobj = CrudMethods();
QuestionDB qDB = QuestionDB();
@override
void dispose() {
// clear users
_users.clear();
// destroy SDK
_engine.leaveChannel();
_engine.destroy();
super.dispose();
}
@override
void initState() {
qDB.getQuestionByID(widget.questionid).then((value) {
setState(() {
questionDetails = value;
});
});
crudobj.getCallLogs().then((value) {
setState(() {
callLogs = value;
});
});
databaseReference
.child("hys_calling_data")
.child("usercallstatus")
.child(_currentUserId)
.set({"callstatus": true});
super.initState();
// initialize agora sdk
initialize();
}
Future<void> initialize() async {
if (appID.isEmpty) {
setState(() {
_infoStrings.add(
'APP_ID missing, please provide your APP_ID in settings.dart',
);
_infoStrings.add('Agora Engine is not starting');
});
return;
}
await _initAgoraRtcEngine();
_addAgoraEventHandlers();
// await _engine.enableWebSdkInteroperability(true);
await _engine.joinChannel(null, widget.channelName, null, 0);
}
Future<void> _initAgoraRtcEngine() async {
RtcEngineConfig config = RtcEngineConfig(appID);
_engine = await RtcEngine.createWithConfig(config);
await _engine.disableVideo();
await _engine.enableAudio();
}
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);
});
},
firstRemoteVideoFrame: (uid, width, height, elapsed) {
setState(() {
final info = 'firstRemoteVideoFrame: $uid';
_infoStrings.add(info);
});
},
));
}
_questionExpandable() {
return widget.questionid != ""
? InkWell(
onTap: () {
_showquestionDialogBox();
},
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(40)),
child: Text("View Question",
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
),
)
: SizedBox();
}
void _showquestionDialogBox() {
AlertDialog alertDialog = AlertDialog(
backgroundColor: Color.fromRGBO(242, 246, 248, 1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
content: Container(
height: 220,
width: 300,
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Color.fromRGBO(242, 246, 248, 1),
borderRadius: BorderRadius.all(Radius.circular(20))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
questionDetails.get("questiontype") == "text"
? ReadMoreText(
questionDetails.get("question"),
textAlign: TextAlign.left,
trimLines: 4,
colorClickableText: Color(0xff0962ff),
trimMode: TrimMode.Line,
trimCollapsedText: 'read more',
trimExpandedText: 'Show less',
style: TextStyle(
fontFamily: 'Nunito Sans',
fontSize: 14,
color: Color.fromRGBO(0, 0, 0, 0.8),
fontWeight: FontWeight.w400,
),
lessStyle: TextStyle(
fontFamily: 'Nunito Sans',
fontSize: 12,
color: Color(0xff0962ff),
fontWeight: FontWeight.w700,
),
moreStyle: TextStyle(
fontFamily: 'Nunito Sans',
fontSize: 12,
color: Color(0xff0962ff),
fontWeight: FontWeight.w700,
),
)
: questionDetails.get("questiontype") == "ocr"
? Container(
margin: EdgeInsets.only(top: 5, left: 10, right: 10),
child: SizedBox(
child: TeXView(
loadingWidgetBuilder: (context) {
return Container(
width:
MediaQuery.of(context).size.width / 1.31,
child: PlaceholderLines(
count: 2,
animate: true,
color: Colors.white,
),
);
},
child: TeXViewColumn(children: [
TeXViewInkWell(
id: "0",
child: TeXViewDocument(
questionDetails.get("question"),
style: TeXViewStyle(
fontStyle: TeXViewFontStyle(
fontFamily: 'Nunito Sans',
fontWeight: TeXViewFontWeight.w400,
fontSize: 9,
sizeUnit: TeXViewSizeUnit.Pt),
padding: TeXViewPadding.all(5),
)),
),
]),
style: TeXViewStyle(
elevation: 10,
backgroundColor:
Color.fromRGBO(242, 246, 248, 1),
),
),
),
)
: SizedBox(),
],
)),
);
showDialog(context: context, builder: (_) => alertDialog);
}
Widget _toolbar() {
return Container(
// alignment: Alignment.bottomCenter,
height: MediaQuery.of(context).size.height,
// padding: const EdgeInsets.symmetric(vertical: 48),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawMaterialButton(
onPressed: _onToggleMute,
child: Icon(
muted ? Icons.mic_off : Icons.mic,
color: muted ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: muted ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: () => _onCallEnd(context),
child: Icon(
Icons.call_end,
color: Colors.white,
size: 35.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
),
RawMaterialButton(
onPressed: _onSwitchCamera,
child: Icon(
Icons.switch_camera,
color: Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(12.0),
)
],
),
SizedBox(
height: 10,
),
_questionExpandable(),
SizedBox(
height: 10,
),
],
),
);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
return new Future(() => false);
},
child: Scaffold(backgroundColor: Colors.black, body: _body()));
}
_body() {
databaseReference
.child("hys_calling_data")
.child("sm_calls")
.once()
.then((DataSnapshot snapshot) {
setState(() {
if (mounted) {
setState(() {
callData = snapshot;
});
}
});
});
if ((callData != null) amp;amp; (questionDetails != null) amp;amp; (callLogs != null)) {
if ((callData.value[widget.channelName]["iscallcancelledafterReceived"] ==
true)) {
FlutterRingtonePlayer.stop();
// _users.clear();
// // destroy SDK
// _engine.leaveChannel();
// _engine.destroy();
for (int i = 0; i < callLogs.docs.length; i ) {
if (widget.channelName == callLogs.docs[i].id) {
int timecount = DateTime.now()
.difference(DateTime.parse(callLogs.docs[i].get("starttime")))
.inSeconds;
crudobj.updateCallLogs(widget.channelName, {
"callstatus": "received",
"endtime": DateTime.now().toString(),
"duration": timecount
} );
}
}
databaseReference
.child("hys_calling_data")
.child("sm_calls")
.child(widget.channelName)
.update({
'iscallreceivedbyReceiver': false,
'iscallcancelledbycaller': false,
'iscallrejected': false,
"message": "NO"
});
databaseReference
.child("hys_calling_data")
.child("usercallstatus")
.child(_currentUserId)
.set({"callstatus": false});
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
if ((widget.questionid != "")) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
CallingFeedBack(widget.questionid, widget.userid)));
} else {
crudobj.addUserCreditPointsData(
3, "Answered", current_date, comparedate);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarWidget()));
}
});
}
return Center(
child: Stack(
children: <Widget>[
_viewRows(),
_toolbar(),
],
),
);
}
}
List<Widget> _getRenderViews() {
final List<StatefulWidget> list = [];
list.add(RtcLocalView.SurfaceView());
_users.forEach((int uid) => list.add(RtcRemoteView.SurfaceView(uid: uid)));
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();
}
void _onCallEnd(BuildContext context) {
// _users.clear();
// // destroy SDK
// _engine.leaveChannel();
// _engine.destroy();
for (int i = 0; i < callLogs.docs.length; i ) {
If (widget.channelName == callLogs.docs[i].id) {
int timecount = DateTime.now()
.difference(DateTime.parse(callLogs.docs[i].get("starttime")))
.inSeconds;
crudobj.updateCallLogs(widget.channelName, {
"callstatus": "received",
"endtime": DateTime.now().toString(),
"duration": timecount
});
}
}
databaseReference
.child("hys_calling_data")
.child("sm_calls")
.child(widget.channelName)
.update({
'iscallreceivedbyReceiver': false,
'iscallcancelledbycaller': false,
'iscallrejected': false,
"iscallcancelledafterReceived": true,
"message": "NO"
});
databaseReference
.child("hys_calling_data")
.child("usercallstatus")
.child(_currentUserId)
.set({"callstatus": false});
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
if ((widget.questionid != "")) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
CallingFeedBack(widget.questionid, widget.userid)));
} else {
crudobj.addUserCreditPointsData(
3, "Answered", current_date, comparedate);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarWidget()));
}
});
}
void _onToggleMute() {
setState(() {
muted = !muted;
});
_engine.muteLocalAudioStream(muted);
}
void _onSwitchCamera() {
_engine.switchCamera();
}
}
Я использую тот же код для видеозвонка, просто включив функцию enableVideo, тогда она работает нормально, но для аудио она не передает звук.
Future<void> _initAgoraRtcEngine() async {
RtcEngineConfig config = RtcEngineConfig(appID);
_engine = await RtcEngine.createWithConfig(config);
await _engine.disableVideo();
await _engine.enableAudio();
}
используя эту функцию, я определяю часть аудио-видео
Комментарии:
1. Эй, вы можете сослаться на этот код, так как я реализовал голосовой вызов с помощью Agora github.com/champ96k/agora-voice-calling , я надеюсь, что это вам поможет 🙂