mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Record voice on Android 21
This commit is contained in:
parent
6f947e979b
commit
bfc70be5bb
7 changed files with 310 additions and 66 deletions
|
@ -144,7 +144,7 @@ android {
|
||||||
|
|
||||||
buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping"
|
buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping"
|
||||||
|
|
||||||
buildConfigField "Long", "VOICE_MESSAGE_DURATION_LIMIT_MS", "120000L"
|
buildConfigField "Long", "VOICE_MESSAGE_DURATION_LIMIT_MS", "120_000L"
|
||||||
|
|
||||||
// If set, MSC3086 asserted identity messages sent on VoIP calls will cause the call to appear in the room corresponding to the asserted identity.
|
// If set, MSC3086 asserted identity messages sent on VoIP calls will cause the call to appear in the room corresponding to the asserted identity.
|
||||||
// This *must* only be set in trusted environments.
|
// This *must* only be set in trusted environments.
|
||||||
|
@ -411,6 +411,9 @@ dependencies {
|
||||||
// Passphrase strength helper
|
// Passphrase strength helper
|
||||||
implementation 'com.nulab-inc:zxcvbn:1.5.2'
|
implementation 'com.nulab-inc:zxcvbn:1.5.2'
|
||||||
|
|
||||||
|
// To convert voice message on old platforms
|
||||||
|
implementation 'com.arthenica:ffmpeg-kit-audio:4.4.LTS'
|
||||||
|
|
||||||
//Alerter
|
//Alerter
|
||||||
implementation 'com.tapadoo.android:alerter:7.0.1'
|
implementation 'com.tapadoo.android:alerter:7.0.1'
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@ package im.vector.app.features.home.room.detail.composer
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.media.MediaRecorder
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
import im.vector.app.core.utils.CountUpTimer
|
import im.vector.app.core.utils.CountUpTimer
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
||||||
import im.vector.app.features.voice.VoiceFailure
|
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.multipicker.entity.MultiPickerAudioType
|
import im.vector.lib.multipicker.entity.MultiPickerAudioType
|
||||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
@ -34,7 +34,6 @@ import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.FileOutputStream
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,54 +41,24 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class VoiceMessageHelper @Inject constructor(
|
class VoiceMessageHelper @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val playbackTracker: VoiceMessagePlaybackTracker
|
private val playbackTracker: VoiceMessagePlaybackTracker,
|
||||||
|
voiceRecorderProvider: VoiceRecorderProvider
|
||||||
) {
|
) {
|
||||||
private var mediaPlayer: MediaPlayer? = null
|
private var mediaPlayer: MediaPlayer? = null
|
||||||
private var mediaRecorder: MediaRecorder? = null
|
private var voiceRecorder: VoiceRecorder = voiceRecorderProvider.provideVoiceRecorder()
|
||||||
private val outputDirectory = File(context.cacheDir, "downloads")
|
|
||||||
private var outputFile: File? = null
|
|
||||||
private var lastRecordingFile: File? = null // In case of user pauses recording, plays another one in timeline
|
|
||||||
|
|
||||||
private val amplitudeList = mutableListOf<Int>()
|
private val amplitudeList = mutableListOf<Int>()
|
||||||
|
|
||||||
private var amplitudeTicker: CountUpTimer? = null
|
private var amplitudeTicker: CountUpTimer? = null
|
||||||
private var playbackTicker: CountUpTimer? = null
|
private var playbackTicker: CountUpTimer? = null
|
||||||
|
|
||||||
init {
|
|
||||||
if (!outputDirectory.exists()) {
|
|
||||||
outputDirectory.mkdirs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initMediaRecorder() {
|
|
||||||
MediaRecorder().let {
|
|
||||||
it.setAudioSource(MediaRecorder.AudioSource.DEFAULT)
|
|
||||||
it.setOutputFormat(MediaRecorder.OutputFormat.OGG)
|
|
||||||
it.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS)
|
|
||||||
it.setAudioEncodingBitRate(24000)
|
|
||||||
it.setAudioSamplingRate(48000)
|
|
||||||
mediaRecorder = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startRecording() {
|
fun startRecording() {
|
||||||
stopPlayback()
|
stopPlayback()
|
||||||
playbackTracker.makeAllPlaybacksIdle()
|
playbackTracker.makeAllPlaybacksIdle()
|
||||||
|
|
||||||
outputFile = File(outputDirectory, "Voice message.ogg")
|
|
||||||
lastRecordingFile = outputFile
|
|
||||||
amplitudeList.clear()
|
amplitudeList.clear()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
initMediaRecorder()
|
voiceRecorder.startRecord()
|
||||||
val mr = mediaRecorder!!
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
mr.setOutputFile(outputFile)
|
|
||||||
} else {
|
|
||||||
mr.setOutputFile(FileOutputStream(outputFile).fd)
|
|
||||||
}
|
|
||||||
mr.prepare()
|
|
||||||
mr.start()
|
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
throw VoiceFailure.UnableToRecord(failure)
|
throw VoiceFailure.UnableToRecord(failure)
|
||||||
}
|
}
|
||||||
|
@ -97,9 +66,16 @@ class VoiceMessageHelper @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopRecording(): MultiPickerAudioType? {
|
fun stopRecording(): MultiPickerAudioType? {
|
||||||
internalStopRecording()
|
tryOrNull("Cannot stop media recording amplitude") {
|
||||||
|
stopRecordingAmplitudes()
|
||||||
|
}
|
||||||
|
val voiceMessageFile = tryOrNull("Cannot stop media recorder!") {
|
||||||
|
voiceRecorder.stopRecord()
|
||||||
|
voiceRecorder.getVoiceMessageFile()
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
outputFile?.let {
|
// TODO Improve this
|
||||||
|
voiceMessageFile?.let {
|
||||||
val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it)
|
val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it)
|
||||||
return outputFileUri
|
return outputFileUri
|
||||||
?.toMultiPickerAudioType(context)
|
?.toMultiPickerAudioType(context)
|
||||||
|
@ -113,38 +89,24 @@ class VoiceMessageHelper @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun internalStopRecording() {
|
/**
|
||||||
|
* When entering in playback mode actually
|
||||||
|
*/
|
||||||
|
fun pauseRecording() {
|
||||||
|
voiceRecorder.stopRecord()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteRecording() {
|
||||||
tryOrNull("Cannot stop media recording amplitude") {
|
tryOrNull("Cannot stop media recording amplitude") {
|
||||||
stopRecordingAmplitudes()
|
stopRecordingAmplitudes()
|
||||||
}
|
}
|
||||||
tryOrNull("Cannot stop media recorder!") {
|
tryOrNull("Cannot stop media recorder!") {
|
||||||
// Usually throws when the record is less than 1 second.
|
voiceRecorder.cancelRecord()
|
||||||
releaseMediaRecorder()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun releaseMediaRecorder() {
|
|
||||||
mediaRecorder?.let {
|
|
||||||
it.stop()
|
|
||||||
it.reset()
|
|
||||||
it.release()
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaRecorder = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pauseRecording() {
|
|
||||||
releaseMediaRecorder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteRecording() {
|
|
||||||
internalStopRecording()
|
|
||||||
outputFile?.delete()
|
|
||||||
outputFile = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startOrPauseRecordingPlayback() {
|
fun startOrPauseRecordingPlayback() {
|
||||||
lastRecordingFile?.let {
|
voiceRecorder.getCurrentRecord()?.let {
|
||||||
startOrPausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID, it)
|
startOrPausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,9 +163,8 @@ class VoiceMessageHelper @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAmplitudeTick() {
|
private fun onAmplitudeTick() {
|
||||||
val mr = mediaRecorder ?: return
|
|
||||||
try {
|
try {
|
||||||
val maxAmplitude = mr.maxAmplitude
|
val maxAmplitude = voiceRecorder.getMaxAmplitude()
|
||||||
amplitudeList.add(maxAmplitude)
|
amplitudeList.add(maxAmplitude)
|
||||||
playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList)
|
playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList)
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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.media.MediaRecorder
|
||||||
|
import android.os.Build
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
abstract class AbstractVoiceRecorder(
|
||||||
|
context: Context,
|
||||||
|
private val filenameExt: String
|
||||||
|
) : VoiceRecorder {
|
||||||
|
private val outputDirectory = File(context.cacheDir, "voice_records")
|
||||||
|
|
||||||
|
private var mediaRecorder: MediaRecorder? = null
|
||||||
|
private var outputFile: File? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (!outputDirectory.exists()) {
|
||||||
|
outputDirectory.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun setOutputFormat(mediaRecorder: MediaRecorder)
|
||||||
|
abstract fun convertFile(recordedFile: File?): File?
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
MediaRecorder().let {
|
||||||
|
it.setAudioSource(MediaRecorder.AudioSource.DEFAULT)
|
||||||
|
setOutputFormat(it)
|
||||||
|
it.setAudioEncodingBitRate(24000)
|
||||||
|
it.setAudioSamplingRate(48000)
|
||||||
|
mediaRecorder = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startRecord() {
|
||||||
|
init()
|
||||||
|
outputFile = File(outputDirectory, "Voice message.$filenameExt")
|
||||||
|
|
||||||
|
val mr = mediaRecorder ?: return
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
mr.setOutputFile(outputFile)
|
||||||
|
} else {
|
||||||
|
mr.setOutputFile(FileOutputStream(outputFile).fd)
|
||||||
|
}
|
||||||
|
mr.prepare()
|
||||||
|
mr.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopRecord() {
|
||||||
|
// Can throw when the record is less than 1 second.
|
||||||
|
mediaRecorder?.let {
|
||||||
|
it.stop()
|
||||||
|
it.reset()
|
||||||
|
it.release()
|
||||||
|
}
|
||||||
|
mediaRecorder = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelRecord() {
|
||||||
|
stopRecord()
|
||||||
|
|
||||||
|
outputFile?.delete()
|
||||||
|
outputFile = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMaxAmplitude(): Int {
|
||||||
|
return mediaRecorder?.maxAmplitude ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrentRecord(): File? {
|
||||||
|
return outputFile
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getVoiceMessageFile(): File? {
|
||||||
|
return convertFile(outputFile)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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 java.io.File
|
||||||
|
|
||||||
|
interface VoiceRecorder {
|
||||||
|
/**
|
||||||
|
* Start the recording
|
||||||
|
*/
|
||||||
|
fun startRecord()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the recording
|
||||||
|
*/
|
||||||
|
fun stopRecord()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the file
|
||||||
|
*/
|
||||||
|
fun cancelRecord()
|
||||||
|
|
||||||
|
fun getMaxAmplitude(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not guaranteed to be a ogg file
|
||||||
|
*/
|
||||||
|
fun getCurrentRecord(): File?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guaranteed to be a ogg file
|
||||||
|
*/
|
||||||
|
fun getVoiceMessageFile(): File?
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* 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.media.MediaRecorder
|
||||||
|
import com.arthenica.ffmpegkit.FFmpegKit
|
||||||
|
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
||||||
|
import com.arthenica.ffmpegkit.Level
|
||||||
|
import com.arthenica.ffmpegkit.ReturnCode
|
||||||
|
import im.vector.app.BuildConfig
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class VoiceRecorderL(context: Context) : AbstractVoiceRecorder(context, "mp4") {
|
||||||
|
override fun setOutputFormat(mediaRecorder: MediaRecorder) {
|
||||||
|
// Use AAC/MP4 format here
|
||||||
|
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
||||||
|
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convertFile(recordedFile: File?): File? {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
FFmpegKitConfig.setLogLevel(Level.AV_LOG_INFO)
|
||||||
|
}
|
||||||
|
recordedFile ?: return null
|
||||||
|
// Convert to OGG
|
||||||
|
val targetFile = File(recordedFile.path.removeSuffix("mp4") + "ogg")
|
||||||
|
if (targetFile.exists()) {
|
||||||
|
targetFile.delete()
|
||||||
|
}
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
val session = FFmpegKit.execute("-i \"${recordedFile.path}\" -c:a libvorbis \"${targetFile.path}\"")
|
||||||
|
val duration = System.currentTimeMillis() - start
|
||||||
|
Timber.d("Convert to ogg in $duration ms. Size in bytes from ${recordedFile.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.media.MediaRecorder
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
class VoiceRecorderQ(context: Context) : AbstractVoiceRecorder(context, "ogg") {
|
||||||
|
override fun setOutputFormat(mediaRecorder: MediaRecorder) {
|
||||||
|
// We can directly use OGG here
|
||||||
|
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG)
|
||||||
|
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convertFile(recordedFile: File?): File? {
|
||||||
|
// Nothing to do here
|
||||||
|
return recordedFile
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue