mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
#4094 - Added Opus Ogg support through FFmpegKit backed media conversion.
This commit is contained in:
parent
1b90b2530f
commit
aa6064431e
7 changed files with 106 additions and 23 deletions
|
@ -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
|
||||
|
|
3
Podfile
3
Podfile
|
@ -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']
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()))
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
@import MatrixSDK;
|
||||
@import MatrixKit;
|
||||
|
||||
#include <ffmpegkit/FFmpegKit.h>
|
||||
|
||||
#import "WebViewViewController.h"
|
||||
#import "RiotSplitViewController.h"
|
||||
#import "RiotNavigationController.h"
|
||||
|
|
Loading…
Reference in a new issue