New voice message recording mode (#2051)

* Lock voice message recording

* Use the VoiceMessageCache to store the recording file

* Rework on some composer toolbar buttons

* Update accessibility labels for voice message recording button

* PreviewTests
This commit is contained in:
Nicolas Mauri
2023-11-10 16:32:22 +01:00
committed by GitHub
parent 5a0dab1ce8
commit da831f6725
34 changed files with 283 additions and 296 deletions
@@ -22,6 +22,12 @@ class VoiceMessageCache: VoiceMessageCacheProtocol {
FileManager.default.temporaryDirectory.appendingPathComponent("media/voice-message")
}
var urlForRecording: URL {
// Make sure the directory exist
setupTemporaryFilesFolder()
return temporaryFilesFolderURL.appendingPathComponent("voice-message-recording").appendingPathExtension(preferredFileExtension)
}
func fileURL(for mediaSource: MediaSourceProxy) -> URL? {
let url = cacheURL(for: mediaSource)
return FileManager.default.fileExists(atPath: url.path()) ? url : nil
@@ -69,6 +75,7 @@ class VoiceMessageCache: VoiceMessageCacheProtocol {
} else {
try FileManager.default.copyItem(at: source, to: destination)
}
try (destination as NSURL).setResourceValue(URLFileProtection.complete, forKey: .fileProtectionKey)
}
private func cacheURL(for mediaSource: MediaSourceProxy) -> URL {
@@ -22,6 +22,9 @@ enum VoiceMessageCacheError: Error {
}
protocol VoiceMessageCacheProtocol {
/// URL to use for recording
var urlForRecording: URL { get }
/// Returns the URL of the cached audio file for a given media source
/// - Parameter mediaSource: the media source
/// - Returns: the URL of the cached audio file or nil if the file doesn't exist
@@ -33,8 +33,12 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
private let mp4accMimeType = "audio/m4a"
private let waveformSamplesCount = 100
var isRecording: Bool {
audioRecorder.isRecording
}
var recordingURL: URL? {
audioRecorder.audioFileUrl
audioRecorder.audioFileURL
}
var recordingDuration: TimeInterval {
@@ -67,9 +71,10 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
func startRecording() async {
await stopPlayback()
previewAudioPlayer?.reset()
recordingCancelled = false
await audioRecorder.record(with: .uuid(UUID()))
await audioRecorder.record(audioFileURL: voiceMessageCache.urlForRecording)
}
func stopRecording() async {
@@ -78,22 +83,26 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
}
func cancelRecording() async {
MXLog.info("Cancel recording.")
recordingCancelled = true
await audioRecorder.stopRecording()
await audioRecorder.deleteRecording()
previewAudioPlayerState = nil
previewAudioPlayer?.reset()
}
func deleteRecording() async {
MXLog.info("Delete recording.")
await stopPlayback()
await audioRecorder.deleteRecording()
previewAudioPlayer?.reset()
previewAudioPlayerState = nil
}
// MARK: - Preview
func startPlayback() async -> Result<Void, VoiceMessageRecorderError> {
guard let previewAudioPlayerState, let url = audioRecorder.audioFileUrl else {
guard let previewAudioPlayerState, let url = audioRecorder.audioFileURL else {
return .failure(.previewNotAvailable)
}
@@ -132,7 +141,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
}
func buildRecordingWaveform() async -> Result<[UInt16], VoiceMessageRecorderError> {
guard let url = audioRecorder.audioFileUrl else {
guard let url = audioRecorder.audioFileURL else {
return .failure(.missingRecordingFile)
}
// build the waveform
@@ -150,13 +159,18 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
}
func sendVoiceMessage(inRoom roomProxy: RoomProxyProtocol, audioConverter: AudioConverterProtocol) async -> Result<Void, VoiceMessageRecorderError> {
guard let url = audioRecorder.audioFileUrl else {
guard let url = audioRecorder.audioFileURL else {
return .failure(VoiceMessageRecorderError.missingRecordingFile)
}
// convert the file
let sourceFilename = url.deletingPathExtension().lastPathComponent
let oggFile = URL.temporaryDirectory.appendingPathComponent(sourceFilename).appendingPathExtension("ogg")
defer {
// delete the temporary file
try? FileManager.default.removeItem(at: oggFile)
}
do {
try audioConverter.convertToOpusOgg(sourceURL: url, destinationURL: oggFile)
} catch {
@@ -180,8 +194,6 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
audioInfo: audioInfo,
waveform: waveform,
progressSubject: nil) { _ in }
// delete the temporary file
try? FileManager.default.removeItem(at: oggFile)
if case .failure(let error) = result {
MXLog.error("Failed to send the voice message. \(error)")
@@ -219,7 +231,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
actionsSubject.send(.didFailWithError(error: VoiceMessageRecorderError.previewNotAvailable))
return
}
guard let recordingURL = audioRecorder.audioFileUrl, let previewAudioPlayerState else {
guard let recordingURL = audioRecorder.audioFileURL, let previewAudioPlayerState else {
actionsSubject.send(.didFailWithError(error: VoiceMessageRecorderError.previewNotAvailable))
return
}
@@ -235,7 +247,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
private func finalizeRecording() async -> Result<Void, VoiceMessageRecorderError> {
MXLog.info("finalize audio recording")
guard let url = audioRecorder.audioFileUrl, audioRecorder.currentTime > 0 else {
guard let url = audioRecorder.audioFileURL, audioRecorder.currentTime > 0 else {
return .failure(.previewNotAvailable)
}
@@ -35,6 +35,7 @@ enum VoiceMessageRecorderAction {
protocol VoiceMessageRecorderProtocol {
var audioRecorder: AudioRecorderProtocol { get }
var previewAudioPlayerState: AudioPlayerState? { get }
var isRecording: Bool { get }
var recordingURL: URL? { get }
var recordingDuration: TimeInterval { get }