[1.4.7] - 2026.09.23
Fixed
- An unknown
device_typeno longer makes the whole user list fail to parse: the backend adds new client types on a schedule not controlled by SDK releases, and previously, if even onedevice_typein theusersofchannel/detailfell outside this version’s enum, the whole response failed to decode and joining failed outright (reportingThe data couldn't be read...)—meaning a single unfamiliar client joining could lock every older client out.DeviceTypenow addsunknownand the 80–89 server-side agent range (MCU/SIP/H.323/GB28181/RTSP/RTMP/file playback/stream distribution/speech transcription), andusersis now parsed entry by entry, skipping any entry that can’t be decoded. - Subscriptions rejected individually by the SFU no longer fail silently:
sfu/subscriberejects individual tracks inside an HTTP 200 +code:0response body (thefailedfield) rather than raising an error, so the outer retry never triggered, while the caller’ssubscribe()had long since returned success. That subscription could never be rescued afterward: the internal registration was still in place and short-circuited resubscription, and the receive timeout is an edge event—a subscription that was never established produces no edge—so the symptom was remote video permanently stuck on the last frame or black. Now each track is retried 5 times with exponential backoff (about 6 seconds); once retries are exhausted, the internal subscription registration is revoked, handing the right to resubscribe back to your app.
Changed
UserInfo.deviceTypehas a new meaning:nilnow means “this SDK doesn’t recognize this client”, not just “the remote side didn’t report it”. Code that branches on client type should treatnilas an unknown client, not as a default client type.
This is a fix-only release with no public API changes and no breaking changes; integration code from 1.4.6 can upgrade directly. The minimum platforms are unchanged (iOS 16 / macOS 14).
SRTCBroadcastKit is 1.0.12 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.4.6] - 2026.09.23
Fixed
- Camera capture orientation now follows the interface orientation, and lying the device flat no longer falls back to portrait: previously, when the device lay flat on a desk (
faceUp/faceDown) or the orientation was unknown, it was always treated as portrait, so in an app locked to landscape, putting the device on a desk suddenly published portrait video, and the remote side could only see it with black bars at the top and bottom in a landscape window—the “captured video switching between landscape and portrait on its own” reported by customers. NowUIWindowScene.interfaceOrientationis resolved first (it’s always landscape when the host locks landscape), falling back to the device orientation only when it’s unavailable; lying flat and unknown orientations keep the last valid orientation instead of falling back to portrait. - Landscape screen sharing rotated by 180°: ReplayKit’s
RPVideoSampleOrientationKeygives aCGImagePropertyOrientation(describing “how to position this frame so it’s upright”), while the SDK’s internalVideoRotationdescribes “how many degrees clockwise the receiver should rotate”—the two go in opposite directions. Previouslyright/leftwere mapped as if in the same direction, making both landscape orientations off by 180°: when a teacher started screen sharing in landscape, students saw it upside down. The mapping is now corrected.
This is a fix-only release with no public API changes; integration code from 1.4.5 can upgrade directly.The second fix lives in
SRTCBroadcastKit, which is version 1.0.11 in this release. If you implement iOS full-screen screen sharing, after upgrading confirm that your Broadcast Upload Extension target links the SRTCBroadcastKit from this tag (it’s released under the same tag as this package, so you don’t need to—and shouldn’t—specify its version separately). If the extension side is still on the old package, the orientation mapping is unchanged and shared video is still upside down.[1.4.5] - 2026.09.18
Added
- Remote video receive timeout / recovery detection: adds the
ChannelDelegatecallbackchannel(_:didChangeReceiveStreamStatus:)andReceiveStreamStatus(uid/trackId/trackDesc/timedOut), plusRemoteVideoTrack.isReceiveTimedOutfor querying at any time. Judged per track: after subscribing, if no frames of that video arrive for a period of time, it’s judged as timed out, and as soon as frames resume it reports again—use this to drive a “loading / remote network issue” indicator on the video. When the first frame arrives after subscribing, you first receive atimedOut == false, which you can use to hide the initial loading indicator. It corresponds toengineChannel:onReceiveStreamStatusChange:trackId:status:in the olderRTCEngineKit;timedOuthas the same truth value as the oldstatus(truemeans timed out), so there’s no need to invert it when migrating. - Audio unit start/stop: adds
AudioRouteSession.setAudioModuleEnabled(_:)andisAudioModuleEnabled(iOS). For local-only playback scenarios such as recorded live classes, you can stop the VoIP voice processing unit so it doesn’t lowerAVPlayerplayback volume.
Don’t use
didChangeConnectionQuality in place of receive timeout detection. The quality level describes “whether the network is good” for the whole link, 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.This release only adds public APIs; the protocol methods have default empty implementations, there are no breaking changes, and integration code from 1.4.4 can upgrade directly. SRTCBroadcastKit is 1.0.10 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.4.4] - 2026.09.15
Changed
- Shared video streams from view recording on Wangsu (CDN) now carry microphone-recorded audio, just like standard screen sharing; SRTCBroadcastKit is upgraded to 1.0.9.
- The audio recording pipeline is kept warmed up to avoid reduced playback volume: audio device initialization and playback path switches no longer interrupt the recording pipeline unexpectedly.
[1.4.3] - 2026.09.14
Added
- Screen-sharing streams in Wangsu (CDN) channels now carry audio, so cloud recordings are no longer silent: Wangsu’s cloud recording pulls whole streams by stream name, and previously screen-sharing streams contained only video, so recorded sharing segments had no sound. Now, when screen sharing is published, an opus audio track (capped at 32 kbps) is added on the same publishing connection, with no changes needed in your code. This audio is visible only to CDN recording—it isn’t reported in signaling and can’t be negotiated on the subscribing side, so remote users don’t get an extra audio track, and subscription logic and callbacks are completely unaffected.
This audio comes from the same local mixed output as the microphone and has no separate switch: when no microphone track is published, or
LocalMicTrack.mute() has been called, the hardware microphone is muted at the audio device module level, so the mixed data contains no voice, and neither does the recorded audio—“if the microphone isn’t on, you won’t be recorded”. On macOS, when only screen audio is shared, only the screen sound is recorded.Wangsu (CDN) engine only; recording in SeaStart (SFU) channels is mixed per track by the server and isn’t affected by this change.Fixed
- After unpublishing audio failed, the remote side showed the microphone as on but heard nothing: when turning off the microphone, the SDK has to unpublish the internal mixed audio track, which makes an HTTP request that can fail on network jitter (observed as
NSURLError -1005). Previously the SDK cleared the track handle before the request was sent and didn’t roll back on failure—the track still existed on the server, so the next time the microphone was turned on, a second audio track was created, and the remote side often ended up subscribed to the old stream that no longer had any data. Now the handle is released only after unpublishing succeeds, so failures self-heal: the next time the microphone is turned on, the existing track is correctly reused and no second track is created.
SRTCBroadcastKit is 1.0.8 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.4.2] - 2026.09.12
Added
- Full-display sharing on macOS supports excluding windows:
createLocalScreenTrack(source:preset:audioPreset:)adds two parameters with default values,excludedWindowIdsandexcludesCurrentApplication, andScreenCaptureOptionsadds properties with the same names. The values ofexcludedWindowIdsareUInt32(NSWindow.windowNumber), meaningful only when sharing an entire display (ignored when capturing a single window); the list is snapshotted once atstartCapture(), and windows opened afterward all appear in the capture. Both parameters have default values, so existing call sites remain source-compatible.
Changed
- Full-display sharing on macOS now includes your own app’s windows by default: previously the SDK cut all of the process’s windows out of the capture, so “when sharing the full display, the remote side couldn’t see your own app windows”—this wasn’t a system limitation but something the SDK excluded on its own. It now includes them by default, consistent with Zoom / Tencent Meeting, and you specify windows to cut out one by one with
excludedWindowIds. - The default value of
ScreenCaptureSources.availableWindows(includeCurrentApp:)changes fromfalsetotrue, so your own windows can appear in the list as single-window sharing sources.
Fixed
- The microphone was sent along when sharing only screen audio: in mix mode all audio sources share one capture stream, and whether to mix in the microphone used to be decided by the volume of a mixing node—but that node actually had no input, so changing its volume had no effect on the hardware microphone. As a result, when your app hadn’t published a microphone track and was sharing only screen audio, the remote side could still hear local ambient sound. Now the hardware microphone is muted directly at the audio device module level (muted by default, unmuted only when a microphone track is published or
LocalMicTrack.unmute()is called), and sound from screen audio and custom audio injection is unaffected. - With the same root cause,
LocalMicTrack.mute()on macOS was previously a no-op (the remote side could still hear you after calling it); it now actually takes effect.
SRTCBroadcastKit is 1.0.7 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.4.1] - 2026.09.11
Fixed
ChannelDelegate.didChangeActiveSpeakersnever fired in Wangsu (CDN) channels: active speaker data used to exist only on the SeaStart (SFU) engine (sent by the server over the signaling channel), and the CDN path has no such channel, so the same public callback never fired in Wangsu channels, and volume bars and speaker indicators your app drew from it failed silently without any error. Now the CDN side samples local WebRTC standard statistics (audioLevelofinbound-rtp) every 500 ms and produces exactly the sameActiveSpeakersSnapshotas the SFU, so both engines behave the same externally, with no changes needed in your code.
The CDN architecture has no server-side control plane, so speakers can only be derived from local statistics, which gives two inherent differences compared with SeaStart: it can only reflect subscribed audio streams (users you haven’t subscribed to can’t be detected), and its accuracy and timeliness are slightly lower than the server’s judgment. SeaStart still uses the server-delivered path and isn’t affected by this change.
ActiveSpeakersSnapshot contains only remote speakers, not yourself, on both engines. If you need your own volume, compute it yourself from the captured PCM with SRTCEngine.audioCaptureProcessor.SRTCBroadcastKit is 1.0.6 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.4.0] - 2026.09.09
Added
- Virtual background: runs person segmentation in the camera capture pipeline and replaces everything outside the person with a blur or a specified image; installing it doesn’t require a license key.
SRTCEngineaddsinstallVirtualBackground(modelPath:),uninstallVirtualBackground(),enableVirtualBackground(_:),isVirtualBackgroundEnabled,setVirtualBackgroundBlur(level:),setVirtualBackgroundImage(_:),setVirtualBackgroundInferenceInterval(_:),setVirtualBackgroundMaskSync(_:), and the instance accessorvirtualBackground(read-only state for diagnostics anddroppedFrameCount). The accompanying new types areSRTCVirtualBackground,SRTCVirtualBackgroundEffect, andSRTCNativeImage—see Virtual background for details;- Once enabled, it automatically applies to all camera tracks (including those created after enabling), with no need to add anything to
videoProcessors; - There’s a single process-wide state, so you don’t need to resend the configuration after switching between the front and back cameras / switching camera devices;
- When segmentation fails or the output buffer pool is exhausted, it drops the frame rather than sending the unprocessed raw video (to avoid flashing the real background to the remote side); see
virtualBackground.droppedFrameCountfor the dropped-frame count.
- Once enabled, it automatically applies to all camera tracks (including those created after enabling), with no need to add anything to
SRTCEngineaddsdefaultVideoProcessors: configure the video processor pipeline once, and camera tracks created afterward pick it up automatically (a camera track is created anew every time the camera is turned on, so setting processors only on the track loses them).SRTCErroradds four values:virtualBackgroundAlreadyInstalled,virtualBackgroundNotInstalled,virtualBackgroundModelNotFound(String), andvirtualBackgroundSessionFailed(String).
SRTCError is a non-frozen public enum; if you’ve written an exhaustive switch over it, add an @unknown default branch to compile.SRTCBroadcastKit is 1.0.5 in this release; its code is unchanged, and only the version number is bumped along with the SDK. It doesn’t include onnxruntime (the Broadcast Upload Extension process has a 50 MB memory limit).[1.3.3] - 2026.09.07
Fixed
DegradationPreferencenow actually takes effect: when publishing video, it’s passed down to the underlying encoder, which downgrades resolution or frame rate as configured on a poor network (previously this setting was ignored).
Changed
- Camera and screen-sharing presets are unified to
maintainResolution(prioritize sharpness, lower the frame rate when necessary); previously the presets used inconsistent values. Text-heavy scenarios such as sharing documents and presentations are noticeably sharper as a result.
Added
- macOS
DeviceManageraddssetSystemDefaultOutputDevice(_:), which switches the system default audio output device directly (aligned with the Windows behavior).
SRTCBroadcastKit is 1.0.4 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.3.2] - 2026.09.07
Fixed
- Fixed a crash caused by
JSONSerializationthrowing an ObjC exception whencontentin an mqtt message wasnull(NSNullis now filtered uniformly when reading values).
SRTCBroadcastKit is 1.0.3 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.3.1] - 2026.09.07
Added
DeviceTypeaddsharmonyOS(value8), for identifying users who join from HarmonyOS.
DeviceType is a non-frozen public enum; if you’ve written an exhaustive switch over it, add an @unknown default branch to compile.SRTCBroadcastKit is 1.0.2 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.3.0] - 2026.08.20
Added
- iOS audio routing: adds
AudioRouteSession.shared, which manages the iOS audio session and output route in one place. The accompanying new types areAudioRoute(the current route, read-only with five states, including Bluetooth / wired headphones),AudioRouteTarget(switchable target, two states: speaker / receiver),AudioRouteInfo, andAudioCallState, plus the event protocolAudioRouteSessionDelegate(route change / session interruption / interruption recovery / system call state).- Persistent setting
defaultAudioRoute: set before joining whether to use the speaker or the receiver by default; it stays in effect, and fallback after an external device is unplugged follows it; - Temporary setting
setAudioRoute(_:)/clearAudioRouteOverride(): switch temporarily during a call, with higher priority than the persistent setting; - External devices (Bluetooth / wired) are taken over by the system; the SDK only reports them and doesn’t switch to them actively—iOS has no reliable way to target a specific external device, so when the user needs to choose one, use the system’s
AVRoutePickerView.
- Persistent setting
DeviceManageraddsisSpeakerOutputPreferred, for querying whether speaker output is currently in use.
Changed
- On iOS, joining a channel sets up the call audio path and keeps it until you leave; “whether the microphone is on” only decides whether to publish and no longer affects whether underlying audio capture is running. This is the prerequisite for switching between receiver / speaker even without turning on the microphone.
- The semantics of
DeviceManager.setSpeakerOutputPreferred(_:)are aligned with mainstream RTC SDKs:trueswitches to the speaker,falseswitches to the receiver (previouslyfalsemeant “drop the override and return to the system default”, with unpredictable results). The method signature is unchanged, so callers need no changes.
SRTCBroadcastKit is 1.0.1 in this release; its code is unchanged, and only the version number is bumped along with the SDK.[1.2.0] - 2026.08.19
Breaking changes
- Signaling-related values in the error enum are renamed from
mqtt*tosignaling*(signalingConnectFailed/signalingSubscribeFailed), so signaling implementation details are no longer exposed. Code that catches the old values needs to be renamed; the meaning of the values is unchanged.
Added
- iOS full-screen screen sharing:
createLocalScreenTrack(mode:)adds a capture mode parameter;.broadcast(appGroup:)captures the entire system screen based on a ReplayKit Broadcast Upload Extension, while the existing in-app capture is the default.inAppwith unchanged behavior. Also added:- The
SRTCBroadcastKitproduct (used on the extension side, without WebRTC) and the base classSRTCBroadcastSampleHandler; your extension only needsclass SampleHandler: SRTCBroadcastSampleHandler {}; - Wrappers for the system broadcast picker:
SRTCBroadcastPicker(SwiftUI) /SRTCBroadcastPickerView(UIKit); TrackDelegateaddsscreenBroadcastDidStart(_:)andscreenBroadcastDidFinish(_:reason:), for detecting when the user starts / stops the broadcast from the system UI;LocalScreenTrackaddscaptureModeandisBroadcastActive(isCapturingonly means listening is ready; to display a “sharing” state, checkisBroadcastActive).
- The
- Multi-channel: one
SRTCEnginecan join multiple channels at the same time. Addschannels,defaultChannel, andleaveChannel(_:); tracks are engine-level objects that can be published to multiple channels, and capture release follows “the last releaser is responsible”.
Changed
- How publishing audio to multiple channels is validated: changed from the exclusive restriction “only one channel may publish audio at a time” to “every channel publishing audio must hold exactly the same set of audio sources”, which allows the most common case of “each channel publishing one microphone”. When the sets differ,
publishLocalTrackthrowsinvalidStateto prevent cross-channel audio leakage; the subscribing side isn’t restricted.
Fixed
- iOS camera capture now uses an
AVCaptureSessioncreated by the SDK itself, fixing capture failing silently on some real devices so that the remote side couldn’t see video.
SRTCBroadcastKit has its own version numbering and is 1.0.0 in this release. It’s released under the same tag as the SDK and always matches it, so you don’t need to specify its version separately when integrating.[1.1.0] - 2026.08.02
Added
- Adds the video frame rotation enum
VideoRotation, with values._0/._90/._180/._270; the raw value is the angle.
Changed
- The type of the
rotationparameter ofLocalVideoTrack.pushFrame(_:rotation:timestampNs:)and ofVideoFrame.rotationchanges from the underlying WebRTC type toVideoRotation; Track.rtcTrackis no longer public. To detect when a track has finished binding to its underlying media object, use thetrackDidBindRtcTrackevent instead;- The public API no longer exposes any underlying WebRTC types, so you don’t need to import any WebRTC-related modules.
[1.0.0] - 2026.08.02
Added
- First official release, distributed via Swift Package Manager as a prebuilt XCFramework containing slices for three platforms: iOS devices, the iOS Simulator, and macOS;
- The SDK’s main entry class is
SRTCEngine.