summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicoleYarroch <nicole@livio.io>2018-05-10 10:33:23 -0400
committerNicoleYarroch <nicole@livio.io>2018-05-10 10:33:23 -0400
commit06352d53ce64a994d253869fe5bd82c3203b1462 (patch)
treee0fd3f689fa4ba244d4d302452cfe677b8b486bd
parentcc2ef0ac60c4c143b7ac5a87a6eb39915ae9b057 (diff)
downloadsdl_ios-06352d53ce64a994d253869fe5bd82c3203b1462.tar.gz
Added speech recognition to the Swift example app
Signed-off-by: NicoleYarroch <nicole@livio.io>
-rw-r--r--SmartDeviceLink_Example/AudioManager.swift102
-rw-r--r--SmartDeviceLink_Example/MenuManager.swift115
2 files changed, 114 insertions, 103 deletions
diff --git a/SmartDeviceLink_Example/AudioManager.swift b/SmartDeviceLink_Example/AudioManager.swift
index 259303d15..bc5a1444b 100644
--- a/SmartDeviceLink_Example/AudioManager.swift
+++ b/SmartDeviceLink_Example/AudioManager.swift
@@ -12,54 +12,65 @@ import SmartDeviceLink
import SmartDeviceLinkSwift
import Speech
-typealias audioRecordingHandler = ((SDLAudioRecordingState) -> Void)
+fileprivate enum AudioRecordingState {
+ case listening, notListening
+}
-fileprivate enum SearchManagerState {
- case listening, notListening, notAuthorized, badRegion
+fileprivate enum SpeechRecognitionAuthState {
+ case authorized, notAuthorized, badRegion
}
@available(iOS 10.0, *)
class AudioManager: NSObject {
fileprivate let sdlManager: SDLManager!
fileprivate var audioData: Data?
- fileprivate var audioRecordingState: SDLAudioRecordingState
- fileprivate var speechRecognitionListenState: SearchManagerState
+ fileprivate var audioRecordingState: AudioRecordingState
// Speech recognition
+ fileprivate var speechRecognitionAuthState: SpeechRecognitionAuthState
fileprivate var speechRecognitionRequest: SFSpeechAudioBufferRecognitionRequest?
fileprivate var speechRecognizer: SFSpeechRecognizer?
fileprivate var speechRecognitionTask: SFSpeechRecognitionTask?
+ fileprivate var speechTranscription: String = ""
private let speechDefaultLocale = Locale(identifier: "en-US")
init(sdlManager: SDLManager) {
self.sdlManager = sdlManager
audioData = Data()
audioRecordingState = .notListening
+ speechRecognitionAuthState = .notAuthorized
speechRecognizer = SFSpeechRecognizer(locale: speechDefaultLocale)
- speechRecognitionListenState = .notAuthorized
super.init()
speechRecognizer?.delegate = self
- speechRecognitionListenState = AudioManager.checkAuthorization(speechRecognizer: speechRecognizer)
+ speechRecognitionAuthState = AudioManager.checkAuthorization(speechRecognizer: speechRecognizer)
- if speechRecognitionListenState == .notAuthorized {
- requestSFSpeechRecognizerAuthorization()
- }
+ guard AudioManager.checkAuthorization(speechRecognizer: speechRecognizer) != .authorized else { return }
+ requestSFSpeechRecognizerAuthorization()
}
func stopManager() {
audioRecordingState = .notListening
audioData = Data()
+ speechTranscription = ""
}
/// Starts an audio recording using the in-car microphone. During the recording, a pop-up will let the user know that they are being recorded. The pop-up is only dismissed when the recording stops.
func startRecording() {
- guard audioRecordingState == .notListening else { return }
+ guard speechRecognitionAuthState == .authorized else {
+ SDLLog.w("This app does not have permission to access the Speech Recognition API")
+ sdlManager.send(AlertManager.alertWithMessageAndCloseButton("You must give this app permission to access Speech Recognition."))
+ return
+ }
- startSpeechRecognitionTask()
+ guard audioRecordingState == .notListening else {
+ SDLLog.w("Audio recording already in progress")
+ return
+ }
- let recordingDurationMilliseconds: UInt32 = 6000
+ startSpeechRecognitionTask()
+ let recordingDurationMilliseconds: UInt32 = 20000
let performAudioPassThru = SDLPerformAudioPassThru(initialPrompt: "Starting sound recording", audioPassThruDisplayText1: "Say Something", audioPassThruDisplayText2: "Recording for \(recordingDurationMilliseconds / 1000) seconds", samplingRate: .rate16KHZ, bitsPerSample: .sample16Bit, audioType: .PCM, maxDuration: recordingDurationMilliseconds, muteAudio: true, audioDataHandler: audioDataReceivedHandler)
sdlManager.send(request: performAudioPassThru, responseHandler: audioPassThruEndedHandler)
@@ -85,7 +96,6 @@ private extension AudioManager {
return { [weak self] data in
guard let data = data else { return }
if self?.audioRecordingState == .notListening {
- self?.audioData = Data()
self?.audioRecordingState = .listening
}
@@ -104,14 +114,12 @@ private extension AudioManager {
switch response.resultCode {
case .success: // The `PerformAudioPassThru` timed out or the "Done" button was pressed in the pop-up.
SDLLog.d("Audio Pass Thru ended successfully")
+ guard let speechTranscription = self?.speechTranscription else { return }
+ self?.sdlManager.send(AlertManager.alertWithMessageAndCloseButton("You said: \(speechTranscription.isEmpty ? "No speech detected" : speechTranscription)"))
case .aborted: // The "Cancel" button was pressed in the pop-up. Ignore this audio pass thru.
SDLLog.d("Audio recording canceled")
- // self?.audioData = Data()
- // self?.sdlManager.send(AlertManager.alertWithMessageAndCloseButton("Recording canceled"))
default:
- SDLLog.d("Audio recording something happened?: \(response.resultCode)")
- // self?.audioData = Data()
- // self?.sdlManager.send(AlertManager.alertWithMessageAndCloseButton("Recording unsuccessful", textField2: "\(response.resultCode.rawValue.rawValue)"))
+ SDLLog.d("Audio recording not successful: \(response.resultCode)")
}
self?.stopSpeechRecognitionTask()
@@ -156,35 +164,32 @@ private extension AudioManager {
speechRecognitionRequest.taskHint = .search
speechRecognitionTask = speechRecognizer.recognitionTask(with: speechRecognitionRequest) { [weak self] result, error in
- print("Audio: \(String(describing: result)), error: \(String(describing: error))")
- guard let result = result else { return }
+ guard let result = result else {
+ SDLLog.e("No result")
+ return
+ }
if error != nil {
SDLLog.e("Speech recognition error: \(error!.localizedDescription)")
}
let speechTranscription = result.bestTranscription.formattedString
- if result.isFinal {
- SDLLog.d("Ongoing transcription: \(speechTranscription)")
- } else {
- self?.sdlManager.send(AlertManager.alertWithMessageAndCloseButton("You said: \(speechTranscription.isEmpty ? "No speech detected" : speechTranscription)"))
- }
+ SDLLog.d("Ongoing transcription: \(speechTranscription)")
+ self?.speechTranscription = speechTranscription
}
}
/// Cleans up a speech detection session that has ended
func stopSpeechRecognitionTask() {
audioRecordingState = .notListening
+ audioData = Data()
+ speechTranscription = ""
- if speechRecognitionTask != nil {
- speechRecognitionTask!.cancel()
- // speechRecognitionTask = nil
- }
-
- if speechRecognitionRequest != nil {
- speechRecognitionRequest!.endAudio()
- // speechRecognitionRequest = nil
- }
+ guard self.speechRecognitionTask != nil, self.speechRecognitionRequest != nil else { return }
+ self.speechRecognitionTask!.cancel()
+ self.speechRecognitionRequest!.endAudio()
+ self.speechRecognitionTask = nil
+ self.speechRecognitionRequest = nil
}
}
@@ -193,38 +198,37 @@ private extension AudioManager {
@available(iOS 10.0, *)
extension AudioManager: SFSpeechRecognizerDelegate {
func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
- speechRecognitionListenState = AudioManager.checkAuthorization(speechRecognizer: speechRecognizer)
+ speechRecognitionAuthState = AudioManager.checkAuthorization(speechRecognizer: speechRecognizer)
}
- fileprivate static func checkAuthorization(speechRecognizer: SFSpeechRecognizer?) -> SearchManagerState {
- // Check the speech recognizer init'd successfully
+ /// Checks the current authorization status of the Speech Recognition API. The user can change this status via the native Settings app.
+ ///
+ /// - Parameter speechRecognizer: The SFSpeechRecognizer
+ /// - Returns: The current authorization status
+ fileprivate static func checkAuthorization(speechRecognizer: SFSpeechRecognizer?) -> SpeechRecognitionAuthState {
+ // Check if the speech recognizer init'd successfully
guard speechRecognizer != nil else {
return .badRegion
}
- // Check auth status
+ // Check authorization status
switch SFSpeechRecognizer.authorizationStatus() {
case .authorized:
- return .notListening
+ return .authorized
default:
return .notAuthorized
}
}
-}
-@available(iOS 10.0, *)
-private extension AudioManager {
- func requestSFSpeechRecognizerAuthorization() {
+ /// Asks the user via an alert if they want to authorize this app to access the Speech Recognition API.
+ fileprivate func requestSFSpeechRecognizerAuthorization() {
SFSpeechRecognizer.requestAuthorization { authStatus in
- /* The callback may not be called on the main thread. Add an
- operation to the main queue to update the record button's state.
- */
OperationQueue.main.addOperation {
switch authStatus {
case .authorized:
- self.speechRecognitionListenState = .listening
+ self.speechRecognitionAuthState = .authorized
default:
- self.speechRecognitionListenState = .notAuthorized
+ self.speechRecognitionAuthState = .notAuthorized
}
}
}
diff --git a/SmartDeviceLink_Example/MenuManager.swift b/SmartDeviceLink_Example/MenuManager.swift
index 6521095ae..a843b8d98 100644
--- a/SmartDeviceLink_Example/MenuManager.swift
+++ b/SmartDeviceLink_Example/MenuManager.swift
@@ -18,7 +18,7 @@ class MenuManager: NSObject {
return SDLCreateInteractionChoiceSet(id: UInt32(choiceSetId), choiceSet: createChoiceSet())
}
- /// Creates and returns the root menu items.
+ /// Creates and returns the menu items
///
/// - Parameter manager: The SDL Manager
/// - Returns: An array of SDLAddCommand objects
@@ -26,63 +26,19 @@ class MenuManager: NSObject {
return [menuCellSpeakName(with: manager), menuCellGetVehicleSpeed(with: manager), menuCellShowPerformInteraction(with: manager), menuCellRecordInCarMicrophoneAudio(with: manager), menuCellDialNumber(with: manager), menuCellWithSubmenu(with: manager)]
}
- /// Creates and returns voice commands. The voice commands are menu items that are selected using the voice recognition system.
+ /// Creates and returns the voice commands. The voice commands are menu items that are selected using the voice recognition system.
///
/// - Parameter manager: The SDL Manager
/// - Returns: An array of SDLVoiceCommand objects
class func allVoiceMenuItems(with manager: SDLManager) -> [SDLVoiceCommand] {
- guard manager.systemCapabilityManager.vrCapability else {
- SDLLog.e("The head unit does not support voice recognition")
- return []
- }
+// guard manager.systemCapabilityManager.vrCapability else {
+// SDLLog.e("The head unit does not support voice recognition")
+// return []
+// }
return [voiceCommandStart(with: manager), voiceCommandStop(with: manager)]
}
}
-// MARK: - Perform Interaction Choice Set Menu
-
-private extension MenuManager {
- static let choiceSetId = 100
- /// The PICS menu items
- ///
- /// - Returns: An array of SDLChoice items
- class func createChoiceSet() -> [SDLChoice] {
- let firstChoice = SDLChoice(id: 1, menuName: PICSFirstChoice, vrCommands: [PICSFirstChoice])
- let secondChoice = SDLChoice(id: 2, menuName: PICSSecondChoice, vrCommands: [PICSSecondChoice])
- let thirdChoice = SDLChoice(id: 3, menuName: PICSThirdChoice, vrCommands: [PICSThirdChoice])
- return [firstChoice, secondChoice, thirdChoice]
- }
-
- /// Creates a PICS with three menu items and customized voice commands
- ///
- /// - Returns: A SDLPerformInteraction request
- class func createPerformInteraction() -> SDLPerformInteraction {
- let performInteraction = SDLPerformInteraction(initialPrompt: PICSInitialPrompt, initialText: PICSInitialText, interactionChoiceSetIDList: [choiceSetId as NSNumber], helpPrompt: PICSHelpPrompt, timeoutPrompt: PICSTimeoutPrompt, interactionMode: .both, timeout: 10000)
- performInteraction.interactionLayout = .listOnly
- return performInteraction
- }
-
- /// Shows a PICS. The handler is called when the user selects a menu item or when the menu times out after a set amount of time. A custom text-to-speech phrase is spoken when the menu is closed.
- ///
- /// - Parameter manager: The SDL Manager
- class func showPerformInteractionChoiceSet(with manager: SDLManager) {
- manager.send(request: createPerformInteraction()) { (_, response, error) in
- guard response?.resultCode == .success else {
- SDLLog.e("The Show Perform Interaction Choice Set request failed: \(String(describing: error?.localizedDescription))")
- return
- }
-
- if response?.resultCode == .timedOut {
- // The menu timed out before the user could select an item
- manager.send(SDLSpeak(tts: TTSYouMissed))
- } else if response?.resultCode == .success {
- // The user selected an item in the menu
- manager.send(SDLSpeak(tts: TTSGoodJob))
- }
- }
- }
-}
-
// MARK: - Root Menu
private extension MenuManager {
@@ -119,7 +75,7 @@ private extension MenuManager {
})
}
- /// Menu item that starts recording sounds via the in-car microphone when selected.
+ /// Menu item that starts recording sounds via the in-car microphone when selected
///
/// - Parameter manager: The SDL Manager
/// - Returns: A SDLMenuCell object
@@ -132,11 +88,11 @@ private extension MenuManager {
}
return SDLMenuCell(title: ACRecordInCarMicrophoneAudioMenuName, icon: SDLArtwork(image: UIImage(named: SpeakBWIconImageName)!, persistent: true, as: .PNG), voiceCommands: [ACRecordInCarMicrophoneAudioMenuName], handler: { (triggerSource) in
- manager.send(AlertManager.alertWithMessageAndCloseButton("Speech recognition feature only available on iOS versions 10+"))
+ manager.send(AlertManager.alertWithMessageAndCloseButton("Speech recognition feature only available on iOS 10+"))
})
}
- /// Menu item that dials a phone number when selected.
+ /// Menu item that dials a phone number when selected
///
/// - Parameter manager: The SDL Manager
/// - Returns: A SDLMenuCell object
@@ -151,7 +107,7 @@ private extension MenuManager {
})
}
- /// Menu item that opens a submenu when selected.
+ /// Menu item that opens a submenu when selected
///
/// - Parameter manager: The SDL Manager
/// - Returns: A SDLMenuCell object
@@ -168,16 +124,67 @@ private extension MenuManager {
}
}
+// MARK: - Perform Interaction Choice Set Menu
+
+private extension MenuManager {
+ static let choiceSetId = 100
+ /// The PICS menu items
+ ///
+ /// - Returns: An array of SDLChoice items
+ class func createChoiceSet() -> [SDLChoice] {
+ let firstChoice = SDLChoice(id: 1, menuName: PICSFirstChoice, vrCommands: [PICSFirstChoice])
+ let secondChoice = SDLChoice(id: 2, menuName: PICSSecondChoice, vrCommands: [PICSSecondChoice])
+ let thirdChoice = SDLChoice(id: 3, menuName: PICSThirdChoice, vrCommands: [PICSThirdChoice])
+ return [firstChoice, secondChoice, thirdChoice]
+ }
+
+ /// Creates a PICS with three menu items and customized voice commands
+ ///
+ /// - Returns: A SDLPerformInteraction request
+ class func createPerformInteraction() -> SDLPerformInteraction {
+ let performInteraction = SDLPerformInteraction(initialPrompt: PICSInitialPrompt, initialText: PICSInitialText, interactionChoiceSetIDList: [choiceSetId as NSNumber], helpPrompt: PICSHelpPrompt, timeoutPrompt: PICSTimeoutPrompt, interactionMode: .both, timeout: 10000)
+ performInteraction.interactionLayout = .listOnly
+ return performInteraction
+ }
+
+ /// Shows a PICS. The handler is called when the user selects a menu item or when the menu times out after a set amount of time. A custom text-to-speech phrase is spoken when the menu is closed.
+ ///
+ /// - Parameter manager: The SDL Manager
+ class func showPerformInteractionChoiceSet(with manager: SDLManager) {
+ manager.send(request: createPerformInteraction()) { (_, response, error) in
+ guard response?.resultCode == .success else {
+ SDLLog.e("The Show Perform Interaction Choice Set request failed: \(String(describing: error?.localizedDescription))")
+ return
+ }
+
+ if response?.resultCode == .timedOut {
+ // The menu timed out before the user could select an item
+ manager.send(SDLSpeak(tts: TTSYouMissed))
+ } else if response?.resultCode == .success {
+ // The user selected an item in the menu
+ manager.send(SDLSpeak(tts: TTSGoodJob))
+ }
+ }
+ }
+}
// MARK: - Menu Voice Commands
private extension MenuManager {
+ /// Voice command menu item that shows an alert when triggered via the VR system
+ ///
+ /// - Parameter manager: The SDL Manager
+ /// - Returns: A SDLVoiceCommand object
class func voiceCommandStart(with manager: SDLManager) -> SDLVoiceCommand {
return SDLVoiceCommand(voiceCommands: ["Start"], handler: {
manager.send(AlertManager.alertWithMessageAndCloseButton("Start voice command selected!"))
})
}
+ /// Voice command menu item that shows an alert when triggered via the VR system
+ ///
+ /// - Parameter manager: The SDL Manager
+ /// - Returns: A SDLVoiceCommand object
class func voiceCommandStop(with manager: SDLManager) -> SDLVoiceCommand {
return SDLVoiceCommand(voiceCommands: ["Stop"], handler: {
manager.send(AlertManager.alertWithMessageAndCloseButton("Stop voice command selected!"))