mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-13 10:46:36 +00:00
a6e4e68bc5
* experiments * audio recording in swiftui * recording encapsulated * permission + playback * stopAudioPlayback on cancel * method names * check permission in recording start * run timer on main thread * remove obsolete view * don't call playback timer callback unless player is playing * compose + send view + preview + send * animation + improve state + quality * fix recording not stopping in time * animate to end * remove recorder delegate, fix cancelling during recording * replace print with log * recording start error constructor * CIVoiceView file * chat item wip * chat item wip * refactor settings * layout * send correct duration * item previews * more background, animation * more layout * more layout, send button conditions * context, preview, quote, notification texts * chat item actions * use isEmpty * remove comment * uncomment file.loaded * more layout, hold to record * more layout * preview player stop on disappear * more layout * comment * only one player or recording * remove voice message on chat close * fix state bug * remove commented code * length 30 Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
142 lines
4.2 KiB
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: maxVoiceMessageLength)
|
|
|
|
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 >= maxVoiceMessageLength {
|
|
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?()
|
|
}
|
|
}
|