@@ -17,6 +17,8 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
17
17
@Injected ( \. callCache) private var callCache
18
18
@Injected ( \. timers) private var timers
19
19
20
+ private enum DisposableKey : String { case ringEventReceived }
21
+
20
22
public final class State : ObservableObject , @unchecked Sendable {
21
23
@Published public internal( set) var connection : ConnectionStatus
22
24
@Published public internal( set) var user : User
@@ -62,6 +64,8 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
62
64
/// A protocol that provides a method to determine the rejection reason for a call.
63
65
public lazy var rejectionReasonProvider : RejectionReasonProviding = StreamRejectionReasonProvider ( self )
64
66
67
+ private let eventSubject : PassthroughSubject < WrappedEvent , Never > = . init( )
68
+
65
69
var token : UserToken
66
70
67
71
private var tokenProvider : UserTokenProvider
@@ -78,7 +82,7 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
78
82
private let eventsMiddleware = WSEventsMiddleware ( )
79
83
private var cachedLocation : String ?
80
84
private var connectTask : Task < Void , Error > ?
81
- private var eventHandlers = [ EventHandler ] ( )
85
+ private let disposableBag = DisposableBag ( )
82
86
83
87
/// The notification center used to send and receive notifications about incoming events.
84
88
private( set) lazy var eventNotificationCenter : EventNotificationCenter = {
@@ -216,6 +220,8 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
216
220
if autoConnectOnInit {
217
221
initialConnectIfRequired ( apiKey: apiKey)
218
222
}
223
+
224
+ observeCallRingEvents ( )
219
225
}
220
226
221
227
deinit {
@@ -314,9 +320,6 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
314
320
315
321
/// Disconnects the current `StreamVideo` client.
316
322
public func disconnect( ) async {
317
- eventHandlers. forEach { $0. cancel ( ) }
318
- eventHandlers. removeAll ( )
319
-
320
323
await withCheckedContinuation { [ webSocketClient] continuation in
321
324
if let webSocketClient = webSocketClient {
322
325
webSocketClient. disconnect {
@@ -327,35 +330,50 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
327
330
}
328
331
}
329
332
}
330
-
333
+
334
+ /// Publishes all received video events coming from the coordinator.
335
+ ///
336
+ /// Use this method to observe all incoming `VideoEvent`s regardless of
337
+ /// specific type. Events are filtered to only include those classified as
338
+ /// `coordinatorEvent` cases.
339
+ ///
340
+ /// - Returns: A publisher emitting `VideoEvent` instances.
341
+ public func eventPublisher( ) -> AnyPublisher < VideoEvent , Never > {
342
+ eventSubject
343
+ . compactMap {
344
+ guard case let . coordinatorEvent( event) = $0 else {
345
+ return nil
346
+ }
347
+ return event
348
+ }
349
+ . eraseToAnyPublisher ( )
350
+ }
351
+
352
+ /// Publishes specific typed WebSocket events.
353
+ ///
354
+ /// Use this method to subscribe only to a specific type of event emitted by
355
+ /// the coordinator. The `WSEvent` must conform to `Event`.
356
+ ///
357
+ /// - Parameter event: The type of WebSocket event to observe.
358
+ /// - Returns: A publisher emitting events of the specified `WSEvent` type.
359
+ public func eventPublisher< WSEvent: Event > (
360
+ for event: WSEvent . Type
361
+ ) -> AnyPublisher < WSEvent , Never > {
362
+ eventSubject
363
+ . compactMap { $0. unwrap ( ) ? . rawValue as? WSEvent }
364
+ . eraseToAnyPublisher ( )
365
+ }
366
+
331
367
/// Subscribes to all video events.
332
368
/// - Returns: `AsyncStream` of `VideoEvent`s.
333
369
public func subscribe( ) -> AsyncStream < VideoEvent > {
334
- AsyncStream ( VideoEvent . self) { [ weak self] continuation in
335
- let eventHandler = EventHandler ( handler: { event in
336
- guard case let . coordinatorEvent( event) = event else {
337
- return
338
- }
339
- continuation. yield ( event)
340
- } , cancel: { continuation. finish ( ) } )
341
- self ? . eventHandlers. append ( eventHandler)
342
- }
370
+ eventPublisher ( ) . eraseAsAsyncStream ( )
343
371
}
344
372
345
373
/// Subscribes to a particular WS event.
346
374
/// - Returns: `AsyncStream` of the requested WS event.
347
375
public func subscribe< WSEvent: Event > ( for event: WSEvent . Type ) -> AsyncStream < WSEvent > {
348
- AsyncStream ( event) { [ weak self] continuation in
349
- let eventHandler = EventHandler ( handler: { event in
350
- guard let coordinatorEvent = event. unwrap ( ) else {
351
- return
352
- }
353
- if let event = coordinatorEvent. unwrap ( ) as? WSEvent {
354
- continuation. yield ( event)
355
- }
356
- } , cancel: { continuation. finish ( ) } )
357
- self ? . eventHandlers. append ( eventHandler)
358
- }
376
+ eventPublisher ( for: event) . eraseAsAsyncStream ( )
359
377
}
360
378
361
379
public func queryCalls(
@@ -489,17 +507,6 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
489
507
} else {
490
508
throw ClientError . Unknown ( )
491
509
}
492
- var connected = false
493
- var timeout = false
494
- let control = DefaultTimer . schedule ( timeInterval: 30 , queue: . sdk) {
495
- timeout = true
496
- }
497
- log. debug ( " Listening for WS connection " )
498
- webSocketClient? . onConnected = {
499
- control. cancel ( )
500
- connected = true
501
- log. debug ( " WS connected " )
502
- }
503
510
504
511
do {
505
512
log. debug ( " Listening for WS connection " )
@@ -560,12 +567,6 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
560
567
|| webSocketClient? . connectionState == . authenticating else {
561
568
return " "
562
569
}
563
-
564
- var timeout = false
565
- let control = DefaultTimer . schedule ( timeInterval: 5 , queue: . sdk) {
566
- timeout = true
567
- }
568
- log. debug ( " Waiting for connection id " )
569
570
570
571
do {
571
572
return try await timers
@@ -744,24 +745,55 @@ extension StreamVideo: ConnectionStateDelegate {
744
745
connectionRecoveryHandler? . webSocketClient ( client, didUpdateConnectionState: state)
745
746
}
746
747
}
747
- eventHandlers . forEach { $0 . handler ( . internalEvent( WSDisconnected ( ) ) ) }
748
+ eventSubject . send ( . internalEvent( WSDisconnected ( ) ) )
748
749
case . connected( healthCheckInfo: _) :
749
- eventHandlers . forEach { $0 . handler ( . internalEvent( WSConnected ( ) ) ) }
750
+ eventSubject . send ( . internalEvent( WSConnected ( ) ) )
750
751
default :
751
752
log. debug ( " Web socket connection state update \( state) " )
752
753
}
753
754
}
755
+
756
+ /// Observes incoming call ring events from the coordinator.
757
+ ///
758
+ /// This method subscribes to `typeCallRingEvent` messages from the internal
759
+ /// event stream. When such an event is received, it attempts to retrieve or
760
+ /// create a `Call` object matching the event's call ID and type. Once the
761
+ /// call is found, it updates the call's state with the event data and sets it
762
+ /// as the current `ringingCall`.
763
+ ///
764
+ /// The resulting subscription is stored in `disposableBag` under a specific
765
+ /// key to allow later cancellation or cleanup.
766
+ private func observeCallRingEvents( ) {
767
+ eventSubject
768
+ . eraseToAnyPublisher ( )
769
+ . compactMap { ( source: WrappedEvent ) -> CallRingEvent ? in
770
+ guard
771
+ case let . typeCallRingEvent( event) = source. unwrap ( )
772
+ else {
773
+ return nil
774
+ }
775
+ return event
776
+ }
777
+ . compactMap { [ weak self] ( source: CallRingEvent ) -> ( event: CallRingEvent , call: Call ) ? in
778
+ guard let call = self ? . call ( callType: source. call. type, callId: source. call. id) else {
779
+ return nil
780
+ }
781
+ return ( event: source, call: call)
782
+ }
783
+ . sinkTask ( storeIn: disposableBag) { @MainActor [ weak self] in
784
+ guard let self else { return }
785
+ $0. call. state. update ( from: $0. event)
786
+ self . state. ringingCall = $0. call
787
+ }
788
+ . store ( in: disposableBag, key: DisposableKey . ringEventReceived. rawValue)
789
+ }
754
790
}
755
791
756
792
extension StreamVideo : WSEventsSubscriber {
757
793
758
794
func onEvent( _ event: WrappedEvent ) {
759
- for eventHandler in eventHandlers {
760
- eventHandler. handler ( event)
761
- }
762
- Task { @MainActor [ weak self] in
763
- self ? . checkRingEvent ( event)
764
- }
795
+ eventSubject. send ( event)
796
+ checkRingEvent ( event)
765
797
}
766
798
767
799
private func checkRingEvent( _ event: WrappedEvent ) {
0 commit comments