我正在尝试在我的 flutter 应用程序上发出来电通知。除了下面的事情之外,这几乎是有效的。当应用程序终止时,我收到 firebase 消息,并且可以显示我的通知,但我的 SIPController 未注册,因此在用户应答之前服务器会取消呼叫。有人知道如何处理这个问题吗? 这是我的 main.dart 和我的 SIPController 类:
import 'dart:io';
import 'package:callkeep/callkeep.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/entities/android_params.dart';
import 'package:flutter_callkit_incoming/entities/call_event.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_callkit_incoming/entities/ios_params.dart';
import 'package:flutter_callkit_incoming/entities/notification_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:mvc_pattern/mvc_pattern.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sip_ua/sip_ua.dart';
import 'package:smart_diese/business_logic/services/firebase/diesetel_firebase.dart';
import 'package:smart_diese/business_logic/services/sip/sip_controller.dart';
import 'package:smart_diese/business_logic/services/sip/sip_listener.dart';
import 'package:smart_diese/db/model/utilisateur.dart';
import 'package:smart_diese/db/smart_diese_database.dart';
import 'package:smart_diese/firebase_options.dart';
import 'package:smart_diese/views/app.dart';
import 'business_logic/utils/assets_manager.dart';
/* Top main variables */
int? initScreen = 0;
final FlutterCallkeep callKeep = FlutterCallkeep();
bool callKeepStarted = false;
final SipUaHelperListener listener = SIPListener();
bool notificationAnswered = false;
class PostHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (
X509Certificate cert,
String host,
int port,
) =>
true;
}
}
bool isFlutterLocalNotificationsInitialized = false;
late AndroidNotificationChannel channel;
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
@pragma("vm:entry-point")
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
if (kDebugMode) {
print('Message title: ${message.notification?.title}, body: ${message.notification?.body}, data: ${message.data}');
}
await setupFlutterNotifications();
await showIncomingCall(message, Uuid().generateV4());
}
Future<void> showIncomingCall(RemoteMessage message, String uuid) async{
final callerNum = message.data['callerNum'];
final equipement = await SmartDieseDatabase.instance.readEquipementByNumero(int.parse(callerNum));
final params = CallKitParams(
id: uuid,
nameCaller: equipement.nom,
avatar: null,
handle: equipement.numero.toString(),
textAccept: 'Accept',
textDecline: 'Decline',
duration: 30000,
missedCallNotification: const NotificationParams(
showNotification: true,
isShowCallback: false,
subtitle: 'Missed call',
),
android: const AndroidParams(
isCustomNotification: true,
isShowLogo: false,
incomingCallNotificationChannelName: 'call_channel',
isShowFullLockedScreen: true,
),
ios: const IOSParams(
iconName: 'CallKitLogo',
handleType: '',
supportsVideo: true,
maximumCallGroups: 1,
maximumCallsPerCallGroup: 1,
audioSessionMode: 'default',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: true,
supportsGrouping: false,
supportsUngrouping: false,
),
);
await FlutterCallkitIncoming.showCallkitIncoming(params);
}
Future<void> setupFlutterNotifications() async {
if (isFlutterLocalNotificationsInitialized) {
return;
}
channel = const AndroidNotificationChannel(
'call_channel',
'Calls',
description:
'This channel is used incoming calls.',
importance: Importance.high,
playSound: true
);
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
isFlutterLocalNotificationsInitialized = true;
}
Future<void> initFirebase() async {
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if(initialMessage != null){
firebaseMessagingBackgroundHandler(initialMessage);
}
FlutterCallkitIncoming.onEvent.listen((CallEvent? event) {
if(event!.event == Event.actionCallAccept){
SIPController.controller.signalAnswer();
}
if(event.event == Event.actionCallDecline){
SIPController.controller.signalHangup();
}
});
}
Future<void> main() async {
HttpOverrides.global = PostHttpOverrides();
WidgetsFlutterBinding.ensureInitialized();
final List<Utilisateur> utilisateurs =
await SmartDieseDatabase.instance.readAllUtilisateur();
if (utilisateurs.isNotEmpty && utilisateurs[0].role == 0) {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await initFirebase();
final SharedPreferences preferences = await SharedPreferences.getInstance();
initScreen = preferences.getInt('initScreen');
await preferences.setInt('initScreen', 1);
await DiesetelFirebase.initFirebase();
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
}
await AssetsManager.initAssets();
await dotenv.load();
runApp(const App());
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:mvc_pattern/mvc_pattern.dart';
import 'package:sip_ua/sip_ua.dart';
import 'package:smart_diese/business_logic/model/sip_model.dart';
class SIPController extends ControllerMVC {
/* Singleton */
SIPController._privateConstructor();
static final SIPController _instance = SIPController._privateConstructor();
static SIPController get controller => _instance;
final SIPModel _model = SIPModel();
void callStateChanged(Call call, CallStateEnum callState) {
_model.call = call;
_model.callState = callState;
}
String getSipAdress() {
return _model.call.remote_identity.toString();
}
void updateView() {
if (kDebugMode) {
print("REFRESH");
}
for (StateMVC<StatefulWidget> element in states) {
if (element.mounted) element.refresh();
if (kDebugMode) {
print(element.toString());
}
}
}
void testcall(String contact) {
_model.testcall(contact);
}
void stopHelper() {
_model.stopHelper();
}
void removeSIPListener(SipUaHelperListener l) {
_model.removeSIPListener(l);
}
RegistrationStateEnum? get registerState => _model.registerStatus;
void register(SipUaHelperListener listener) => _model.register(listener);
CallStateEnum? get callState => _model.callState;
void signalAnswer() {
_model.answerCall();
}
void makeCall(String number) {
_model.makeCall(number);
}
void sendREGISTER() {
_model.sendREGISTER();
}
bool isReachable() => _model.isReachable();
void signalDTMF(String dtmf) {
_model.sendDTMF(dtmf);
}
void signalHangup() {
_model.hangupCall();
}
void signalMuted() {
_model.call.mute();
}
void signalUnmuted() {
_model.call.unmute();
}
void setRegisterState(RegistrationState s) {
_model.setRegisterStatus = s;
updateView();
}
}
当应用程序终止时,它已经失去了与 SIP 服务器的连接,并且 SIPController 未运行。
当前实现仅显示通知,但不会恢复 SIP 连接,也无法接听电话。
当收到推送通知时,其处理程序应首先启动应用程序(它将进行常规初始化,连接到 SIP 服务器并接收 SIP 呼叫),然后才显示一些通知。
iOS 应用程序只能通过 PushKit + VoiP 推送令牌启动,并且必须在收到推送(启动)后立即显示 CallKit UI。
还检查服务器端的超时,发送推送后和取消呼叫之前等待的时间。