此软件包包含围绕 SFSpeechRecognizer 的语音识别部分的一些非常简单的封装器,使您可以轻松使用它。
SwiftSpeechRecognizer
使用带有 async
await
和几个 AsyncStream
的 Swift 并发SwiftSpeechRecognizerDependency
上述库的封装器,方便与 Point-Free Dependencies 库或使用 Composable Architecture (TCA) 构建的项目集成。SwiftSpeechCombine
OG 库,仍然在此软件包中可用
NSSpeechRecognitionUsageDescription
,并描述其用途。
import Speech
import SwiftSpeechRecognizer
struct SpeechRecognizer {
var authorizationStatusChanged: (SFSpeechRecognizerAuthorizationStatus) -> Void
var speechRecognitionStatusChanged: (SpeechRecognitionStatus) -> Void
var utteranceChanged: (String) -> Void
private let speechRecognizer = SwiftSpeechRecognizer.live
func start() {
speechRecognizer.requestAuthorization()
Task {
for await authorizationStatus in speechRecognizer.authorizationStatus() {
authorizationStatusChanged(authorizationStatus)
switch authorizationStatus {
case .authorized:
startRecording()
default:
print("Not authorized to use speech recognizer")
}
}
}
}
private func startRecording() {
do {
try speechRecognizer.startRecording()
Task {
for await recognitionStatus in speechRecognizer.recognitionStatus() {
speechRecognitionStatusChanged(recognitionStatus)
}
}
Task {
for await newUtterance in speechRecognizer.newUtterance() {
utteranceChanged(newUtterance)
}
}
} catch {
print("Something went wrong: \(error)")
speechRecognizer.stopRecording()
}
}
}
import SwiftUI
struct ContentView: View {
var body: some View {
Button {
let speechRecognizer = SpeechRecognizer(
authorizationStatusChanged: { newAuthorizationStatus in
print("Authorization status changed: \(newAuthorizationStatus)")
},
speechRecognitionStatusChanged: { newRecognitionStatus in
print("Recognition status changed: \(newRecognitionStatus)")
},
utteranceChanged: { newUtterance in
print("Recognized utterance changed: \(newUtterance)")
}
)
speechRecognizer.start()
} label: {
Text("Start speech recognizer")
}
}
}
在您的 Reducer
中添加 @Dependency(\.speechRecognizer) var speechRecognizer
,您将可以访问上面提到的所有函数。
import ComposableArchitecture
import Speech
import SwiftSpeechRecognizerDependency
public struct SpeechRecognizer: ReducerProtocol {
public struct State: Equatable {
public var status: SpeechRecognitionStatus = .notStarted
public var authorizationStatus: SFSpeechRecognizerAuthorizationStatus = .notDetermined
public var utterance: String?
public init(
status: SpeechRecognitionStatus = .notStarted,
authorizationStatus: SFSpeechRecognizerAuthorizationStatus = .notDetermined,
utterance: String? = nil
) {
self.status = status
self.authorizationStatus = authorizationStatus
self.utterance = utterance
}
}
public enum Action: Equatable {
case buttonTapped
case startRecording
case stopRecording
case setStatus(SpeechRecognitionStatus)
case setUtterance(String?)
case requestAuthorization
case setAuthorizationStatus(SFSpeechRecognizerAuthorizationStatus)
case cancel
}
@Dependency(\.speechRecognizer) var speechRecognizer
public init() {}
private enum AuthorizationStatusTaskID {}
private enum RecognitionStatusTaskID {}
private enum NewUtteranceTaskID {}
public func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .buttonTapped:
switch state.status {
case .recording, .stopping:
return stopRecording(state: &state)
case .notStarted, .stopped:
return startRecording(state: &state)
}
case .startRecording:
return startRecording(state: &state)
case .stopRecording:
return stopRecording(state: &state)
case let .setStatus(status):
state.status = status
switch status {
case .recording, .stopping:
state.utterance = nil
case .notStarted, .stopped:
break
}
return .none
case let .setUtterance(utterance):
state.utterance = utterance
return .none
case let .setAuthorizationStatus(authorizationStatus):
state.authorizationStatus = authorizationStatus
switch authorizationStatus {
case .authorized:
return startRecording(state: &state)
case .notDetermined:
return requestAuthorization(state: &state)
default: break
}
return .none
case .requestAuthorization:
return requestAuthorization(state: &state)
case .cancel:
return .cancel(ids: [AuthorizationStatusTaskID.self, RecognitionStatusTaskID.self, NewUtteranceTaskID.self])
}
}
private func startRecording(state: inout State) -> EffectTask<Action> {
guard state.authorizationStatus == .authorized
else { return requestAuthorization(state: &state) }
do {
try speechRecognizer.startRecording()
return .merge(
.run(operation: { send in
for await recognitionStatus in speechRecognizer.recognitionStatus() {
await send(.setStatus(recognitionStatus))
}
})
.cancellable(id: RecognitionStatusTaskID.self),
.run(operation: { send in
for await newUtterance in speechRecognizer.newUtterance() {
await send(.setUtterance(newUtterance))
}
})
.cancellable(id: NewUtteranceTaskID.self)
)
} catch {
return stopRecording(state: &state)
}
}
private func stopRecording(state: inout State) -> EffectTask<Action> {
speechRecognizer.stopRecording()
return .none
}
private func requestAuthorization(state: inout State) -> EffectTask<Action> {
speechRecognizer.requestAuthorization()
return .run { send in
for await authorizationStatus in speechRecognizer.authorizationStatus() {
await send(.setAuthorizationStatus(authorizationStatus))
}
}
.cancellable(id: AuthorizationStatusTaskID.self)
}
}
您可以实例化/注入 SpeechRecognitionEngine
对象,它具有以下行为
func requestAuthorization()
: 当您想询问用户是否可以使用语音识别时,调用此方法。请遵循 Apple Human Guidelines 建议:Asking Permission to Use Speech Recognition
authorizationStatusPublisher
以了解用户何时以及做出什么决定(见下文)func startRecording() throws
: 当您想启动语音识别时,调用此方法,AVAudioEngine
的所有复杂性都隐藏在此函数之后。
recognizedUtterancePublisher
或 newUtterancePublisher
。请参阅下面的区别func stopRecording()
: 当您想停止语音识别时,调用此方法
var authorizationStatusPublisher
: 订阅此项以了解语音识别的当前授权状态(此授权包括麦克风使用)
var recognizedUtterancePublisher
: 订阅此项以了解何时识别出 String
var isRecognitionAvailablePublisher
: 订阅此项以了解识别状态何时更改。它可能是
notStarted
: 会话尚未开始任何识别recording
: 识别正在处理中stopping
: 刚刚触发了停止识别stopped
: 识别已停止,并准备好开始下一次识别var isRecognitionAvailablePublisher
: 订阅此项以了解设备上何时可以使用语音识别(例如,互联网连接是否允许处理语音识别)
var newUtterancePublisher
: 如果您只对“新”的实际话语感兴趣,请订阅此项。它实际上是 recognizedUtterancePublisher
的快捷方式,同时它删除了重复项,并且只获取实际的 String(因此永远不会是 nil
)
import Combine
import Speech
import SwiftSpeechCombine
let engine: SpeechRecognitionEngine = SpeechRecognitionSpeechEngine()
var cancellables = Set<AnyCancellable>()
// Only start to record if you're authorized to do so!
func speechRecognitionStatusChanged(authorizationStatus: SFSpeechRecognizerAuthorizationStatus) {
guard authorizationStatus == .authorized
else { return }
do {
try engine.startRecording()
engine.newUtterancePublisher
.sink { newUtterance in
// Do whatever you want with the recognized utterance...
print(newUtterance)
}
.store(in: &cancellables)
engine.recognitionStatusPublisher
.sink { status in
// Very useful to notify the user of the current status
setTheButtonState(status: status)
}
.store(in: &cancellables)
} catch {
print(error)
engine.stopRecording()
}
}
func setTheButtonState(status: SpeechRecognitionStatus) {
switch status {
case .notStarted: print("The button is ready to be tapped")
case .recording: print("The button is showing a progress spinner")
case .stopping: print("The button is disabled")
case .stopped: print("The button is ready to be tapped for a new recognition")
}
}
engine.requestAuthorization()
engine.authorizationStatusPublisher
.compactMap { $0 }
.sink { authorizationStatus in
speechRecognitionStatusChanged(authorizationStatus: authorizationStatus)
}
.store(in: &cancellables)
您可以通过将 SwiftSpeechCombine 添加为软件包依赖项来将其添加到 Xcode 项目中。
编辑您的 Package.swift
以添加此库。
let package = Package(
...
dependencies: [
.package(url: "https://github.com/renaudjenny/SwiftSpeechCombine", from: "1.0.0"),
...
],
targets: [
.target(
name: "<Your project name>",
dependencies: [
.product(name: "SwiftSpeechRecognizer", package: "swift-speech-recognizer"), // <-- Modern concurrency
.product(name: "SwiftSpeechRecognizerDependency", package: "swift-speech-recognizer"), // <-- Point-Free Dependencies library wrapper
.product(name: "SwiftSpeechCombine", package: "swift-speech-recognizer"), // <-- Combine wrapper
]),
...
]
)