Skip to content

Commit

Permalink
Merge branch 'release/1.4.9/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
manuroe committed Aug 3, 2021
2 parents cd31272 + 2f5f34b commit 3012520
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 101 deletions.
28 changes: 28 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
Changes in 1.4.9 (2021-08-03)
=================================================

✨ Features
*

🙌 Improvements
* Voice Messages: Increased recording state microphone icon size
* Voice Messages: Using "Voice message - MM.dd.yyyy HH.mm.ss" as the format for recorded audio files

🐛 Bugfix
* Voice Messages: Fixed race conditions when sending voice messages (#4641)

⚠️ API Changes
*

🗣 Translations
*

🧱 Build
*

Others
*

Improvements:


Changes in 1.4.8 (2021-07-29)
=================================================

Expand Down
4 changes: 2 additions & 2 deletions Config/AppIdentifiers.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ APPLICATION_GROUP_IDENTIFIER = group.im.vector
APPLICATION_SCHEME = element

// Version
MARKETING_VERSION = 1.4.8
CURRENT_PROJECT_VERSION = 1.4.8
MARKETING_VERSION = 1.4.9
CURRENT_PROJECT_VERSION = 1.4.9


// Team
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Riot/Assets/et.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,7 @@
// Room Notification Settings
"room_notifs_settings_notify_me_for" = "Teavita mind";
"room_details_notifs" = "Teavitused";
"voice_message_stop_locked_mode_recording" = "Salvestuse peatamiseks ja taasesituseks vajuta lainekese nuppu";
"voice_message_stop_locked_mode_recording" = "Salvestuse peatamiseks ja taasesituseks vajuta salvestuse vaadet";
"voice_message_remaining_recording_time" = "salvestusaega jäänud %@s";

// Mark: - Voice Messages
Expand Down
2 changes: 1 addition & 1 deletion Riot/Assets/hu.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1424,7 +1424,7 @@
// Room Notification Settings
"room_notifs_settings_notify_me_for" = "Értesítés ezért:";
"room_details_notifs" = "Értesítések";
"voice_message_stop_locked_mode_recording" = "Megállításhoz és visszajátszáshoz koppints a hullámhosszra";
"voice_message_stop_locked_mode_recording" = "Megállításhoz és visszajátszáshoz koppints a felvételre";
"voice_message_remaining_recording_time" = "%@s távozott";

// Mark: - Voice Messages
Expand Down
2 changes: 1 addition & 1 deletion Riot/Assets/it.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Permissions usage explanations
"NSCameraUsageDescription" = "La fotocamera viene utilizzata per scattare fotografie, registrare video ed eseguire videochiamate.";
"NSPhotoLibraryUsageDescription" = "La libreria fotografica viene utilizzata per inviare foto e video.";
"NSMicrophoneUsageDescription" = "Il microfono viene utilizzato per registrare video ed effettuare chiamate.";
"NSMicrophoneUsageDescription" = "Element ha bisogno di accedere al microfono per effettuare e ricevere chiamate, registrare video e messaggi vocali.";
"NSContactsUsageDescription" = "Per scoprire i contatti che già usano Matrix, Element può inviare gli indirizzi email e i numeri di telefono della tua rubrica al server identità che hai scelto. Se supportato, viene fatto un hash dei dati personali prima dell'invio - controlla la politica sulla privacy del tuo server di identità per maggiori informazioni.";
"NSCalendarsUsageDescription" = "Vedi le tue riunioni programmate nell'app.";
"NSFaceIDUsageDescription" = "Face ID viene usato per accedere all'app.";
7 changes: 7 additions & 0 deletions Riot/Assets/it.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1395,3 +1395,10 @@
// Room Notification Settings
"room_notifs_settings_notify_me_for" = "Inviami notifiche per";
"room_details_notifs" = "Notifiche";
"voice_message_stop_locked_mode_recording" = "Tocca la registrazione per fermare o ascoltare";
"voice_message_remaining_recording_time" = "%@s rimasti";

// Mark: - Voice Messages

"voice_message_release_to_send" = "Tieni premuto per registrare, rilascia per inviare";
"settings_labs_voice_messages" = "Messaggi vocali";
2 changes: 1 addition & 1 deletion Riot/Assets/pt_BR.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1393,7 +1393,7 @@
"room_notifs_settings_notify_me_for" = "Notifique-me para";
"room_details_notifs" = "Notificações";
"voice_message_remaining_recording_time" = "%@s restando";
"voice_message_stop_locked_mode_recording" = "Toque no comprimento de onda para parar e dar playback";
"voice_message_stop_locked_mode_recording" = "Toque em sua gravação para parar ou escutar";

// Mark: - Voice Messages

Expand Down
2 changes: 1 addition & 1 deletion Riot/Assets/zh_Hans.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1426,7 +1426,7 @@
"settings_ui_theme_picker_message_invert_colours" = "“自动”使用您设备的“反转颜色”设置";
"room_recents_unknown_room_error_message" = "找不到这个房间。 确保它存在";
"room_creation_dm_error" = "我们无法创建您的 DM。 请检查您要邀请的用户,然后重试。";
"voice_message_stop_locked_mode_recording" = "轻按波长停止和回放消息";
"voice_message_stop_locked_mode_recording" = "轻按录音停止或收听";
"voice_message_remaining_recording_time" = "剩 %@s";

// Mark: - Voice Messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class VoiceMessageAttachmentCacheManager {
workQueue.async {
// Run this in the work queue to preserve order
if let finalURL = self.finalURLs[identifier], let duration = self.durations[identifier], let samples = self.samples[identifier]?[numberOfSamples] {
MXLog.debug("[VoiceMessageAttachmentCacheManager] Finished task - using cached results")
let result = VoiceMessageAttachmentCacheManagerLoadResult(eventIdentifier: identifier, url: finalURL, duration: duration, samples: samples)
DispatchQueue.main.async {
completion(Result.success(result))
Expand All @@ -109,110 +110,103 @@ class VoiceMessageAttachmentCacheManager {
completionCallbacks[callbackKey] = [CompletionWrapper(completion)]
}

let dispatchGroup = DispatchGroup()

func sampleFileAtURL(_ url: URL, duration: TimeInterval) {
let analyser = WaveformAnalyzer(audioAssetURL: url)

dispatchGroup.enter()
analyser?.samples(count: numberOfSamples, completionHandler: { samples in
MXLog.debug("[VoiceMessageAttachmentCacheManager] Finished sampling voice message")

dispatchGroup.leave()

guard let samples = samples else {
self.invokeFailureCallbacksForIdentifier(identifier, error: VoiceMessageAttachmentCacheManagerError.samplingError)
return
}

if var existingSamples = self.samples[identifier] {
existingSamples[numberOfSamples] = samples
self.samples[identifier] = existingSamples
} else {
self.samples[identifier] = [numberOfSamples: samples]
}

self.invokeSuccessCallbacksForIdentifier(identifier, url: url, duration: duration, samples: samples)
})
}

if let finalURL = finalURLs[identifier], let duration = durations[identifier] {
sampleFileAtURL(finalURL, duration: duration)
dispatchGroup.wait()
MXLog.debug("[VoiceMessageAttachmentCacheManager] Finished task")
sampleFileAtURL(finalURL, duration: duration, numberOfSamples: numberOfSamples, identifier: identifier)
return
}

func convertFileAtPath(_ path: String?) {
guard let filePath = path else {
return
}

let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let newURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString).appendingPathExtension("m4a")

dispatchGroup.enter()
VoiceMessageAudioConverter.convertToMPEG4AAC(sourceURL: URL(fileURLWithPath: filePath), destinationURL: newURL) { result in
switch result {
case .success:
self.finalURLs[identifier] = newURL
VoiceMessageAudioConverter.mediaDurationAt(newURL) { result in
MXLog.debug("[VoiceMessageAttachmentCacheManager] Finished converting voice message")

switch result {
case .success:
if let duration = try? result.get() {
self.durations[identifier] = duration
sampleFileAtURL(newURL, duration: duration)
} else {
MXLog.error("[VoiceMessageAttachmentCacheManager] enqueueLoadAttachment: Failed to retrieve media duration")
}
case .failure(let error):
MXLog.error("[VoiceMessageAttachmentCacheManager] enqueueLoadAttachment: failed getting audio duration with: \(error)")
}

dispatchGroup.leave()
}
case .failure(let error):
self.invokeFailureCallbacksForIdentifier(identifier, error: VoiceMessageAttachmentCacheManagerError.conversionError(error))
MXLog.error("[VoiceMessageAttachmentCacheManager] enqueueLoadAttachment: failed decoding audio message with: \(error)")
dispatchGroup.leave()
}
}
}

dispatchGroup.enter()
DispatchQueue.main.async { // These don't behave accordingly if called from a background thread
if attachment.isEncrypted {
attachment.decrypt(toTempFile: { filePath in
convertFileAtPath(filePath)
dispatchGroup.leave()
self.workQueue.async {
self.convertFileAtPath(filePath, numberOfSamples: numberOfSamples, identifier: identifier)
}
}, failure: { error in
// A nil error in this case is a cancellation on the MXMediaLoader
if let error = error {
MXLog.error("Failed decrypting attachment with error: \(String(describing: error))")
self.invokeFailureCallbacksForIdentifier(identifier, error: VoiceMessageAttachmentCacheManagerError.decryptionError(error))
}
dispatchGroup.leave()
})
} else {
attachment.prepare({
convertFileAtPath(attachment.cacheFilePath)
dispatchGroup.leave()
self.workQueue.async {
self.convertFileAtPath(attachment.cacheFilePath, numberOfSamples: numberOfSamples, identifier: identifier)
}
}, failure: { error in
// A nil error in this case is a cancellation on the MXMediaLoader
if let error = error {
MXLog.error("Failed preparing attachment with error: \(String(describing: error))")
self.invokeFailureCallbacksForIdentifier(identifier, error: VoiceMessageAttachmentCacheManagerError.preparationError(error))
}
dispatchGroup.leave()
})
}
}
}

private func convertFileAtPath(_ path: String?, numberOfSamples: Int, identifier: String) {
guard let filePath = path else {
return
}

dispatchGroup.wait()
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let newURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString).appendingPathExtension("m4a")

VoiceMessageAudioConverter.convertToMPEG4AAC(sourceURL: URL(fileURLWithPath: filePath), destinationURL: newURL) { result in
MXLog.debug("[VoiceMessageAttachmentCacheManager] Finished converting voice message")
self.workQueue.async {
switch result {
case .success:
self.finalURLs[identifier] = newURL

VoiceMessageAudioConverter.mediaDurationAt(newURL) { result in
self.workQueue.async {
MXLog.debug("[VoiceMessageAttachmentCacheManager] Finished retrieving media duration")

switch result {
case .success:
if let duration = try? result.get() {
self.durations[identifier] = duration
self.sampleFileAtURL(newURL, duration: duration, numberOfSamples: numberOfSamples, identifier: identifier)
} else {
MXLog.error("[VoiceMessageAttachmentCacheManager] Failed retrieving media duration")
}
case .failure(let error):
MXLog.error("[VoiceMessageAttachmentCacheManager] Failed retrieving audio duration with error: \(error)")
}
}
}
case .failure(let error):
MXLog.error("[VoiceMessageAttachmentCacheManager] Failed decoding audio message with error: \(error)")
self.invokeFailureCallbacksForIdentifier(identifier, error: VoiceMessageAttachmentCacheManagerError.conversionError(error))
}
}
}
}

private func sampleFileAtURL(_ url: URL, duration: TimeInterval, numberOfSamples: Int, identifier: String) {
let analyser = WaveformAnalyzer(audioAssetURL: url)

MXLog.debug("[VoiceMessageAttachmentCacheManager] Finished task")
analyser?.samples(count: numberOfSamples, completionHandler: { samples in
self.workQueue.async {
guard let samples = samples else {
MXLog.debug("[VoiceMessageAttachmentCacheManager] Failed sampling voice message")
self.invokeFailureCallbacksForIdentifier(identifier, error: VoiceMessageAttachmentCacheManagerError.samplingError)
return
}

MXLog.debug("[VoiceMessageAttachmentCacheManager] Finished sampling voice message")

if var existingSamples = self.samples[identifier] {
existingSamples[numberOfSamples] = samples
self.samples[identifier] = existingSamples
} else {
self.samples[identifier] = [numberOfSamples: samples]
}

self.invokeSuccessCallbacksForIdentifier(identifier, url: url, duration: duration, samples: samples)
}
})
}

private func invokeSuccessCallbacksForIdentifier(_ identifier: String, url: URL, duration: TimeInterval, samples: [Float]) {
Expand All @@ -232,6 +226,8 @@ class VoiceMessageAttachmentCacheManager {
}

self.completionCallbacks[callbackKey] = nil

MXLog.debug("[VoiceMessageAttachmentCacheManager] Successfully finished task")
}

private func invokeFailureCallbacksForIdentifier(_ identifier: String, error: Error) {
Expand All @@ -249,5 +245,7 @@ class VoiceMessageAttachmentCacheManager {
}

self.completionCallbacks[callbackKey] = nil

MXLog.debug("[VoiceMessageAttachmentCacheManager] Failed task with error: \(error)")
}
}
25 changes: 17 additions & 8 deletions Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
static let maximumAudioRecordingDuration: TimeInterval = 120.0
static let maximumAudioRecordingLengthReachedThreshold: TimeInterval = 10.0
static let elapsedTimeFormat = "m:ss"
static let fileNameFormat = "'Voice message - 'MM.dd.yyyy HH.mm.ss"
static let minimumRecordingDuration = 1.0
}

