Files
simplex-chat/apps/ios/Shared/Model/AudioRecPlay.swift

142 lines
4.2 KiB
Swift

//
// AudioRecPlay.swift
// SimpleX (iOS)
//
// Created by Evgeny on 19/11/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import Foundation
import AVFoundation
import SwiftUI
import SimpleXChat
class AudioRecorder {
var onTimer: ((TimeInterval) -> Void)?
var onFinishRecording: (() -> Void)?
var audioRecorder: AVAudioRecorder?
var recordingTimer: Timer?
init(onTimer: @escaping ((TimeInterval) -> Void), onFinishRecording: @escaping (() -> Void)) {
self.onTimer = onTimer
self.onFinishRecording = onFinishRecording
}
enum StartError {
case permission
case error(String)
}
func start(fileName: String) async -> StartError? {
let av = AVAudioSession.sharedInstance()
if !(await checkPermission()) { return .permission }
do {
try av.setCategory(AVAudioSession.Category.playAndRecord, options: .defaultToSpeaker)
try av.setActive(true)
let settings: [String : Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 12000,
AVEncoderBitRateKey: 12000,
AVNumberOfChannelsKey: 1
]
audioRecorder = try AVAudioRecorder(url: getAppFilePath(fileName), settings: settings)
audioRecorder?.record(forDuration: MAX_VOICE_MESSAGE_LENGTH)
await MainActor.run {
recordingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
guard let time = self.audioRecorder?.currentTime else { return }
self.onTimer?(time)
if time >= MAX_VOICE_MESSAGE_LENGTH {
self.stop()
self.onFinishRecording?()
}
}
}
return nil
} catch let error {
logger.error("AudioRecorder startAudioRecording error \(error.localizedDescription)")
return .error(error.localizedDescription)
}
}
func stop() {
if let recorder = audioRecorder {
recorder.stop()
}
audioRecorder = nil
if let timer = recordingTimer {
timer.invalidate()
}
recordingTimer = nil
}
private func checkPermission() async -> Bool {
let av = AVAudioSession.sharedInstance()
switch av.recordPermission {
case .granted: return true
case .denied: return false
case .undetermined:
return await withCheckedContinuation { cont in
DispatchQueue.main.async {
av.requestRecordPermission { allowed in
cont.resume(returning: allowed)
}
}
}
@unknown default: return false
}
}
}
class AudioPlayer: NSObject, AVAudioPlayerDelegate {
var onTimer: ((TimeInterval) -> Void)?
var onFinishPlayback: (() -> Void)?
var audioPlayer: AVAudioPlayer?
var playbackTimer: Timer?
init(onTimer: @escaping ((TimeInterval) -> Void), onFinishPlayback: @escaping (() -> Void)) {
self.onTimer = onTimer
self.onFinishPlayback = onFinishPlayback
}
func start(fileName: String) {
audioPlayer = try? AVAudioPlayer(contentsOf: getAppFilePath(fileName))
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
audioPlayer?.play()
playbackTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
if self.audioPlayer?.isPlaying ?? false {
guard let time = self.audioPlayer?.currentTime else { return }
self.onTimer?(time)
}
}
}
func pause() {
audioPlayer?.pause()
}
func play() {
audioPlayer?.play()
}
func stop() {
if let player = audioPlayer {
player.stop()
}
audioPlayer = nil
if let timer = playbackTimer {
timer.invalidate()
}
playbackTimer = nil
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
stop()
self.onFinishPlayback?()
}
}