From 020681756565727ad6dcac76d660ff3f40353d56 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 12 Oct 2025 23:16:58 +0800 Subject: [PATCH 1/7] Fixes --- .../Extensions/CustomStringConvertible.swift | 2 + .../Participant/LocalParticipant.swift | 161 +++++++++++------- .../LiveKit/Protocols/VideoViewDelegate.swift | 1 + .../LiveKit/SwiftUI/SwiftUIVideoView.swift | 2 + .../Track/Capturers/CameraCapturer.swift | 3 + .../Views/VideoView+MulticastDelegate.swift | 1 + .../LiveKit/Views/VideoView+PinchToZoom.swift | 1 + Sources/LiveKit/Views/VideoView.swift | 7 + 8 files changed, 116 insertions(+), 62 deletions(-) diff --git a/Sources/LiveKit/Extensions/CustomStringConvertible.swift b/Sources/LiveKit/Extensions/CustomStringConvertible.swift index 3bea77f34..354fa5d18 100644 --- a/Sources/LiveKit/Extensions/CustomStringConvertible.swift +++ b/Sources/LiveKit/Extensions/CustomStringConvertible.swift @@ -156,12 +156,14 @@ extension Livekit_SignalResponse: CustomStringConvertible { // MARK: - NativeView +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") public extension VideoView { override var description: String { "VideoView(track: \(String(describing: track)))" } } +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView.RenderMode: CustomStringConvertible { public var description: String { switch self { diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index da3a89583..9b1886d95 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -300,16 +300,33 @@ extension LocalParticipant { // MARK: - Simplified API public extension LocalParticipant { + @available(iOSApplicationExtension, unavailable, message: "Camera capture is not available in app extensions") @objc @discardableResult func setCamera(enabled: Bool, captureOptions: CameraCaptureOptions? = nil, publishOptions: VideoPublishOptions? = nil) async throws -> LocalTrackPublication? { - try await set(source: .camera, - enabled: enabled, - captureOptions: captureOptions, - publishOptions: publishOptions) + try await _publishSerialRunner.run { + let room = try self.requireRoom() + + // Try to get existing publication + if let publication = self.getTrackPublication(source: .camera) as? LocalTrackPublication { + if enabled { + try await publication.unmute() + return publication + } else { + try await publication.mute() + return publication + } + } else if enabled { + let localTrack = LocalVideoTrack.createCameraTrack(options: captureOptions ?? room._state.roomOptions.defaultCameraCaptureOptions, + reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) + return try await self._publish(track: localTrack, options: publishOptions) + } + + return nil + } } @objc @@ -318,10 +335,26 @@ public extension LocalParticipant { captureOptions: AudioCaptureOptions? = nil, publishOptions: AudioPublishOptions? = nil) async throws -> LocalTrackPublication? { - try await set(source: .microphone, - enabled: enabled, - captureOptions: captureOptions, - publishOptions: publishOptions) + try await _publishSerialRunner.run { + let room = try self.requireRoom() + + // Try to get existing publication + if let publication = self.getTrackPublication(source: .microphone) as? LocalTrackPublication { + if enabled { + try await publication.unmute() + return publication + } else { + try await publication.mute() + return publication + } + } else if enabled { + let localTrack = LocalAudioTrack.createTrack(options: captureOptions ?? room._state.roomOptions.defaultAudioCaptureOptions, + reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) + return try await self._publish(track: localTrack, options: publishOptions) + } + + return nil + } } /// Enable or disable screen sharing. This has different behavior depending on the platform. @@ -335,80 +368,84 @@ public extension LocalParticipant { /// For advanced usage, you can create a relevant ``LocalVideoTrack`` and call ``LocalParticipant/publishVideoTrack(track:publishOptions:)``. @objc @discardableResult - func setScreenShare(enabled: Bool) async throws -> LocalTrackPublication? { - try await set(source: .screenShareVideo, enabled: enabled) - } - - @objc - @discardableResult - func set(source: Track.Source, - enabled: Bool, - captureOptions: CaptureOptions? = nil, - publishOptions: TrackPublishOptions? = nil) async throws -> LocalTrackPublication? + func setScreenShare(enabled: Bool, + captureOptions: ScreenShareCaptureOptions? = nil, + publishOptions: VideoPublishOptions? = nil) async throws -> LocalTrackPublication? { try await _publishSerialRunner.run { let room = try self.requireRoom() // Try to get existing publication - if let publication = self.getTrackPublication(source: source) as? LocalTrackPublication { + if let publication = self.getTrackPublication(source: .screenShareVideo) as? LocalTrackPublication { if enabled { try await publication.unmute() return publication } else { - if source == .camera || source == .microphone { - try await publication.mute() - } else { - try await self.unpublish(publication: publication) - } + try await self.unpublish(publication: publication) return publication } } else if enabled { - // Try to create a new track - if source == .camera { - let localTrack = LocalVideoTrack.createCameraTrack(options: (captureOptions as? CameraCaptureOptions) ?? room._state.roomOptions.defaultCameraCaptureOptions, - reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) - return try await self._publish(track: localTrack, options: publishOptions) - } else if source == .microphone { - let localTrack = LocalAudioTrack.createTrack(options: (captureOptions as? AudioCaptureOptions) ?? room._state.roomOptions.defaultAudioCaptureOptions, - reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) - return try await self._publish(track: localTrack, options: publishOptions) - } else if source == .screenShareVideo { - #if os(iOS) - - let localTrack: LocalVideoTrack - let defaultOptions = room._state.roomOptions.defaultScreenShareCaptureOptions - - if defaultOptions.useBroadcastExtension { - if captureOptions != nil { - logger.warning("Ignoring screen capture options passed to local participant's `\(#function)`; using room defaults instead.") - logger.warning("When using a broadcast extension, screen capture options must be set as room defaults.") - } - guard BroadcastManager.shared.isBroadcasting else { - BroadcastManager.shared.requestActivation() - return nil - } - // Wait until broadcasting to publish track - localTrack = LocalVideoTrack.createBroadcastScreenCapturerTrack(options: defaultOptions) - } else { - let options = (captureOptions as? ScreenShareCaptureOptions) ?? defaultOptions - localTrack = LocalVideoTrack.createInAppScreenShareTrack(options: options) + #if os(iOS) + + let localTrack: LocalVideoTrack + let defaultOptions = room._state.roomOptions.defaultScreenShareCaptureOptions + + if defaultOptions.useBroadcastExtension { + if captureOptions != nil { + logger.warning("Ignoring screen capture options passed to local participant's `\(#function)`; using room defaults instead.") + logger.warning("When using a broadcast extension, screen capture options must be set as room defaults.") } - return try await self._publish(track: localTrack, options: publishOptions) - #elseif os(macOS) - if #available(macOS 12.3, *) { - let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource() - let track = LocalVideoTrack.createMacOSScreenShareTrack(source: mainDisplay, - options: (captureOptions as? ScreenShareCaptureOptions) ?? room._state.roomOptions.defaultScreenShareCaptureOptions, - reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) - return try await self._publish(track: track, options: publishOptions) + guard BroadcastManager.shared.isBroadcasting else { + BroadcastManager.shared.requestActivation() + return nil } - #endif + // Wait until broadcasting to publish track + localTrack = LocalVideoTrack.createBroadcastScreenCapturerTrack(options: defaultOptions) + } else { + let options = captureOptions ?? defaultOptions + localTrack = LocalVideoTrack.createInAppScreenShareTrack(options: options) + } + return try await self._publish(track: localTrack, options: publishOptions) + #elseif os(macOS) + if #available(macOS 12.3, *) { + let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource() + let track = LocalVideoTrack.createMacOSScreenShareTrack(source: mainDisplay, + options: (captureOptions as? ScreenShareCaptureOptions) ?? room._state.roomOptions.defaultScreenShareCaptureOptions, + reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) + return try await self._publish(track: track, options: publishOptions) } + #endif } return nil } } + + @available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") + @objc + @discardableResult + func set(source: Track.Source, + enabled: Bool, + captureOptions: CaptureOptions? = nil, + publishOptions: TrackPublishOptions? = nil) async throws -> LocalTrackPublication? + { + switch source { + case .camera: + try await setCamera(enabled: enabled, + captureOptions: captureOptions as? CameraCaptureOptions, + publishOptions: publishOptions as? VideoPublishOptions) + case .microphone: + try await setMicrophone(enabled: enabled, + captureOptions: captureOptions as? AudioCaptureOptions, + publishOptions: publishOptions as? AudioPublishOptions) + case .screenShareVideo: + try await setScreenShare(enabled: enabled, + captureOptions: captureOptions as? ScreenShareCaptureOptions, + publishOptions: publishOptions as? VideoPublishOptions) + default: + nil + } + } } // MARK: - Simulcast codecs diff --git a/Sources/LiveKit/Protocols/VideoViewDelegate.swift b/Sources/LiveKit/Protocols/VideoViewDelegate.swift index 3089f81b3..25f2da248 100644 --- a/Sources/LiveKit/Protocols/VideoViewDelegate.swift +++ b/Sources/LiveKit/Protocols/VideoViewDelegate.swift @@ -18,6 +18,7 @@ import Foundation internal import LiveKitWebRTC +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") @objc public protocol VideoViewDelegate: AnyObject, Sendable { /// Dimensions of the VideoView itself has updated diff --git a/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift b/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift index ec20a5e44..bc2f622cc 100644 --- a/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift +++ b/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift @@ -19,6 +19,7 @@ import SwiftUI /// A ``VideoView`` that can be used in SwiftUI. /// Supports both iOS and macOS. +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") public struct SwiftUIVideoView: NativeViewRepresentable { public typealias ViewType = VideoView @@ -80,6 +81,7 @@ public struct SwiftUIVideoView: NativeViewRepresentable { } /// This class receives ``VideoViewDelegate`` events since a struct can't be used for a delegate +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") @MainActor class VideoViewDelegateReceiver: VideoViewDelegate, Loggable { @Binding var isRendering: Bool diff --git a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift index 3914a014e..a29a86d6c 100644 --- a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift @@ -23,6 +23,7 @@ import ReplayKit internal import LiveKitWebRTC +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") public class CameraCapturer: VideoCapturer, @unchecked Sendable { /// Current device used for capturing @objc @@ -261,6 +262,7 @@ public class CameraCapturer: VideoCapturer, @unchecked Sendable { } } +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") class VideoCapturerDelegateAdapter: NSObject, LKRTCVideoCapturerDelegate, Loggable { weak var cameraCapturer: CameraCapturer? @@ -284,6 +286,7 @@ class VideoCapturerDelegateAdapter: NSObject, LKRTCVideoCapturerDelegate, Loggab } } +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") public extension LocalVideoTrack { @objc static func createCameraTrack() -> LocalVideoTrack { diff --git a/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift b/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift index e78dd9cc0..9582a3d80 100644 --- a/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift +++ b/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift @@ -16,6 +16,7 @@ import Foundation +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView: MulticastDelegateProtocol { @objc(addDelegate:) public nonisolated func add(delegate: VideoViewDelegate) { diff --git a/Sources/LiveKit/Views/VideoView+PinchToZoom.swift b/Sources/LiveKit/Views/VideoView+PinchToZoom.swift index b8f855b5b..8540ea9a2 100644 --- a/Sources/LiveKit/Views/VideoView+PinchToZoom.swift +++ b/Sources/LiveKit/Views/VideoView+PinchToZoom.swift @@ -16,6 +16,7 @@ internal import LiveKitWebRTC +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView { static let rampRate: Float = 32.0 /// Options for pinch to zoom in / out feature. diff --git a/Sources/LiveKit/Views/VideoView.swift b/Sources/LiveKit/Views/VideoView.swift index 2614504cb..0957b0f5b 100644 --- a/Sources/LiveKit/Views/VideoView.swift +++ b/Sources/LiveKit/Views/VideoView.swift @@ -22,6 +22,7 @@ internal import LiveKitWebRTC /// A ``NativeViewType`` that conforms to ``RTCVideoRenderer``. typealias NativeRendererView = LKRTCVideoRenderer & Mirrorable & NativeViewType +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") @objc public class VideoView: NativeView, Loggable { // MARK: - MulticastDelegate @@ -515,6 +516,7 @@ public class VideoView: NativeView, Loggable { // MARK: - Private +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") @MainActor private extension VideoView { private func ensureDebugTextView() -> TextView { @@ -583,6 +585,7 @@ private extension VideoView { // MARK: - RTCVideoRenderer +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView: VideoRenderer { public var isAdaptiveStreamEnabled: Bool { _state.read { $0.didLayout && !$0.isHidden && $0.isEnabled } @@ -732,6 +735,7 @@ extension VideoView: VideoRenderer { // MARK: - Internal +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView { nonisolated static func track(_ track1: VideoTrack?, isEqualWith track2: VideoTrack?) -> Bool { // equal if both tracks are nil @@ -745,6 +749,7 @@ extension VideoView { // MARK: - Static helper methods +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView { public static func isMetalAvailable() -> Bool { #if os(iOS) @@ -834,6 +839,7 @@ extension NSView { #endif #if os(iOS) || os(macOS) +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension LKRTCMTLVideoView: Mirrorable { func set(isMirrored: Bool) { if isMirrored { @@ -857,6 +863,7 @@ extension LKRTCMTLVideoView: Mirrorable { #endif #if os(iOS) +@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView.TransitionMode { func toAnimationOption(fromPosition position: AVCaptureDevice.Position? = nil) -> UIView.AnimationOptions? { switch self { From 174711fee92249de02254511a9b059e790dbaa51 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Mon, 13 Oct 2025 00:07:22 +0800 Subject: [PATCH 2/7] Clean up --- Sources/LiveKit/Participant/LocalParticipant.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index 9b1886d95..595d54ab9 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -314,11 +314,10 @@ public extension LocalParticipant { if let publication = self.getTrackPublication(source: .camera) as? LocalTrackPublication { if enabled { try await publication.unmute() - return publication } else { try await publication.mute() - return publication } + return publication } else if enabled { let localTrack = LocalVideoTrack.createCameraTrack(options: captureOptions ?? room._state.roomOptions.defaultCameraCaptureOptions, reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) @@ -342,11 +341,10 @@ public extension LocalParticipant { if let publication = self.getTrackPublication(source: .microphone) as? LocalTrackPublication { if enabled { try await publication.unmute() - return publication } else { try await publication.mute() - return publication } + return publication } else if enabled { let localTrack = LocalAudioTrack.createTrack(options: captureOptions ?? room._state.roomOptions.defaultAudioCaptureOptions, reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) @@ -379,11 +377,10 @@ public extension LocalParticipant { if let publication = self.getTrackPublication(source: .screenShareVideo) as? LocalTrackPublication { if enabled { try await publication.unmute() - return publication } else { try await self.unpublish(publication: publication) - return publication } + return publication } else if enabled { #if os(iOS) @@ -410,7 +407,7 @@ public extension LocalParticipant { if #available(macOS 12.3, *) { let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource() let track = LocalVideoTrack.createMacOSScreenShareTrack(source: mainDisplay, - options: (captureOptions as? ScreenShareCaptureOptions) ?? room._state.roomOptions.defaultScreenShareCaptureOptions, + options: captureOptions ?? room._state.roomOptions.defaultScreenShareCaptureOptions, reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) return try await self._publish(track: track, options: publishOptions) } From 7da6c8e6b9aac52b650918013598e533e0a94cb7 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 14 Oct 2025 02:45:08 +0800 Subject: [PATCH 3/7] Revert "Clean up" This reverts commit 174711fee92249de02254511a9b059e790dbaa51. --- Sources/LiveKit/Participant/LocalParticipant.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index 595d54ab9..9b1886d95 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -314,10 +314,11 @@ public extension LocalParticipant { if let publication = self.getTrackPublication(source: .camera) as? LocalTrackPublication { if enabled { try await publication.unmute() + return publication } else { try await publication.mute() + return publication } - return publication } else if enabled { let localTrack = LocalVideoTrack.createCameraTrack(options: captureOptions ?? room._state.roomOptions.defaultCameraCaptureOptions, reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) @@ -341,10 +342,11 @@ public extension LocalParticipant { if let publication = self.getTrackPublication(source: .microphone) as? LocalTrackPublication { if enabled { try await publication.unmute() + return publication } else { try await publication.mute() + return publication } - return publication } else if enabled { let localTrack = LocalAudioTrack.createTrack(options: captureOptions ?? room._state.roomOptions.defaultAudioCaptureOptions, reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) @@ -377,10 +379,11 @@ public extension LocalParticipant { if let publication = self.getTrackPublication(source: .screenShareVideo) as? LocalTrackPublication { if enabled { try await publication.unmute() + return publication } else { try await self.unpublish(publication: publication) + return publication } - return publication } else if enabled { #if os(iOS) @@ -407,7 +410,7 @@ public extension LocalParticipant { if #available(macOS 12.3, *) { let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource() let track = LocalVideoTrack.createMacOSScreenShareTrack(source: mainDisplay, - options: captureOptions ?? room._state.roomOptions.defaultScreenShareCaptureOptions, + options: (captureOptions as? ScreenShareCaptureOptions) ?? room._state.roomOptions.defaultScreenShareCaptureOptions, reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) return try await self._publish(track: track, options: publishOptions) } From f8839d4b3f6bbd2a76949b9282161837a8b63cff Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 14 Oct 2025 02:45:14 +0800 Subject: [PATCH 4/7] Revert "Fixes" This reverts commit 020681756565727ad6dcac76d660ff3f40353d56. --- .../Extensions/CustomStringConvertible.swift | 2 - .../Participant/LocalParticipant.swift | 161 +++++++----------- .../LiveKit/Protocols/VideoViewDelegate.swift | 1 - .../LiveKit/SwiftUI/SwiftUIVideoView.swift | 2 - .../Track/Capturers/CameraCapturer.swift | 3 - .../Views/VideoView+MulticastDelegate.swift | 1 - .../LiveKit/Views/VideoView+PinchToZoom.swift | 1 - Sources/LiveKit/Views/VideoView.swift | 7 - 8 files changed, 62 insertions(+), 116 deletions(-) diff --git a/Sources/LiveKit/Extensions/CustomStringConvertible.swift b/Sources/LiveKit/Extensions/CustomStringConvertible.swift index 354fa5d18..3bea77f34 100644 --- a/Sources/LiveKit/Extensions/CustomStringConvertible.swift +++ b/Sources/LiveKit/Extensions/CustomStringConvertible.swift @@ -156,14 +156,12 @@ extension Livekit_SignalResponse: CustomStringConvertible { // MARK: - NativeView -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") public extension VideoView { override var description: String { "VideoView(track: \(String(describing: track)))" } } -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView.RenderMode: CustomStringConvertible { public var description: String { switch self { diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index 9b1886d95..da3a89583 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -300,33 +300,16 @@ extension LocalParticipant { // MARK: - Simplified API public extension LocalParticipant { - @available(iOSApplicationExtension, unavailable, message: "Camera capture is not available in app extensions") @objc @discardableResult func setCamera(enabled: Bool, captureOptions: CameraCaptureOptions? = nil, publishOptions: VideoPublishOptions? = nil) async throws -> LocalTrackPublication? { - try await _publishSerialRunner.run { - let room = try self.requireRoom() - - // Try to get existing publication - if let publication = self.getTrackPublication(source: .camera) as? LocalTrackPublication { - if enabled { - try await publication.unmute() - return publication - } else { - try await publication.mute() - return publication - } - } else if enabled { - let localTrack = LocalVideoTrack.createCameraTrack(options: captureOptions ?? room._state.roomOptions.defaultCameraCaptureOptions, - reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) - return try await self._publish(track: localTrack, options: publishOptions) - } - - return nil - } + try await set(source: .camera, + enabled: enabled, + captureOptions: captureOptions, + publishOptions: publishOptions) } @objc @@ -335,26 +318,10 @@ public extension LocalParticipant { captureOptions: AudioCaptureOptions? = nil, publishOptions: AudioPublishOptions? = nil) async throws -> LocalTrackPublication? { - try await _publishSerialRunner.run { - let room = try self.requireRoom() - - // Try to get existing publication - if let publication = self.getTrackPublication(source: .microphone) as? LocalTrackPublication { - if enabled { - try await publication.unmute() - return publication - } else { - try await publication.mute() - return publication - } - } else if enabled { - let localTrack = LocalAudioTrack.createTrack(options: captureOptions ?? room._state.roomOptions.defaultAudioCaptureOptions, - reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) - return try await self._publish(track: localTrack, options: publishOptions) - } - - return nil - } + try await set(source: .microphone, + enabled: enabled, + captureOptions: captureOptions, + publishOptions: publishOptions) } /// Enable or disable screen sharing. This has different behavior depending on the platform. @@ -368,84 +335,80 @@ public extension LocalParticipant { /// For advanced usage, you can create a relevant ``LocalVideoTrack`` and call ``LocalParticipant/publishVideoTrack(track:publishOptions:)``. @objc @discardableResult - func setScreenShare(enabled: Bool, - captureOptions: ScreenShareCaptureOptions? = nil, - publishOptions: VideoPublishOptions? = nil) async throws -> LocalTrackPublication? + func setScreenShare(enabled: Bool) async throws -> LocalTrackPublication? { + try await set(source: .screenShareVideo, enabled: enabled) + } + + @objc + @discardableResult + func set(source: Track.Source, + enabled: Bool, + captureOptions: CaptureOptions? = nil, + publishOptions: TrackPublishOptions? = nil) async throws -> LocalTrackPublication? { try await _publishSerialRunner.run { let room = try self.requireRoom() // Try to get existing publication - if let publication = self.getTrackPublication(source: .screenShareVideo) as? LocalTrackPublication { + if let publication = self.getTrackPublication(source: source) as? LocalTrackPublication { if enabled { try await publication.unmute() return publication } else { - try await self.unpublish(publication: publication) + if source == .camera || source == .microphone { + try await publication.mute() + } else { + try await self.unpublish(publication: publication) + } return publication } } else if enabled { - #if os(iOS) - - let localTrack: LocalVideoTrack - let defaultOptions = room._state.roomOptions.defaultScreenShareCaptureOptions - - if defaultOptions.useBroadcastExtension { - if captureOptions != nil { - logger.warning("Ignoring screen capture options passed to local participant's `\(#function)`; using room defaults instead.") - logger.warning("When using a broadcast extension, screen capture options must be set as room defaults.") + // Try to create a new track + if source == .camera { + let localTrack = LocalVideoTrack.createCameraTrack(options: (captureOptions as? CameraCaptureOptions) ?? room._state.roomOptions.defaultCameraCaptureOptions, + reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) + return try await self._publish(track: localTrack, options: publishOptions) + } else if source == .microphone { + let localTrack = LocalAudioTrack.createTrack(options: (captureOptions as? AudioCaptureOptions) ?? room._state.roomOptions.defaultAudioCaptureOptions, + reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) + return try await self._publish(track: localTrack, options: publishOptions) + } else if source == .screenShareVideo { + #if os(iOS) + + let localTrack: LocalVideoTrack + let defaultOptions = room._state.roomOptions.defaultScreenShareCaptureOptions + + if defaultOptions.useBroadcastExtension { + if captureOptions != nil { + logger.warning("Ignoring screen capture options passed to local participant's `\(#function)`; using room defaults instead.") + logger.warning("When using a broadcast extension, screen capture options must be set as room defaults.") + } + guard BroadcastManager.shared.isBroadcasting else { + BroadcastManager.shared.requestActivation() + return nil + } + // Wait until broadcasting to publish track + localTrack = LocalVideoTrack.createBroadcastScreenCapturerTrack(options: defaultOptions) + } else { + let options = (captureOptions as? ScreenShareCaptureOptions) ?? defaultOptions + localTrack = LocalVideoTrack.createInAppScreenShareTrack(options: options) } - guard BroadcastManager.shared.isBroadcasting else { - BroadcastManager.shared.requestActivation() - return nil + return try await self._publish(track: localTrack, options: publishOptions) + #elseif os(macOS) + if #available(macOS 12.3, *) { + let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource() + let track = LocalVideoTrack.createMacOSScreenShareTrack(source: mainDisplay, + options: (captureOptions as? ScreenShareCaptureOptions) ?? room._state.roomOptions.defaultScreenShareCaptureOptions, + reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) + return try await self._publish(track: track, options: publishOptions) } - // Wait until broadcasting to publish track - localTrack = LocalVideoTrack.createBroadcastScreenCapturerTrack(options: defaultOptions) - } else { - let options = captureOptions ?? defaultOptions - localTrack = LocalVideoTrack.createInAppScreenShareTrack(options: options) - } - return try await self._publish(track: localTrack, options: publishOptions) - #elseif os(macOS) - if #available(macOS 12.3, *) { - let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource() - let track = LocalVideoTrack.createMacOSScreenShareTrack(source: mainDisplay, - options: (captureOptions as? ScreenShareCaptureOptions) ?? room._state.roomOptions.defaultScreenShareCaptureOptions, - reportStatistics: room._state.roomOptions.reportRemoteTrackStatistics) - return try await self._publish(track: track, options: publishOptions) + #endif } - #endif } return nil } } - - @available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") - @objc - @discardableResult - func set(source: Track.Source, - enabled: Bool, - captureOptions: CaptureOptions? = nil, - publishOptions: TrackPublishOptions? = nil) async throws -> LocalTrackPublication? - { - switch source { - case .camera: - try await setCamera(enabled: enabled, - captureOptions: captureOptions as? CameraCaptureOptions, - publishOptions: publishOptions as? VideoPublishOptions) - case .microphone: - try await setMicrophone(enabled: enabled, - captureOptions: captureOptions as? AudioCaptureOptions, - publishOptions: publishOptions as? AudioPublishOptions) - case .screenShareVideo: - try await setScreenShare(enabled: enabled, - captureOptions: captureOptions as? ScreenShareCaptureOptions, - publishOptions: publishOptions as? VideoPublishOptions) - default: - nil - } - } } // MARK: - Simulcast codecs diff --git a/Sources/LiveKit/Protocols/VideoViewDelegate.swift b/Sources/LiveKit/Protocols/VideoViewDelegate.swift index 25f2da248..3089f81b3 100644 --- a/Sources/LiveKit/Protocols/VideoViewDelegate.swift +++ b/Sources/LiveKit/Protocols/VideoViewDelegate.swift @@ -18,7 +18,6 @@ import Foundation internal import LiveKitWebRTC -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") @objc public protocol VideoViewDelegate: AnyObject, Sendable { /// Dimensions of the VideoView itself has updated diff --git a/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift b/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift index bc2f622cc..ec20a5e44 100644 --- a/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift +++ b/Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift @@ -19,7 +19,6 @@ import SwiftUI /// A ``VideoView`` that can be used in SwiftUI. /// Supports both iOS and macOS. -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") public struct SwiftUIVideoView: NativeViewRepresentable { public typealias ViewType = VideoView @@ -81,7 +80,6 @@ public struct SwiftUIVideoView: NativeViewRepresentable { } /// This class receives ``VideoViewDelegate`` events since a struct can't be used for a delegate -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") @MainActor class VideoViewDelegateReceiver: VideoViewDelegate, Loggable { @Binding var isRendering: Bool diff --git a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift index a29a86d6c..3914a014e 100644 --- a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift @@ -23,7 +23,6 @@ import ReplayKit internal import LiveKitWebRTC -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") public class CameraCapturer: VideoCapturer, @unchecked Sendable { /// Current device used for capturing @objc @@ -262,7 +261,6 @@ public class CameraCapturer: VideoCapturer, @unchecked Sendable { } } -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") class VideoCapturerDelegateAdapter: NSObject, LKRTCVideoCapturerDelegate, Loggable { weak var cameraCapturer: CameraCapturer? @@ -286,7 +284,6 @@ class VideoCapturerDelegateAdapter: NSObject, LKRTCVideoCapturerDelegate, Loggab } } -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") public extension LocalVideoTrack { @objc static func createCameraTrack() -> LocalVideoTrack { diff --git a/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift b/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift index 9582a3d80..e78dd9cc0 100644 --- a/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift +++ b/Sources/LiveKit/Views/VideoView+MulticastDelegate.swift @@ -16,7 +16,6 @@ import Foundation -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView: MulticastDelegateProtocol { @objc(addDelegate:) public nonisolated func add(delegate: VideoViewDelegate) { diff --git a/Sources/LiveKit/Views/VideoView+PinchToZoom.swift b/Sources/LiveKit/Views/VideoView+PinchToZoom.swift index 8540ea9a2..b8f855b5b 100644 --- a/Sources/LiveKit/Views/VideoView+PinchToZoom.swift +++ b/Sources/LiveKit/Views/VideoView+PinchToZoom.swift @@ -16,7 +16,6 @@ internal import LiveKitWebRTC -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView { static let rampRate: Float = 32.0 /// Options for pinch to zoom in / out feature. diff --git a/Sources/LiveKit/Views/VideoView.swift b/Sources/LiveKit/Views/VideoView.swift index 0957b0f5b..2614504cb 100644 --- a/Sources/LiveKit/Views/VideoView.swift +++ b/Sources/LiveKit/Views/VideoView.swift @@ -22,7 +22,6 @@ internal import LiveKitWebRTC /// A ``NativeViewType`` that conforms to ``RTCVideoRenderer``. typealias NativeRendererView = LKRTCVideoRenderer & Mirrorable & NativeViewType -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") @objc public class VideoView: NativeView, Loggable { // MARK: - MulticastDelegate @@ -516,7 +515,6 @@ public class VideoView: NativeView, Loggable { // MARK: - Private -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") @MainActor private extension VideoView { private func ensureDebugTextView() -> TextView { @@ -585,7 +583,6 @@ private extension VideoView { // MARK: - RTCVideoRenderer -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView: VideoRenderer { public var isAdaptiveStreamEnabled: Bool { _state.read { $0.didLayout && !$0.isHidden && $0.isEnabled } @@ -735,7 +732,6 @@ extension VideoView: VideoRenderer { // MARK: - Internal -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView { nonisolated static func track(_ track1: VideoTrack?, isEqualWith track2: VideoTrack?) -> Bool { // equal if both tracks are nil @@ -749,7 +745,6 @@ extension VideoView { // MARK: - Static helper methods -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView { public static func isMetalAvailable() -> Bool { #if os(iOS) @@ -839,7 +834,6 @@ extension NSView { #endif #if os(iOS) || os(macOS) -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension LKRTCMTLVideoView: Mirrorable { func set(isMirrored: Bool) { if isMirrored { @@ -863,7 +857,6 @@ extension LKRTCMTLVideoView: Mirrorable { #endif #if os(iOS) -@available(iOSApplicationExtension, unavailable, message: "Not available in app extensions") extension VideoView.TransitionMode { func toAnimationOption(fromPosition position: AVCaptureDevice.Position? = nil) -> UIView.AnimationOptions? { switch self { From f235fe17d050ac12cec25fed04aad7069ef6d77d Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:29:30 +0800 Subject: [PATCH 5/7] Update lib ver 137.7151.10 --- LiveKitClient.podspec | 2 +- Package.swift | 2 +- Package@swift-6.0.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LiveKitClient.podspec b/LiveKitClient.podspec index b543526e7..60ddcd6f9 100644 --- a/LiveKitClient.podspec +++ b/LiveKitClient.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |spec| spec.source_files = "Sources/**/*" - spec.dependency("LiveKitWebRTC", "= 137.7151.09") + spec.dependency("LiveKitWebRTC", "= 137.7151.10") spec.dependency("SwiftProtobuf") spec.dependency("Logging", "= 1.5.4") spec.dependency("DequeModule", "= 1.1.4") diff --git a/Package.swift b/Package.swift index 336700972..d3488438c 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ let package = Package( ], dependencies: [ // LK-Prefixed Dynamic WebRTC XCFramework - .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.09"), + .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.10"), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.6.2"), .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.3.0"), diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index fffbb3abc..eb022d20c 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -20,7 +20,7 @@ let package = Package( ], dependencies: [ // LK-Prefixed Dynamic WebRTC XCFramework - .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.09"), + .package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.10"), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.6.2"), .package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.3.0"), From b355183dd718c47e75d21b941c1cd56a6b28e69a Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 14 Oct 2025 20:11:40 +0800 Subject: [PATCH 6/7] Update ci.yaml --- .github/workflows/ci.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d6f45545b..5f88092a3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -63,6 +63,10 @@ jobs: xcode: latest platform: "iOS Simulator,name=iPhone 17 Pro,OS=26.0" symbol-graph: true + - os: macos-26 + xcode: latest + platform: "iOS Simulator,name=iPhone 17 Pro,OS=26.0" + extension-api-only: true - os: macos-26 xcode: latest platform: "macOS" @@ -116,6 +120,7 @@ jobs: -destination 'platform=${{ matrix.platform }}' \ -enableAddressSanitizer ${{ matrix.asan == true && 'YES' || 'NO' }} \ -enableThreadSanitizer ${{ matrix.tsan == true && 'YES' || 'NO' }} \ + APPLICATION_EXTENSION_API_ONLY=${{ matrix.extension-api-only == true && 'YES' || 'NO' }} \ | xcbeautify --renderer github-actions - name: Run Tests @@ -134,6 +139,7 @@ jobs: -destination 'platform=${{ matrix.platform }}' \ -enableAddressSanitizer ${{ matrix.asan == true && 'YES' || 'NO' }} \ -enableThreadSanitizer ${{ matrix.tsan == true && 'YES' || 'NO' }} \ + APPLICATION_EXTENSION_API_ONLY=${{ matrix.extension-api-only == true && 'YES' || 'NO' }} \ -only-testing:$test \ -parallel-testing-enabled YES \ | xcbeautify --renderer github-actions @@ -151,6 +157,7 @@ jobs: -scheme LiveKit \ -configuration Release \ -destination 'platform=${{ matrix.platform }}' \ + APPLICATION_EXTENSION_API_ONLY=${{ matrix.extension-api-only == true && 'YES' || 'NO' }} \ OTHER_SWIFT_FLAGS="-Xfrontend -emit-symbol-graph\ -Xfrontend -emit-symbol-graph-dir\ -Xfrontend \"${PWD}/symbol-graph\"" \ From 14002416f8d48e34bc0589fef8ecab9b5c586f17 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:01:43 +0700 Subject: [PATCH 7/7] changes --- .changes/fix-app-ext-compile | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changes/fix-app-ext-compile diff --git a/.changes/fix-app-ext-compile b/.changes/fix-app-ext-compile new file mode 100644 index 000000000..c2147d6ad --- /dev/null +++ b/.changes/fix-app-ext-compile @@ -0,0 +1 @@ +patch type="fixed" "App extension compile issues"