From 1bead88286b24fe2b9c370732bd3997c72299a3d Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Tue, 3 Dec 2024 16:06:41 -0800 Subject: [PATCH 1/8] Don't show error dialog when broadcast stops without error --- .../Broadcast/Uploader/LKSampleHandler.swift | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift index c6498bc9a..715c78443 100644 --- a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift +++ b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift @@ -20,6 +20,8 @@ import ReplayKit #endif +import ObjectiveC.runtime + open class LKSampleHandler: RPBroadcastSampleHandler { private var clientConnection: BroadcastUploadSocketConnection? private var uploader: SampleUploader? @@ -55,14 +57,6 @@ open class LKSampleHandler: RPBroadcastSampleHandler { openConnection() } - override public func broadcastPaused() { - // User has requested to pause the broadcast. Samples will stop being delivered. - } - - override public func broadcastResumed() { - // User has requested to resume the broadcast. Samples delivery will resume. - } - override public func broadcastFinished() { // User has requested to finish the broadcast. DarwinNotificationCenter.shared.postNotification(.broadcastStopped) @@ -81,14 +75,21 @@ open class LKSampleHandler: RPBroadcastSampleHandler { private func setupConnection() { clientConnection?.didClose = { [weak self] error in logger.log(level: .debug, "client connection did close \(String(describing: error))") + guard let self else { + return + } if let error { - self?.finishBroadcastWithError(error) + self.finishBroadcastWithError(error) } else { - // the displayed failure message is more user friendly when using NSError instead of Error - let LKScreenSharingStopped = 10001 - let customError = NSError(domain: RPRecordingErrorDomain, code: LKScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"]) - self?.finishBroadcastWithError(customError) + // Call finishBroadcastWithError with nil error, which ends the broadcast without an error popup + // This is unsupported/undocumented but appears to work and is preferable to a cryptic error message + // See https://stackoverflow.com/a/63402492 for more discussion + let selector = #selector(RPBroadcastSampleHandler.finishBroadcastWithError) + if let methodIMP = self.method(for: selector) { + typealias MethodType = @convention(c) (AnyObject, Selector, NSError?) -> Void + unsafeBitCast(methodIMP, to: MethodType.self)(self, selector, nil) + } } } } From 8dc70548655ea3a91b30824005b580b37653d708 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Tue, 3 Dec 2024 16:25:31 -0800 Subject: [PATCH 2/8] override --- .../Broadcast/Uploader/LKSampleHandler.swift | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift index 715c78443..a92b3b13d 100644 --- a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift +++ b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift @@ -72,6 +72,35 @@ open class LKSampleHandler: RPBroadcastSampleHandler { } } + /// Override point to change the behavior when the socket connection has closed. + /// The default behavior is to pass errors through, and otherwise show nothing to the user. + /// + /// You should call `finishBroadcastWithError` in your implementation, but you can + /// add custom logging or present a custom error to the user instead. + /// + /// To present a custom error message: + /// ``` + /// self.finishBroadcastWithError(NSError( + /// domain: RPRecordingErrorDomain, + /// code: 10001, + /// userInfo: [NSLocalizedDescriptionKey: "My Custom Error Message"] + /// )) + /// ``` + open func connectionDidClose(error: Error?) { + if let error { + self.finishBroadcastWithError(error) + } else { + // Call finishBroadcastWithError with nil error, which ends the broadcast without an error popup + // This is unsupported/undocumented but appears to work and is preferable to a cryptic default LiveKit error message + // See https://stackoverflow.com/a/63402492 for more discussion + let selector = #selector(RPBroadcastSampleHandler.finishBroadcastWithError) + if let methodIMP = self.method(for: selector) { + typealias MethodType = @convention(c) (AnyObject, Selector, NSError?) -> Void + unsafeBitCast(methodIMP, to: MethodType.self)(self, selector, nil) + } + } + } + private func setupConnection() { clientConnection?.didClose = { [weak self] error in logger.log(level: .debug, "client connection did close \(String(describing: error))") @@ -79,18 +108,7 @@ open class LKSampleHandler: RPBroadcastSampleHandler { return } - if let error { - self.finishBroadcastWithError(error) - } else { - // Call finishBroadcastWithError with nil error, which ends the broadcast without an error popup - // This is unsupported/undocumented but appears to work and is preferable to a cryptic error message - // See https://stackoverflow.com/a/63402492 for more discussion - let selector = #selector(RPBroadcastSampleHandler.finishBroadcastWithError) - if let methodIMP = self.method(for: selector) { - typealias MethodType = @convention(c) (AnyObject, Selector, NSError?) -> Void - unsafeBitCast(methodIMP, to: MethodType.self)(self, selector, nil) - } - } + self.connectionDidClose(error: error) } } From 73c413bf1b61f96130e4bbfe5022a5731c0d60a5 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Tue, 3 Dec 2024 17:18:18 -0800 Subject: [PATCH 3/8] error --- Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift index a92b3b13d..31090bb68 100644 --- a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift +++ b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift @@ -95,7 +95,7 @@ open class LKSampleHandler: RPBroadcastSampleHandler { // See https://stackoverflow.com/a/63402492 for more discussion let selector = #selector(RPBroadcastSampleHandler.finishBroadcastWithError) if let methodIMP = self.method(for: selector) { - typealias MethodType = @convention(c) (AnyObject, Selector, NSError?) -> Void + typealias MethodType = @convention(c) (AnyObject, Selector, Error?) -> Void unsafeBitCast(methodIMP, to: MethodType.self)(self, selector, nil) } } From 9b82dac1d065b528147f70f2bf5ce2cad6febef5 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Tue, 3 Dec 2024 20:12:03 -0800 Subject: [PATCH 4/8] Use Objective-C bridge instead of runtime cast --- Package.swift | 5 +++++ Package@swift-5.9.swift | 5 +++++ .../LiveKit/Broadcast/Uploader/LKSampleHandler.swift | 11 ++--------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index cb96f4391..bf7183a9a 100644 --- a/Package.swift +++ b/Package.swift @@ -27,12 +27,17 @@ let package = Package( .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.13.4"), ], targets: [ + .target( + name: "LKObjCHelpers", + publicHeadersPath: "include" + ), .target( name: "LiveKit", dependencies: [ .product(name: "LiveKitWebRTC", package: "webrtc-xcframework"), .product(name: "SwiftProtobuf", package: "swift-protobuf"), .product(name: "Logging", package: "swift-log"), + "LKObjCHelpers", ], resources: [ .process("PrivacyInfo.xcprivacy"), diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index 7a62645ae..37e1c664d 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -29,12 +29,17 @@ let package = Package( .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.13.4"), ], targets: [ + .target( + name: "LKObjCHelpers", + publicHeadersPath: "include" + ), .target( name: "LiveKit", dependencies: [ .product(name: "LiveKitWebRTC", package: "webrtc-xcframework"), .product(name: "SwiftProtobuf", package: "swift-protobuf"), .product(name: "Logging", package: "swift-log"), + "LKObjCHelpers", ], resources: [ .process("PrivacyInfo.xcprivacy"), diff --git a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift index 31090bb68..92123d5a8 100644 --- a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift +++ b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift @@ -20,7 +20,7 @@ import ReplayKit #endif -import ObjectiveC.runtime +import LKObjCHelpers open class LKSampleHandler: RPBroadcastSampleHandler { private var clientConnection: BroadcastUploadSocketConnection? @@ -90,14 +90,7 @@ open class LKSampleHandler: RPBroadcastSampleHandler { if let error { self.finishBroadcastWithError(error) } else { - // Call finishBroadcastWithError with nil error, which ends the broadcast without an error popup - // This is unsupported/undocumented but appears to work and is preferable to a cryptic default LiveKit error message - // See https://stackoverflow.com/a/63402492 for more discussion - let selector = #selector(RPBroadcastSampleHandler.finishBroadcastWithError) - if let methodIMP = self.method(for: selector) { - typealias MethodType = @convention(c) (AnyObject, Selector, Error?) -> Void - unsafeBitCast(methodIMP, to: MethodType.self)(self, selector, nil) - } + LKObjCHelpers.finishBroadcastWithoutError(self) } } From 90c2e87092f92a1418d6f01ce9334b47e1e3de09 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Tue, 3 Dec 2024 20:21:31 -0800 Subject: [PATCH 5/8] add --- Sources/LKObjCHelpers/LKObjcCHelpers.m | 19 +++++++++++++++++++ Sources/LKObjCHelpers/include/LKObjcHelpers.h | 8 ++++++++ 2 files changed, 27 insertions(+) create mode 100644 Sources/LKObjCHelpers/LKObjcCHelpers.m create mode 100644 Sources/LKObjCHelpers/include/LKObjcHelpers.h diff --git a/Sources/LKObjCHelpers/LKObjcCHelpers.m b/Sources/LKObjCHelpers/LKObjcCHelpers.m new file mode 100644 index 000000000..b01e19d60 --- /dev/null +++ b/Sources/LKObjCHelpers/LKObjcCHelpers.m @@ -0,0 +1,19 @@ +#import "LKObjcHelpers.h" + +@implementation LKObjCHelpers + +NS_ASSUME_NONNULL_BEGIN + ++ (void)finishBroadcastWithoutError:(RPBroadcastSampleHandler *)handler API_AVAILABLE(macos(11.0), ios(10.0), visionos(1.0), tvos(10.0)) { + // Call finishBroadcastWithError with nil error, which ends the broadcast without an error popup + // This is unsupported/undocumented but appears to work and is preferable to an error dialog with a cryptic default message + // See https://stackoverflow.com/a/63402492 for more discussion + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wnonnull" + [handler finishBroadcastWithError:nil]; + #pragma clang diagnostic pop +} + +NS_ASSUME_NONNULL_END + +@end diff --git a/Sources/LKObjCHelpers/include/LKObjcHelpers.h b/Sources/LKObjCHelpers/include/LKObjcHelpers.h new file mode 100644 index 000000000..79de41c50 --- /dev/null +++ b/Sources/LKObjCHelpers/include/LKObjcHelpers.h @@ -0,0 +1,8 @@ +#import +#import + +@interface LKObjCHelpers : NSObject + ++ (void)finishBroadcastWithoutError:(RPBroadcastSampleHandler *)handler API_AVAILABLE(macos(11.0), ios(10.0), visionos(1.0), tvos(10.0)); + +@end From 210f332c20ad3788c2be1aa75227a4ac34a3a3f9 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Tue, 3 Dec 2024 21:06:30 -0800 Subject: [PATCH 6/8] fix --- Sources/LKObjCHelpers/LKObjcCHelpers.m | 2 +- Sources/LKObjCHelpers/include/LKObjcHelpers.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/LKObjCHelpers/LKObjcCHelpers.m b/Sources/LKObjCHelpers/LKObjcCHelpers.m index b01e19d60..d60164234 100644 --- a/Sources/LKObjCHelpers/LKObjcCHelpers.m +++ b/Sources/LKObjCHelpers/LKObjcCHelpers.m @@ -4,7 +4,7 @@ @implementation LKObjCHelpers NS_ASSUME_NONNULL_BEGIN -+ (void)finishBroadcastWithoutError:(RPBroadcastSampleHandler *)handler API_AVAILABLE(macos(11.0), ios(10.0), visionos(1.0), tvos(10.0)) { ++ (void)finishBroadcastWithoutError:(RPBroadcastSampleHandler *)handler API_AVAILABLE(macos(11.0)) { // Call finishBroadcastWithError with nil error, which ends the broadcast without an error popup // This is unsupported/undocumented but appears to work and is preferable to an error dialog with a cryptic default message // See https://stackoverflow.com/a/63402492 for more discussion diff --git a/Sources/LKObjCHelpers/include/LKObjcHelpers.h b/Sources/LKObjCHelpers/include/LKObjcHelpers.h index 79de41c50..1f92b91e2 100644 --- a/Sources/LKObjCHelpers/include/LKObjcHelpers.h +++ b/Sources/LKObjCHelpers/include/LKObjcHelpers.h @@ -3,6 +3,6 @@ @interface LKObjCHelpers : NSObject -+ (void)finishBroadcastWithoutError:(RPBroadcastSampleHandler *)handler API_AVAILABLE(macos(11.0), ios(10.0), visionos(1.0), tvos(10.0)); ++ (void)finishBroadcastWithoutError:(RPBroadcastSampleHandler *)handler API_AVAILABLE(macos(11.0)); @end From ee0e9a5e4942dcb25505b3048dadc1c4e92eefc4 Mon Sep 17 00:00:00 2001 From: hiroshihorie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:55:37 +0700 Subject: [PATCH 7/8] apply swiftformat --- Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift index 92123d5a8..ba81b2364 100644 --- a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift +++ b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift @@ -88,7 +88,7 @@ open class LKSampleHandler: RPBroadcastSampleHandler { /// ``` open func connectionDidClose(error: Error?) { if let error { - self.finishBroadcastWithError(error) + finishBroadcastWithError(error) } else { LKObjCHelpers.finishBroadcastWithoutError(self) } From 7ecde2e992fede219b39b4e3bfc2975c50cf3815 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Wed, 4 Dec 2024 09:27:01 -0800 Subject: [PATCH 8/8] put --- Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift index 92123d5a8..6b4e0aa72 100644 --- a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift +++ b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift @@ -57,6 +57,14 @@ open class LKSampleHandler: RPBroadcastSampleHandler { openConnection() } + override public func broadcastPaused() { + // User has requested to pause the broadcast. Samples will stop being delivered. + } + + override public func broadcastResumed() { + // User has requested to resume the broadcast. Samples delivery will resume. + } + override public func broadcastFinished() { // User has requested to finish the broadcast. DarwinNotificationCenter.shared.postNotification(.broadcastStopped)