### Swift Token Authentication Service for LiveKit Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt Fetches LiveKit connection credentials (server URL, room name, participant details) from a token server. This example uses a sandbox endpoint for development and outlines where to integrate custom authentication for production environments. ```swift import Foundation actor TokenService { struct ConnectionDetails: Codable { let serverUrl: String let roomName: String let participantName: String let participantToken: String } func fetchConnectionDetails( roomName: String, participantName: String ) async throws -> ConnectionDetails? { // For development: LiveKit Cloud sandbox guard let sandboxId = Bundle.main.object(forInfoDictionaryKey: "LiveKitSandboxId") as? String else { return nil } var urlComponents = URLComponents(string: "https://cloud-api.livekit.io/api/sandbox/connection-details")! urlComponents.queryItems = [ URLQueryItem(name: "roomName", value: roomName), URLQueryItem(name: "participantName", value: participantName), ] var request = URLRequest(url: urlComponents.url!) request.httpMethod = "POST" request.addValue(sandboxId, forHTTPHeaderField: "X-Sandbox-ID") let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { return nil } return try JSONDecoder().decode(ConnectionDetails.self, from: data) } } // Usage in production: // Replace sandbox logic with your own token server endpoint // that integrates with your authentication system ``` -------------------------------- ### Connect to LiveKit Room with Pre-Connect Audio Buffer (Swift) Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt Connects to a LiveKit room and enables instant audio capture even before the connection is fully established. This is useful for reducing latency in voice interactions. It utilizes dependencies for room management and token services. ```swift import LiveKit @MainActor @Observable final class AppViewModel { @Dependency(\.room) private var room @Dependency(\.tokenService) private var tokenService private(set) var connectionState: ConnectionState = .disconnected private(set) var isListening = false private(set) var agent: Participant? func connect() async { do { if agentFeatures.contains(.voice) { try await connectWithVoice() } else { try await connectWithoutVoice() } try await checkAgentConnected() } catch { errorHandler(error) resetState() } } // Capture audio before connection completes for instant responsiveness private func connectWithVoice() async throws { try await room.withPreConnectAudio { await MainActor.run { self.isListening = true } let connectionDetails = try await self.getConnection() try await self.room.connect( url: connectionDetails.serverUrl, token: connectionDetails.participantToken, connectOptions: .init(enableMicrophone: true) ) } } private func getConnection() async throws -> TokenService.ConnectionDetails { let roomName = "room-\(Int.random(in: 1000...9999))" let participantName = "user-\(Int.random(in: 1000...9999))" return try await tokenService.fetchConnectionDetails( roomName: roomName, participantName: participantName )! } func disconnect() async { await room.disconnect() resetState() } } ``` -------------------------------- ### Swift App Entry Point and Feature Configuration Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt Defines the main application structure and allows configuration of agent features like voice, text, and video input using SwiftUI and Swift's Observation framework. It sets up the environment for the application's UI and defines feature flags as an OptionSet. ```swift import LiveKit import SwiftUI @main struct VoiceAgentApp: App { private let viewModel = AppViewModel() var body: some Scene { WindowGroup { AppView() .environment(viewModel) } } } // Configure agent features struct AgentFeatures: OptionSet { let rawValue: Int static let voice = Self(rawValue: 1 << 0) static let text = Self(rawValue: 1 << 1) static let video = Self(rawValue: 1 << 2) // Enable voice and text input for your agent static let current: Self = [.voice, .text] } ``` -------------------------------- ### Dependency Injection Container with LiveKit Swift Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt Implements a singleton dependency injection container for sharing LiveKit Room and service instances across view models. It uses a property wrapper for clean access to dependencies. Primarily for SwiftUI or UIKit applications using LiveKit. ```swift import LiveKit @MainActor final class Dependencies { static let shared = Dependencies() private init() {} // Core LiveKit room with screen share support lazy var room = Room( roomOptions: RoomOptions( defaultScreenShareCaptureOptions: ScreenShareCaptureOptions( useBroadcastExtension: true ) ) ) // Authentication service lazy var tokenService = TokenService() // Message handling pipeline private lazy var localMessageSender = LocalMessageSender(room: room) lazy var messageSenders: [any MessageSender] = [localMessageSender] lazy var messageReceivers: [any MessageReceiver] = [ TranscriptionStreamReceiver(room: room), localMessageSender, ] // Error handler injection point lazy var errorHandler: (Error?) -> Void = { _ in } } // Property wrapper for clean dependency access @MainActor @propertyWrapper struct Dependency { let keyPath: KeyPath var wrappedValue: T { Dependencies.shared[keyPath: keyPath] } } // Usage in view models: // @Dependency(\.room) private var room // @Dependency(\.tokenService) private var tokenService ``` -------------------------------- ### Process Transcription Streams with LiveKit Swift Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt Receives and accumulates real-time transcription data from LiveKit agents. It handles partial messages, appends content from the same stream, and identifies final transcription segments. Requires the LiveKit SDK. ```swift import LiveKit actor TranscriptionStreamReceiver: MessageReceiver { private struct PartialMessageID: Hashable { let segmentID: String let participantID: Participant.Identity } private struct PartialMessage { var content: String let timestamp: Date var streamID: String mutating func appendContent(_ newContent: String) { content += newContent } } private let transcriptionTopic = "lk.transcription" private let room: Room private lazy var partialMessages: [PartialMessageID: PartialMessage] = [: ] init(room: Room) { self.room = room } func messages() async throws -> AsyncStream { let (stream, continuation) = AsyncStream.makeStream(of: ReceivedMessage.self) // Register handler for transcription text streams try await room.registerTextStreamHandler(for: transcriptionTopic) { [weak self] reader, participantIdentity in guard let self else { return } for try await message in reader where !message.isEmpty { await continuation.yield( processIncoming( partialMessage: message, reader: reader, participantIdentity: participantIdentity ) ) } } continuation.onTermination = { [weak self] _ in Task { await self?.room.unregisterTextStreamHandler(for: self?.transcriptionTopic ?? "") } } return stream } // Accumulates partial transcription chunks into complete messages private func processIncoming( partialMessage message: String, reader: TextStreamReader, participantIdentity: Participant.Identity ) -> ReceivedMessage { let segmentID = reader.info.attributes["lk.segment_id"] ?? reader.info.id let partialID = PartialMessageID(segmentID: segmentID, participantIdentity: participantIdentity) let currentStreamID = reader.info.id let timestamp: Date let updatedContent: String if var existingMessage = partialMessages[partialID] { if existingMessage.streamID == currentStreamID { existingMessage.appendContent(message) // Same stream, append } else { existingMessage.content = message // Different stream, replace existingMessage.streamID = currentStreamID } updatedContent = existingMessage.content timestamp = existingMessage.timestamp partialMessages[partialID] = existingMessage } else { updatedContent = message timestamp = reader.info.timestamp partialMessages[partialID] = PartialMessage( content: updatedContent, timestamp: timestamp, streamID: currentStreamID ) } let isFinal = reader.info.attributes["lk.transcription_final"] == "true" if isFinal { partialMessages[partialID] = nil } return ReceivedMessage( id: segmentID, timestamp: timestamp, content: participantIdentity == room.localParticipant.identity ? .userTranscript(updatedContent) : .agentTranscript(updatedContent) ) } } ``` -------------------------------- ### Display Agent Avatar Video with Transitions (SwiftUI) Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt This SwiftUI view displays the agent's avatar video feed when available, featuring smooth transitions. It handles showing the avatar camera track, falling back to an audio visualizer if no video is present, or showing a placeholder visualizer. Dependencies include LiveKitComponents and SwiftUI. It uses SceneStorage for managing video transitions. ```swift import LiveKitComponents import SwiftUI struct AgentParticipantView: View { @Environment(AppViewModel.self) private var viewModel @SceneStorage("videoTransition") private var videoTransition = false var body: some View { ZStack { // Show avatar camera when agent publishes video track if let avatarCameraTrack = viewModel.avatarCameraTrack { SwiftUIVideoView(avatarCameraTrack) .clipShape(RoundedRectangle(cornerRadius: 16)) .aspectRatio(avatarCameraTrack.aspectRatio, contentMode: .fit) .shadow(radius: 20, y: 10) .mask( GeometryReader { proxy in let targetSize = max(proxy.size.width, proxy.size.height) Circle() .frame(width: videoTransition ? targetSize : 48) .position(x: 0.5 * proxy.size.width, y: 0.5 * proxy.size.height) .scaleEffect(2) .animation(.smooth(duration: 1.5), value: videoTransition) } ) .onAppear { videoTransition = true } } // Show audio visualizer when no avatar available else if let agentAudioTrack = viewModel.agentAudioTrack { BarAudioVisualizer( audioTrack: agentAudioTrack, agentState: viewModel.agent?.agentState ?? .listening, barCount: 5, barSpacingFactor: 0.05, barMinOpacity: 0.1 ) .frame(maxWidth: 600, maxHeight: 384) .transition(.opacity) } // Placeholder visualizer else if viewModel.isInteractive { BarAudioVisualizer( audioTrack: nil, agentState: .listening, barCount: 1, barMinOpacity: 0.1 ) .frame(maxWidth: 84, maxHeight: 384) .transition(.opacity) } } .animation(.snappy, value: viewModel.agentAudioTrack?.id) } } ``` -------------------------------- ### Manage LiveKit Media Tracks (Microphone, Camera, Screen Share) in Swift Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt Provides functionality to toggle microphone, camera, and screen sharing in a LiveKit application. It enforces mutual exclusivity for video tracks, ensuring only one video source (camera or screen share) is active at a time. Errors during track management are handled via a centralized error handler. ```swift @MainActor @Observable final class AppViewModel { private(set) var isMicrophoneEnabled = false private(set) var audioTrack: (any AudioTrack)? private(set) var isCameraEnabled = false private(set) var cameraTrack: (any VideoTrack)? private(set) var isScreenShareEnabled = false private(set) var screenShareTrack: (any VideoTrack)? func toggleMicrophone() async { do { try await room.localParticipant.setMicrophone(enabled: !isMicrophoneEnabled) } catch { errorHandler(error) } } func toggleCamera() async { let enable = !isCameraEnabled do { // Disable screen share when enabling camera (one video track at a time) if enable, isScreenShareEnabled { try await room.localParticipant.setScreenShare(enabled: false) } let device = try await CameraCapturer.captureDevices() .first(where: { $0.uniqueID == selectedVideoDeviceID }) try await room.localParticipant.setCamera( enabled: enable, captureOptions: CameraCaptureOptions(device: device) ) } catch { errorHandler(error) } } func toggleScreenShare() async { let enable = !isScreenShareEnabled do { // Disable camera when enabling screen share (one video track at a time) if enable, isCameraEnabled { try await room.localParticipant.setCamera(enabled: false) } try await room.localParticipant.setScreenShare(enabled: enable) } catch { errorHandler(error) } } } ``` -------------------------------- ### Swift Message Streaming Architecture and Chat ViewModel Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt Implements a message streaming architecture using Swift structs for message types and protocols for extensibility. The ChatViewModel aggregates message providers, observes incoming messages, and handles sending user messages, utilizing LiveKit's dependency injection for message receivers and senders. ```swift import Foundation // Message types struct ReceivedMessage: Identifiable, Equatable, Sendable { let id: String let timestamp: Date let content: Content enum Content: Equatable, Sendable { case agentTranscript(String) case userTranscript(String) } } struct SentMessage: Identifiable, Equatable, Sendable { let id: String let timestamp: Date let content: Content enum Content: Equatable, Sendable { case userText(String) } } // Protocols for extensibility protocol MessageReceiver: Sendable { func messages() async throws -> AsyncStream } protocol MessageSender: Sendable { func send(_ message: SentMessage) async throws } // ChatViewModel aggregates all message providers @MainActor @Observable final class ChatViewModel { private(set) var messages: OrderedDictionary = [:] @Dependency(\.messageReceivers) private var messageReceivers @Dependency(\.messageSenders) private var messageSenders init() { observeMessages() } private func observeMessages() { for messageReceiver in messageReceivers { Task { [weak self] in for await message in try await messageReceiver.messages() { guard let self else { return } messages.updateValue(message, forKey: message.id) } } } } func sendMessage(_ text: String) async { let message = SentMessage( id: UUID().uuidString, timestamp: Date(), content: .userText(text) ) do { for sender in messageSenders { try await sender.send(message) } } catch { errorHandler(error) } } } ``` -------------------------------- ### Swift Text Message Sender with Local Loopback using LiveKit Source: https://context7.com/livekit-examples/agent-starter-swift/llms.txt Implements a LocalMessageSender in Swift that conforms to both MessageSender and MessageReceiver protocols. It sends text messages to an agent via a LiveKit data channel and provides a local echo for immediate UI feedback by yielding the message back to its own receiver stream. ```swift import LiveKit actor LocalMessageSender: MessageSender, MessageReceiver { private let room: Room private let topic: String private var messageContinuation: AsyncStream.Continuation? init(room: Room, topic: String = "lk.chat") { self.room = room self.topic = topic } func send(_ message: SentMessage) async throws { guard case let .userText(text) = message.content else { return } // Send to agent via LiveKit data channel try await room.localParticipant.sendText(text, for: topic) // Loopback for immediate UI display let loopbackMessage = ReceivedMessage( id: message.id, timestamp: message.timestamp, content: .userTranscript(text) ) messageContinuation?.yield(loopbackMessage) } func messages() async throws -> AsyncStream { let (stream, continuation) = AsyncStream.makeStream() messageContinuation = continuation return stream } } ``` -------------------------------- ### CLI Command to Create Swift Agent App Source: https://github.com/livekit-examples/agent-starter-swift/blob/main/README.md This command uses the LiveKit CLI to automatically clone the Swift agent starter template and connect it to a LiveKit Cloud sandbox. It creates a new Xcode project in the current directory. ```bash lk app create --template agent-starter-swift --sandbox ``` -------------------------------- ### Configuring Agent Features in Swift Source: https://github.com/livekit-examples/agent-starter-swift/blob/main/README.md This code snippet shows how to configure the enabled input features for the LiveKit agent app in Swift. By updating `AgentFeatures.current`, developers can choose to include or exclude features like voice, text, and video input. ```swift // To update the features enabled in the app, edit VoiceAgent/VoiceAgentApp.swift and update AgentFeatures.current to include or exclude the features you need. // By default, only voice and text input are enabled. // Available input types: // - .voice: Allows the user to speak to the agent using their microphone. **Requires microphone permissions.** // - .text: Allows the user to type to the agent. See [the docs](https://docs.livekit.io/agents/build/text/) for more details. // - .video: Allows the user to share their camera or screen to the agent. This requires a supported model like the Gemini Live API. See [the docs](https://docs.livekit.io/agents/build/vision/#video) for more details. ``` -------------------------------- ### Disabling Preconnect Audio Buffering in Swift Source: https://github.com/livekit-examples/agent-starter-swift/blob/main/README.md Demonstrates how to modify the Swift code to disable preconnect audio buffering. Instead of wrapping the connection in `withPreConnectAudio`, a standard `room.connect` call is used, with microphone enablement handled either during connection or after it completes. ```swift // To disable preconnect buffering but keep voice: // Replace the withPreConnectAudio { ... } block with a standard room.connect call and enable the microphone after connect, for example: // - Connect with connectOptions: .init(enableMicrophone: true) without wrapping in withPreConnectAudio, or // - Connect with microphone disabled and call room.localParticipant.setMicrophone(enabled: true) after connection. ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.