mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-19 08:03:50 +02:00
#4094 - Added voice messages locked mode playback.
This commit is contained in:
@@ -16,13 +16,14 @@
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
import DSWaveformImage
|
||||
|
||||
@objc public protocol VoiceMessageControllerDelegate: AnyObject {
|
||||
func voiceMessageControllerDidRequestMicrophonePermission(_ voiceMessageController: VoiceMessageController)
|
||||
func voiceMessageController(_ voiceMessageController: VoiceMessageController, didRequestSendForFileAtURL url: URL, completion: @escaping (Bool) -> Void)
|
||||
}
|
||||
|
||||
public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, VoiceMessageAudioRecorderDelegate {
|
||||
public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, VoiceMessageAudioRecorderDelegate, VoiceMessageAudioPlayerDelegate {
|
||||
|
||||
private let themeService: ThemeService
|
||||
private let _voiceMessageToolbarView: VoiceMessageToolbarView
|
||||
@@ -31,6 +32,9 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
|
||||
private var audioRecorder: VoiceMessageAudioRecorder?
|
||||
|
||||
private var audioPlayer: VoiceMessageAudioPlayer?
|
||||
private var waveformAnalyser: WaveformAnalyzer?
|
||||
|
||||
private var audioSamples: [Float] = []
|
||||
private var isInLockedMode: Bool = false
|
||||
|
||||
@@ -55,7 +59,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
displayLink.isPaused = true
|
||||
displayLink.add(to: .current, forMode: .common)
|
||||
|
||||
self._voiceMessageToolbarView.update(theme: self.themeService.theme)
|
||||
_voiceMessageToolbarView.update(theme: themeService.theme)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleThemeDidChange), name: .themeServiceDidChangeTheme, object: nil)
|
||||
|
||||
updateUI()
|
||||
@@ -72,9 +76,9 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||
let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString).appendingPathExtension("m4a")
|
||||
|
||||
self.audioRecorder = VoiceMessageAudioRecorder()
|
||||
self.audioRecorder?.delegate = self
|
||||
self.audioRecorder?.recordWithOuputURL(temporaryFileURL)
|
||||
audioRecorder = VoiceMessageAudioRecorder()
|
||||
audioRecorder?.delegate = self
|
||||
audioRecorder?.recordWithOuputURL(temporaryFileURL)
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestRecordingFinish(_ toolbarView: VoiceMessageToolbarView) {
|
||||
@@ -85,10 +89,17 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
return
|
||||
}
|
||||
|
||||
delegate?.voiceMessageController(self, didRequestSendForFileAtURL: url) { [weak self] success in
|
||||
UINotificationFeedbackGenerator().notificationOccurred( (success ? .success : .error))
|
||||
self?.deleteRecordingAtURL(url)
|
||||
guard isInLockedMode else {
|
||||
sendRecordingAtURL(url)
|
||||
return
|
||||
}
|
||||
|
||||
audioPlayer = VoiceMessageAudioPlayer()
|
||||
audioPlayer?.delegate = self
|
||||
audioPlayer?.loadContentFromURL(url)
|
||||
audioSamples = []
|
||||
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestRecordingCancel(_ toolbarView: VoiceMessageToolbarView) {
|
||||
@@ -96,6 +107,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
audioRecorder?.stopRecording()
|
||||
deleteRecordingAtURL(audioRecorder?.url)
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestLockedModeRecording(_ toolbarView: VoiceMessageToolbarView) {
|
||||
@@ -103,6 +115,26 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestPlaybackToggle(_ toolbarView: VoiceMessageToolbarView) {
|
||||
if audioPlayer?.isPlaying ?? false {
|
||||
audioPlayer?.pause()
|
||||
} else {
|
||||
audioPlayer?.play()
|
||||
}
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestSend(_ toolbarView: VoiceMessageToolbarView) {
|
||||
guard let url = audioRecorder?.url else {
|
||||
MXLog.error("Invalid audio recording URL")
|
||||
return
|
||||
}
|
||||
|
||||
sendRecordingAtURL(url)
|
||||
|
||||
isInLockedMode = false
|
||||
updateUI()
|
||||
}
|
||||
|
||||
// MARK: - AudioRecorderDelegate
|
||||
|
||||
func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder) {
|
||||
@@ -120,8 +152,36 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
MXLog.error("Failed recording voice message.")
|
||||
}
|
||||
|
||||
// MARK: - VoiceMessageAudioPlayerDelegate
|
||||
|
||||
func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func audioPlayerDidFinishPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
audioPlayer.seekToTime(0.0)
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func audioPlayer(_ audioPlayer: VoiceMessageAudioPlayer, didFailWithError: Error) {
|
||||
updateUI()
|
||||
|
||||
MXLog.error("Failed playing voice message.")
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func sendRecordingAtURL(_ url: URL) {
|
||||
delegate?.voiceMessageController(self, didRequestSendForFileAtURL: url) { [weak self] success in
|
||||
UINotificationFeedbackGenerator().notificationOccurred( (success ? .success : .error))
|
||||
self?.deleteRecordingAtURL(url)
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteRecordingAtURL(_ url: URL?) {
|
||||
guard let url = url else {
|
||||
return
|
||||
@@ -135,7 +195,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
}
|
||||
|
||||
@objc private func handleThemeDidChange() {
|
||||
self._voiceMessageToolbarView.update(theme: self.themeService.theme)
|
||||
_voiceMessageToolbarView.update(theme: themeService.theme)
|
||||
}
|
||||
|
||||
@objc private func handleDisplayLinkTick() {
|
||||
@@ -143,7 +203,19 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
displayLink.isPaused = !(audioRecorder?.isRecording ?? false)
|
||||
|
||||
let shouldUpdateFromAudioPlayer = isInLockedMode && !(audioRecorder?.isRecording ?? false)
|
||||
|
||||
guard shouldUpdateFromAudioPlayer else {
|
||||
updateUIFromAudioRecorder()
|
||||
return
|
||||
}
|
||||
|
||||
updateUIFromAudioPlayer()
|
||||
}
|
||||
|
||||
private func updateUIFromAudioRecorder() {
|
||||
displayLink.isPaused = !(self.audioRecorder?.isRecording ?? false)
|
||||
|
||||
let requiredNumberOfSamples = _voiceMessageToolbarView.getRequiredNumberOfSamples()
|
||||
|
||||
@@ -151,15 +223,53 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
audioSamples = [Float](repeating: 0.0, count: requiredNumberOfSamples)
|
||||
}
|
||||
|
||||
if let sample = audioRecorder?.averagePowerForChannelNumber(0) {
|
||||
audioSamples.append(sample)
|
||||
audioSamples.remove(at: 0)
|
||||
let sample = audioRecorder?.averagePowerForChannelNumber(0) ?? 0.0
|
||||
audioSamples.append(sample)
|
||||
audioSamples.remove(at: 0)
|
||||
|
||||
var details = VoiceMessageToolbarViewDetails()
|
||||
details.state = (self.audioRecorder?.isRecording ?? false ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
|
||||
details.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: self.audioRecorder?.currentTime ?? 0.0))
|
||||
details.audioSamples = audioSamples
|
||||
_voiceMessageToolbarView.configureWithDetails(details)
|
||||
}
|
||||
|
||||
private func updateUIFromAudioPlayer() {
|
||||
guard let audioPlayer = audioPlayer else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = audioPlayer.url else {
|
||||
MXLog.error("Invalid audio player url.")
|
||||
return
|
||||
}
|
||||
|
||||
displayLink.isPaused = !audioPlayer.isPlaying
|
||||
|
||||
let requiredNumberOfSamples = _voiceMessageToolbarView.getRequiredNumberOfSamples()
|
||||
if audioSamples.count != requiredNumberOfSamples {
|
||||
audioSamples = [Float](repeating: 0.0, count: requiredNumberOfSamples)
|
||||
|
||||
waveformAnalyser = WaveformAnalyzer(audioAssetURL: url)
|
||||
waveformAnalyser?.samples(count: requiredNumberOfSamples, completionHandler: { [weak self] samples in
|
||||
guard let samples = samples else {
|
||||
MXLog.error("Could not sample audio recording.")
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self?.audioSamples = samples
|
||||
self?.updateUIFromAudioPlayer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var details = VoiceMessageToolbarViewDetails()
|
||||
details.state = (audioRecorder?.isRecording ?? false ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
|
||||
details.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: audioRecorder?.currentTime ?? 0.0))
|
||||
details.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: (audioPlayer.isPlaying ? audioPlayer.currentTime : audioPlayer.duration)))
|
||||
details.audioSamples = audioSamples
|
||||
details.isPlaying = audioPlayer.isPlaying
|
||||
details.progress = (audioPlayer.duration > 0.0 ? audioPlayer.currentTime / audioPlayer.duration : 0.0)
|
||||
_voiceMessageToolbarView.configureWithDetails(details)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user