diff --git a/lib/features/calls/presentation/call_controller.dart b/lib/features/calls/presentation/call_controller.dart index b987525..c5f82a2 100644 --- a/lib/features/calls/presentation/call_controller.dart +++ b/lib/features/calls/presentation/call_controller.dart @@ -1,9 +1,10 @@ -// Version: 1.2.0 | Created: 2026-04-01 | Updated: 2026-04-02 +// Version: 1.3.0 | Created: 2026-04-01 | Updated: 2026-04-03 // Call controller — manages LiveKit room connection lifecycle. // Transitions through idle → connecting → active → ended states. import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:livekit_client/livekit_client.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -16,12 +17,15 @@ part 'call_controller.g.dart'; class CallController extends _$CallController { Timer? _durationTimer; Duration _elapsed = Duration.zero; + EventsListener? _roomListener; @override CallState build() { ref.onDispose(() { _durationTimer?.cancel(); _durationTimer = null; + _roomListener?.dispose(); + _roomListener = null; ref.read(liveKitServiceProvider).disconnect(); }); return const CallState.idle(); @@ -112,5 +116,26 @@ class CallController extends _$CallController { state = current.copyWith(duration: _elapsed); } }); + + // Auto-end when the remote party leaves (1:1 call behaviour). + // Wait 5 seconds after the last remote participant disconnects before + // ending — gives time for brief network interruptions to recover. + _roomListener?.dispose(); + _roomListener = room.createListener(); + _roomListener!.on((event) { + debugPrint( + '[Call] Participant left: ${event.participant.identity}, ' + 'remaining: ${room.remoteParticipants.length}', + ); + if (room.remoteParticipants.isEmpty) { + debugPrint('[Call] No remote participants — auto-ending in 5s'); + Future.delayed(const Duration(seconds: 5), () { + // Re-check in case someone rejoined during the delay. + if (room.remoteParticipants.isEmpty && state is CallActive) { + endCall(); + } + }); + } + }); } }