observer property
Implementation
final NECallEngineDelegate observer = NECallEngineDelegate(
onReceiveInvited: (NEInviteInfo info) async {
CallKitUILog.i(_tag,
'NECallObserver onReceiveInvited(callerAccId:${info.callerAccId}, callType:${info.callType}, callId:${info.callId})');
CallState.instance.currentCallId = info.callId ?? '';
// 先处理来电逻辑(设置 callRole 为 called),再播放铃声
// 否则 startRing 读取的 callRole 还是默认值,会播放错误的铃声
await CallState.instance.handleCallReceivedData(
info.callerAccId, [], info.channelId ?? '', info.callType);
await _runtimeAdapter.startIncomingRing();
await NECallKitPlatform.instance.updateCallStateToNative();
// await CallManager.instance.enableWakeLock(true);
if (Platform.isIOS) {
if (CallState.instance.enableIncomingBanner) {
CallState.instance.isInNativeIncomingBanner = true;
CallManager.instance.showIncomingBanner();
} else {
CallState.instance.isInNativeIncomingBanner = false;
CallManager.instance.launchCallingPage();
}
} else if (Platform.isAndroid) {
if (await CallManager.instance.isScreenLocked()) {
CallManager.instance.openLockScreenApp();
return;
}
if (CallState.instance.enableIncomingBanner &&
await NECallKitPlatform.instance.hasFloatPermission() &&
!(await CallManager.instance.isSamsungDevice())) {
CallState.instance.isInNativeIncomingBanner = true;
// 确保 Java 侧 sIncomingBannerEnabled = true,否则 WindowManager.showIncomingBanner() 会提前返回
await NECallKitPlatform.instance.setIncomingBannerEnabled(true);
CallManager.instance.showIncomingBanner();
} else {
if (await NECallKitPlatform.instance.isAppInForeground()) {
CallState.instance.isInNativeIncomingBanner = false;
CallManager.instance.launchCallingPage();
} else {
CallManager.instance.pullBackgroundApp();
}
}
} else if (Platform.isMacOS || Platform.isWindows) {
// Desktop reuses the Flutter calling page, so an incoming invite must
// explicitly drive the same launch flow that mobile foreground calls use.
CallState.instance.isInNativeIncomingBanner = false;
CallManager.instance.launchCallingPage();
} else if (PlatformCompat.isOhos) {
// OHOS 平台:直接启动通话页面,无需特殊权限检查
if (CallState.instance.enableIncomingBanner) {
CallState.instance.isInNativeIncomingBanner = true;
await NECallKitPlatform.instance.setIncomingBannerEnabled(true);
CallManager.instance.showIncomingBanner();
} else {
if (await NECallKitPlatform.instance.isAppInForeground()) {
CallState.instance.isInNativeIncomingBanner = false;
CallManager.instance.launchCallingPage();
} else {
CallManager.instance.pullBackgroundApp();
}
}
}
},
onCallEnd: (NECallEndInfo info) async {
CallKitUILog.i(_tag,
'NECallObserver onCallEnd(reasonCode:${info.reasonCode}, message:${info.message}, callId:${info.callId}, currentCallId:${CallState.instance.currentCallId})');
if ((Platform.isMacOS || Platform.isWindows) &&
info.callId != null &&
info.callId!.isNotEmpty &&
CallState.instance.currentCallId.isNotEmpty &&
info.callId != CallState.instance.currentCallId) {
CallKitUILog.i(_tag,
'NECallObserver onCallEnd ignored stale desktop event: eventCallId=${info.callId}, currentCallId=${CallState.instance.currentCallId}');
return;
}
if (info.reasonCode == NECallTerminalCode.busy) {
CallManager.instance.showToast(NECallKitUI.localizations.userBusy);
} else if (info.reasonCode == NECallTerminalCode.calleeReject ||
info.reasonCode == NECallTerminalCode.callerRejected) {
if (CallState.instance.selfUser.callRole == NECallRole.caller) {
CallManager.instance
.showToast(NECallKitUI.localizations.remoteUserReject);
}
} else if (info.reasonCode == NECallTerminalCode.otherAccepted) {
CallManager.instance
.showToast(NECallKitUI.localizations.answerOnOtherDevice);
} else if (info.reasonCode == NECallTerminalCode.otherRejected) {
CallManager.instance
.showToast(NECallKitUI.localizations.rejectOnOtherDevice);
} else if (info.reasonCode == NECallTerminalCode.timeOut) {
if (CallState.instance.selfUser.callRole == NECallRole.caller) {
CallManager.instance
.showToast(NECallKitUI.localizations.remoteTimeout);
}
} else if (shouldShowRemoteCancelToast(
reasonCode: info.reasonCode,
callRole: CallState.instance.selfUser.callRole,
)) {
CallManager.instance.showToast(NECallKitUI.localizations.remoteCancel);
} else {
CallKitUILog.i(_tag, 'NECallObserver onCallEnd: ${info.reasonCode}');
}
await _runtimeAdapter.stopRing();
if (CallState.instance.callType == NECallType.video &&
CallState.instance.isCameraOpen) {
CallManager.instance.closeCamera();
}
// 如果启用了应用外悬浮窗,清理画中画资源(iOS 和鸿蒙)
if ((Platform.isIOS || PlatformCompat.isOhos) &&
CallState.instance.enableFloatWindowOutOfApp &&
CallState.instance.enableFloatWindow) {
await NECallKitPlatform.instance.disposePIP();
}
// 如果悬浮窗打开中,关闭悬浮窗(处理未启用应用外悬浮窗但普通悬浮窗已显示的情况)
if (CallState.instance.isOpenFloatWindow) {
CallKitUILog.i(_tag, 'NECallObserver onCallEnd: stopping float window');
await NECallKitPlatform.instance.stopFloatWindow();
}
CallManager.instance.clearBannerActiveState();
// iOS 来电横幅自动 dismiss(T023)
if (Platform.isIOS && CallState.instance.isInNativeIncomingBanner) {
await NECallKitPlatform.instance.cancelIncomingBanner();
}
CallState.instance.cleanState();
NEEventNotify().notify(setStateEventOnCallEnd);
CallManager.instance.enableWakeLock(false);
CallState.instance.stopTimer();
NECallKitPlatform.instance.updateCallStateToNative();
},
onCallConnected: (NECallInfo info) async {
CallKitUILog.i(_tag,
'NECallObserver onCallConnected(callId:${info.callId}, callType:${info.callType})');
CallState.instance.currentCallId = info.callId;
CallState.instance.startTime =
DateTime.now().millisecondsSinceEpoch ~/ 1000;
await _runtimeAdapter.stopRing();
CallState.instance.callType = info.callType;
CallState.instance.selfUser.callStatus = NECallStatus.accept;
if (CallState.instance.isMicrophoneMute) {
CallManager.instance.closeMicrophone();
} else {
CallManager.instance.openMicrophone();
}
// 视频通话接通后默认开启扬声器(延迟1秒)
if (info.callType == NECallType.video) {
CallState.instance.isEnableSpeaker = true;
// 延迟1秒后开启扬声器
Future.delayed(const Duration(seconds: 1), () async {
await CallManager.instance
.setSpeakerphoneOn(CallState.instance.isEnableSpeaker);
CallKitUILog.i(
_tag, 'NECallObserver: Delayed speaker enabled after 1 second');
});
} else {
// 音频通话立即设置(使用听筒)
await CallManager.instance
.setSpeakerphoneOn(CallState.instance.isEnableSpeaker);
}
CallState.instance.startTimer();
CallState.instance.isLocalViewBig = false;
CallState.instance.preferLocalPreviewBeforeRemoteVideo =
info.callType == NECallType.video;
CallState.instance.isInNativeIncomingBanner = false;
CallKitUILog.i(_tag,
'NECallObserver onCallConnected: enableFloatWindowOutOfApp = ${CallState.instance.enableFloatWindowOutOfApp}, enableFloatWindow = ${CallState.instance.enableFloatWindow}');
// 如果启用了应用外悬浮窗,设置画中画(iOS 和 OHOS)
if ((Platform.isIOS || PlatformCompat.isOhos) &&
CallState.instance.enableFloatWindowOutOfApp &&
CallState.instance.enableFloatWindow &&
CallState.instance.callType == NECallType.video) {
// 被叫接听成功会先走一次原生状态同步,主叫直连场景不会。
// 这里先把 accept 状态同步给原生,再初始化 PiP,避免首次 setupPIP
// 仍看到旧的 waiting 状态而直接跳过。
await NECallKitPlatform.instance.updateCallStateToNative();
final success = await NECallKitPlatform.instance.setupPIP();
CallKitUILog.i(
_tag, 'NECallObserver onCallConnected: setupPIP result = $success');
}
NEEventNotify().notify(setStateEvent);
NEEventNotify().notify(setStateEventOnCallBegin);
NECallKitPlatform.instance.updateCallStateToNative();
},
onLCKAccept: (NELCKAcceptResult result) async {
CallKitUILog.i(_tag,
'NECallObserver onLCKAccept(code:${result.code}, callId:${result.callInfo?.callId}, currentCallId:${CallState.instance.currentCallId})');
if (!Platform.isIOS || result.code != 0) {
return;
}
await CallManager.applySingleCallAcceptSuccess(
runtimeAdapter: _runtimeAdapter,
callState: CallState.instance,
syncCallStateToNative: () {
NECallKitPlatform.instance.updateCallStateToNative();
},
callInfo: result.callInfo,
);
CallState.instance.isInNativeIncomingBanner = false;
CallManager.instance.clearBannerActiveState();
await NECallKitPlatform.instance.cancelIncomingBanner();
NEEventNotify().notify(setStateEvent);
if (shouldEnterCallingPageAfterLckAccept(
currentPage: NECallKitNavigatorObserver.currentPage,
isOpenFloatWindow: CallState.instance.isOpenFloatWindow,
)) {
CallManager.instance.enterCallingPageFromBanner();
}
},
onCallTypeChange: (NECallTypeChangeInfo info) {
CallKitUILog.i(_tag,
'NECallObserver onCallTypeChange(callType:${info.callType}, state:${info.state})');
CallState.instance.callType = info.callType;
NEEventNotify().notify(setStateEvent);
NECallKitPlatform.instance.updateCallStateToNative();
},
onVideoAvailable: (bool available, String userID) {
CallKitUILog.i(_tag,
'NECallObserver onVideoAvailable(userId:$userID, isVideoAvailable:$available)');
for (var remoteUser in CallState.instance.remoteUserList) {
if (remoteUser.id == userID) {
remoteUser.videoAvailable = available;
if (available &&
CallState.instance.preferLocalPreviewBeforeRemoteVideo) {
CallState.instance.preferLocalPreviewBeforeRemoteVideo = false;
}
NEEventNotify().notify(setStateEvent);
NECallKitPlatform.instance.updateCallStateToNative();
return;
}
}
},
onVideoMuted: (bool muted, String userID) {
CallKitUILog.i(
_tag, 'NECallObserver onVideoMuted(userId:$userID, muted:$muted)');
// 处理视频静音逻辑
for (var remoteUser in CallState.instance.remoteUserList) {
if (remoteUser.id == userID) {
remoteUser.videoAvailable = !muted;
if (!muted &&
CallState.instance.preferLocalPreviewBeforeRemoteVideo) {
CallState.instance.preferLocalPreviewBeforeRemoteVideo = false;
}
NEEventNotify().notify(setStateEvent);
NECallKitPlatform.instance.updateCallStateToNative();
return;
}
}
},
onAudioMuted: (bool muted, String userID) {
CallKitUILog.i(
_tag, 'NECallObserver onAudioMuted(userId:$userID, muted:$muted)');
for (var remoteUser in CallState.instance.remoteUserList) {
if (remoteUser.id == userID) {
remoteUser.audioAvailable = !muted;
NEEventNotify().notify(setStateEvent);
return;
}
}
NECallKitPlatform.instance.updateCallStateToNative();
},
onLocalAudioMuted: (bool muted) {
CallKitUILog.i(_tag, 'NECallObserver onLocalAudioMuted(muted:$muted)');
CallState.instance.isMicrophoneMute = muted;
NECallKitPlatform.instance.updateCallStateToNative();
},
onRtcInitEnd: () {
CallKitUILog.i(_tag, 'NECallObserver onRtcInitEnd()');
NECallKitPlatform.instance.updateCallStateToNative();
// RTC 初始化完成
},
onNERtcEngineVirtualBackgroundSourceEnabled: (bool enabled, int reason) {
CallKitUILog.i(_tag,
'NECallObserver onNERtcEngineVirtualBackgroundSourceEnabled(enabled:$enabled, reason:$reason)');
// 处理虚拟背景
NECallKitPlatform.instance.updateCallStateToNative();
},
);