diff --git a/Sources/LiveKit/Core/Engine.swift b/Sources/LiveKit/Core/Engine.swift index d27f0e1d8..a59683b6b 100644 --- a/Sources/LiveKit/Core/Engine.swift +++ b/Sources/LiveKit/Core/Engine.swift @@ -502,7 +502,7 @@ extension Engine { try await signalClient.sendSyncState(answer: previousAnswer.toPBType(), offer: previousOffer?.toPBType(), - subscription: subscription, publishTracks: room._state.localParticipant?.publishedTracksInfo(), + subscription: subscription, publishTracks: room.localParticipant.publishedTracksInfo(), dataChannels: publisherDC.infos()) } } diff --git a/Sources/LiveKit/Core/Room+Convenience.swift b/Sources/LiveKit/Core/Room+Convenience.swift index 4eba0dfbc..747472b0f 100644 --- a/Sources/LiveKit/Core/Room+Convenience.swift +++ b/Sources/LiveKit/Core/Room+Convenience.swift @@ -19,11 +19,9 @@ import Foundation public extension Room { var allParticipants: [Sid: Participant] { var result: [Sid: Participant] = remoteParticipants - - if let localParticipant { + if !localParticipant.sid.isEmpty { result.updateValue(localParticipant, forKey: localParticipant.sid) } - return result } } diff --git a/Sources/LiveKit/Core/Room+EngineDelegate.swift b/Sources/LiveKit/Core/Room+EngineDelegate.swift index deca66786..a7a579dfa 100644 --- a/Sources/LiveKit/Core/Room+EngineDelegate.swift +++ b/Sources/LiveKit/Core/Room+EngineDelegate.swift @@ -29,7 +29,7 @@ extension Room: EngineDelegate { } // Re-send track permissions - if case .connected = state.connectionState, let localParticipant { + if case .connected = state.connectionState { Task { do { try await localParticipant.sendTrackSubscriptionPermissions() @@ -82,9 +82,7 @@ extension Room: EngineDelegate { var seenSids = [String: Bool]() for speaker in speakers { seenSids[speaker.sid] = true - if let localParticipant = state.localParticipant, - speaker.sid == localParticipant.sid - { + if speaker.sid == localParticipant.sid { localParticipant._state.mutate { $0.audioLevel = speaker.level $0.isSpeaking = true @@ -101,7 +99,7 @@ extension Room: EngineDelegate { } } - if let localParticipant = state.localParticipant, seenSids[localParticipant.sid] == nil { + if seenSids[localParticipant.sid] == nil { localParticipant._state.mutate { $0.audioLevel = 0.0 $0.isSpeaking = false diff --git a/Sources/LiveKit/Core/Room+SignalClientDelegate.swift b/Sources/LiveKit/Core/Room+SignalClientDelegate.swift index 07d256133..e647356ef 100644 --- a/Sources/LiveKit/Core/Room+SignalClientDelegate.swift +++ b/Sources/LiveKit/Core/Room+SignalClientDelegate.swift @@ -36,7 +36,6 @@ extension Room: SignalClientDelegate { func signalClient(_: SignalClient, didUpdate trackSid: String, subscribedQualities: [Livekit_SubscribedQuality]) { log("qualities: \(subscribedQualities.map { String(describing: $0) }.joined(separator: ", "))") - guard let localParticipant = _state.localParticipant else { return } localParticipant.onSubscribedQualitiesUpdate(trackSid: trackSid, subscribedQualities: subscribedQualities) } @@ -55,9 +54,7 @@ extension Room: SignalClientDelegate { $0.serverRegion = joinResponse.serverRegion.isEmpty ? nil : joinResponse.serverRegion $0.isRecording = joinResponse.room.activeRecording - if joinResponse.hasParticipant { - $0.localParticipant = LocalParticipant(from: joinResponse.participant, room: self) - } + localParticipant.updateFromInfo(info: joinResponse.participant) if !joinResponse.otherParticipants.isEmpty { for otherParticipant in joinResponse.otherParticipants { @@ -84,7 +81,7 @@ extension Room: SignalClientDelegate { var lastSpeakers = state.activeSpeakers.reduce(into: [Sid: Participant]()) { $0[$1.sid] = $1 } for speaker in speakers { - guard let participant = speaker.sid == state.localParticipant?.sid ? state.localParticipant : state.remoteParticipants[speaker.sid] else { + guard let participant = speaker.sid == localParticipant.sid ? localParticipant : state.remoteParticipants[speaker.sid] else { continue } @@ -118,9 +115,7 @@ extension Room: SignalClientDelegate { log("connectionQuality: \(connectionQuality)", .trace) for entry in connectionQuality { - if let localParticipant = _state.localParticipant, - entry.participantSid == localParticipant.sid - { + if entry.participantSid == localParticipant.sid { // update for LocalParticipant localParticipant._state.mutate { $0.connectionQuality = entry.quality.toLKType() } } else if let participant = _state.remoteParticipants[entry.participantSid] { @@ -133,7 +128,7 @@ extension Room: SignalClientDelegate { func signalClient(_: SignalClient, didUpdateRemoteMute trackSid: String, muted: Bool) { log("trackSid: \(trackSid) muted: \(muted)") - guard let publication = _state.localParticipant?._state.tracks[trackSid] as? LocalTrackPublication else { + guard let publication = localParticipant._state.tracks[trackSid] as? LocalTrackPublication else { // publication was not found but the delegate was handled return } @@ -180,8 +175,8 @@ extension Room: SignalClientDelegate { _state.mutate { for info in participants { - if info.sid == $0.localParticipant?.sid { - $0.localParticipant?.updateFromInfo(info: info) + if info.sid == localParticipant.sid { + localParticipant.updateFromInfo(info: info) continue } @@ -221,9 +216,7 @@ extension Room: SignalClientDelegate { func signalClient(_: SignalClient, didUnpublish localTrack: Livekit_TrackUnpublishedResponse) { log() - guard let localParticipant, - let publication = localParticipant._state.tracks[localTrack.trackSid] as? LocalTrackPublication - else { + guard let publication = localParticipant._state.tracks[localTrack.trackSid] as? LocalTrackPublication else { log("track publication not found", .warning) return } diff --git a/Sources/LiveKit/Core/Room.swift b/Sources/LiveKit/Core/Room.swift index ac6b59017..8dbc3c41e 100644 --- a/Sources/LiveKit/Core/Room.swift +++ b/Sources/LiveKit/Core/Room.swift @@ -47,9 +47,6 @@ public class Room: NSObject, ObservableObject, Loggable { @objc public var serverRegion: String? { _state.serverRegion } - @objc - public var localParticipant: LocalParticipant? { _state.localParticipant } - @objc public var remoteParticipants: [Sid: RemoteParticipant] { _state.remoteParticipants } @@ -93,6 +90,9 @@ public class Room: NSObject, ObservableObject, Loggable { public var e2eeManager: E2EEManager? + @objc + public lazy var localParticipant: LocalParticipant = .init(room: self) + struct State: Equatable { var options: RoomOptions @@ -102,7 +102,6 @@ public class Room: NSObject, ObservableObject, Loggable { var serverVersion: String? var serverRegion: String? - var localParticipant: LocalParticipant? var remoteParticipants = [Sid: RemoteParticipant]() var activeSpeakers = [Participant]() @@ -220,11 +219,6 @@ public class Room: NSObject, ObservableObject, Loggable { let state = _state.copy() - guard state.localParticipant == nil else { - log("localParticipant is not nil", .warning) - throw EngineError.state(message: "localParticipant is not nil") - } - // update options if specified if let roomOptions, roomOptions != state.options { _state.mutate { $0.options = roomOptions } @@ -238,7 +232,7 @@ public class Room: NSObject, ObservableObject, Loggable { try await engine.connect(url, token, connectOptions: connectOptions) - log("Connected to \(String(describing: self)) \(String(describing: state.localParticipant))", .info) + log("Connected to \(String(describing: self))", .info) } @objc @@ -310,7 +304,6 @@ extension Room { } _state.mutate { - $0.localParticipant = nil $0.remoteParticipants = [:] } } @@ -356,8 +349,6 @@ extension Room: AppStateDelegate { func appDidEnterBackground() { guard _state.options.suspendLocalVideoTracksInBackground else { return } - guard let localParticipant else { return } - let cameraVideoTracks = localParticipant.localVideoTracks.filter { $0.source == .camera } guard !cameraVideoTracks.isEmpty else { return } @@ -374,8 +365,6 @@ extension Room: AppStateDelegate { } func appWillEnterForeground() { - guard let localParticipant else { return } - let cameraVideoTracks = localParticipant.localVideoTracks.filter { $0.source == .camera } guard !cameraVideoTracks.isEmpty else { return } diff --git a/Sources/LiveKit/E2EE/E2EEManager.swift b/Sources/LiveKit/E2EE/E2EEManager.swift index bbbb623b8..d173ab441 100644 --- a/Sources/LiveKit/E2EE/E2EEManager.swift +++ b/Sources/LiveKit/E2EE/E2EEManager.swift @@ -63,30 +63,30 @@ public class E2EEManager: NSObject, ObservableObject, Loggable { } self.room = room self.room?.delegates.add(delegate: self) - self.room?.localParticipant?.tracks.forEach { (_: Sid, publication: TrackPublication) in + self.room?.localParticipant.tracks.forEach { (_: Sid, publication: TrackPublication) in if publication.encryptionType == EncryptionType.none { - self.log("E2EEManager::setup: local participant \(self.room!.localParticipant!.identity) track \(publication.sid) encryptionType is none, skip") + self.log("E2EEManager::setup: local participant \(self.room!.localParticipant.sid) track \(publication.sid) encryptionType is none, skip") return } if publication.track?.rtpSender == nil { self.log("E2EEManager::setup: publication.track?.rtpSender is nil, skip to create FrameCryptor!") return } - let fc = addRtpSender(sender: publication.track!.rtpSender!, participantId: self.room!.localParticipant!.identity, trackSid: publication.sid) + let fc = addRtpSender(sender: publication.track!.rtpSender!, participantSid: self.room!.localParticipant.sid, trackSid: publication.sid) trackPublications[fc] = publication } self.room?.remoteParticipants.forEach { (_: Sid, participant: RemoteParticipant) in participant.tracks.forEach { (_: Sid, publication: TrackPublication) in if publication.encryptionType == EncryptionType.none { - self.log("E2EEManager::setup: remote participant \(participant.identity) track \(publication.sid) encryptionType is none, skip") + self.log("E2EEManager::setup: remote participant \(participant.sid) track \(publication.sid) encryptionType is none, skip") return } if publication.track?.rtpReceiver == nil { self.log("E2EEManager::setup: publication.track?.rtpReceiver is nil, skip to create FrameCryptor!") return } - let fc = addRtpReceiver(receiver: publication.track!.rtpReceiver!, participantId: participant.identity, trackSid: publication.sid) + let fc = addRtpReceiver(receiver: publication.track!.rtpReceiver!, participantSid: participant.sid, trackSid: publication.sid) trackPublications[fc] = publication } } @@ -99,20 +99,20 @@ public class E2EEManager: NSObject, ObservableObject, Loggable { } } - func addRtpSender(sender: LKRTCRtpSender, participantId: String, trackSid: Sid) -> LKRTCFrameCryptor { - log("addRtpSender \(participantId) to E2EEManager") - let frameCryptor = LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, rtpSender: sender, participantId: participantId, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: e2eeOptions.keyProvider.rtcKeyProvider!) + func addRtpSender(sender: LKRTCRtpSender, participantSid: String, trackSid: Sid) -> LKRTCFrameCryptor { + log("addRtpSender \(participantSid) to E2EEManager") + let frameCryptor = LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, rtpSender: sender, participantId: participantSid, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: e2eeOptions.keyProvider.rtcKeyProvider!) frameCryptor.delegate = delegateAdapter - frameCryptors[[participantId: trackSid]] = frameCryptor + frameCryptors[[participantSid: trackSid]] = frameCryptor frameCryptor.enabled = enabled return frameCryptor } - func addRtpReceiver(receiver: LKRTCRtpReceiver, participantId: String, trackSid: Sid) -> LKRTCFrameCryptor { - log("addRtpReceiver \(participantId) to E2EEManager") - let frameCryptor = LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, rtpReceiver: receiver, participantId: participantId, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: e2eeOptions.keyProvider.rtcKeyProvider!) + func addRtpReceiver(receiver: LKRTCRtpReceiver, participantSid: String, trackSid: Sid) -> LKRTCFrameCryptor { + log("addRtpReceiver \(participantSid) to E2EEManager") + let frameCryptor = LKRTCFrameCryptor(factory: Engine.peerConnectionFactory, rtpReceiver: receiver, participantId: participantSid, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: e2eeOptions.keyProvider.rtcKeyProvider!) frameCryptor.delegate = delegateAdapter - frameCryptors[[participantId: trackSid]] = frameCryptor + frameCryptors[[participantSid: trackSid]] = frameCryptor frameCryptor.enabled = enabled return frameCryptor } @@ -148,25 +148,25 @@ extension E2EEManager { extension E2EEManager: RoomDelegate { public func room(_: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) { if publication.encryptionType == EncryptionType.none { - log("E2EEManager::RoomDelegate: local participant \(localParticipant.identity) track \(publication.sid) encryptionType is none, skip") + log("E2EEManager::RoomDelegate: local participant \(String(describing: localParticipant.sid)) track \(publication.sid) encryptionType is none, skip") return } if publication.track?.rtpSender == nil { log("E2EEManager::RoomDelegate: publication.track?.rtpSender is nil, skip to create FrameCryptor!") return } - let fc = addRtpSender(sender: publication.track!.rtpSender!, participantId: localParticipant.identity, trackSid: publication.sid) + let fc = addRtpSender(sender: publication.track!.rtpSender!, participantSid: localParticipant.sid, trackSid: publication.sid) trackPublications[fc] = publication } public func room(_: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) { let frameCryptor = frameCryptors.first(where: { (key: [String: Sid], _: LKRTCFrameCryptor) -> Bool in - key[localParticipant.identity] == publication.sid + key[localParticipant.sid] == publication.sid })?.value frameCryptor?.delegate = nil frameCryptor?.enabled = false - frameCryptors.removeValue(forKey: [localParticipant.identity: publication.sid]) + frameCryptors.removeValue(forKey: [localParticipant.sid: publication.sid]) if frameCryptor != nil { trackPublications.removeValue(forKey: frameCryptor!) @@ -175,25 +175,25 @@ extension E2EEManager: RoomDelegate { public func room(_: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track _: Track) { if publication.encryptionType == EncryptionType.none { - log("E2EEManager::RoomDelegate: remote participant \(participant.identity) track \(publication.sid) encryptionType is none, skip") + log("E2EEManager::RoomDelegate: remote participant \(String(describing: participant.sid)) track \(publication.sid) encryptionType is none, skip") return } if publication.track?.rtpReceiver == nil { log("E2EEManager::RoomDelegate: publication.track?.rtpReceiver is nil, skip to create FrameCryptor!") return } - let fc = addRtpReceiver(receiver: publication.track!.rtpReceiver!, participantId: participant.identity, trackSid: publication.sid) + let fc = addRtpReceiver(receiver: publication.track!.rtpReceiver!, participantSid: participant.sid, trackSid: publication.sid) trackPublications[fc] = publication } public func room(_: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track _: Track) { let frameCryptor = frameCryptors.first(where: { (key: [String: Sid], _: LKRTCFrameCryptor) -> Bool in - key[participant.identity] == publication.sid + key[participant.sid] == publication.sid })?.value frameCryptor?.delegate = nil frameCryptor?.enabled = false - frameCryptors.removeValue(forKey: [participant.identity: publication.sid]) + frameCryptors.removeValue(forKey: [participant.sid: publication.sid]) if frameCryptor != nil { trackPublications.removeValue(forKey: frameCryptor!) diff --git a/Sources/LiveKit/Participant/LocalParticipant.swift b/Sources/LiveKit/Participant/LocalParticipant.swift index a6dea48bc..50cabe055 100644 --- a/Sources/LiveKit/Participant/LocalParticipant.swift +++ b/Sources/LiveKit/Participant/LocalParticipant.swift @@ -33,15 +33,8 @@ public class LocalParticipant: Participant { private var allParticipantsAllowed: Bool = true private var trackPermissions: [ParticipantTrackPermission] = [] - convenience init(from info: Livekit_ParticipantInfo, - room: Room) - { - self.init(sid: info.sid, - identity: info.identity, - name: info.name, - room: room) - - updateFromInfo(info: info) + init(room: Room) { + super.init(sid: "", room: room) } func getTrackPublication(sid: Sid) -> LocalTrackPublication? { @@ -321,7 +314,7 @@ public class LocalParticipant: Participant { // TODO: Revert internal state on failure - try await room.engine.signalClient.sendUpdateLocalMetadata(metadata, name: name) + try await room.engine.signalClient.sendUpdateLocalMetadata(metadata, name: name ?? "") } /// Sets and updates the name of the local participant. diff --git a/Sources/LiveKit/Participant/Participant.swift b/Sources/LiveKit/Participant/Participant.swift index 9d804d75e..b14f24a50 100644 --- a/Sources/LiveKit/Participant/Participant.swift +++ b/Sources/LiveKit/Participant/Participant.swift @@ -26,14 +26,15 @@ public class Participant: NSObject, ObservableObject, Loggable { let queue = DispatchQueue(label: "LiveKitSDK.participant", qos: .default) + /// This will be an empty String for LocalParticipants until connected. @objc public var sid: Sid { _state.sid } @objc - public var identity: String { _state.identity } + public var identity: String? { _state.identity } @objc - public var name: String { _state.name } + public var name: String? { _state.name } @objc public var audioLevel: Float { _state.audioLevel } @@ -74,9 +75,9 @@ public class Participant: NSObject, ObservableObject, Loggable { // MARK: - Internal struct State: Equatable, Hashable { - let sid: Sid - var identity: String - var name: String + var sid: Sid + var identity: String? + var name: String? var audioLevel: Float = 0.0 var isSpeaking: Bool = false var metadata: String? @@ -88,19 +89,11 @@ public class Participant: NSObject, ObservableObject, Loggable { var _state: StateSync - init(sid: String, - identity: String, - name: String, - room: Room) - { + init(sid: String, room: Room) { self.room = room // initial state - _state = StateSync(State( - sid: sid, - identity: identity, - name: name - )) + _state = StateSync(State(sid: sid)) super.init() @@ -131,11 +124,11 @@ public class Participant: NSObject, ObservableObject, Loggable { // name updated if newState.name != oldState.name { // notfy participant delegates - self.delegates.notify(label: { "participant.didUpdateName: \(newState.name)" }) { + self.delegates.notify(label: { "participant.didUpdateName: \(String(describing: newState.name))" }) { $0.participant?(self, didUpdateName: newState.name) } // notify room delegates - self.room.delegates.notify(label: { "room.didUpdateName: \(newState.name)" }) { + self.room.delegates.notify(label: { "room.didUpdateName: \(String(describing: newState.name))" }) { $0.room?(self.room, participant: self, didUpdateName: newState.name) } } @@ -162,7 +155,7 @@ public class Participant: NSObject, ObservableObject, Loggable { func cleanUp(notify _notify: Bool = true) async { await unpublishAll(notify: _notify) // Reset state - _state.mutate { $0 = State(sid: $0.sid, identity: $0.identity, name: $0.name) } + _state.mutate { $0 = State(sid: "") } } func unpublishAll(notify _: Bool = true) async { @@ -176,6 +169,7 @@ public class Participant: NSObject, ObservableObject, Loggable { func updateFromInfo(info: Livekit_ParticipantInfo) { _state.mutate { + $0.sid = info.sid $0.identity = info.identity $0.name = info.name $0.metadata = info.metadata diff --git a/Sources/LiveKit/Participant/RemoteParticipant.swift b/Sources/LiveKit/Participant/RemoteParticipant.swift index c47451cb7..f2de44f0a 100644 --- a/Sources/LiveKit/Participant/RemoteParticipant.swift +++ b/Sources/LiveKit/Participant/RemoteParticipant.swift @@ -20,14 +20,8 @@ import Foundation @objc public class RemoteParticipant: Participant { - init(sid: Sid, - info: Livekit_ParticipantInfo?, - room: Room) - { - super.init(sid: sid, - identity: info?.identity ?? "", - name: info?.name ?? "", - room: room) + init(sid: Sid, info: Livekit_ParticipantInfo?, room: Room) { + super.init(sid: sid, room: room) if let info { updateFromInfo(info: info) diff --git a/Sources/LiveKit/Protocols/ParticipantDelegate.swift b/Sources/LiveKit/Protocols/ParticipantDelegate.swift index 7fe52a5b4..5f53c2f37 100644 --- a/Sources/LiveKit/Protocols/ParticipantDelegate.swift +++ b/Sources/LiveKit/Protocols/ParticipantDelegate.swift @@ -34,7 +34,7 @@ public protocol ParticipantDelegate: AnyObject { /// A ``Participant``'s name has updated. /// `participant` Can be a ``LocalParticipant`` or a ``RemoteParticipant``. @objc(participant:didUpdateName:) optional - func participant(_ participant: Participant, didUpdateName: String) + func participant(_ participant: Participant, didUpdateName: String?) /// The isSpeaking status of a ``Participant`` has changed. /// `participant` Can be a ``LocalParticipant`` or a ``RemoteParticipant``. diff --git a/Sources/LiveKit/Protocols/RoomDelegate.swift b/Sources/LiveKit/Protocols/RoomDelegate.swift index 220c613b6..00babfe87 100644 --- a/Sources/LiveKit/Protocols/RoomDelegate.swift +++ b/Sources/LiveKit/Protocols/RoomDelegate.swift @@ -79,7 +79,7 @@ public protocol RoomDelegateObjC: AnyObject { /// Same with ``ParticipantDelegate/participant(_:didUpdateName:)``. @objc(room:participant:didUpdateName:) optional - func room(_ room: Room, participant: Participant, didUpdateName: String) + func room(_ room: Room, participant: Participant, didUpdateName: String?) /// Same with ``ParticipantDelegate/participant(_:didUpdate:)-7zxk1``. @objc(room:participant:didUpdateConnectionQuality:) optional