Skip to main content

ChannelDelegate

Register with channel.delegates.add(delegate: self).

Connection events


User events


Track events

The most common entry point for your app is didAddTrack, because that’s usually where you decide whether to subscribe to remote video or remote audio. didChangeReceiveStreamStatus is judged per track: after subscribing, if no frames of that video arrive for a period of time, it reports a timeout (timedOut == true), and as soon as frames resume it reports again (timedOut == false). Use it to toggle a “loading / remote network issue” indicator on a given video tile. When the first frame arrives after subscribing, you first receive a recovery report, which you can use to hide the initial loading indicator. You can also query RemoteVideoTrack.isReceiveTimedOut at any time.
Don’t use didChangeConnectionQuality in its place. The quality level is one value for the whole link and describes “whether the network is good”, not “whether this video has stopped”: when a single track stops being published, the sender’s camera freezes, or one video fails to decode, the link level can stay excellent the whole time; conversely, when the network jitters and the level drops, several videos may actually still be producing frames normally. Substituting the level for a per-track stall judgment inevitably causes false alarms.

Message and channel events


Call quality events

Quality uses two event streams; pick one by purpose: didReceiveQualityReport delivers raw values every report cycle, suited for signal-strength icons and diagnostics panels; didChangeConnectionQuality fires only when the level changes, suited for “poor network” prompts and proactive downgrading. Don’t use the former to drive prompts or downgrading—it flashes repeatedly when the level jitters. didChangeActiveSpeakers delivers a full snapshot (already sorted by volume in descending order), so your app just overwrites the UI without merging increments itself; when nobody is speaking, it’s an empty array.
These four events exist only on the SeaStart (SFU) engine—they travel over the signaling DataChannel on the subscribing PeerConnection, and the Wangsu (CDN) engine has no such path. See Call quality and active speakers for details.

TrackDelegate

Register with track.delegates.add(delegate: self).

Track-level events

trackDidBindRtcTrack(_:) is especially useful for video rendering, because the remote track object may appear first and the underlying media track finishes binding a bit later—receiving this event is what means you can render. The two screenBroadcast events fire only with iOS full-screen capture (ScreenCaptureMode.broadcast): full-screen sharing is started by the user from the system UI and may be stopped directly from the system pill; these actions happen outside the app, so you can only detect them through events. A successful startCapture() only means the SDK is ready; receiving screenBroadcastDidStart is what really means “sharing”. See Screen sharing for details.

AudioRouteSessionDelegate

iOS only. Register with AudioRouteSession.shared.delegates.add(delegate: self). All callbacks run on the main thread, and the protocol provides default empty implementations, so implement only the methods you care about. reason distinguishes the source of the change (.oldDeviceUnavailable unplugged, .newDeviceAvailable plugged in, .override actively overridden by the app); when troubleshooting route issues, it’s often more valuable than the result itself. audioRouteSessionDidRecoverFromInterruption(_:) is not equivalent to the system’s AVAudioSession.interruptionNotification(.ended): at the moment a system phone call hangs up, the audio hardware hasn’t been released yet, so the SDK waits until the call has really ended and the app is back in the foreground before rebuilding the session, and retries on failure—this callback fires only after a real, successful recovery. Your app just refreshes the UI here and doesn’t need to handle this timing itself. See Audio routing for details.