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/LKObjCHelpers/LKObjcCHelpers.m b/Sources/LKObjCHelpers/LKObjcCHelpers.m new file mode 100644 index 000000000..d60164234 --- /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)) { + // 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..1f92b91e2 --- /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)); + +@end diff --git a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift index c6498bc9a..2fc9eacb9 100644 --- a/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift +++ b/Sources/LiveKit/Broadcast/Uploader/LKSampleHandler.swift @@ -20,6 +20,8 @@ import ReplayKit #endif +import LKObjCHelpers + open class LKSampleHandler: RPBroadcastSampleHandler { private var clientConnection: BroadcastUploadSocketConnection? private var uploader: SampleUploader? @@ -78,18 +80,36 @@ 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 { + finishBroadcastWithError(error) + } else { + LKObjCHelpers.finishBroadcastWithoutError(self) + } + } + private func setupConnection() { clientConnection?.didClose = { [weak self] error in logger.log(level: .debug, "client connection did close \(String(describing: error))") - - if let 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) + guard let self else { + return } + + self.connectionDidClose(error: error) } }