private let themeService: ThemeService
private let mediaServiceProvider: VoiceMessageMediaServiceProvider
private let temporaryFileURL: URL
private var temporaryFileURL: URL!

private let _voiceMessageToolbarView: VoiceMessageToolbarView
private var displayLink: CADisplayLink!
Expand All @@ -48,12 +49,18 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
private var isInLockedMode: Bool = false
private var notifiedRemainingTime = false

private static let timeFormatter: DateFormatter = {
private static let elapsedTimeFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = Constants.elapsedTimeFormat
return dateFormatter
}()

private static let fileNameDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = Constants.fileNameFormat
return dateFormatter
}()

@objc public weak var delegate: VoiceMessageControllerDelegate?

@objc public var isRecordingAudio: Bool {
Expand All @@ -68,9 +75,6 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
self.themeService = themeService
self.mediaServiceProvider = mediaServiceProvider

let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString).appendingPathExtension("m4a")

_voiceMessageToolbarView = VoiceMessageToolbarView.loadFromNib()

super.init()
Expand Down Expand Up @@ -107,6 +111,11 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,

audioRecorder = mediaServiceProvider.audioRecorder()
audioRecorder?.registerDelegate(self)

let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let fileName = VoiceMessageController.fileNameDateFormatter.string(from: Date())
temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(fileName).appendingPathExtension("m4a")

