Flutter Agora.io calling screen - flutter

I want to add in-app (video) calling like Messenger (Facebook) does. It works when one party creates channel and another one joins.
But is there a way to create calling screen where party B can accept or reject call? I am looking in Agora.io documentation but cannot find anything suitable for this.
This is my code though...
Future<void> initialize() async {
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;
}
await _initAgoraRtcEngine();
_addAgoraEventHandlers();
await AgoraRtcEngine.enableWebSdkInteroperability(true);
await AgoraRtcEngine.setParameters('''
{\"che.video.lowBitRateStreamParameter\":{\"width\":320,\"height\":180,\"frameRate\":15,\"bitRate\":140}}''');
await AgoraRtcEngine.joinChannel(null, 'Test', null, 0);
}
Future<void> _initAgoraRtcEngine() async {
AgoraRtcEngine.create(APP_ID);
AgoraRtcEngine.enableVideo();
}
void _addAgoraEventHandlers() {
AgoraRtcEngine.onError = (dynamic code) {
setState(() {
final info = 'onError: $code';
_infoStrings.add(info);
});
};
AgoraRtcEngine.onJoinChannelSuccess = (
String channel,
int uid,
int elapsed,
) {
setState(() {
final info = 'onJoinChannel: $channel, uid: $uid';
_infoStrings.add(info);
});
};
AgoraRtcEngine.onLeaveChannel = () {
setState(() {
_infoStrings.add('onLeaveChannel');
_users.clear();
});
};
AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {
setState(() {
final info = 'userJoined: $uid';
_infoStrings.add(info);
_users.add(uid);
});
};
AgoraRtcEngine.onUserOffline = (int uid, int reason) {
setState(() {
final info = 'userOffline: $uid';
_infoStrings.add(info);
_users.remove(uid);
});
};
AgoraRtcEngine.onFirstRemoteVideoFrame = (
int uid,
int width,
int height,
int elapsed,
) {
setState(() {
final info = 'firstRemoteVideo: $uid ${width}x $height';
_infoStrings.add(info);
});
};
}

You will need to push channelId to other user mobile in this case.
The CS Guy has created very useful video on you tube to implement this step as well as calling screen.
https://www.youtube.com/watch?v=v9ngriCV0J0

You need to use Native ConnectionService for Android and Callkit of iOS.
You can find the official Agora samples for the above feature here: https://github.com/AgoraIO/Advanced-Video/tree/master/Calling-Interface, but I don't think Agora has call-interface sample in Flutter, you have to write the wrapper on your own for now.

widget.chatRoomId is the id specified for both of the users when you create a chatroom for them.
Future<void> onJoin() async {
// update input validation
if (widget.chatRoomId.isNotEmpty) {
// await for camera and mic permissions before pushing video page
await _handleCameraAndMic();
// push video page with given channel name
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CallPage(
channelName: widget.chatRoomId,
// TODO: set to _role
role: ClientRole.Broadcaster,
),
),
);
}
}

Related

got an AgoraRtcException(20, Make sure you call RtcEngine.initialize first) even after initializing the engine

