Convert voice message to be able to play on Android 28 and below

This commit is contained in:
Benoit Marty 2021-07-16 11:00:25 +02:00
parent 343ea42ef5
commit 13ae0ba5f1
2 changed files with 78 additions and 1 deletions

View File

@ -57,6 +57,7 @@ import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.voice.VoicePlayerHelper
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@ -121,6 +122,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val jitsiService: JitsiService, private val jitsiService: JitsiService,
private val voiceMessageHelper: VoiceMessageHelper, private val voiceMessageHelper: VoiceMessageHelper,
private val voicePlayerHelper: VoicePlayerHelper,
timelineFactory: TimelineFactory timelineFactory: TimelineFactory
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
@ -647,8 +649,10 @@ class RoomDetailViewModel @AssistedInject constructor(
try { try {
// Download can fail // Download can fail
val audioFile = session.fileService().downloadFile(action.messageAudioContent) val audioFile = session.fileService().downloadFile(action.messageAudioContent)
// Conversion can fail, fallback to the original file in this case and let the player fail for us
val convertedFile = voicePlayerHelper.convertFile(audioFile) ?: audioFile
// Play can fail // Play can fail
voiceMessageHelper.startOrPausePlayback(action.eventId, audioFile) voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile)
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.Failure(failure)) _viewEvents.post(RoomDetailViewEvents.Failure(failure))
} }

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 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.
*/
package im.vector.app.features.voice
import android.content.Context
import android.os.Build
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode
import timber.log.Timber
import java.io.File
import javax.inject.Inject
class VoicePlayerHelper @Inject constructor(
context: Context
) {
private val outputDirectory = File(context.cacheDir, "voice_records")
init {
if (!outputDirectory.exists()) {
outputDirectory.mkdirs()
}
}
/**
* Ensure the file is encoded using aac audio codec
*/
fun convertFile(file: File): File? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Nothing to do
file
} else {
// Convert to mp4
val targetFile = File(outputDirectory, "Voice.mp4")
if (targetFile.exists()) {
targetFile.delete()
}
val start = System.currentTimeMillis()
val session = FFmpegKit.execute("-i \"${file.path}\" -c:a aac \"${targetFile.path}\"")
val duration = System.currentTimeMillis() - start
Timber.d("Convert to mp4 in $duration ms. Size in bytes from ${file.length()} to ${targetFile.length()}")
return when {
ReturnCode.isSuccess(session.returnCode) -> {
// SUCCESS
targetFile
}
ReturnCode.isCancel(session.returnCode) -> {
// CANCEL
null
}
else -> {
// FAILURE
Timber.e("Command failed with state ${session.state} and rc ${session.returnCode}.${session.failStackTrace}")
// TODO throw?
null
}
}
}
}
}