Skip to content

Commit

Permalink
Improve handling of bundle settings (#550)
Browse files Browse the repository at this point in the history
Summary of changes:
- Report error when required bundle settings are not in place for
broadcast screen capture.
- Introduce `BundleInfo` property wrapper to reduce code duplication.
  • Loading branch information
ladvoc authored Jan 17, 2025
1 parent 7b4ec4d commit 8994740
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 31 deletions.
39 changes: 25 additions & 14 deletions Sources/LiveKit/Broadcast/BroadcastScreenCapturer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ internal import LiveKitWebRTC
#endif

class BroadcastScreenCapturer: BufferCapturer {
static let kRTCScreensharingSocketFD = "rtc_SSFD"
static let kAppGroupIdentifierKey = "RTCAppGroupIdentifier"
static let kRTCScreenSharingExtension = "RTCScreenSharingExtension"

var frameReader: SocketConnectionFrameReader?

override func startCapture() async throws -> Bool {
let didStart = try await super.startCapture()

guard didStart else { return false }

guard let identifier = lookUpAppGroupIdentifier(),
let filePath = filePathForIdentifier(identifier) else { return false }
guard let groupIdentifier = Self.groupIdentifier,
let socketPath = Self.socketPath(for: groupIdentifier)
else {
logger.error("Bundle settings improperly configured for screen capture")
return false
}

let bounds = await UIScreen.main.bounds
let width = bounds.size.width
Expand All @@ -57,7 +57,7 @@ class BroadcastScreenCapturer: BufferCapturer {
set(dimensions: targetDimensions)

let frameReader = SocketConnectionFrameReader()
guard let socketConnection = BroadcastServerSocketConnection(filePath: filePath, streamDelegate: frameReader)
guard let socketConnection = BroadcastServerSocketConnection(filePath: socketPath, streamDelegate: frameReader)
else { return false }
frameReader.didCapture = { pixelBuffer, rotation in
self.capture(pixelBuffer, rotation: rotation.toLKType())
Expand Down Expand Up @@ -85,16 +85,27 @@ class BroadcastScreenCapturer: BufferCapturer {
return true
}

private func lookUpAppGroupIdentifier() -> String? {
Bundle.main.infoDictionary?[BroadcastScreenCapturer.kAppGroupIdentifierKey] as? String
/// Identifier of the app group shared by the primary app and broadcast extension.
@BundleInfo("RTCAppGroupIdentifier")
static var groupIdentifier: String?

/// Bundle identifier of the broadcast extension.
@BundleInfo("RTCScreenSharingExtension")
static var screenSharingExtension: String?

/// Path to the socket file used for interprocess communication.
static var socketPath: String? {
guard let groupIdentifier = Self.groupIdentifier else { return nil }
return Self.socketPath(for: groupIdentifier)
}

private func filePathForIdentifier(_ identifier: String) -> String? {
guard let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifier)
else { return nil }
private static let kRTCScreensharingSocketFD = "rtc_SSFD"

let filePath = sharedContainer.appendingPathComponent(BroadcastScreenCapturer.kRTCScreensharingSocketFD).path
return filePath
private static func socketPath(for groupIdentifier: String) -> String? {
guard let sharedContainer = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)
else { return nil }
return sharedContainer.appendingPathComponent(Self.kRTCScreensharingSocketFD).path
}
}

Expand Down
37 changes: 37 additions & 0 deletions Sources/LiveKit/Broadcast/BundleInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2025 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation

/// A property wrapper type that reflects a value from a bundle's info dictionary.
@propertyWrapper
struct BundleInfo<Value>: Sendable {
private let key: String
private let bundle: Bundle

init(_ key: String, bundle: Bundle = .main) {
self.key = key
self.bundle = bundle
}

var wrappedValue: Value? {
guard let value = bundle.infoDictionary?[key] as? Value else {
logger.warning("Missing bundle property with key `\(key)`")
return nil
}
return value
}
}
20 changes: 5 additions & 15 deletions Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,14 @@ open class LKSampleHandler: RPBroadcastSampleHandler {
private var clientConnection: BroadcastUploadSocketConnection?
private var uploader: SampleUploader?

public var appGroupIdentifier: String? {
Bundle.main.infoDictionary?[BroadcastScreenCapturer.kAppGroupIdentifierKey] as? String
}

public var socketFilePath: String {
guard let appGroupIdentifier,
let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
else {
return ""
}

return sharedContainer.appendingPathComponent(BroadcastScreenCapturer.kRTCScreensharingSocketFD).path
}

override public init() {
super.init()

if let connection = BroadcastUploadSocketConnection(filePath: socketFilePath) {
let socketPath = BroadcastScreenCapturer.socketPath
if socketPath == nil {
logger.error("Bundle settings improperly configured for screen capture")
}
if let connection = BroadcastUploadSocketConnection(filePath: socketPath ?? "") {
clientConnection = connection
setupConnection()

Expand Down
6 changes: 4 additions & 2 deletions Sources/LiveKit/Participant/LocalParticipant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,10 @@ public extension LocalParticipant {
let localTrack: LocalVideoTrack
let options = (captureOptions as? ScreenShareCaptureOptions) ?? room._state.roomOptions.defaultScreenShareCaptureOptions
if options.useBroadcastExtension {
let screenShareExtensionId = Bundle.main.infoDictionary?[BroadcastScreenCapturer.kRTCScreenSharingExtension] as? String
await RPSystemBroadcastPickerView.show(for: screenShareExtensionId, showsMicrophoneButton: false)
await RPSystemBroadcastPickerView.show(
for: BroadcastScreenCapturer.screenSharingExtension,
showsMicrophoneButton: false
)
localTrack = LocalVideoTrack.createBroadcastScreenCapturerTrack(options: options)
} else {
localTrack = LocalVideoTrack.createInAppScreenShareTrack(options: options)
Expand Down

0 comments on commit 8994740

Please sign in to comment.