audioRecorder?.recordWithOutputURL(temporaryFileURL)
}

Expand Down Expand Up @@ -260,7 +269,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
})

dispatchGroup.enter()
let destinationURL = sourceURL.deletingPathExtension().appendingPathExtension("opus")
let destinationURL = sourceURL.deletingPathExtension().appendingPathExtension("ogg")
VoiceMessageAudioConverter.convertToOpusOgg(sourceURL: sourceURL, destinationURL: destinationURL) { result in
switch result {
case .success:
Expand Down Expand Up @@ -342,7 +351,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,

var details = VoiceMessageToolbarViewDetails()
details.state = (isRecording ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
details.elapsedTime = VoiceMessageController.timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: currentTime))
details.elapsedTime = VoiceMessageController.elapsedTimeFormatter.string(from: Date(timeIntervalSinceReferenceDate: currentTime))
details.audioSamples = audioSamples

if isRecording {
Expand Down Expand Up @@ -391,7 +400,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,

var details = VoiceMessageToolbarViewDetails()
details.state = (audioRecorder?.isRecording ?? false ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
details.elapsedTime = VoiceMessageController.timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: (audioPlayer.isPlaying ? audioPlayer.currentTime : audioPlayer.duration)))
details.elapsedTime = VoiceMessageController.elapsedTimeFormatter.string(from: Date(timeIntervalSinceReferenceDate: (audioPlayer.isPlaying ? audioPlayer.currentTime : audioPlayer.duration)))
details.audioSamples = audioSamples
details.isPlaying = audioPlayer.isPlaying
details.progress = (audioPlayer.isPlaying ? (audioPlayer.duration > 0.0 ? audioPlayer.currentTime / audioPlayer.duration : 0.0) : 0.0)
Expand Down
Loading

0 comments on commit 3012520

Please sign in to comment.