mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
1 Commits
v1.6.46
...
feature/bm
Author | SHA1 | Date | |
---|---|---|---|
|
bb84bba92b |
@@ -116,8 +116,6 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "im.vector.app"
|
||||
// Set to API 21: see #405
|
||||
minSdk versions.minSdk
|
||||
targetSdk versions.targetSdk
|
||||
multiDexEnabled true
|
||||
|
||||
@@ -254,7 +252,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "store"
|
||||
flavorDimensions "store", "api"
|
||||
|
||||
productFlavors {
|
||||
gplay {
|
||||
@@ -283,6 +281,18 @@ android {
|
||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
|
||||
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
|
||||
}
|
||||
|
||||
api21 {
|
||||
dimension "api"
|
||||
// Set to API 21: see #405
|
||||
minSdk versions.minSdk
|
||||
maxSdk 28
|
||||
}
|
||||
|
||||
api29 {
|
||||
dimension "api"
|
||||
minSdk 29
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
@@ -419,7 +429,7 @@ dependencies {
|
||||
implementation 'com.nulab-inc:zxcvbn:1.5.2'
|
||||
|
||||
// To convert voice message on old platforms
|
||||
implementation 'com.arthenica:ffmpeg-kit-audio:4.5.LTS'
|
||||
api21Implementation 'com.arthenica:ffmpeg-kit-audio:4.5.LTS'
|
||||
|
||||
// Alerter
|
||||
implementation 'com.github.tapadoo:alerter:7.2.4'
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
* Copyright (c) 2022 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.
|
||||
@@ -25,8 +25,9 @@ import com.arthenica.ffmpegkit.ReturnCode
|
||||
import im.vector.app.BuildConfig
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
class VoiceRecorderL(context: Context) : AbstractVoiceRecorder(context, "mp4") {
|
||||
class DefaultVoiceRecorder @Inject constructor(context: Context) : AbstractVoiceRecorder(context, "mp4") {
|
||||
override fun setOutputFormat(mediaRecorder: MediaRecorder) {
|
||||
// Use AAC/MP4 format here
|
||||
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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 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 by lazy {
|
||||
File(context.cacheDir, "voice_records").also {
|
||||
it.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the file is encoded using aac audio codec
|
||||
*/
|
||||
fun convertFile(file: File): File {
|
||||
// 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
|
||||
// Fallback to the original file in this case and let the player fail for us
|
||||
file
|
||||
}
|
||||
else -> {
|
||||
// FAILURE
|
||||
Timber.e("Command failed with state ${session.state} and rc ${session.returnCode}.${session.failStackTrace}")
|
||||
// Fallback to the original file in this case and let the player fail for us
|
||||
file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
* Copyright (c) 2022 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.
|
||||
@@ -21,9 +21,10 @@ import android.media.MediaRecorder
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
class VoiceRecorderQ(context: Context) : AbstractVoiceRecorder(context, "ogg") {
|
||||
class DefaultVoiceRecorder @Inject constructor(context: Context) : AbstractVoiceRecorder(context, "ogg") {
|
||||
override fun setOutputFormat(mediaRecorder: MediaRecorder) {
|
||||
// We can directly use OGG here
|
||||
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG)
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
* Copyright (c) 2022 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.
|
||||
@@ -16,18 +16,12 @@
|
||||
|
||||
package im.vector.app.features.voice
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
class VoiceRecorderProvider @Inject constructor(
|
||||
private val context: Context
|
||||
) {
|
||||
fun provideVoiceRecorder(): VoiceRecorder {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
VoiceRecorderQ(context)
|
||||
} else {
|
||||
VoiceRecorderL(context)
|
||||
}
|
||||
}
|
||||
class VoicePlayerHelper @Inject constructor() {
|
||||
/**
|
||||
* No op
|
||||
*/
|
||||
fun convertFile(file: File) = file
|
||||
}
|
@@ -43,6 +43,8 @@ import im.vector.app.features.pin.PinCodeStore
|
||||
import im.vector.app.features.pin.SharedPrefPinCodeStore
|
||||
import im.vector.app.features.ui.SharedPreferencesUiStateRepository
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import im.vector.app.features.voice.DefaultVoiceRecorder
|
||||
import im.vector.app.features.voice.VoiceRecorder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -81,6 +83,9 @@ abstract class VectorBindModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindEmojiSpanify(emojiCompatWrapper: EmojiCompatWrapper): EmojiSpanify
|
||||
|
||||
@Binds
|
||||
abstract fun bindVoiceRecorder(recorder: DefaultVoiceRecorder): VoiceRecorder
|
||||
}
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
|
@@ -738,8 +738,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||
try {
|
||||
// Download can fail
|
||||
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
|
||||
val convertedFile = voicePlayerHelper.convertFile(audioFile)
|
||||
// Play can fail
|
||||
voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile)
|
||||
} catch (failure: Throwable) {
|
||||
|
@@ -24,7 +24,6 @@ import im.vector.app.BuildConfig
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
||||
import im.vector.app.features.voice.VoiceFailure
|
||||
import im.vector.app.features.voice.VoiceRecorder
|
||||
import im.vector.app.features.voice.VoiceRecorderProvider
|
||||
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||
import im.vector.lib.multipicker.entity.MultiPickerAudioType
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||
@@ -43,10 +42,9 @@ import javax.inject.Inject
|
||||
class VoiceMessageHelper @Inject constructor(
|
||||
private val context: Context,
|
||||
private val playbackTracker: VoiceMessagePlaybackTracker,
|
||||
voiceRecorderProvider: VoiceRecorderProvider
|
||||
private val voiceRecorder: VoiceRecorder
|
||||
) {
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var voiceRecorder: VoiceRecorder = voiceRecorderProvider.provideVoiceRecorder()
|
||||
|
||||
private val amplitudeList = mutableListOf<Int>()
|
||||
|
||||
|
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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 by lazy {
|
||||
File(context.cacheDir, "voice_records").also {
|
||||
it.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user