From 66bd743ef5d8c1429e4e137fcaa9f09ad244d458 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 17 Dec 2023 18:04:44 +0900 Subject: [PATCH] Simplify error type (#282) --- .../LiveKit/Core/DataChannelPairActor.swift | 4 +- .../Core/Engine+SignalClientDelegate.swift | 4 +- .../Core/Engine+TransportDelegate.swift | 4 +- Sources/LiveKit/Core/Engine.swift | 40 ++-- .../LiveKit/Core/Room+EngineDelegate.swift | 16 +- .../LiveKit/Core/Room+MulticastDelegate.swift | 16 +- .../Core/Room+SignalClientDelegate.swift | 2 +- Sources/LiveKit/Core/Room.swift | 24 +- Sources/LiveKit/Core/SignalClient.swift | 48 ++-- Sources/LiveKit/Core/Transport.swift | 7 +- Sources/LiveKit/Errors.swift | 212 ++++++++++-------- .../Participant/LocalParticipant.swift | 12 +- .../Participant/RemoteParticipant.swift | 4 +- Sources/LiveKit/Protocols/RoomDelegate.swift | 18 +- Sources/LiveKit/Support/AsyncCompleter.swift | 18 +- Sources/LiveKit/Support/HTTP.swift | 2 +- Sources/LiveKit/Support/WebSocket.swift | 5 +- .../Track/Capturers/CameraCapturer.swift | 14 +- .../Track/Capturers/MacOSScreenCapturer.swift | 10 +- .../LocalTrackPublication.swift | 4 +- .../RemoteTrackPublication.swift | 8 +- .../TrackPublications/TrackPublication.swift | 2 +- .../LiveKit/Types/ConnectionState+ObjC.swift | 25 --- Sources/LiveKit/Types/ConnectionState.swift | 52 +---- Sources/LiveKit/Types/DisconnectReason.swift | 75 ------- Sources/LiveKit/Types/IceCandidate.swift | 4 +- Sources/LiveKit/Types/VideoCodec.swift | 4 +- 27 files changed, 243 insertions(+), 391 deletions(-) delete mode 100644 Sources/LiveKit/Types/ConnectionState+ObjC.swift delete mode 100644 Sources/LiveKit/Types/DisconnectReason.swift diff --git a/Sources/LiveKit/Core/DataChannelPairActor.swift b/Sources/LiveKit/Core/DataChannelPairActor.swift index 0662a30de..5af7497c9 100644 --- a/Sources/LiveKit/Core/DataChannelPairActor.swift +++ b/Sources/LiveKit/Core/DataChannelPairActor.swift @@ -76,7 +76,7 @@ actor DataChannelPairActor: NSObject, Loggable { public func send(userPacket: Livekit_UserPacket, reliability: Reliability) throws { guard isOpen else { - throw InternalError.state(message: "Data channel is not open") + throw LiveKitError(.invalidState, message: "Data channel is not open") } let packet = Livekit_DataPacket.with { @@ -89,7 +89,7 @@ actor DataChannelPairActor: NSObject, Loggable { let channel = (reliability == .reliable) ? _reliableChannel : _lossyChannel guard let sendDataResult = channel?.sendData(rtcData), sendDataResult else { - throw InternalError.state(message: "sendData failed") + throw LiveKitError(.invalidState, message: "sendData failed") } } diff --git a/Sources/LiveKit/Core/Engine+SignalClientDelegate.swift b/Sources/LiveKit/Core/Engine+SignalClientDelegate.swift index 7a0c5f284..6671bd406 100644 --- a/Sources/LiveKit/Core/Engine+SignalClientDelegate.swift +++ b/Sources/LiveKit/Core/Engine+SignalClientDelegate.swift @@ -23,9 +23,9 @@ extension Engine: SignalClientDelegate { // connectionState did update if state.connectionState != oldState.connectionState, // did disconnect - case let .disconnected(reason) = state.connectionState, + case .disconnected = state.connectionState, // only attempt re-connect if disconnected(reason: network) - case .networkError = reason, + case .network = state.disconnectError?.type, // engine is currently connected state case .connected = _state.connectionState { diff --git a/Sources/LiveKit/Core/Engine+TransportDelegate.swift b/Sources/LiveKit/Core/Engine+TransportDelegate.swift index 9263be7b9..ea8886802 100644 --- a/Sources/LiveKit/Core/Engine+TransportDelegate.swift +++ b/Sources/LiveKit/Core/Engine+TransportDelegate.swift @@ -32,7 +32,7 @@ extension Engine: TransportDelegate { publisherTransportConnectedCompleter.resume(returning: ()) } - if _state.connectionState.isConnected { + if _state.connectionState == .connected { // Attempt re-connect if primary or publisher transport failed if transport.isPrimary || (_state.hasPublished && transport.target == .publisher), [.disconnected, .failed].contains(pcState) { log("[reconnect] starting, reason: transport disconnected or failed") @@ -56,7 +56,7 @@ extension Engine: TransportDelegate { // execute block when connected execute(when: { state, _ in state.connectionState == .connected }, // always remove this block when disconnected - removeWhen: { state, _ in state.connectionState == .disconnected() }) + removeWhen: { state, _ in state.connectionState == .disconnected }) { [weak self] in guard let self else { return } self.notify { $0.engine(self, didAddTrack: track, rtpReceiver: rtpReceiver, streams: streams) } diff --git a/Sources/LiveKit/Core/Engine.swift b/Sources/LiveKit/Core/Engine.swift index e5c69a349..64efb9dc2 100644 --- a/Sources/LiveKit/Core/Engine.swift +++ b/Sources/LiveKit/Core/Engine.swift @@ -34,7 +34,8 @@ class Engine: MulticastDelegate { // preferred reconnect mode which will be used only for next attempt var nextPreferredReconnectMode: ReconnectMode? var reconnectMode: ReconnectMode? - var connectionState: ConnectionState = .disconnected() + var connectionState: ConnectionState = .disconnected + var disconnectError: LiveKitError? var connectStopwatch = Stopwatch(label: "connect") var hasPublished: Bool = false } @@ -159,20 +160,22 @@ class Engine: MulticastDelegate { $0.connectionState = .connected } - } catch is CancellationError { - // Cancelled by .user - try await cleanUp(reason: .user) } catch { - try await cleanUp(reason: .networkError(error)) + try await cleanUp(withError: error) + // Re-throw error + throw error } } // cleanUp (reset) both Room & Engine's state - func cleanUp(reason: DisconnectReason? = nil, isFullReconnect: Bool = false) async throws { + func cleanUp(withError disconnectError: Error? = nil, + isFullReconnect: Bool = false) async throws + { // This should never happen since Engine is owned by Room let room = try requireRoom() // Call Room's cleanUp - await room.cleanUp(reason: reason, isFullReconnect: isFullReconnect) + await room.cleanUp(withError: disconnectError, + isFullReconnect: isFullReconnect) } // Resets state of transports @@ -362,18 +365,18 @@ extension Engine { func startReconnect() async throws { guard case .connected = _state.connectionState else { - log("[reconnect] must be called with connected state", .warning) - throw EngineError.state(message: "Must be called with connected state") + log("[Reconnect] Must be called with connected state", .error) + throw LiveKitError(.invalidState) } guard let url = _state.url, let token = _state.token else { - log("[reconnect] url or token is nil", .warning) - throw EngineError.state(message: "url or token is nil") + log("[Reconnect] Url or token is nil", .error) + throw LiveKitError(.invalidState) } guard subscriber != nil, publisher != nil else { - log("[reconnect] publisher or subscriber is nil", .warning) - throw EngineError.state(message: "Publisher or Subscriber is nil") + log("[Reconnect] Publisher or subscriber is nil", .error) + throw LiveKitError(.invalidState) } // quick connect sequence, does not update connection state @@ -420,7 +423,8 @@ extension Engine { guard let url = _state.url, let token = _state.token else { - throw EngineError.state(message: "url or token is nil") + log("[Reconnect] Url or token is nil") + throw LiveKitError(.invalidState) } try await fullConnectSequence(url, token) @@ -461,12 +465,12 @@ extension Engine { do { try await retryingTask.value // Re-connect sequence successful - log("[reconnect] sequence completed") + log("[reconnect] Sequence completed") _state.mutate { $0.connectionState = .connected } } catch { log("[Reconnect] Sequence failed with error: \(error)") // Finally disconnect if all attempts fail - try await cleanUp(reason: .networkError(error)) + try await cleanUp(withError: error) } } } @@ -517,12 +521,12 @@ extension Engine { extension Engine { func requireRoom() throws -> Room { - guard let room = _room else { throw EngineError.state(message: "Room is nil") } + guard let room = _room else { throw LiveKitError(.invalidState, message: "Room is nil") } return room } func requirePublisher() throws -> Transport { - guard let publisher else { throw EngineError.state(message: "Publisher is nil") } + guard let publisher else { throw LiveKitError(.invalidState, message: "Publisher is nil") } return publisher } } diff --git a/Sources/LiveKit/Core/Room+EngineDelegate.swift b/Sources/LiveKit/Core/Room+EngineDelegate.swift index 2faf57563..731713530 100644 --- a/Sources/LiveKit/Core/Room+EngineDelegate.swift +++ b/Sources/LiveKit/Core/Room+EngineDelegate.swift @@ -40,29 +40,23 @@ extension Room: EngineDelegate { } delegates.notify(label: { "room.didUpdate connectionState: \(state.connectionState) oldValue: \(oldState.connectionState)" }) { - // Objective-C support - $0.room?(self, didUpdate: state.connectionState.toObjCType(), oldValue: oldState.connectionState.toObjCType()) - // Swift only - if let delegateSwift = $0 as? RoomDelegate { - delegateSwift.room(self, didUpdate: state.connectionState, oldValue: oldState.connectionState) - } + $0.room?(self, didUpdate: state.connectionState, oldValue: oldState.connectionState) } // Legacy connection delegates if case .connected = state.connectionState { let didReconnect = oldState.connectionState == .reconnecting delegates.notify { $0.room?(self, didConnect: didReconnect) } - } else if case let .disconnected(reason) = state.connectionState { + } else if case .disconnected = state.connectionState { if case .connecting = oldState.connectionState { - let error = reason?.networkError ?? NetworkError.disconnected(message: "Did fail to connect", rawError: nil) - delegates.notify { $0.room?(self, didFailToConnect: error) } + delegates.notify { $0.room?(self, didFailToConnect: oldState.disconnectError) } } else { - delegates.notify { $0.room?(self, didDisconnect: reason?.networkError) } + delegates.notify { $0.room?(self, didDisconnect: state.disconnectError) } } } } - if state.connectionState.isReconnecting, state.reconnectMode == .full, oldState.reconnectMode != .full { + if state.connectionState == .reconnecting, state.reconnectMode == .full, oldState.reconnectMode != .full { Task { // Started full reconnect await cleanUpParticipants(notify: true) diff --git a/Sources/LiveKit/Core/Room+MulticastDelegate.swift b/Sources/LiveKit/Core/Room+MulticastDelegate.swift index 5b918a476..478a5e40f 100644 --- a/Sources/LiveKit/Core/Room+MulticastDelegate.swift +++ b/Sources/LiveKit/Core/Room+MulticastDelegate.swift @@ -17,10 +17,12 @@ import Foundation extension Room: MulticastDelegateProtocol { + @objc(addDelegate:) public func add(delegate: RoomDelegate) { delegates.add(delegate: delegate) } + @objc(removeDelegate:) public func remove(delegate: RoomDelegate) { delegates.remove(delegate: delegate) } @@ -29,18 +31,4 @@ extension Room: MulticastDelegateProtocol { public func removeAllDelegates() { delegates.removeAllDelegates() } - - /// Only for Objective-C. - @objc(addDelegate:) - @available(swift, obsoleted: 1.0) - public func addObjC(delegate: RoomDelegateObjC) { - delegates.add(delegate: delegate) - } - - /// Only for Objective-C. - @objc(removeDelegate:) - @available(swift, obsoleted: 1.0) - public func removeObjC(delegate: RoomDelegateObjC) { - delegates.remove(delegate: delegate) - } } diff --git a/Sources/LiveKit/Core/Room+SignalClientDelegate.swift b/Sources/LiveKit/Core/Room+SignalClientDelegate.swift index 40e99c646..7c4fbca2a 100644 --- a/Sources/LiveKit/Core/Room+SignalClientDelegate.swift +++ b/Sources/LiveKit/Core/Room+SignalClientDelegate.swift @@ -28,7 +28,7 @@ extension Room: SignalClientDelegate { } else { Task { // Server indicates it's not recoverable - await cleanUp(reason: reason.toLKType()) + await cleanUp(withError: LiveKitError.from(reason: reason)) } } } diff --git a/Sources/LiveKit/Core/Room.swift b/Sources/LiveKit/Core/Room.swift index ab608774c..24f9c3c92 100644 --- a/Sources/LiveKit/Core/Room.swift +++ b/Sources/LiveKit/Core/Room.swift @@ -24,7 +24,7 @@ import Foundation public class Room: NSObject, ObservableObject, Loggable { // MARK: - MulticastDelegate - public let delegates = MulticastDelegate() + public let delegates = MulticastDelegate() // MARK: - Public @@ -76,12 +76,11 @@ public class Room: NSObject, ObservableObject, Loggable { public var token: String? { engine._state.token } /// Current ``ConnectionState`` of the ``Room``. + @objc public var connectionState: ConnectionState { engine._state.connectionState } - /// Only for Objective-C. - @objc(connectionState) - @available(swift, obsoleted: 1.0) - public var connectionStateObjC: ConnectionStateObjC { engine._state.connectionState.toObjCType() } + @objc + public var disconnectError: LiveKitError? { engine._state.disconnectError } public var connectStopwatch: Stopwatch { engine._state.connectStopwatch } @@ -141,7 +140,7 @@ public class Room: NSObject, ObservableObject, Loggable { } @objc - public init(delegate: RoomDelegateObjC? = nil, + public init(delegate: RoomDelegate? = nil, connectOptions: ConnectOptions? = nil, roomOptions: RoomOptions? = nil) { @@ -252,7 +251,7 @@ public class Room: NSObject, ObservableObject, Loggable { log("Failed to send leave with error: \(error)") } - await cleanUp(reason: .user) + await cleanUp() } } @@ -260,10 +259,10 @@ public class Room: NSObject, ObservableObject, Loggable { extension Room { // Resets state of Room - func cleanUp(reason: DisconnectReason? = nil, + func cleanUp(withError disconnectError: Error? = nil, isFullReconnect: Bool = false) async { - log("Reason: \(String(describing: reason))") + log("withError: \(String(describing: disconnectError))") // Start Engine cleanUp sequence @@ -281,11 +280,12 @@ extension Room { connectionState: $0.connectionState ) : Engine.State( connectOptions: $0.connectOptions, - connectionState: .disconnected(reason: reason) + connectionState: .disconnected, + disconnectError: LiveKitError.from(error: disconnectError) ) } - await engine.signalClient.cleanUp(reason: reason) + await engine.signalClient.cleanUp(withError: disconnectError) await engine.cleanUpRTC() await cleanUpParticipants() // Reset state @@ -315,7 +315,7 @@ extension Room { func _onParticipantDidDisconnect(identity: Identity) async throws { guard let participant = _state.mutate({ $0.remoteParticipants.removeValue(forKey: identity) }) else { - throw EngineError.state(message: "Participant not found for \(identity)") + throw LiveKitError(.invalidState, message: "Participant not found for \(identity)") } await participant.cleanUp(notify: true) diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index 1010e0316..85df3d21e 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -27,7 +27,8 @@ class SignalClient: MulticastDelegate { // MARK: - Internal public struct State: Equatable { - var connectionState: ConnectionState = .disconnected() + var connectionState: ConnectionState = .disconnected + var disconnectError: LiveKitError? } // MARK: - Private @@ -89,7 +90,7 @@ class SignalClient: MulticastDelegate { reconnectMode: reconnectMode, adaptiveStream: adaptiveStream) else { - throw InternalError.parse(message: "Failed to parse url") + throw LiveKitError(.failedToParseUrl) } log("Connecting with url: \(urlString)") @@ -110,7 +111,7 @@ class SignalClient: MulticastDelegate { self.onWebSocketMessage(message: message) } } catch { - await self.cleanUp(reason: .networkError(error)) + await self.cleanUp(withError: error) } self.log("Did exit WebSocket message loop...") } @@ -123,17 +124,17 @@ class SignalClient: MulticastDelegate { } catch { // Skip validation if user cancelled if error is CancellationError { - await cleanUp(reason: .user) + await cleanUp(withError: error) throw error } // Skip validation if reconnect mode if reconnectMode != nil { - await cleanUp(reason: .networkError(error)) + await cleanUp(withError: error) throw error } - await cleanUp(reason: .networkError(error)) + await cleanUp(withError: error) // Validate... @@ -143,21 +144,24 @@ class SignalClient: MulticastDelegate { adaptiveStream: adaptiveStream, validate: true) else { - throw InternalError.parse(message: "Failed to parse validation url") + throw LiveKitError(.failedToParseUrl, message: "Failed to parse validation url") } log("Validating with url: \(validateUrl)...") let validationResponse = try await HTTP.requestString(from: validateUrl) log("Validate response: \(validationResponse)") // re-throw with validation response - throw SignalClientError.connect(message: "Validation response: \"\(validationResponse)\"") + throw LiveKitError(.network, message: "Validation response: \"\(validationResponse)\"") } } - func cleanUp(reason: DisconnectReason? = nil) async { - log("reason: \(String(describing: reason))") + func cleanUp(withError disconnectError: Error? = nil) async { + log("withError: \(String(describing: disconnectError))") - _state.mutate { $0.connectionState = .disconnected(reason: reason) } + _state.mutate { + $0.connectionState = .disconnected + $0.disconnectError = LiveKitError.from(error: disconnectError) + } _pingIntervalTimer = nil _pingTimeoutTimer = nil @@ -182,24 +186,23 @@ class SignalClient: MulticastDelegate { private extension SignalClient { // send request or enqueue while reconnecting func sendRequest(_ request: Livekit_SignalRequest, enqueueIfReconnecting: Bool = true) async throws { - guard !(_state.connectionState.isReconnecting && request.canEnqueue() && enqueueIfReconnecting) else { + guard !(_state.connectionState == .reconnecting && request.canEnqueue() && enqueueIfReconnecting) else { log("Queuing request while reconnecting, request: \(request)") await _requestQueue.enqueue(request) return } guard case .connected = _state.connectionState else { - log("not connected", .error) - throw SignalClientError.state(message: "Not connected") + log("Not connected", .error) + throw LiveKitError(.invalidState, message: "Not connected") } guard let data = try? request.serializedData() else { - log("could not serialize data", .error) - throw InternalError.convert(message: "Could not serialize data") + log("Could not serialize request data", .error) + throw LiveKitError(.failedToConvertData, message: "Failed to convert data") } - let webSocket = try await requireWebSocket() - + let webSocket = try requireWebSocket() try await webSocket.send(data: data) } @@ -529,7 +532,7 @@ extension SignalClient { defer { if shouldDisconnect { Task { - await cleanUp(reason: .networkError(NetworkError.disconnected(message: "Simulate scenario"))) + await cleanUp() } } } @@ -563,7 +566,7 @@ private extension SignalClient { guard let self else { return } self.log("ping/pong timed out", .error) Task { - await self.cleanUp(reason: .networkError(SignalClientError.serverPingTimedOut())) + await self.cleanUp(withError: LiveKitError(.serverPingTimedOut)) } } timer.resume() @@ -618,10 +621,11 @@ extension Livekit_SignalRequest { } private extension SignalClient { - func requireWebSocket() async throws -> WebSocket { + func requireWebSocket() throws -> WebSocket { // This shouldn't happen guard let result = _webSocket else { - throw SignalClientError.state(message: "WebSocket is nil") + log("WebSocket is nil", .error) + throw LiveKitError(.invalidState, message: "WebSocket is nil") } return result diff --git a/Sources/LiveKit/Core/Transport.swift b/Sources/LiveKit/Core/Transport.swift index 9f442d4c6..08734e9fd 100644 --- a/Sources/LiveKit/Core/Transport.swift +++ b/Sources/LiveKit/Core/Transport.swift @@ -83,7 +83,8 @@ class Transport: MulticastDelegate { guard let pc = Engine.createPeerConnection(config, constraints: .defaultPCConstraints) else { - throw EngineError.webRTC(message: "failed to create peerConnection") + // log("[WebRTC] Failed to create PeerConnection", .error) + throw LiveKitError(.webRTC, message: "Failed to create PeerConnection") } self.target = target @@ -277,7 +278,7 @@ extension Transport { transceiverInit: LKRTCRtpTransceiverInit) throws -> LKRTCRtpTransceiver { guard let transceiver = DispatchQueue.liveKitWebRTC.sync(execute: { _pc.addTransceiver(with: track, init: transceiverInit) }) else { - throw EngineError.webRTC(message: "Failed to add transceiver") + throw LiveKitError(.webRTC, message: "Failed to add transceiver") } return transceiver @@ -285,7 +286,7 @@ extension Transport { func remove(track sender: LKRTCRtpSender) throws { guard DispatchQueue.liveKitWebRTC.sync(execute: { _pc.removeTrack(sender) }) else { - throw EngineError.webRTC(message: "Failed to remove track") + throw LiveKitError(.webRTC, message: "Failed to remove track") } } diff --git a/Sources/LiveKit/Errors.swift b/Sources/LiveKit/Errors.swift index 2361911cf..a70f7881e 100644 --- a/Sources/LiveKit/Errors.swift +++ b/Sources/LiveKit/Errors.swift @@ -18,123 +18,147 @@ import Foundation @_implementationOnly import WebRTC -public protocol LiveKitError: Error, CustomStringConvertible {} - -extension LiveKitError { - func buildDescription(_ name: String, _ message: String? = nil, rawError: Error? = nil) -> String { - "\(String(describing: type(of: self))).\(name)" + (message != nil ? " \(message!)" : "") + (rawError != nil ? " rawError: \(rawError!.localizedDescription)" : "") - } +public enum LiveKitErrorType: Int { + case unknown = 0 + case cancelled = 100 + case timedOut = 101 + case failedToParseUrl = 102 + case failedToConvertData = 103 + case invalidState = 104 + + case webRTC = 201 + + case network // Network issue + + // Server + case duplicateIdentity = 500 + case serverShutdown = 501 + case participantRemoved = 502 + case roomDeleted = 503 + case stateMismatch = 504 + case joinFailure = 505 + + // + case serverPingTimedOut = 601 + + // Device related + case deviceNotFound = 701 + case captureFormatNotFound = 702 + case unableToResolveFPSRange = 703 + case capturerDimensionsNotResolved = 704 } -public extension LiveKitError where Self: LocalizedError { - var localizedDescription: String { - description - } -} - -public enum RoomError: LiveKitError { - case missingRoomId(String) - case invalidURL(String) - case protocolError(String) - - public var description: String { - "RoomError" - } -} - -public enum InternalError: LiveKitError { - case state(message: String? = nil) - case parse(message: String? = nil) - case convert(message: String? = nil) - case timeout(message: String? = nil) - +extension LiveKitErrorType: CustomStringConvertible { public var description: String { switch self { - case let .state(message): return buildDescription("state", message) - case let .parse(message): return buildDescription("parse", message) - case let .convert(message): return buildDescription("convert", message) - case let .timeout(message): return buildDescription("timeout", message) + case .cancelled: + return "Cancelled" + case .timedOut: + return "Timed out" + case .failedToParseUrl: + return "Failed to parse URL" + case .failedToConvertData: + return "Failed to convert data" + case .invalidState: + return "Invalid state" + case .webRTC: + return "WebRTC error" + case .network: + return "Network error" + case .duplicateIdentity: + return "Duplicate Participant identity" + case .serverShutdown: + return "Server shutdown" + case .participantRemoved: + return "Participant removed" + case .roomDeleted: + return "Reoom deleted" + case .stateMismatch: + return "Server state mismatch" + case .joinFailure: + return "Server join failure" + case .serverPingTimedOut: + return "Server ping timed out" + case .deviceNotFound: + return "Device not found" + case .captureFormatNotFound: + return "Capture format not found" + case .unableToResolveFPSRange: + return "Unable to resolved FPS range" + case .capturerDimensionsNotResolved: + return "Capturer dimensions not resolved" + default: return "Unknown" } } } -public enum EngineError: LiveKitError { - // WebRTC lib returned error - case webRTC(message: String?, Error? = nil) - case state(message: String? = nil) - case timedOut(message: String? = nil) +@objc +public class LiveKitError: NSError { + public let type: LiveKitErrorType + public let message: String? + public let underlyingError: Error? - public var description: String { - switch self { - case let .webRTC(message, _): return buildDescription("webRTC", message) - case let .state(message): return buildDescription("state", message) - case let .timedOut(message): return buildDescription("timedOut", message) - } + override public var underlyingErrors: [Error] { + [underlyingError].compactMap { $0 } } -} - -public enum TrackError: LiveKitError { - case state(message: String? = nil) - case type(message: String? = nil) - case duplicate(message: String? = nil) - case capturer(message: String? = nil) - case publish(message: String? = nil) - case unpublish(message: String? = nil) - case timedOut(message: String? = nil) - public var description: String { - switch self { - case let .state(message): return buildDescription("state", message) - case let .type(message): return buildDescription("type", message) - case let .duplicate(message): return buildDescription("duplicate", message) - case let .capturer(message): return buildDescription("capturer", message) - case let .publish(message): return buildDescription("publish", message) - case let .unpublish(message): return buildDescription("unpublish", message) - case let .timedOut(message): return buildDescription("timedOut", message) + public init(_ type: LiveKitErrorType, + message: String? = nil, + internalError: Error? = nil) + { + func _computeDescription() -> String { + if let message { + return "\(String(describing: type))(\(message))" + } + return String(describing: type) } - } -} -public enum SignalClientError: LiveKitError { - case cancelled - case state(message: String? = nil) - case socketError(rawError: Error?) - case close(message: String? = nil) - case connect(message: String? = nil) - case timedOut(message: String? = nil) - case serverPingTimedOut(message: String? = nil) + self.type = type + self.message = message + underlyingError = internalError + super.init(domain: "io.livekit.swift-sdk", + code: type.rawValue, + userInfo: [NSLocalizedDescriptionKey: _computeDescription()]) + } - public var description: String { - switch self { - case .cancelled: return buildDescription("cancelled") - case let .state(message): return buildDescription("state", message) - case let .socketError(rawError): return buildDescription("socketError", rawError: rawError) - case let .close(message): return buildDescription("close", message) - case let .connect(message): return buildDescription("connect", message) - case let .timedOut(message): return buildDescription("timedOut", message) - case let .serverPingTimedOut(message): return buildDescription("serverPingTimedOut", message) - } + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") } } -public enum NetworkError: LiveKitError { - case disconnected(message: String? = nil, rawError: Error? = nil) - case response(message: String? = nil) +extension LiveKitError { + static func from(error: Error?) -> LiveKitError? { + guard let error else { return nil } + if let error = error as? LiveKitError { + return error + } - public var description: String { - switch self { - case let .disconnected(message, rawError): return buildDescription("disconnected", message, rawError: rawError) - case let .response(message): return buildDescription("response", message) + if error is CancellationError { + return LiveKitError(.cancelled) } + + // TODO: Identify more network error types + logger.log("Uncategorized error for: \(String(describing: error))", type: LiveKitError.self) + return LiveKitError(.unknown) } -} -public enum TransportError: LiveKitError { - case timedOut(message: String? = nil) + static func from(reason: Livekit_DisconnectReason) -> LiveKitError { + LiveKitError(reason.toLKType()) + } +} - public var description: String { +extension Livekit_DisconnectReason { + func toLKType() -> LiveKitErrorType { switch self { - case let .timedOut(message): return buildDescription("timedOut", message) + case .clientInitiated: return .cancelled + case .duplicateIdentity: return .duplicateIdentity + case .serverShutdown: return .serverShutdown + case .participantRemoved: return .participantRemoved + case .roomDeleted: return .roomDeleted + case .stateMismatch: return .stateMismatch + case .joinFailure: return .joinFailure + default: return .unknown } } } diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index 6f1196817..b9c92ba92 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -47,15 +47,15 @@ public class LocalParticipant: Participant { log("[publish] \(track) options: \(String(describing: publishOptions ?? nil))...", .info) guard let publisher = room.engine.publisher else { - throw EngineError.state(message: "Publisher is nil") + throw LiveKitError(.invalidState, message: "Publisher is nil") } guard _state.trackPublications.values.first(where: { $0.track === track }) == nil else { - throw TrackError.publish(message: "This track has already been published.") + throw LiveKitError(.invalidState, message: "This track has already been published.") } guard track is LocalVideoTrack || track is LocalAudioTrack else { - throw TrackError.publish(message: "Unknown LocalTrack type") + throw LiveKitError(.invalidState, message: "Unknown LocalTrack type") } // Try to start the Track @@ -80,7 +80,7 @@ public class LocalParticipant: Participant { if let track = track as? LocalVideoTrack { guard let dimensions else { - throw TrackError.publish(message: "VideoCapturer dimensions are unknown") + throw LiveKitError(.capturerDimensionsNotResolved, message: "VideoCapturer dimensions are not resolved") } self.log("[publish] computing encode settings with dimensions: \(dimensions)...") @@ -550,11 +550,11 @@ extension LocalParticipant { log("[Publish/Backup] Additional video codec: \(videoCodec)...") guard let track = localTrackPublication.track as? LocalVideoTrack else { - throw EngineError.state(message: "Track is nil") + throw LiveKitError(.invalidState, message: "Track is nil") } if !videoCodec.isBackup { - throw EngineError.state(message: "Attempted to publish a non-backup video codec as backup") + throw LiveKitError(.invalidState, message: "Attempted to publish a non-backup video codec as backup") } let publisher = try room.engine.requirePublisher() diff --git a/Sources/LiveKit/Participant/RemoteParticipant.swift b/Sources/LiveKit/Participant/RemoteParticipant.swift index 4b4309091..a635c935f 100644 --- a/Sources/LiveKit/Participant/RemoteParticipant.swift +++ b/Sources/LiveKit/Participant/RemoteParticipant.swift @@ -87,7 +87,7 @@ public class RemoteParticipant: Participant { guard let publication = getTrackPublication(sid: sid) else { log("Could not subscribe to mediaTrack \(sid), unable to locate track publication. existing sids: (\(_state.trackPublications.keys.joined(separator: ", ")))", .error) - let error = TrackError.state(message: "Could not find published track with sid: \(sid)") + let error = LiveKitError(.invalidState, message: "Could not find published track with sid: \(sid)") delegates.notify(label: { "participant.didFailToSubscribe trackSid: \(sid)" }) { $0.participant?(self, didFailToSubscribe: sid, error: error) } @@ -109,7 +109,7 @@ public class RemoteParticipant: Participant { track: rtcTrack, reportStatistics: room._state.options.reportRemoteTrackStatistics) default: - let error = TrackError.type(message: "Unsupported type: \(rtcTrack.kind.description)") + let error = LiveKitError(.invalidState, message: "Unsupported type: \(rtcTrack.kind.description)") delegates.notify(label: { "participant.didFailToSubscribe trackSid: \(sid)" }) { $0.participant?(self, didFailToSubscribe: sid, error: error) } diff --git a/Sources/LiveKit/Protocols/RoomDelegate.swift b/Sources/LiveKit/Protocols/RoomDelegate.swift index 00babfe87..0ccf7a46a 100644 --- a/Sources/LiveKit/Protocols/RoomDelegate.swift +++ b/Sources/LiveKit/Protocols/RoomDelegate.swift @@ -32,9 +32,9 @@ import Foundation /// ``` /// See the source code of [Swift Example App](https://github.com/livekit/client-example-swift) for more examples. @objc -public protocol RoomDelegateObjC: AnyObject { +public protocol RoomDelegate: AnyObject { @objc(room:didUpdateConnectionState:oldConnectionState:) optional - func room(_ room: Room, didUpdate connectionState: ConnectionStateObjC, oldValue oldConnectionState: ConnectionStateObjC) + func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue oldConnectionState: ConnectionState) /// Successfully connected to the room. @objc(room:didConnectIsReconnect:) optional @@ -42,12 +42,12 @@ public protocol RoomDelegateObjC: AnyObject { /// Could not connect to the room. @objc(room:didFailToConnectWithError:) optional - func room(_ room: Room, didFailToConnect error: Error) + func room(_ room: Room, didFailToConnect error: LiveKitError?) /// Client disconnected from the room unexpectedly. /// Using ``room(_:didUpdate:oldValue:)`` is preferred since `.disconnected` state of ``ConnectionState`` provides ``DisconnectReason`` (Swift only). @objc(room:didDisconnectWithError:) optional - func room(_ room: Room, didDisconnect error: Error?) + func room(_ room: Room, didDisconnect error: LiveKitError?) /// When a ``RemoteParticipant`` joins after the ``LocalParticipant``. /// It will not emit events for participants that are already in the room. @@ -137,13 +137,3 @@ public protocol RoomDelegateObjC: AnyObject { @objc(room:publication:didUpdateE2EEState:) optional func room(_ room: Room, publication: TrackPublication, didUpdateE2EEState: E2EEState) } - -public protocol RoomDelegate: RoomDelegateObjC { - /// When the ``ConnectionState`` has updated. - func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) -} - -/// Default implementation for ``RoomDelegate`` -public extension RoomDelegate { - func room(_: Room, didUpdate _: ConnectionState, oldValue _: ConnectionState) {} -} diff --git a/Sources/LiveKit/Support/AsyncCompleter.swift b/Sources/LiveKit/Support/AsyncCompleter.swift index fe27da80c..4c3eb6f9d 100644 --- a/Sources/LiveKit/Support/AsyncCompleter.swift +++ b/Sources/LiveKit/Support/AsyncCompleter.swift @@ -16,18 +16,6 @@ import Foundation -enum AsyncCompleterError: LiveKitError { - case timedOut - case cancelled - - public var description: String { - switch self { - case .timedOut: return "Timed out" - case .cancelled: return "Cancelled" - } - } -} - /// Manages a map of AsyncCompleters actor CompleterMapActor { // MARK: - Public @@ -104,8 +92,8 @@ class AsyncCompleter: Loggable { _lock.sync { _cancelTimer() if let continuation = _continuation { - log("\(label) cancelled") - continuation.resume(throwing: AsyncCompleterError.cancelled) + log("\(label) Cancelled") + continuation.resume(throwing: LiveKitError(.cancelled)) } _continuation = nil _returningValue = nil @@ -159,7 +147,7 @@ class AsyncCompleter: Loggable { guard let self else { return } self.log("\(self.label) timedOut") self._lock.sync { - self._continuation?.resume(throwing: AsyncCompleterError.timedOut) + self._continuation?.resume(throwing: LiveKitError(.timedOut)) self._continuation = nil } self.reset() diff --git a/Sources/LiveKit/Support/HTTP.swift b/Sources/LiveKit/Support/HTTP.swift index bf3560075..0c9dea822 100644 --- a/Sources/LiveKit/Support/HTTP.swift +++ b/Sources/LiveKit/Support/HTTP.swift @@ -34,7 +34,7 @@ class HTTP: NSObject { public static func requestString(from url: URL) async throws -> String { let data = try await requestData(from: url) guard let string = String(data: data, encoding: .utf8) else { - throw InternalError.state(message: "Failed to convert string") + throw LiveKitError(.failedToConvertData, message: "Failed to convert string") } return string } diff --git a/Sources/LiveKit/Support/WebSocket.swift b/Sources/LiveKit/Support/WebSocket.swift index 856dabb30..aa840a99a 100644 --- a/Sources/LiveKit/Support/WebSocket.swift +++ b/Sources/LiveKit/Support/WebSocket.swift @@ -64,7 +64,7 @@ class WebSocket: NSObject, Loggable, AsyncSequence, URLSessionWebSocketDelegate func close() { task.cancel(with: .goingAway, reason: nil) - connectContinuation?.resume(throwing: SignalClientError.socketError(rawError: nil)) + connectContinuation?.resume(throwing: LiveKitError(.cancelled)) connectContinuation = nil streamContinuation?.finish() streamContinuation = nil @@ -115,8 +115,7 @@ class WebSocket: NSObject, Loggable, AsyncSequence, URLSessionWebSocketDelegate func urlSession(_: URLSession, task _: URLSessionTask, didCompleteWithError error: Error?) { log("didCompleteWithError: \(String(describing: error))", .error) - let error = error ?? NetworkError.disconnected(message: "WebSocket didCompleteWithError") - connectContinuation?.resume(throwing: error) + connectContinuation?.resume(throwing: LiveKitError.from(error: error) ?? LiveKitError(.unknown)) connectContinuation = nil streamContinuation?.finish() streamContinuation = nil diff --git a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift index 5f93263df..e8a776c63 100644 --- a/Sources/LiveKit/Track/Capturers/CameraCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/CameraCapturer.swift @@ -96,8 +96,8 @@ public class CameraCapturer: VideoCapturer { public func switchCameraPosition() async throws -> Bool { // Cannot toggle if current position is unknown guard position != .unspecified else { - log("Failed to toggle camera position", .warning) - throw TrackError.state(message: "Camera position unknown") + log("Failed to toggle camera position", .error) + throw LiveKitError(.invalidState, message: "Failed to toggle camera position") } return try await set(cameraPosition: position == .front ? .back : .front) @@ -130,7 +130,7 @@ public class CameraCapturer: VideoCapturer { guard let device = devices.first(where: { $0.position == self.options.position }) ?? devices.first else { log("No camera video capture devices available", .error) - throw TrackError.capturer(message: "No camera video capture devices available") + throw LiveKitError(.deviceNotFound, message: "No camera video capture devices available") } // list of all formats in order of dimensions size @@ -161,16 +161,16 @@ public class CameraCapturer: VideoCapturer { // format should be resolved at this point guard let selectedFormat else { - log("Unable to resolve format", .error) - throw TrackError.capturer(message: "Unable to determine format for camera capturer") + log("Unable to resolve capture format", .error) + throw LiveKitError(.captureFormatNotFound, message: "Unable to resolve capture format") } let fpsRange = selectedFormat.format.fpsRange() // this should never happen guard fpsRange != 0 ... 0 else { - log("unable to resolve fps range", .error) - throw TrackError.capturer(message: "Unable to determine supported fps range for format: \(selectedFormat)") + log("Unable to determine supported fps range for format: \(selectedFormat)", .error) + throw LiveKitError(.unableToResolveFPSRange, message: "Unable to determine supported fps range for format: \(selectedFormat)") } // default to fps in options diff --git a/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift b/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift index fc7243b2b..66d83c451 100644 --- a/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift @@ -56,7 +56,8 @@ import Foundation guard didStart else { return false } guard let captureSource else { - throw TrackError.capturer(message: "captureSource is nil") + log("captureSource is nil", .error) + throw LiveKitError(.invalidState, message: "captureSource is nil") } let filter: SCContentFilter @@ -74,7 +75,8 @@ import Foundation filter = SCContentFilter(display: nativeDisplay, excludingApplications: excludedApps, exceptingWindows: []) } else { - throw TrackError.capturer(message: "Unable to resolve SCContentFilter") + log("Unable to resolve SCContentFilter", .error) + throw LiveKitError(.invalidState, message: "Unable to resolve SCContentFilter") } let configuration = SCStreamConfiguration() @@ -107,7 +109,7 @@ import Foundation guard didStop else { return false } guard let stream = _scStream else { - throw TrackError.capturer(message: "SCStream is nil") + throw LiveKitError(.invalidState, message: "SCStream is nil") } // Stop resending paused frames @@ -398,7 +400,7 @@ import Foundation let displaySources = try await sources(for: .display) guard let source = displaySources.compactMap({ $0 as? MacOSDisplay }).first(where: { $0.displayID == CGMainDisplayID() }) else { - throw TrackError.capturer(message: "Main display source not found") + throw LiveKitError(.invalidState, message: "Main display source not found") } return source diff --git a/Sources/LiveKit/TrackPublications/LocalTrackPublication.swift b/Sources/LiveKit/TrackPublications/LocalTrackPublication.swift index 221d779c6..20216bc18 100644 --- a/Sources/LiveKit/TrackPublications/LocalTrackPublication.swift +++ b/Sources/LiveKit/TrackPublications/LocalTrackPublication.swift @@ -29,7 +29,7 @@ public class LocalTrackPublication: TrackPublication { public func mute() async throws { guard let track = track as? LocalTrack else { - throw InternalError.state(message: "track is nil or not a LocalTrack") + throw LiveKitError(.invalidState, message: "track is nil or not a LocalTrack") } try await track._mute() @@ -37,7 +37,7 @@ public class LocalTrackPublication: TrackPublication { public func unmute() async throws { guard let track = track as? LocalTrack else { - throw InternalError.state(message: "track is nil or not a LocalTrack") + throw LiveKitError(.invalidState, message: "track is nil or not a LocalTrack") } try await track._unmute() diff --git a/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift b/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift index 9f98af64e..1dd514258 100644 --- a/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift +++ b/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift @@ -164,7 +164,7 @@ private extension RemoteTrackPublication { var engineConnectionState: ConnectionState { guard let participant else { log("Participant is nil", .warning) - return .disconnected() + return .disconnected } return participant.room.engine._state.connectionState @@ -173,7 +173,7 @@ private extension RemoteTrackPublication { func userCanModifyTrackSettings() async throws { // adaptiveStream must be disabled and must be subscribed if isAdaptiveStreamEnabled || !subscribed { - throw TrackError.state(message: "adaptiveStream must be disabled and track must be subscribed") + throw LiveKitError(.invalidState, message: "adaptiveStream must be disabled and track must be subscribed") } } } @@ -238,7 +238,7 @@ extension RemoteTrackPublication { if state.isSendingTrackSettings { // Previous send hasn't completed yet... - throw EngineError.state(message: "Already busy sending new track settings") + throw LiveKitError(.invalidState, message: "Already busy sending new track settings") } // update state @@ -303,7 +303,7 @@ extension RemoteTrackPublication { asTimer.suspend() // don't continue if the engine is disconnected - guard !engineConnectionState.isDisconnected else { + guard engineConnectionState != .disconnected else { log("engine is disconnected") return } diff --git a/Sources/LiveKit/TrackPublications/TrackPublication.swift b/Sources/LiveKit/TrackPublications/TrackPublication.swift index 0bd99c19d..b223d68b8 100644 --- a/Sources/LiveKit/TrackPublications/TrackPublication.swift +++ b/Sources/LiveKit/TrackPublications/TrackPublication.swift @@ -236,7 +236,7 @@ extension TrackPublication: TrackDelegateInternal { extension TrackPublication { func requireParticipant() async throws -> Participant { guard let participant else { - throw EngineError.state(message: "Participant is nil") + throw LiveKitError(.invalidState, message: "Participant is nil") } return participant diff --git a/Sources/LiveKit/Types/ConnectionState+ObjC.swift b/Sources/LiveKit/Types/ConnectionState+ObjC.swift deleted file mode 100644 index e9f8e6c87..000000000 --- a/Sources/LiveKit/Types/ConnectionState+ObjC.swift +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023 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 - -@objc(ConnectionState) -public enum ConnectionStateObjC: Int { - case disconnected - case connecting - case reconnecting - case connected -} diff --git a/Sources/LiveKit/Types/ConnectionState.swift b/Sources/LiveKit/Types/ConnectionState.swift index aeaeedb2d..6ac5aab71 100644 --- a/Sources/LiveKit/Types/ConnectionState.swift +++ b/Sources/LiveKit/Types/ConnectionState.swift @@ -22,58 +22,16 @@ public enum ReconnectMode: Int { case full } -public enum ConnectionState { - case disconnected(reason: DisconnectReason? = nil) +@objc +public enum ConnectionState: Int { + case disconnected case connecting case reconnecting case connected - - func toObjCType() -> ConnectionStateObjC { - switch self { - case .disconnected: return .disconnected - case .connecting: return .connecting - case .reconnecting: return .reconnecting - case .connected: return .connected - } - } } extension ConnectionState: Identifiable { - public var id: String { - String(describing: self) - } -} - -extension ConnectionState: Equatable { - public static func == (lhs: ConnectionState, rhs: ConnectionState) -> Bool { - switch (lhs, rhs) { - case (.disconnected, .disconnected), - (.connecting, .connecting), - (.reconnecting, .reconnecting), - (.connected, .connected): - return true - default: return false - } - } - - public var isConnected: Bool { - guard case .connected = self else { return false } - return true - } - - public var isReconnecting: Bool { - guard case .reconnecting = self else { return false } - return true - } - - public var isDisconnected: Bool { - guard case .disconnected = self else { return false } - return true - } - - public var disconnectedWithNetworkError: Error? { - guard case let .disconnected(reason) = self, - case let .networkError(error) = reason else { return nil } - return error + public var id: Int { + rawValue } } diff --git a/Sources/LiveKit/Types/DisconnectReason.swift b/Sources/LiveKit/Types/DisconnectReason.swift deleted file mode 100644 index 78645fbfe..000000000 --- a/Sources/LiveKit/Types/DisconnectReason.swift +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2023 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 - -public enum DisconnectReason { - case user // User initiated - case networkError(_ error: Error) - // New cases - case unknown - case duplicateIdentity - case serverShutdown - case participantRemoved - case roomDeleted - case stateMismatch - case joinFailure -} - -extension Livekit_DisconnectReason { - func toLKType() -> DisconnectReason { - switch self { - case .clientInitiated: return .user - case .duplicateIdentity: return .duplicateIdentity - case .serverShutdown: return .serverShutdown - case .participantRemoved: return .participantRemoved - case .roomDeleted: return .roomDeleted - case .stateMismatch: return .stateMismatch - case .joinFailure: return .joinFailure - default: return .unknown - } - } -} - -extension DisconnectReason: Equatable { - public static func == (lhs: DisconnectReason, rhs: DisconnectReason) -> Bool { - lhs.isEqual(to: rhs) - } - - public func isEqual(to rhs: DisconnectReason, includingAssociatedValues _: Bool = true) -> Bool { - switch (self, rhs) { - case (.user, .user): return true - case (.networkError, .networkError): return true - // New cases - case (.unknown, .unknown): return true - case (.duplicateIdentity, .duplicateIdentity): return true - case (.serverShutdown, .serverShutdown): return true - case (.participantRemoved, .participantRemoved): return true - case (.roomDeleted, .roomDeleted): return true - case (.stateMismatch, .stateMismatch): return true - case (.joinFailure, .joinFailure): return true - default: return false - } - } - - var networkError: Error? { - if case let .networkError(error) = self { - return error - } - - return nil - } -} diff --git a/Sources/LiveKit/Types/IceCandidate.swift b/Sources/LiveKit/Types/IceCandidate.swift index bb72aa14e..0cde6e73f 100644 --- a/Sources/LiveKit/Types/IceCandidate.swift +++ b/Sources/LiveKit/Types/IceCandidate.swift @@ -31,7 +31,7 @@ struct IceCandidate: Codable { func toJsonString() throws -> String { let data = try JSONEncoder().encode(self) guard let string = String(data: data, encoding: .utf8) else { - throw InternalError.convert(message: "Failed to convert Data to String") + throw LiveKitError(.failedToConvertData, message: "Failed to convert Data to String") } return string } @@ -47,7 +47,7 @@ extension LKRTCIceCandidate { convenience init(fromJsonString string: String) throws { // String to Data guard let data = string.data(using: .utf8) else { - throw InternalError.convert(message: "Failed to convert String to Data") + throw LiveKitError(.failedToConvertData, message: "Failed to convert String to Data") } // Decode JSON let iceCandidate: IceCandidate = try JSONDecoder().decode(IceCandidate.self, from: data) diff --git a/Sources/LiveKit/Types/VideoCodec.swift b/Sources/LiveKit/Types/VideoCodec.swift index 72c267cc5..bbe8005aa 100644 --- a/Sources/LiveKit/Types/VideoCodec.swift +++ b/Sources/LiveKit/Types/VideoCodec.swift @@ -21,7 +21,7 @@ public class VideoCodec: NSObject, Identifiable { public static func from(id: String) throws -> VideoCodec { // Try to find codec from id... guard let codec = all.first(where: { $0.id == id }) else { - throw EngineError.state(message: "Failed to create VideoCodec from id") + throw LiveKitError(.invalidState, message: "Failed to create VideoCodec from id") } return codec @@ -31,7 +31,7 @@ public class VideoCodec: NSObject, Identifiable { let parts = mimeType.lowercased().split(separator: "/") var id = String(parts.first!) if parts.count > 1 { - if parts[0] != "video" { throw EngineError.state(message: "MIME type must be video") } + if parts[0] != "video" { throw LiveKitError(.invalidState, message: "MIME type must be video") } id = String(parts[1]) } return try from(id: id)