Skip to content

Commit 5f2807d

Browse files
ladvochiroshihorie
andauthored
Revamp broadcast IPC (#565)
This PR revamps inter-process communication between the main app and the broadcast extension, replacing deprecated Core Foundation APIs. Additionally, it enhances testability, enables bi-directional communication, and introduces a more flexible message structure. Summary of new types: - `IPCChannel`: An abstraction for inter-process communication, built on top of the [Network](https://developer.apple.com/documentation/network) framework. Allows asynchronous sending and receiving of messages consisting of a dynamic header (any type conforming to `Codable`) and a data payload. - `BroadcastImageCodec`: Encapsulates functionality for encoding/decoding image samples for transport. For now, this uses the same method of JPEG encoding/decoding as the current implementation. - `BroadcastUploader`: Sends samples from ReplayKit to the main app, built on top of `IPCChannel`. - `BroadcastReceiver`: Receives samples from the broadcast extension, built on top of `IPCChannel`. This PR sets the groundwork for adding support for audio samples, but I decided to submit that in a separate PR (#576) as this is a large change (though not breaking). --------- Co-authored-by: Hiroshi Horie <[email protected]>
1 parent aa0a21d commit 5f2807d

22 files changed

+1123
-915
lines changed

.nanpa/ipc-revamp.kdl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
patch type="change" "Revamp IPC between main app and broadcast extension"

Sources/LiveKit/Broadcast/BroadcastBundleInfo.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ enum BroadcastBundleInfo {
3535
}
3636

3737
/// Path to the socket file used for interprocess communication.
38-
static var socketPath: String? {
38+
static var socketPath: SocketPath? {
3939
guard let groupIdentifier else { return nil }
4040
return Self.socketPath(for: groupIdentifier)
4141
}
@@ -54,11 +54,12 @@ enum BroadcastBundleInfo {
5454
private static let extensionSuffix = "broadcast"
5555
private static let socketFileDescriptor = "rtc_SSFD"
5656

57-
private static func socketPath(for groupIdentifier: String) -> String? {
57+
private static func socketPath(for groupIdentifier: String) -> SocketPath? {
5858
guard let sharedContainer = FileManager.default
5959
.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)
6060
else { return nil }
61-
return sharedContainer.appendingPathComponent(Self.socketFileDescriptor).path
61+
let path = sharedContainer.appendingPathComponent(Self.socketFileDescriptor).path
62+
return SocketPath(path)
6263
}
6364
}
6465

Sources/LiveKit/Broadcast/BroadcastScreenCapturer.swift

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,13 @@ internal import LiveKitWebRTC
2929
#endif
3030

3131
class BroadcastScreenCapturer: BufferCapturer {
32-
var frameReader: SocketConnectionFrameReader?
32+
private var receiver: BroadcastReceiver?
3333

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

3737
guard didStart else { return false }
3838

39-
guard let socketPath = BroadcastBundleInfo.socketPath else {
40-
logger.error("Bundle settings improperly configured for screen capture")
41-
return false
42-
}
43-
4439
let bounds = await UIScreen.main.bounds
4540
let width = bounds.size.width
4641
let height = bounds.size.height
@@ -53,22 +48,35 @@ class BroadcastScreenCapturer: BufferCapturer {
5348
.toEncodeSafeDimensions()
5449

5550
set(dimensions: targetDimensions)
51+
return createReceiver()
52+
}
5653

57-
let frameReader = SocketConnectionFrameReader()
58-
guard let socketConnection = BroadcastServerSocketConnection(filePath: socketPath, streamDelegate: frameReader)
59-
else { return false }
60-
frameReader.didCapture = { pixelBuffer, rotation in
61-
self.capture(pixelBuffer, rotation: rotation.toLKType())
54+
private func createReceiver() -> Bool {
55+
guard receiver == nil else {
56+
return false
6257
}
63-
frameReader.didEnd = { [weak self] in
64-
guard let self else { return }
65-
Task {
66-
try await self.stopCapture()
58+
guard let socketPath = BroadcastBundleInfo.socketPath else {
59+
logger.error("Bundle settings improperly configured for screen capture")
60+
return false
61+
}
62+
Task { [weak self] in
63+
do {
64+
let receiver = try await BroadcastReceiver(socketPath: socketPath)
65+
logger.debug("Broadcast receiver connected")
66+
self?.receiver = receiver
67+
68+
for try await sample in receiver.incomingSamples {
69+
switch sample {
70+
case let .image(imageBuffer, rotation):
71+
self?.capture(imageBuffer, rotation: rotation)
72+
}
73+
}
74+
logger.debug("Broadcast receiver closed")
75+
} catch {
76+
logger.error("Broadcast receiver error: \(error)")
6777
}
78+
_ = try? await self?.stopCapture()
6879
}
69-
frameReader.startCapture(with: socketConnection)
70-
self.frameReader = frameReader
71-
7280
return true
7381
}
7482

@@ -77,9 +85,7 @@ class BroadcastScreenCapturer: BufferCapturer {
7785

7886
// Already stopped
7987
guard didStop else { return false }
80-
81-
frameReader?.stopCapture()
82-
frameReader = nil
88+
receiver?.close()
8389
return true
8490
}
8591
}

Sources/LiveKit/Broadcast/BroadcastServerSocketConnection.swift

Lines changed: 0 additions & 200 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2025 LiveKit
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#if os(iOS)
18+
19+
/// Message header for communication between uploader and receiver.
20+
enum BroadcastIPCHeader: Codable {
21+
/// Image sample sent by uploader.
22+
case image(BroadcastImageCodec.Metadata, VideoRotation)
23+
}
24+
25+
#endif

0 commit comments

Comments
 (0)