[Change]Improve ringing lifecycle#1078
Conversation
📝 WalkthroughWalkthroughAdds automatic termination for outgoing ringing calls when the app moves to the background via a new internal OutgoingRingingController that observes StreamVideo.State.ringingCall and application lifecycle, with Call wiring to create and clean up the controller. Changes
Sequence DiagramsequenceDiagram
actor User
participant ORingCtrl as OutgoingRingingController
participant StreamVideo as StreamVideo.State
participant AppAdapter as ApplicationStateAdapter
participant Call as Call
User->>Call: start/create outgoing call (ring = true)
Call->>ORingCtrl: configureOutgoingRingingController(callCID, handler)
ORingCtrl->>StreamVideo: subscribe to ringingCall updates
StreamVideo-->>ORingCtrl: ringingCall set/updated
alt ringingCall.cid == callCID
ORingCtrl->>AppAdapter: activate app-state observation
AppAdapter-->>ORingCtrl: emits background state
ORingCtrl->>ORingCtrl: log warning
ORingCtrl->>Call: invoke endCall handler (async)
Call->>Call: terminate outgoing call
ORingCtrl->>AppAdapter: deactivate observation
else ringingCall.cid != callCID
ORingCtrl->>AppAdapter: ensure observation deactivated
end
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/StreamVideo/Call.swift (1)
389-401:⚠️ Potential issue | 🟠 MajorWire
ring(request:)into the outgoing-ringing lifecycle.
ring(request:)triggers outgoing ringing but does not callconfigureOutgoingRingingController()or setstreamVideo.state.ringingCall, so the background auto-end flow is skipped for this API path.🔧 Proposed fix
`@discardableResult` public func ring(request: RingCallRequest) async throws -> RingCallResponse { let response = try await coordinatorClient.ringCall( type: callType, id: callId, ringCallRequest: request ) + + configureOutgoingRingingController() + Task(disposableBag: disposableBag) { `@MainActor` [weak self] in + self?.streamVideo.state.ringingCall = self + } + return response }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/StreamVideo/Call.swift` around lines 389 - 401, ring(request:) currently sends the coordinator ring but doesn't start the outgoing-ringing lifecycle; after the successful coordinatorClient.ringCall(...) returns, set the ringing call state (e.g., streamVideo.state.ringingCall = callId or an appropriate RingCallResponse value) and invoke configureOutgoingRingingController() so the background auto-end flow is installed before returning; make these calls inside the public func ring(request: RingCallRequest) after obtaining response and before return, using the existing configureOutgoingRingingController() method and the streamVideo.state.ringingCall property.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CHANGELOG.md`:
- Line 10: The changelog entry contains a typo: replace the misspelled word
"outgoin" with "outgoing" in the line "- The SDK will now end an outgoin call if
the app moves to background while ringing.
[`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)" so it reads
"...end an outgoing call..."; update the CHANGELOG.md entry accordingly.
In `@Sources/StreamVideo/Call.swift`:
- Line 1570: The outgoingRingingController is only reset in performLeave,
leaving observers alive when a call is rejected or hits terminal paths; update
the reject and terminal cleanup paths to also nil out outgoingRingingController.
Specifically, in the rejecting-stage code that clears ringingCall and removes
the call from cache (refer to Call+RejectingStage.swift where ringingCall is
cleared) and in any terminal/reject handlers, add logic to set
Call.outgoingRingingController = nil (or call the same cleanup helper used by
performLeave) so the controller and its observers are always removed regardless
of how the call ends.
---
Outside diff comments:
In `@Sources/StreamVideo/Call.swift`:
- Around line 389-401: ring(request:) currently sends the coordinator ring but
doesn't start the outgoing-ringing lifecycle; after the successful
coordinatorClient.ringCall(...) returns, set the ringing call state (e.g.,
streamVideo.state.ringingCall = callId or an appropriate RingCallResponse value)
and invoke configureOutgoingRingingController() so the background auto-end flow
is installed before returning; make these calls inside the public func
ring(request: RingCallRequest) after obtaining response and before return, using
the existing configureOutgoingRingingController() method and the
streamVideo.state.ringingCall property.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 68421344-095a-4dd6-acf0-075975ce1f47
📒 Files selected for processing (5)
CHANGELOG.mdSources/StreamVideo/Call.swiftSources/StreamVideo/Controllers/OutgoingRingingController.swiftSources/StreamVideo/StreamVideo.swiftStreamVideoTests/Controllers/OutgoingRingingController_Tests.swift
| ### 🔄 Changed | ||
| - Propagated publish/unpublish failures from local video and screen-share capture | ||
| sessions instead of swallowing them after logging. [#1072](https://github.com/GetStream/stream-video-swift/pull/1072) | ||
| - The SDK will now end an outgoin call if the app moves to background while ringing. [#1078](https://github.com/GetStream/stream-video-swift/pull/1078) |
There was a problem hiding this comment.
Fix the typo in the changelog entry.
Line 10 says outgoin instead of outgoing.
✏️ Proposed fix
-- The SDK will now end an outgoin call if the app moves to background while ringing. [`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)
+- The SDK will now end an outgoing call if the app moves to background while ringing. [`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - The SDK will now end an outgoin call if the app moves to background while ringing. [#1078](https://github.com/GetStream/stream-video-swift/pull/1078) | |
| - The SDK will now end an outgoing call if the app moves to background while ringing. [`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078) |
🧰 Tools
🪛 LanguageTool
[grammar] ~10-~10: Ensure spelling is correct
Context: ...ft/pull/1072) - The SDK will now end an outgoin call if the app moves to background whi...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@CHANGELOG.md` at line 10, The changelog entry contains a typo: replace the
misspelled word "outgoin" with "outgoing" in the line "- The SDK will now end an
outgoin call if the app moves to background while ringing.
[`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)" so it reads
"...end an outgoing call..."; update the CHANGELOG.md entry accordingly.
| /// to happen on the call object (e.g. rejoin) will need to fetch a new instance from `StreamVideo` | ||
| /// client. | ||
| callCache.remove(for: cId) | ||
| outgoingRingingController = nil |
There was a problem hiding this comment.
Reset outgoingRingingController in reject/terminal paths too.
Cleanup currently happens only in performLeave (Line 1570).
In Sources/StreamVideo/CallStateMachine/Stages/Call+RejectingStage.swift (Lines 94-96), rejection clears ringingCall and removes the call from cache without clearing outgoingRingingController, which can leave stale observers alive while the Call is still retained elsewhere.
🔧 Proposed fix direction
// Sources/StreamVideo/Call.swift
+@MainActor
+internal func clearOutgoingRingingController() {
+ outgoingRingingController = nil
+}
`@MainActor`
private func performLeave() {
@@
- outgoingRingingController = nil
+ clearOutgoingRingingController()
@@
}// Sources/StreamVideo/CallStateMachine/Stages/Call+RejectingStage.swift
if streamVideo.state.ringingCall?.cId == call.cId {
await Task(disposableBag: disposableBag) { `@MainActor` [weak streamVideo] in
streamVideo?.state.ringingCall = nil
}.value
}
+
+await Task(disposableBag: disposableBag) { `@MainActor` [weak call] in
+ call?.clearOutgoingRingingController()
+}.valueAlso applies to: 1788-1793
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Sources/StreamVideo/Call.swift` at line 1570, The outgoingRingingController
is only reset in performLeave, leaving observers alive when a call is rejected
or hits terminal paths; update the reject and terminal cleanup paths to also nil
out outgoingRingingController. Specifically, in the rejecting-stage code that
clears ringingCall and removes the call from cache (refer to
Call+RejectingStage.swift where ringingCall is cleared) and in any
terminal/reject handlers, add logic to set Call.outgoingRingingController = nil
(or call the same cleanup helper used by performLeave) so the controller and its
observers are always removed regardless of how the call ends.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
CHANGELOG.md (1)
10-10:⚠️ Potential issue | 🟡 MinorFix the changelog typo.
Line 10 still says
outgoin; it should beoutgoing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CHANGELOG.md` at line 10, Fix the typo in CHANGELOG.md by replacing the word "outgoin" with "outgoing" in the sentence that reads "The SDK will now end an outgoin call..." so it correctly states "The SDK will now end an outgoing call if the app moves to background while ringing." Ensure only the single word is corrected and surrounding punctuation/links remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@CHANGELOG.md`:
- Line 10: Fix the typo in CHANGELOG.md by replacing the word "outgoin" with
"outgoing" in the sentence that reads "The SDK will now end an outgoin call..."
so it correctly states "The SDK will now end an outgoing call if the app moves
to background while ringing." Ensure only the single word is corrected and
surrounding punctuation/links remain unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9bdb2a35-5ad5-480f-9b13-58266dd8ad70
📒 Files selected for processing (2)
CHANGELOG.mdSources/StreamVideo/Call.swift
🚧 Files skipped from review as they are similar to previous changes (1)
- Sources/StreamVideo/Call.swift
Public Interface🚀 No changes affecting the public interface. |
SDK Size
|
StreamVideo XCSize
|
|



🔗 Issue Links
Resolves https://linear.app/stream/issue/IOS-1468/enhancementwhen-moving-to-background-during-outgoing-call-ringing-end
🎯 Goal
Harden the ringing and call lifecycle flows.
🛠 Implementation
Outgoing ringing is now observed explicitly so a matching ringing call is ended if the app transitions to the background before the ring flow completes. The call state machine and related cleanup paths were updated so
ringingCallandactiveCallremain consistent across join, reject, timeout, and end scenarios.🧪 Manual Testing Notes
☑️ Contributor Checklist
Summary by CodeRabbit