Skip to content

Commit a8d06b9

Browse files
committed
improve reject and leave reasons
1 parent 5201b9b commit a8d06b9

9 files changed

Lines changed: 144 additions & 10 deletions

File tree

packages/stream_video/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## Upcoming
2+
3+
### 🐞 Fixed
4+
* Improved disconnect/reject reason propagation.
5+
16
## 1.3.2
27

38
### 🐞 Fixed

packages/stream_video/lib/src/call/call.dart

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,11 @@ class Call {
536536
// Notify the client about the permission request.
537537
return onPermissionRequest?.call(event);
538538
case StreamCallRejectedEvent _:
539-
return _stateManager.coordinatorCallRejected(event);
539+
await _handleCoordinatorCallRejected(event);
540+
return;
540541
case StreamCallAcceptedEvent _:
541-
return _stateManager.coordinatorCallAccepted(event);
542+
await _handleCoordinatorCallAccepted(event);
543+
return;
542544
case StreamCallEndedEvent _:
543545
return _stateManager.coordinatorCallEnded(event);
544546
case StreamCallPermissionsUpdatedEvent _:
@@ -645,6 +647,46 @@ class Call {
645647
}
646648
}
647649

650+
Future<void> _handleCoordinatorCallAccepted(
651+
StreamCallAcceptedEvent event,
652+
) async {
653+
final currentUserId = _stateManager.callState.currentUserId;
654+
final status = state.value.status;
655+
656+
if (event.acceptedByUserId == currentUserId &&
657+
status is CallStatusIncoming &&
658+
!status.acceptedByMe) {
659+
_logger.i(
660+
() => '[onCoordinatorEvent] call accepted on another device, '
661+
'rejecting locally with userRespondedElsewhere',
662+
);
663+
await reject(reason: CallRejectReason.userRespondedElsewhere());
664+
return;
665+
}
666+
667+
_stateManager.coordinatorCallAccepted(event);
668+
}
669+
670+
Future<void> _handleCoordinatorCallRejected(
671+
StreamCallRejectedEvent event,
672+
) async {
673+
final currentUserId = _stateManager.callState.currentUserId;
674+
final status = state.value.status;
675+
676+
if (event.rejectedByUserId == currentUserId &&
677+
status is CallStatusIncoming &&
678+
!status.acceptedByMe) {
679+
_logger.i(
680+
() => '[onCoordinatorEvent] call rejected on another device, '
681+
'rejecting locally with userRespondedElsewhere',
682+
);
683+
await reject(reason: CallRejectReason.userRespondedElsewhere());
684+
return;
685+
}
686+
687+
_stateManager.coordinatorCallRejected(event);
688+
}
689+
648690
void _handleModerationWarningEvent(
649691
StreamCallModerationWarningEvent event,
650692
) {
@@ -779,7 +821,7 @@ class Call {
779821
/// Rejects the incoming call.
780822
Future<Result<None>> reject({CallRejectReason? reason}) async {
781823
final state = this.state.value;
782-
_logger.i(() => '[reject] state: $state');
824+
_logger.i(() => '[reject] reason: $reason');
783825

784826
final result = await _coordinatorClient.rejectCall(
785827
cid: state.callCid,
@@ -2005,7 +2047,7 @@ class Call {
20052047
}
20062048

20072049
try {
2008-
_session?.leave(reason: 'user is leaving the call');
2050+
_session?.leave(reason: _sfuLeaveReason(reason));
20092051
} finally {
20102052
await _clear('leave');
20112053
}
@@ -2020,6 +2062,30 @@ class Call {
20202062
}
20212063
}
20222064

2065+
String _sfuLeaveReason(DisconnectReason? reason) {
2066+
if (reason == null) return 'user is leaving the call';
2067+
if (reason is DisconnectReasonRejected) {
2068+
return 'rejected: ${reason.reason?.value ?? 'unspecified'}';
2069+
} else if (reason is DisconnectReasonReplaced) {
2070+
return 'replaced by another call';
2071+
} else if (reason is DisconnectReasonReconnectionFailed) {
2072+
return 'reconnection failed';
2073+
} else if (reason is DisconnectReasonLastParticipantLeft) {
2074+
return 'last participant left';
2075+
} else if (reason is DisconnectReasonFailure) {
2076+
return 'failure: ${reason.error}';
2077+
} else if (reason is DisconnectReasonSfuError) {
2078+
return 'sfu error: ${reason.error}';
2079+
} else if (reason is DisconnectReasonCallEnded) {
2080+
return 'call ended externally';
2081+
} else if (reason is DisconnectReasonEnded) {
2082+
return 'call ended';
2083+
} else if (reason is DisconnectReasonTimeout) {
2084+
return 'timeout';
2085+
}
2086+
return 'user is leaving the call';
2087+
}
2088+
20232089
Future<void> _clear(String src) async {
20242090
_logger.d(() => '[clear] src: $src');
20252091

packages/stream_video/lib/src/call/call_reject_reason.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,47 @@ import 'package:equatable/equatable.dart';
22

33
/// Reason for rejecting a call.
44
///
5-
/// busy - when the callee is busy and cannot accept the call
65
/// decline - when the callee intentionally declines the call
76
/// cancel - when the caller cancels the call
7+
/// busy - when the callee is busy and cannot accept the call
88
/// timeout - when the call times out
9+
/// callEnded - when the call was ended externally (backend event, creator
10+
/// cancelled, or all other participants rejected) before the user answered
11+
/// callEndedLocally - when the local SDK already ended the call and the
12+
/// ringing flow is being brought back in sync
913
class CallRejectReason with EquatableMixin {
1014
const CallRejectReason._(this.value);
1115

1216
factory CallRejectReason.decline() => const CallRejectReason._('decline');
1317
factory CallRejectReason.cancel() => const CallRejectReason._('cancel');
1418
factory CallRejectReason.busy() => const CallRejectReason._('busy');
1519
factory CallRejectReason.timeout() => const CallRejectReason._('timeout');
20+
21+
/// The call was ended externally before the user could answer — e.g. a
22+
/// backend `call.ended` event, the creator cancelled, or every other
23+
/// participant already rejected.
24+
factory CallRejectReason.callEnded() =>
25+
const CallRejectReason._('call-ended');
26+
27+
/// The local SDK already ended the call and the ringing UI / CallKit is
28+
/// being brought back in sync.
29+
factory CallRejectReason.callEndedLocally() =>
30+
const CallRejectReason._('call-ended-locally');
31+
32+
/// The same user already accepted, rejected, or missed the ringing call on
33+
/// another device.
34+
factory CallRejectReason.userRespondedElsewhere() =>
35+
const CallRejectReason._('user-responded-elsewhere');
36+
37+
/// The caller cancelled the ringing flow before any other invitee accepted.
38+
factory CallRejectReason.creatorRejected() =>
39+
const CallRejectReason._('ring: creator rejected');
40+
41+
/// Every invitee other than the current user and the caller has already
42+
/// rejected the ringing call.
43+
factory CallRejectReason.allOtherParticipantsRejected() =>
44+
const CallRejectReason._('ring: everyone rejected');
45+
1646
factory CallRejectReason.custom(String customType) =>
1747
CallRejectReason._(customType);
1848

packages/stream_video/lib/src/call/session/call_session.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ class CallSession extends Disposable {
510510
}
511511

512512
void leave({String? reason}) {
513-
_logger.d(() => '[leave] no args');
513+
_logger.d(() => '[leave] reason: $reason');
514514
_isLeavingOrClosed = true;
515515
sfuWS.leave(sessionId: sessionId, reason: reason);
516516
}

packages/stream_video/lib/src/call/state/mixins/state_coordinator_mixin.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ mixin StateCoordinatorMixin on StateNotifier<CallState> {
108108
status: CallStatus.disconnected(
109109
DisconnectReason.rejected(
110110
byUserId: event.rejectedByUserId,
111-
reason: CallRejectReason.custom('ring: everyone rejected'),
111+
reason: CallRejectReason.allOtherParticipantsRejected(),
112112
),
113113
),
114114
sessionId: '',
@@ -126,7 +126,7 @@ mixin StateCoordinatorMixin on StateNotifier<CallState> {
126126
status: CallStatus.disconnected(
127127
DisconnectReason.rejected(
128128
byUserId: event.rejectedByUserId,
129-
reason: CallRejectReason.custom('ring: creator rejected'),
129+
reason: CallRejectReason.creatorRejected(),
130130
),
131131
),
132132
sessionId: '',

packages/stream_video/lib/src/models/disconnect_reason.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ abstract class DisconnectReason extends Equatable {
3434
return DisconnectReasonEnded();
3535
}
3636

37+
/// The call was ended externally (e.g. a backend `call.ended` event or
38+
/// CallKit reporting the call ended) while the user was still connected.
39+
factory DisconnectReason.callEnded() {
40+
return DisconnectReasonCallEnded();
41+
}
42+
3743
factory DisconnectReason.replaced() {
3844
return DisconnectReasonReplaced();
3945
}
@@ -194,6 +200,22 @@ class DisconnectReasonReconnectionFailed extends DisconnectReason {
194200
}
195201
}
196202

203+
class DisconnectReasonCallEnded extends DisconnectReason {
204+
factory DisconnectReasonCallEnded() {
205+
return _instance;
206+
}
207+
208+
const DisconnectReasonCallEnded._internal();
209+
210+
static const DisconnectReasonCallEnded _instance =
211+
DisconnectReasonCallEnded._internal();
212+
213+
@override
214+
String toString() {
215+
return 'CallEnded';
216+
}
217+
}
218+
197219
class DisconnectReasonManuallyClosed extends DisconnectReason {
198220
factory DisconnectReasonManuallyClosed() {
199221
return _instance;

packages/stream_video/lib/src/stream_video.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import 'models/call_preferences.dart';
4646
import 'models/call_received_data.dart';
4747
import 'models/call_ringing_data.dart';
4848
import 'models/call_status.dart';
49+
import 'models/disconnect_reason.dart';
4950
import 'models/guest_created_data.dart';
5051
import 'models/push_device.dart';
5152
import 'models/push_provider.dart';
@@ -997,7 +998,9 @@ class StreamVideo extends Disposable {
997998
final incomingCall = _state.incomingCall.valueOrNull;
998999

9991000
if (activeCall?.callCid.value == cid) {
1000-
final result = await activeCall?.leave();
1001+
final result = await activeCall?.leave(
1002+
reason: DisconnectReason.callEnded(),
1003+
);
10011004

10021005
if (result is Failure) {
10031006
_logger.d(() => '[onCallEnded] error leaving call: ${result.error}');
@@ -1006,7 +1009,7 @@ class StreamVideo extends Disposable {
10061009
final status = incomingCall?.state.value.status;
10071010
if (status is CallStatusIncoming && !status.acceptedByMe) {
10081011
final result = await incomingCall?.reject(
1009-
reason: CallRejectReason.decline(),
1012+
reason: CallRejectReason.callEnded(),
10101013
);
10111014
if (result is Failure) {
10121015
_logger.d(

packages/stream_video_flutter/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## Upcoming
2+
3+
### 🐞 Fixed
4+
* Improved disconnect/reject reason propagation.
5+
16
## 1.3.2
27

38
### 🐞 Fixed

packages/stream_video_push_notification/lib/src/stream_video_push_notification.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
6363
_logger.d(
6464
() => '[subscribeToEvents] Call ended event: ${event.callCid}',
6565
);
66+
RingingEventBroadcaster().suppressEvent();
6667
endCallByCid(event.callCid.toString());
6768
}),
6869
);
@@ -88,6 +89,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
8889
'[subscribeToEvents] No participants left, ending call: ${event.callCid}',
8990
);
9091

92+
RingingEventBroadcaster().suppressEvent();
9193
await endCallByCid(event.callCid.toString());
9294
}
9395
}),
@@ -107,6 +109,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
107109
'[subscribeToEvents] Call rejected by the current user or call owner, ending call: ${event.callCid}',
108110
);
109111

112+
RingingEventBroadcaster().suppressEvent();
110113
await endCallByCid(event.callCid.toString());
111114
}
112115
}),

0 commit comments

Comments
 (0)