trying to make a 1:1 video meeting with agora with flutter and after following the docs i got
AgoraRtcException(20, Make sure you call RtcEngine.initialize first) exception although I am sure I am initializing it first however this the initialize code
void initState() {
super.initState();
setupVideoSDKEngine();
join();
the setupVideoSDKEngine() method code is
Future<void> setupVideoSDKEngine() async {
// retrieve or request camera and microphone permissions
await [Permission.microphone, Permission.camera].request();
//create an instance of the Agora engine
agoraEngine = createAgoraRtcEngine();
await agoraEngine
.initialize(RtcEngineContext(appId: Environment.agoraAppId));
await agoraEngine.enableVideo();
// Register the event handler
agoraEngine.registerEventHandler(
RtcEngineEventHandler(
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
showMessage(
"Local user uid:${connection.localUid} joined the channel");
setState(() {
_isJoined = true;
});
},
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
showMessage("Remote user uid:$remoteUid joined the channel");
setState(() {
_remoteUid = uid;
player.stop();
customTimer!.resetAndStart();
});
},
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
showMessage("Remote user uid:$remoteUid left the channel");
callEnded = true;
setState(() {
_remoteUid = null;
});
print('stats ${reason.name}');
if (!userOffline) {
Future.delayed(Duration(seconds: 1), () => Navigator.pop(context));
}
userOffline = true;
},
),
);
}
I am expecting to join the channel but nothing happens and it throws this error
I tried to delete the app and reinstall it but nothing happens
and got this exception too AgoraRtcException(-17, null)
you can to call this before using any other agora function i.e. You are trying to use agora sdk function without initializing it so here it is
This will be written globally
late final RtcEngineEx _engine;
this will be written in initState
_engine = createAgoraRtcEngineEx();
await _engine.initialize(const RtcEngineContext(
appId: APP_ID,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
This piece of advice I'm giving is for the correct usage of async and await keywords.
In your initState() function, you are calling two functions one after another; out of which the first function setupVideoSDKEngine() is an async function, and another function join() chould also be an async because the Agora join channel code returns a Future.
await engine.joinChannel(agoraToken, channelId, '', 0,);
Your code right now does not wait for the engine initialization and starts joining channel. Thus, the error. So you got to await your initialization and then write your join channel code.
For eg.
/* permission stuffs */
await agoraEngine.initialize(RtcEngineContext(appId: Environment.agoraAppId));
/* do event handling tasks */
// Now join the channel.
await engine.joinChannel(agoraToken, channelId, '', 0,);
It is recommended that you put the whole code into one async function.

Flutter incoming video/audio call notification using Agora

I have been working on an application and I need to implement in app audio and video calling in my app which I have done using Agora.io but the issue is I have to display incoming call notification does not matter if app is in foreground or in background. I have tried many things but still I am unable to configure that out. I am using agora_rtc_engine package for making calls.
Any help would be appreciated.
Thanks
The code I am working with currently:
Call Methods
class CallMethods {
final callRef = FirebaseFirestore.instance.collection('Calls');
Stream<DocumentSnapshot> callstream({#required String id}) =>
callRef.doc(id).snapshots();
Future<bool> makeCall({#required Call call}) async {
try {
log('Making call');
call.hasdialed = true;
Map<String, dynamic> hasDialedMap = call.toMap(call);
call.hasdialed = false;
Map<String, dynamic> hasNotDialedMap = call.toMap(call);
await callRef.doc(call.senderid).set(hasDialedMap);
await callRef.doc(call.receiverid).set(hasNotDialedMap);
return true;
} catch (e) {
print(e);
return false;
}
}
Future<bool> endCall({#required Call call}) async {
try {
log('ending call');
await callRef.doc(call.senderid).delete();
await callRef.doc(call.receiverid).delete();
return true;
} catch (e) {
print(e);
return false;
}
}
}
Call Utils: Which is used to make calls
class CallUtils {
static final CallMethods callmethods = CallMethods();
static dial(
BuildContext context, {
#required User from,
#required var to,
}) async {
Call call = Call(
senderid: from.id,
// senderpic: from.avatar.url,
callername: from.name,
receiverid: to.id,
// receiverpic: to.avatar.url,
receivername: to.name,
channelid: Random().nextInt(999999).toString(),
);
bool callmade = await callmethods.makeCall(call: call);
call.hasdialed = true;
if (callmade) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoCallScreen(call: call),
),
);
}
}
}
After that I have a pickup layout which is used to wrap all the screens to display incoming call notification.
Pickup Call Layout:
(user.value.id != null)
? StreamBuilder<DocumentSnapshot>(
stream: callmethods.callstream(id: user.value.id),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.data() != null) {
Call call = Call.fromMap(snapshot.data.data());
if (!call.hasdialed) {
return PickupScreen(call: call);
} else {
return widget.scaffold;
}
} else {
return widget.scaffold;
}
},
)
: widget.scaffold,
It can be done via firebase push notifications & backend API service.
Sender side:
As soon as a call is made, you would post your backend api service with caller and receiver id, and your backend service is further responsible to send a push notification with a payload to the receiver.
Receiver side:
When receiver gets a push notification, you can configure it to open your app automatically and show a screen with all the payload information. Maybe you can show him a screen with accept and decline button and if he accepts, you can connect him to Agora.
Check this for payload configuration.

Flutter AudioService get Fetch Url every time

first of all hi,
Start Service
await AudioService.start(
backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
params: {
'index': globalIndex,
'offline': offline,
'quality': preferredQuality
},
androidNotificationChannelName: 'BlackHole',
androidNotificationColor: 0xFF181818,
androidNotificationIcon: 'drawable/ic_stat_music_note',
androidEnableQueue: true,
androidStopForegroundOnPause: stopServiceOnPause,
);
await AudioService.updateQueue(globalQueue);
await AudioService.play();
Override Here the url part is added. But if I send request to the api for all elements, the performance will be bad.
#override
Future<void> onUpdateQueue(List<MediaItem> _queue) async {
await AudioServiceBackground.setQueue(_queue);
await AudioServiceBackground.setMediaItem(_queue[index!]);
concatenatingAudioSource = ConcatenatingAudioSource(
children: _queue
.map((item)
{
return AudioSource.uri(
Uri.parse(item.extras!['url'].toString()),
tag: item);
}
)
.toList(),
);
await _player.setAudioSource(concatenatingAudioSource);
await _player.seek(Duration.zero, index: index);
queue = _queue;
}
Instead, how can I make a request to the remote api and update the url without the item playing?
Here is something similar but I couldn't get it to work
How to fetch song details every time from an API before playing in just_audio and audio_service
This is how I solved my problem.
#override
Future<void> onSkipToQueueItem(String mediaId) async {
final newIndex = queue.indexWhere((item) => item.id == mediaId);
if (newIndex == -1) return;
queue[newIndex].extras!['url'] = await SaavnAPI().get_mp3(queue[newIndex].extras!['url'].toString());
AudioServiceBackground.setMediaItem(queue[newIndex]);
_player.setUrl(queue[newIndex].extras!['url'].toString());
await AudioServiceBackground.setQueue(queue);
index = newIndex;
_player.seek(Duration.zero, index: newIndex);
if (!offline) addRecentlyPlayed(queue[newIndex]);
}

agora.io flutter Access denied finding property "net.dns1"

// Get microphone permission
await PermissionHandler().requestPermissions(
[PermissionGroup.microphone],
);
var engine = await RtcEngine.create(APP_ID);
engine.enableAudio();
// Define event handler
engine.setEventHandler(RtcEngineEventHandler(
joinChannelSuccess: (String channel, int uid, int elapsed) {
print('joinChannelSuccess ${channel} ${uid}');
setState(() {
_joined = true;
});
}, userJoined: (int uid, int elapsed) {
print('userJoined ${uid}');
setState(() {
_remoteUid = uid;
});
}, userOffline: (int uid, UserOfflineReason reason) {
print('userOffline ${uid}');
setState(() {
_remoteUid = null;
});
}));
// Join channel 123
await engine.joinChannel(Token, '123', null, 0);
i am tring to build audio calling app using agora.io but i keep getting this error saying "Access denied finding property "net.dns1" "

Hide background location notification in failure

I'm using workmanager to retrieve user's location in background every 15 minutes. When the location fetch fails, I receive a notification with the error as you can see in picture. I would like to know how can I prevent the notification to show up in failure cases.
void callbackDispatcher() {
Workmanager.executeTask((taskName, inputData) async {
if (taskName == FETCH_USER_POSITION_IN_BACKGROUND_TASK_NAME) {
// TODO: Find a better way to get user position, maybe with ServiceLocator or even better with BLoC
final dataSource = GeolocatorDataSource();
final remoteDataSource = FirestoreRemoteDataSource(
firebaseFirestore: FirebaseFirestore.instance,
);
final repository = GeolocationRepository(
geolocationDataSource: dataSource,
remoteDataSource: remoteDataSource,
);
final positionEither = await repository.getUserPosition();
positionEither.fold((failure) async {
print('failure: $failure');
}, (position) async {
print('position = $position');
final storePositionEither =
await repository.storeUserPosition(position, inputData['uid']);
storePositionEither.fold((failure) async {
print('failure: $failure');
}, (isStored) async {
print("Position has been successfully stored in background!");
});
});
}
return Future.value(true);
});
}
void _initializeWorkManagerWhenAuthenticated(String userId) {
bool isProduction = bool.fromEnvironment('dart.vm.product');
Workmanager.initialize(
callbackDispatcher,
isInDebugMode: !isProduction,
);
Workmanager.registerPeriodicTask(
FETCH_USER_POSITION_IN_BACKGROUND_TASK_ID,
FETCH_USER_POSITION_IN_BACKGROUND_TASK_NAME,
frequency: Duration(minutes: 15),
existingWorkPolicy: ExistingWorkPolicy.keep,
inputData: {
'userId': userId,
},
);
}
Have you checked if the notifications are appearing when you run with isInDebugMode: false?
See: https://github.com/fluttercommunity/flutter_workmanager/blob/ea274c33b60ef1a4e29bdd392a477f67466dc25d/lib/src/workmanager.dart#L90