#4094 - Added Opus Ogg support through FFmpegKit backed media conversion.

This commit is contained in:
Stefan Ceriu 2021-06-24 12:28:50 +03:00
parent 1b90b2530f
commit aa6064431e
7 changed files with 106 additions and 23 deletions

View file

@ -25,7 +25,7 @@
KEYCHAIN_ACCESS_GROUP = $(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER).keychain.shared
// Build settings
IPHONEOS_DEPLOYMENT_TARGET = 11.0
IPHONEOS_DEPLOYMENT_TARGET = 12.1
SDKROOT = iphoneos
TARGETED_DEVICE_FAMILY = 1,2
SWIFT_VERSION = 5.3.1

View file

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '11.0'
platform :ios, '12.1'
# Use frameforks to allow usage of pod written in Swift (like PiwikTracker)
use_frameworks!
@ -70,6 +70,7 @@ abstract_target 'RiotPods' do
pod 'SwiftJWT', '~> 3.6.200'
pod 'SideMenu', '~> 6.5'
pod 'DSWaveformImage', '~> 6.1.1'
pod 'ffmpeg-kit-ios-audio', '~> 4.4'
pod 'FLEX', '~> 4.4.1', :configurations => ['Debug']

View file

@ -6187,7 +6187,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
- (void)voiceMessageController:(VoiceMessageController *)voiceMessageController didRequestSendForFileAtURL:(NSURL *)url completion:(void (^)(BOOL))completion
{
[self.roomDataSource sendVoiceMessage:url mimeType:@"audio/m4a" success:^(NSString *eventId) {
[self.roomDataSource sendVoiceMessage:url mimeType:nil success:^(NSString *eventId) {
MXLogDebug(@"Success with event id %@", eventId);
completion(YES);
} failure:^(NSError *error) {

View file

@ -0,0 +1,65 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum VoiceMessageAudioConverterError: Error {
case generic(String)
case cancelled
}
struct VoiceMessageAudioConverter {
static func convertToOpusOgg(sourceURL: URL, destinationURL: URL, completion: @escaping (Result<Void, VoiceMessageAudioConverterError>) -> Void) {
let command = "-hide_banner -y -i \"\(sourceURL.path)\" -c:a libopus \"\(destinationURL.path)\""
executeCommand(command, completion: completion)
}
static func convertToMPEG4AAC(sourceURL: URL, destinationURL: URL, completion: @escaping (Result<Void, VoiceMessageAudioConverterError>) -> Void) {
let command = "-hide_banner -y -i \"\(sourceURL.path)\" -c:a aac_at \"\(destinationURL.path)\""
executeCommand(command, completion: completion)
}
static private func executeCommand(_ command: String, completion: @escaping (Result<Void, VoiceMessageAudioConverterError>) -> Void) {
FFmpegKitConfig.setLogLevel(0)
FFmpegKit.executeAsync(command) { session in
guard let session = session else {
completion(.failure(.generic("Invalid session")))
return
}
guard let returnCode = session.getReturnCode() else {
completion(.failure(.generic("Invalid return code")))
return
}
DispatchQueue.main.async {
if returnCode.isSuccess() {
completion(.success(()))
} else if returnCode.isCancel() {
completion(.failure(.cancelled))
} else {
completion(.failure(.generic(String(returnCode.getValue()))))
MXLog.error("""
Failed converting voice message with state: \(String(describing: FFmpegKitConfig.sessionState(toString: session.getState()))), \
returnCode: \(String(describing: returnCode)), \
stackTrace: \(String(describing: session.getFailStackTrace()))
""")
}
}
}
}
}

View file

@ -188,10 +188,23 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
// 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 sendRecordingAtURL(_ sourceURL: URL) {
let destinationURL = sourceURL.deletingPathExtension().appendingPathExtension("opus")
VoiceMessageAudioConverter.convertToOpusOgg(sourceURL: sourceURL, destinationURL: destinationURL) { [weak self] result in
guard let self = self else { return }
switch result {
case .success:
self.delegate?.voiceMessageController(self, didRequestSendForFileAtURL: destinationURL) { [weak self] success in
UINotificationFeedbackGenerator().notificationOccurred((success ? .success : .error))
self?.deleteRecordingAtURL(sourceURL)
self?.deleteRecordingAtURL(destinationURL)
}
case .failure(let error):
MXLog.error("Failed failed encoding audio message with: \(error)")
}
}
}

View file

@ -147,7 +147,7 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
if attachment.isEncrypted {
attachment.decrypt(toTempFile: { [weak self] filePath in
self?.loadFileAtPath(filePath)
self?.convertAndLoadFileAtPath(filePath)
}, failure: { [weak self] error in
// A nil error in this case is a cancellation on the MXMediaLoader
if let error = error {
@ -157,7 +157,7 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
})
} else {
attachment.prepare({ [weak self] in
self?.loadFileAtPath(attachment.cacheFilePath)
self?.convertAndLoadFileAtPath(attachment.cacheFilePath)
}, failure: { [weak self] error in
// A nil error in this case is a cancellation on the MXMediaLoader
if let error = error {
@ -168,26 +168,28 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
}
}
private func loadFileAtPath(_ path: String?) {
private func convertAndLoadFileAtPath(_ path: String?) {
guard let filePath = path else {
return
}
let url = URL(fileURLWithPath: filePath)
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let newURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString).appendingPathExtension("m4a")
// AVPlayer doesn't want to play it otherwise. https://stackoverflow.com/a/9350824
let newURL = url.appendingPathExtension("m4a")
do {
try? FileManager.default.removeItem(at: newURL)
try FileManager.default.moveItem(at: url, to: newURL)
} catch {
self.state = .error
MXLog.error("Failed appending voice message extension.")
return
VoiceMessageAudioConverter.convertToMPEG4AAC(sourceURL: URL(fileURLWithPath: filePath), destinationURL: newURL) { [weak self] result in
switch result {
case .success:
self?.loadFileAtURL(newURL)
case .failure(let error):
self?.state = .error
MXLog.error("Failed failed decoding audio message with: \(error)")
}
}
}
private func loadFileAtURL(_ url: URL) {
audioPlayer.loadContentFromURL(newURL)
audioPlayer.loadContentFromURL(url)
let requiredNumberOfSamples = playbackView.getRequiredNumberOfSamples()
@ -195,7 +197,7 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
return
}
let analyser = WaveformAnalyzer(audioAssetURL: newURL)
let analyser = WaveformAnalyzer(audioAssetURL: url)
analyser?.samples(count: requiredNumberOfSamples, completionHandler: { [weak self] samples in
guard let samples = samples else {
self?.state = .error

View file

@ -5,6 +5,8 @@
@import MatrixSDK;
@import MatrixKit;
#include <ffmpegkit/FFmpegKit.h>
#import "WebViewViewController.h"
#import "RiotSplitViewController.h"
#import "RiotNavigationController.h"