// // SpeechForwarder.swift // ListenerGS // // Created by Jeremy Rand on 2021-10-18. // import Foundation import os import Speech class SpeechForwarder : ObservableObject { @Published var listening = false @Published var connected = false @Published var textHeard = "" let LISTEN_STATE_MSG = 1 let LISTEN_TEXT_MSG = 2 let port = 19026 private var client: TCPClient? private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: Locale.preferredLanguages[0]))! private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? private var recognitionTask: SFSpeechRecognitionTask? private let audioEngine = AVAudioEngine() private let logger = Logger() func connect(destination : String) { logger.debug("Attempting to connect to \(destination)") client = TCPClient(address: destination, port: Int32(port)) guard let client = client else { return } switch client.connect(timeout: 10) { case .success: connected = true logger.debug("Connected to \(destination)") case .failure(let error): client.close() self.client = nil logger.error("Failed to connect to \(destination): \(String(describing: error))") break } } func disconnect() { if (listening) { listen() } guard let client = client else { return } client.close() connected = false } func listen() { self.listening.toggle() if (self.listening) { SFSpeechRecognizer.requestAuthorization { authStatus in // The authorization status results in changes to the // app’s interface, so process the results on the app’s // main queue. OperationQueue.main.addOperation { switch authStatus { case .authorized: break case .denied: self.listening = false break case .restricted: self.listening = false break case .notDetermined: self.listening = false break default: self.listening = false break } } } } guard let client = client else { return } if (self.listening) { switch (client.send(data: isListening())) { case .success: break case .failure(let error): self.listening = false logger.error("Unable to send header: \(String(describing: error))") } } if (self.listening) { do { try startRecording() logger.debug("Started listening") } catch { self.listening = false } } if (!self.listening) { logger.debug("Stopped listening") audioEngine.stop() recognitionRequest?.endAudio() switch (client.send(data: isListening())) { case .success: break case .failure(let error): self.listening = false logger.error("Failed to send header: \(String(describing: error))") } } } private func isListening() -> Data { return pack(" 0) { if (latestText.prefix(commonChars) == self.textHeard.prefix(commonChars)) { break } commonChars -= 1 } var stringToSend = "" if (commonChars < self.textHeard.count) { stringToSend = String(repeating: "\u{7f}", count: self.textHeard.count - commonChars) } stringToSend.append(contentsOf: latestText.suffix(latestText.count - commonChars).replacingOccurrences(of: "\n", with: "\r")) if (stringToSend.count > 0) { // TODO - Handle strings to send that are longer than 64K (doubt that would happen though) let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringBuiltInEncodings.macRoman.rawValue)) let encoding = String.Encoding(rawValue: nsEnc) // String.Encoding if let bytes = stringToSend.data(using: encoding) { switch (client.send(data: pack("