我在我的迅速项目中实现了音频捕获服务,该服务应该处理音频记录并将其转换为文本。
class AudioCaptureService:
// EXLCUDED A BUNCH OF SETUP CODE
func record(textView: UITextView, microphoneButton: UIButton) {
if audioEngine.isRunning {
audioEngine.stop()
recognitionRequest?.endAudio()
microphoneButton.setImage(#imageLiteral(resourceName: "microphone-full-white").withRenderingMode(.alwaysOriginal), for: .normal)
print("stopped recording...")
} else if !audioEngine.isRunning, isRecordingEnabled{
startRecording(textView: textView)
print("start recording...")
microphoneButton.setImage(#imageLiteral(resourceName: "microphone-red").withRenderingMode(.alwaysOriginal), for: .normal)
}
}
fileprivate func startRecording(textView: UITextView) {
if recognitionTask != nil {
recognitionTask?.cancel()
recognitionTask = nil
}
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryRecord)
try audioSession.setMode(AVAudioSessionModeMeasurement)
try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
} catch {
print("audioSession properties weren't set because of an error.")
}
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
let inputNode = audioEngine.inputNode
guard let recognitionRequest = recognitionRequest else {
fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
}
recognitionRequest.shouldReportPartialResults = true
recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in
var isFinal = false
if result != nil {
textView.text = result?.bestTranscription.formattedString
isFinal = (result?.isFinal)!
}
if error != nil || isFinal {
self.audioEngine.stop()
inputNode.removeTap(onBus: 0)
self.recognitionRequest = nil
self.recognitionTask = nil
//self.microphoneButton.isEnabled = true
}
})
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
self.recognitionRequest?.append(buffer)
}
audioEngine.prepare()
do {
try audioEngine.start()
} catch {
print("audioEngine couldn't start because of an error.")
}
textView.text = ""
}
}
在一个单独的视图控制器中,我有一个麦克风按钮,可以触发此录制功能。当用户单击“麦克风”按钮时,该按钮应变为红色以指示其录音,然后用户输入的音频将显示在该视图控制器的textview中:
class PreviewController: UIViewController {
var notesOpen = false
let audioCaptureService = AudioCaptureService()
let microphoneButton: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(handleRecord), for: .touchUpInside)
button.setImage(#imageLiteral(resourceName: "muted-white").withRenderingMode(.alwaysOriginal), for: .normal)
button.isEnabled = false
return button
}()
lazy var notesView: UITextView = {
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let tv = UITextView()
tv.layer.cornerRadius = 8
tv.font = UIFont.boldSystemFont(ofSize: 12)
tv.backgroundColor = .white
tv.keyboardDismissMode = .onDrag
return tv
}()
//EXCLUDED SOME BASIC SETUP CODE
@objc func handleRecord() {
print("record button pressed")
toggleNotesView()
audioCaptureService.record(textView: notesView, microphoneButton: microphoneButton)
}
目前,我的实现可行,但是我怀疑它是否可以改进。我不认为我应该担心将textView和麦克风按钮传递到AudioCaptureService中吗?理想情况下,我想在没有AudioCaptureService的情况下将这些东西分开,这取决于传递给它的textView和按钮可以正常工作。
我正在阅读有关协议的信息,并认为这可能是一种解决方案,但是我似乎无法全神贯注于实现该协议的方式。
我以为我可以做类似的事情:
protocol AudioCaptureServiceDelegate {
func record(textView: UITextView)
}
但是,然后谁将由PreviewController类作为委托?我对如何更好地实现代码有些困惑,任何建议都会有所帮助。
答案 0 :(得分:0)
如果它只是输出委托,KISS会说在UITextView中符合,请参见下文。如果您要在委托中处理更多操作,则绝对应该进入PreviewViewController,因为UITextView仅显示文本。
由于音频!= View,有人建议将其放入某种控制器中,这值得争论。控制器是指控制PreviewViewController的东西,该控件随后在PreviewViewController上设置文本以在每次文本更改时显示。然后,recordButton通过PreviewViewControllers的委托到达控制器,该控制器随后处理记录,也许将记录存储在某个地方,等等。
总而言之,ViewController可以让其父控制器处理记录并显示文本,并根据父控制器在ViewController上设置的状态来更改recordButton。
// Variant 1
protocol AudioCaptureServiceOutputDelegate: class {
func audioCaptureServiceOutputDelegate(outputChanged: String)
}
extension UITextView: AudioCaptureServiceOutputDelegate {
func audioCaptureServiceOutputDelegate(outputChanged: String) {
self.text = outputChanged
}
}
// Variant 2
protocol AudioCaptureServiceOutputDelegate2: class {
var text: String! { get set }
}
extension UITextView: AudioCaptureServiceOutputDelegate2 {}
// Variant 3
protocol AudioCaptureServiceOutputDelegate3: class {
var audioCaptureServiceOutputText: String { get set }
}
extension UITextView: AudioCaptureServiceOutputDelegate3 {
var audioCaptureServiceOutputText: String {
get { return text }
set { text = newValue }
}
}
这三个功能都一样好。变体2稍差一些,因为Controller / ViewController实现具有随机名称text
的变量可能有点太通用了。
一个控制器可能更喜欢1,因为它在get时应该返回什么?它可能已经存储了几条录音。
func record(outputDelegate: AudioCaptureServiceOutputDelegate, microphoneButton: UIButton) {
// ....
startRecording(outputDelegate: outputDelegate)
// ....
}
fileprivate func startRecording(outputDelegate: AudioCaptureServiceOutputDelegate) {
// ....
outputDelegate.audioCaptureServiceOutputDelegate(outputChanged: result?.bestTranscription.formattedString ?? "Error: result is nil.")
// ....
outputDelegate.audioCaptureServiceOutputDelegate(outputChanged: "")
}