fix(videocall): fix SDK re-entry lifecycle and add logout on session clear

- Skip login when SDK client is already logged in (early return with isSdkReady)
- Set isSdkReady immediately when login() returns true instead of waiting for async callback
- Add ref.mounted check after userInfo future to prevent state updates on disposed provider
- Logout VideocallClient on session clear to prevent stale SDK sessions
- Use getActiveCallItem() in onCallItemUpdate for fresh call state (matches JC demo app)
This commit is contained in:
2026-04-27 00:22:48 +02:00
parent 69297f826e
commit 24ddbf34e4
3 changed files with 21 additions and 9 deletions

View File

@@ -20,6 +20,7 @@ import 'package:sf_tracking/sf_tracking.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
import 'package:fonts/fonts.dart';
import 'package:videocall_sdk/videocall_sdk.dart';
class SaveFamilyApp extends ConsumerStatefulWidget {
const SaveFamilyApp({super.key});
@@ -74,6 +75,7 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
_walletHeartbeat?.stop();
_legacyHeartbeat?.stop();
_webSocket?.disconnect();
GetIt.I<VideocallClient>().logout();
FirebaseMessaging.instance.deleteToken().catchError((Object e) {
debugPrint('[FCM] deleteToken on logout failed: $e');
});

View File

@@ -48,6 +48,7 @@ class VideocallController extends _$VideocallController {
final deviceId = device?.identificator ?? '';
ref.onDispose(() {
debugPrint('[Videocall] controller disposed');
_callAddSub?.cancel();
_callUpdateSub?.cancel();
_callRemoveSub?.cancel();
@@ -59,6 +60,7 @@ class VideocallController extends _$VideocallController {
_deviceService.stopCamera();
}
});
debugPrint('[Videocall] build() called, scheduling _initSdk');
Future.microtask(_initSdk);
return VideocallState(deviceId: deviceId);
@@ -86,20 +88,26 @@ class VideocallController extends _$VideocallController {
Future<void> _autoLogin() async {
final user = await ref.read(userInfoProvider.future);
if (!ref.mounted) return;
final sanitizedEmail = user.email.replaceAll('@', '_').replaceAll('.', '_');
final userId = 'p_$sanitizedEmail';
debugPrint('[Videocall] _autoLogin: userId=$userId');
debugPrint('[Videocall] _autoLogin: userId=$userId, clientState=${_client.state}');
if (_client.state == VideocallClientState.loggedIn) {
debugPrint('[Videocall] _autoLogin: already logged in, skipping login');
state = state.copyWith(localUserId: userId, isSdkReady: true);
return;
}
final ok = await _client.login(userId: userId, password: user.id);
debugPrint(
'[Videocall] _autoLogin: login result=$ok, mounted=${ref.mounted}',
);
debugPrint('[Videocall] _autoLogin: login result=$ok');
if (!ref.mounted) return;
if (!ok) {
debugPrint('[Videocall] _autoLogin: login FAILED');
state = state.copyWith(errorEvent: VideocallErrorEvent.authentication);
return;
}
debugPrint('[Videocall] _autoLogin: setting isSdkReady=true');
debugPrint('[Videocall] _autoLogin: login accepted, setting isSdkReady=true');
state = state.copyWith(localUserId: userId, isSdkReady: true);
}

View File

@@ -250,12 +250,14 @@ class VideocallCallService with JCCallCallback {
}
@override
void onCallItemUpdate(JCCallItem item, ChangeParam changeParam) {
final mappedState = _mapCallState(item.getState());
void onCallItemUpdate(JCCallItem item, ChangeParam changeParam) async {
final activeItem = await _call?.getActiveCallItem();
final source = activeItem ?? item;
final mappedState = _mapCallState(source.getState());
debugPrint(
'[VideocallSDK] onCallItemUpdate: rawState=${item.getState()}, mappedState=$mappedState, uploadSelf=${item.uploadVideoStreamSelf}, uploadOther=${item.uploadVideoStreamOther}',
'[VideocallSDK] onCallItemUpdate: paramState=${item.getState()}, activeState=${source.getState()}, mappedState=$mappedState, uploadSelf=${source.uploadVideoStreamSelf}, uploadOther=${source.uploadVideoStreamOther}',
);
_currentItem = _buildItem(item, mappedState);
_currentItem = _buildItem(source, mappedState);
_callItemUpdateController.add(_currentItem!);
}