diff --git a/packages/stream_video/lib/src/call/call.dart b/packages/stream_video/lib/src/call/call.dart index 0adbeb827..48dab220e 100644 --- a/packages/stream_video/lib/src/call/call.dart +++ b/packages/stream_video/lib/src/call/call.dart @@ -41,7 +41,9 @@ typedef OnCallPermissionRequest = void Function( typedef GetCurrentUserId = String? Function(); typedef SetActiveCall = Future Function(Call?); -typedef GetActiveCallCid = StreamCallCid? Function(); +typedef SetOutgoingCall = Future Function(Call?); +typedef GetActiveCall = Call? Function(); +typedef GetOutgoingCall = Call? Function(); const _idState = 1; const _idUserId = 2; @@ -68,7 +70,9 @@ class Call { required CoordinatorClient coordinatorClient, required StateEmitter currentUser, required SetActiveCall setActiveCall, - required GetActiveCallCid getActiveCallCid, + required SetOutgoingCall setOutgoingCall, + required GetActiveCall getActiveCall, + required GetOutgoingCall getOutgoingCall, RetryPolicy? retryPolicy, SdpPolicy? sdpPolicy, CallPreferences? preferences, @@ -79,7 +83,9 @@ class Call { coordinatorClient: coordinatorClient, currentUser: currentUser, setActiveCall: setActiveCall, - getActiveCallCid: getActiveCallCid, + setOutgoingCall: setOutgoingCall, + getActiveCall: getActiveCall, + getOutgoingCall: getOutgoingCall, retryPolicy: retryPolicy, sdpPolicy: sdpPolicy, preferences: preferences, @@ -94,7 +100,9 @@ class Call { required CoordinatorClient coordinatorClient, required StateEmitter currentUser, required SetActiveCall setActiveCall, - required GetActiveCallCid getActiveCallCid, + required SetOutgoingCall setOutgoingCall, + required GetActiveCall getActiveCall, + required GetOutgoingCall getOutgoingCall, RetryPolicy? retryPolicy, SdpPolicy? sdpPolicy, CallPreferences? preferences, @@ -105,7 +113,9 @@ class Call { coordinatorClient: coordinatorClient, currentUser: currentUser, setActiveCall: setActiveCall, - getActiveCallCid: getActiveCallCid, + setOutgoingCall: setOutgoingCall, + getActiveCall: getActiveCall, + getOutgoingCall: getOutgoingCall, retryPolicy: retryPolicy, sdpPolicy: sdpPolicy, preferences: preferences, @@ -125,7 +135,9 @@ class Call { required CoordinatorClient coordinatorClient, required StateEmitter currentUser, required SetActiveCall setActiveCall, - required GetActiveCallCid getActiveCallCid, + required SetOutgoingCall setOutgoingCall, + required GetActiveCall getActiveCall, + required GetOutgoingCall getOutgoingCall, RetryPolicy? retryPolicy, SdpPolicy? sdpPolicy, CallPreferences? preferences, @@ -136,7 +148,9 @@ class Call { coordinatorClient: coordinatorClient, currentUser: currentUser, setActiveCall: setActiveCall, - getActiveCallCid: getActiveCallCid, + setOutgoingCall: setOutgoingCall, + getActiveCall: getActiveCall, + getOutgoingCall: getOutgoingCall, retryPolicy: retryPolicy, sdpPolicy: sdpPolicy, preferences: preferences, @@ -148,7 +162,9 @@ class Call { required CoordinatorClient coordinatorClient, required StateEmitter currentUser, required SetActiveCall setActiveCall, - required GetActiveCallCid getActiveCallCid, + required SetOutgoingCall setOutgoingCall, + required GetActiveCall getActiveCall, + required GetOutgoingCall getOutgoingCall, RetryPolicy? retryPolicy, SdpPolicy? sdpPolicy, CallPreferences? preferences, @@ -172,7 +188,9 @@ class Call { coordinatorClient: coordinatorClient, currentUser: currentUser, setActiveCall: setActiveCall, - getActiveCallCid: getActiveCallCid, + setOutgoingCall: setOutgoingCall, + getActiveCall: getActiveCall, + getOutgoingCall: getOutgoingCall, preferences: finalCallPreferences, stateManager: stateManager, credentials: credentials, @@ -185,7 +203,9 @@ class Call { Call._({ required StateEmitter currentUser, required SetActiveCall setActiveCall, - required GetActiveCallCid getActiveCallCid, + required SetOutgoingCall setOutgoingCall, + required GetActiveCall getActiveCall, + required GetOutgoingCall getOutgoingCall, required CoordinatorClient coordinatorClient, required CallPreferences preferences, required CallStateNotifier stateManager, @@ -206,7 +226,9 @@ class Call { .whereNotNull() .distinct(), _setActiveCall = setActiveCall, - _getActiveCallCid = getActiveCallCid, + _setOutgoingCall = setOutgoingCall, + _getActiveCall = getActiveCall, + _getOutgoingCall = getOutgoingCall, _coordinatorClient = coordinatorClient, _preferences = preferences, _retryPolicy = retryPolicy, @@ -226,7 +248,9 @@ class Call { final GetCurrentUserId _getCurrentUserId; final Stream _currentUserIdUpdates; final SetActiveCall _setActiveCall; - final GetActiveCallCid _getActiveCallCid; + final SetOutgoingCall _setOutgoingCall; + final GetActiveCall _getActiveCall; + final GetOutgoingCall _getOutgoingCall; final CoordinatorClient _coordinatorClient; final RetryPolicy _retryPolicy; final CallPreferences _preferences; @@ -241,6 +265,8 @@ class Call { StreamCallType get type => state.value.callType; + bool get isActiveCall => _getActiveCall()?.callCid == callCid; + String get id => state.value.callId; StateEmitter get state => _stateManager.callStateStream; @@ -380,6 +406,21 @@ class Call { _logger.w(() => '[acceptCall] rejected (invalid status): $status'); return Result.error('invalid status: $status'); } + + final outgoingCall = _getOutgoingCall(); + if (outgoingCall?.callCid != callCid) { + await outgoingCall?.reject(reason: 'cancel'); + await outgoingCall?.leave(); + await _setOutgoingCall(null); + } + + final activeCall = _getActiveCall(); + if (activeCall?.callCid != callCid) { + await activeCall?.reject(reason: 'cancel'); + await activeCall?.leave(); + await _setActiveCall(null); + } + final result = await _coordinatorClient.acceptCall(cid: state.callCid); if (result is Success) { _stateManager.lifecycleCallAccepted(); @@ -448,7 +489,7 @@ class Call { return const Result.success(none); } - if (_getActiveCallCid() == callCid) { + if (_getActiveCall()?.callCid == callCid) { _logger.w( () => '[join] rejected (a call with the same cid is in progress)', ); @@ -997,6 +1038,7 @@ class Call { await _session?.dispose(); _session = null; await _setActiveCall(null); + await _setOutgoingCall(null); _logger.v(() => '[clear] completed'); } @@ -1291,6 +1333,10 @@ class Call { return Result.error('[getOrCreate] failed; no user_id found'); } + if (ringing) { + await _setOutgoingCall(this); + } + final response = await _coordinatorClient.getOrCreateCall( callCid: callCid, ringing: ringing, diff --git a/packages/stream_video/lib/src/call/session/call_session.dart b/packages/stream_video/lib/src/call/session/call_session.dart index 5b42091c9..c20d54e14 100644 --- a/packages/stream_video/lib/src/call/session/call_session.dart +++ b/packages/stream_video/lib/src/call/session/call_session.dart @@ -766,7 +766,8 @@ class CallSession extends Disposable { Future> setSubscriptions( List subscriptionChanges, ) async { - _logger.d(() => '[setSubscriptions] subscriptionChanges: $subscriptionChanges'); + _logger.d( + () => '[setSubscriptions] subscriptionChanges: $subscriptionChanges'); final participants = stateManager.callState.callParticipants; final exclude = {SfuTrackType.video, SfuTrackType.screenShare}; @@ -792,7 +793,8 @@ class CallSession extends Disposable { Future> updateSubscription( SubscriptionChange subscriptionChange, ) async { - _logger.d(() => '[updateSubscription] subscriptionChange: $subscriptionChange'); + _logger.d( + () => '[updateSubscription] subscriptionChange: $subscriptionChange'); return _saBuffer.post(subscriptionChange); } @@ -885,6 +887,7 @@ class CallSession extends Disposable { enabled: enabled, constraints: constraints, ); + return result.map((_) => none); } diff --git a/packages/stream_video/lib/src/coordinator/open_api/coordinator_ws.dart b/packages/stream_video/lib/src/coordinator/open_api/coordinator_ws.dart index 3d6df7679..9afdd9389 100644 --- a/packages/stream_video/lib/src/coordinator/open_api/coordinator_ws.dart +++ b/packages/stream_video/lib/src/coordinator/open_api/coordinator_ws.dart @@ -70,7 +70,6 @@ class CoordinatorWebSocket extends StreamWebSocket bool _refreshToken = false; - @override SharedEmitter get events => _events; final _events = MutableSharedEmitterImpl(); diff --git a/packages/stream_video/lib/src/core/client_state.dart b/packages/stream_video/lib/src/core/client_state.dart index 4fed26369..ea56c3d2d 100644 --- a/packages/stream_video/lib/src/core/client_state.dart +++ b/packages/stream_video/lib/src/core/client_state.dart @@ -1,5 +1,4 @@ import '../call/call.dart'; -import '../models/call_cid.dart'; import '../models/user.dart'; import '../state_emitter.dart'; import 'connection_state.dart'; @@ -21,7 +20,10 @@ abstract class ClientState { StateEmitter get activeCall; /// Emits when a call was created by another user with ringing set as True. - StateEmitter get incomingCall; + StateEmitter get incomingCall; + + /// Emits when a call was created by current user with ringing set as True. + StateEmitter get outgoingCall; } class MutableClientState implements ClientState { @@ -29,6 +31,7 @@ class MutableClientState implements ClientState { : user = MutableStateEmitterImpl(user), activeCall = MutableStateEmitterImpl(null), incomingCall = MutableStateEmitterImpl(null), + outgoingCall = MutableStateEmitterImpl(null), connection = MutableStateEmitterImpl( ConnectionState.disconnected(user.id), ); @@ -40,7 +43,10 @@ class MutableClientState implements ClientState { final MutableStateEmitter activeCall; @override - final MutableStateEmitter incomingCall; + final MutableStateEmitter incomingCall; + + @override + final MutableStateEmitter outgoingCall; @override final MutableStateEmitter connection; @@ -50,16 +56,23 @@ class MutableClientState implements ClientState { Future clear() async { activeCall.value = null; + outgoingCall.value = null; connection.value = ConnectionState.disconnected(user.value.id); } - StreamCallCid? getActiveCallCid() => activeCall.valueOrNull?.callCid; + Call? getActiveCall() => activeCall.valueOrNull; + Call? getOutgoingCall() => outgoingCall.valueOrNull; Future setActiveCall(Call? call) async { - final ongoingCall = activeCall.valueOrNull; - if (ongoingCall != null && call != null) { - await ongoingCall.leave(); + final currentlyActiveCall = activeCall.valueOrNull; + if (currentlyActiveCall != null && call != null) { + await currentlyActiveCall.leave(); } + activeCall.value = call; } + + Future setOutgoingCall(Call? call) async { + outgoingCall.value = call; + } } diff --git a/packages/stream_video/lib/src/stream_video.dart b/packages/stream_video/lib/src/stream_video.dart index 7a6e45cf5..35bf9423a 100644 --- a/packages/stream_video/lib/src/stream_video.dart +++ b/packages/stream_video/lib/src/stream_video.dart @@ -493,7 +493,9 @@ class StreamVideo extends Disposable { coordinatorClient: _client, currentUser: _state.user, setActiveCall: _state.setActiveCall, - getActiveCallCid: _state.getActiveCallCid, + setOutgoingCall: _state.setOutgoingCall, + getActiveCall: _state.getActiveCall, + getOutgoingCall: _state.getOutgoingCall, retryPolicy: _options.retryPolicy, sdpPolicy: _options.sdpPolicy, preferences: preferences, @@ -509,7 +511,9 @@ class StreamVideo extends Disposable { coordinatorClient: _client, currentUser: _state.user, setActiveCall: _state.setActiveCall, - getActiveCallCid: _state.getActiveCallCid, + setOutgoingCall: _state.setOutgoingCall, + getActiveCall: _state.getActiveCall, + getOutgoingCall: _state.getOutgoingCall, retryPolicy: _options.retryPolicy, sdpPolicy: _options.sdpPolicy, preferences: preferences, @@ -721,7 +725,7 @@ class StreamVideo extends Disposable { } if (_state.incomingCall.valueOrNull?.callCid.value == cid) { - return Result.success(_state.incomingCall.value); + return Result.success(_state.incomingCall.value!); } final callCid = StreamCallCid(cid: cid); diff --git a/packages/stream_video_flutter/example/lib/screen/home_screen.dart b/packages/stream_video_flutter/example/lib/screen/home_screen.dart index 81062d614..53be0436d 100644 --- a/packages/stream_video_flutter/example/lib/screen/home_screen.dart +++ b/packages/stream_video_flutter/example/lib/screen/home_screen.dart @@ -18,15 +18,18 @@ class _HomeScreenState extends State { final StreamVideo _streamVideo = StreamVideo.instance; late final currentUser = _streamVideo.currentUser; - StreamSubscription? _onIncomingCallSubscription; + StreamSubscription? _onIncomingCallSubscription; @override void initState() { super.initState(); _onIncomingCallSubscription?.cancel(); - _onIncomingCallSubscription = _streamVideo.state.incomingCall.listen( - _onNavigateToCall, - ); + _onIncomingCallSubscription = + _streamVideo.state.incomingCall.listen((call) { + if (call != null) { + _onNavigateToCall(call); + } + }); } @override