231 lines
7.1 KiB
Swift
231 lines
7.1 KiB
Swift
import SwiftUI
|
|
|
|
import PipecatClientIOSDaily
|
|
import PipecatClientIOS
|
|
|
|
class CallContainerModel: ObservableObject {
|
|
|
|
@Published var voiceClientStatus: String = TransportState.disconnected.description
|
|
@Published var isInCall: Bool = false
|
|
@Published var isBotReady: Bool = false
|
|
@Published var timerCount = 0
|
|
|
|
@Published var isMicEnabled: Bool = false
|
|
|
|
@Published var toastMessage: String? = nil
|
|
@Published var showToast: Bool = false
|
|
|
|
@Published
|
|
var remoteAudioLevel: Float = 0
|
|
@Published
|
|
var localAudioLevel: Float = 0
|
|
|
|
private var meetingTimer: Timer?
|
|
|
|
var rtviClientIOS: RTVIClient?
|
|
|
|
@Published var selectedMic: MediaDeviceId? = nil {
|
|
didSet {
|
|
guard let selectedMic else { return } // don't store nil
|
|
var settings = SettingsManager.getSettings()
|
|
settings.selectedMic = selectedMic.id
|
|
SettingsManager.updateSettings(settings: settings)
|
|
}
|
|
}
|
|
@Published var availableMics: [MediaDeviceInfo] = []
|
|
|
|
init() {
|
|
// Changing the log level
|
|
PipecatClientIOS.setLogLevel(.warn)
|
|
}
|
|
|
|
@MainActor
|
|
func connect(backendURL: String) {
|
|
let baseUrl = backendURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if(baseUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty){
|
|
self.showError(message: "Need to fill the backendURL. For more info visit: https://bots.daily.co")
|
|
return
|
|
}
|
|
|
|
let currentSettings = SettingsManager.getSettings()
|
|
let rtviClientOptions = RTVIClientOptions.init(
|
|
enableMic: currentSettings.enableMic,
|
|
enableCam: false,
|
|
params: RTVIClientParams(
|
|
baseUrl: baseUrl,
|
|
endpoints: RTVIURLEndpoints(connect: "/connect")
|
|
)
|
|
)
|
|
self.rtviClientIOS = RTVIClient.init(
|
|
transport: DailyTransport.init(options: rtviClientOptions),
|
|
options: rtviClientOptions
|
|
)
|
|
self.rtviClientIOS?.delegate = self
|
|
self.rtviClientIOS?.start() { result in
|
|
switch result {
|
|
case .failure(let error):
|
|
self.showError(message: error.localizedDescription)
|
|
self.rtviClientIOS = nil
|
|
case .success():
|
|
// Apply initial mic preference
|
|
if let selectedMic = currentSettings.selectedMic {
|
|
self.selectMic(MediaDeviceId(id: selectedMic))
|
|
}
|
|
// Populate available devices list
|
|
self.availableMics = self.rtviClientIOS?.getAllMics() ?? []
|
|
}
|
|
}
|
|
self.saveCredentials(backendURL: baseUrl)
|
|
}
|
|
|
|
@MainActor
|
|
func disconnect() {
|
|
self.rtviClientIOS?.disconnect(completion: nil)
|
|
self.rtviClientIOS?.release()
|
|
}
|
|
|
|
func showError(message: String) {
|
|
self.toastMessage = message
|
|
self.showToast = true
|
|
// Hide the toast after 5 seconds
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
|
self.showToast = false
|
|
self.toastMessage = nil
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func toggleMicInput() {
|
|
self.rtviClientIOS?.enableMic(enable: !self.isMicEnabled) { result in
|
|
switch result {
|
|
case .success():
|
|
self.isMicEnabled = self.rtviClientIOS?.isMicEnabled ?? false
|
|
case .failure(let error):
|
|
self.showError(message: error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func startTimer(withExpirationTime expirationTime: Int) {
|
|
let currentTime = Int(Date().timeIntervalSince1970)
|
|
self.timerCount = expirationTime - currentTime
|
|
self.meetingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
|
|
DispatchQueue.main.async {
|
|
self.timerCount -= 1
|
|
}
|
|
}
|
|
}
|
|
|
|
private func stopTimer() {
|
|
self.meetingTimer?.invalidate()
|
|
self.meetingTimer = nil
|
|
self.timerCount = 0
|
|
}
|
|
|
|
func saveCredentials(backendURL: String) {
|
|
var currentSettings = SettingsManager.getSettings()
|
|
currentSettings.backendURL = backendURL
|
|
// Saving the settings
|
|
SettingsManager.updateSettings(settings: currentSettings)
|
|
}
|
|
|
|
@MainActor
|
|
func selectMic(_ mic: MediaDeviceId) {
|
|
self.selectedMic = mic
|
|
self.rtviClientIOS?.updateMic(micId: mic, completion: nil)
|
|
}
|
|
}
|
|
|
|
extension CallContainerModel:RTVIClientDelegate {
|
|
|
|
private func handleEvent(eventName: String, eventValue: Any? = nil) {
|
|
if let value = eventValue {
|
|
print("RTVI Demo, received event:\(eventName), value:\(value)")
|
|
} else {
|
|
print("RTVI Demo, received event: \(eventName)")
|
|
}
|
|
}
|
|
|
|
func onTransportStateChanged(state: TransportState) {
|
|
Task { @MainActor in
|
|
self.handleEvent(eventName: "onTransportStateChanged", eventValue: state)
|
|
self.voiceClientStatus = state.description
|
|
self.isInCall = ( state == .connecting || state == .connected || state == .ready || state == .authenticating )
|
|
}
|
|
}
|
|
|
|
func onBotReady(botReadyData: BotReadyData) {
|
|
Task { @MainActor in
|
|
self.handleEvent(eventName: "onBotReady.")
|
|
self.isBotReady = true
|
|
if let expirationTime = self.rtviClientIOS?.expiry() {
|
|
self.startTimer(withExpirationTime: expirationTime)
|
|
}
|
|
}
|
|
}
|
|
|
|
func onConnected() {
|
|
Task { @MainActor in
|
|
self.isMicEnabled = self.rtviClientIOS?.isMicEnabled ?? false
|
|
}
|
|
}
|
|
|
|
func onDisconnected() {
|
|
Task { @MainActor in
|
|
self.stopTimer()
|
|
self.isBotReady = false
|
|
}
|
|
}
|
|
|
|
func onRemoteAudioLevel(level: Float, participant: Participant) {
|
|
Task { @MainActor in
|
|
self.remoteAudioLevel = level
|
|
}
|
|
}
|
|
|
|
func onUserAudioLevel(level: Float) {
|
|
Task { @MainActor in
|
|
self.localAudioLevel = level
|
|
}
|
|
}
|
|
|
|
func onUserTranscript(data: Transcript) {
|
|
Task { @MainActor in
|
|
if (data.final ?? false) {
|
|
self.handleEvent(eventName: "onUserTranscript", eventValue: data.text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func onBotTranscript(data: String) {
|
|
Task { @MainActor in
|
|
self.handleEvent(eventName: "onBotTranscript", eventValue: data)
|
|
}
|
|
}
|
|
|
|
func onError(message: String) {
|
|
Task { @MainActor in
|
|
self.handleEvent(eventName: "onError", eventValue: message)
|
|
self.showError(message: message)
|
|
}
|
|
}
|
|
|
|
func onTracksUpdated(tracks: Tracks) {
|
|
Task { @MainActor in
|
|
self.handleEvent(eventName: "onTracksUpdated", eventValue: tracks)
|
|
}
|
|
}
|
|
|
|
func onAvailableMicsUpdated(mics: [MediaDeviceInfo]) {
|
|
Task { @MainActor in
|
|
self.availableMics = mics
|
|
}
|
|
}
|
|
|
|
func onMicUpdated(mic: MediaDeviceInfo?) {
|
|
Task { @MainActor in
|
|
self.selectedMic = mic?.id
|
|
}
|
|
}
|
|
}
|