From e1addb607a6fc750b5573c87b361c96bbbd0f219 Mon Sep 17 00:00:00 2001 From: llfbandit Date: Tue, 21 May 2024 11:47:59 +0200 Subject: [PATCH] feat(Android): Mute other audio streams when recording. closes #295, closes #276 --- record_android/CHANGELOG.md | 4 +- .../methodcall/MethodCallHandlerImpl.kt | 1 + .../record/methodcall/RecorderWrapper.kt | 3 +- .../llfbandit/record/record/RecordConfig.kt | 1 + .../record/record/recorder/AudioRecorder.kt | 57 ++++++++++++++++++- .../lib/src/types/record_config.dart | 14 ++++- 6 files changed, 76 insertions(+), 4 deletions(-) diff --git a/record_android/CHANGELOG.md b/record_android/CHANGELOG.md index 339c0c38..0f3382ca 100644 --- a/record_android/CHANGELOG.md +++ b/record_android/CHANGELOG.md @@ -1,7 +1,9 @@ ## 1.2.0 * feat: Re-introduced native MediaRecorder. Set `RecordConfig.androidConfig.useLegacy` to `true`. This comes with limitations compared to advanced recorder. * feat: Advanced AudioRecorder will try to adjust given configuration if unsupported or out of range (sample rate, bitrate and channel count). -Those two above should help for older devices or bad vendor implementations. + * Those two features should help for older devices, bad vendor implementations or misusage of configuration values. +* feat: ability to mute all audio streams when recording. The settings are restored when the recording is stopped. + * Notice: streams will stay at current state on pause/resume. ## 1.1.0 * fix: Properly close container when recording is stopped. diff --git a/record_android/android/src/main/kotlin/com/llfbandit/record/methodcall/MethodCallHandlerImpl.kt b/record_android/android/src/main/kotlin/com/llfbandit/record/methodcall/MethodCallHandlerImpl.kt index aa0ca98e..97b663fa 100644 --- a/record_android/android/src/main/kotlin/com/llfbandit/record/methodcall/MethodCallHandlerImpl.kt +++ b/record_android/android/src/main/kotlin/com/llfbandit/record/methodcall/MethodCallHandlerImpl.kt @@ -153,6 +153,7 @@ class MethodCallHandlerImpl( Utils.firstNonNull(call.argument("echoCancel"), false), Utils.firstNonNull(call.argument("noiseSuppress"), false), Utils.firstNonNull(androidConfig?.get("useLegacy") as Boolean?, false), + Utils.firstNonNull(androidConfig?.get("muteAudio") as Boolean?, false), ) } } \ No newline at end of file diff --git a/record_android/android/src/main/kotlin/com/llfbandit/record/methodcall/RecorderWrapper.kt b/record_android/android/src/main/kotlin/com/llfbandit/record/methodcall/RecorderWrapper.kt index cccb198a..81d6c0be 100644 --- a/record_android/android/src/main/kotlin/com/llfbandit/record/methodcall/RecorderWrapper.kt +++ b/record_android/android/src/main/kotlin/com/llfbandit/record/methodcall/RecorderWrapper.kt @@ -148,7 +148,8 @@ internal class RecorderWrapper( return AudioRecorder( recorderStateStreamHandler, - recorderRecordStreamHandler + recorderRecordStreamHandler, + context ) } diff --git a/record_android/android/src/main/kotlin/com/llfbandit/record/record/RecordConfig.kt b/record_android/android/src/main/kotlin/com/llfbandit/record/record/RecordConfig.kt index a98a2feb..8757c733 100644 --- a/record_android/android/src/main/kotlin/com/llfbandit/record/record/RecordConfig.kt +++ b/record_android/android/src/main/kotlin/com/llfbandit/record/record/RecordConfig.kt @@ -13,6 +13,7 @@ class RecordConfig( val echoCancel: Boolean = false, val noiseSuppress: Boolean = false, val useLegacy: Boolean = false, + val muteAudio: Boolean = false, ) { val numChannels: Int = 2.coerceAtMost(1.coerceAtLeast(numChannels)) } diff --git a/record_android/android/src/main/kotlin/com/llfbandit/record/record/recorder/AudioRecorder.kt b/record_android/android/src/main/kotlin/com/llfbandit/record/record/recorder/AudioRecorder.kt index 37cf89ab..6623e8e2 100644 --- a/record_android/android/src/main/kotlin/com/llfbandit/record/record/recorder/AudioRecorder.kt +++ b/record_android/android/src/main/kotlin/com/llfbandit/record/record/recorder/AudioRecorder.kt @@ -1,11 +1,14 @@ package com.llfbandit.record.record.recorder +import android.content.Context +import android.media.AudioManager import android.util.Log import com.llfbandit.record.record.RecordConfig import com.llfbandit.record.record.RecordState import com.llfbandit.record.record.stream.RecorderRecordStreamHandler import com.llfbandit.record.record.stream.RecorderStateStreamHandler + interface OnAudioRecordListener { fun onRecord() fun onPause() @@ -17,7 +20,8 @@ interface OnAudioRecordListener { class AudioRecorder( // Recorder streams private val recorderStateStreamHandler: RecorderStateStreamHandler, - private val recorderRecordStreamHandler: RecorderRecordStreamHandler + private val recorderRecordStreamHandler: RecorderRecordStreamHandler, + private val appContext: Context ) : IRecorder, OnAudioRecordListener { companion object { private val TAG = AudioRecorder::class.java.simpleName @@ -25,19 +29,44 @@ class AudioRecorder( // Recorder thread with which we will interact private var recorderThread: RecordThread? = null + // Amplitude private var maxAmplitude = -160.0 + // Recording config private var config: RecordConfig? = null + // Stop callback to be synchronized between stop method return & record stop private var stopCb: ((path: String?) -> Unit)? = null + private var muteSettings = HashMap() + private val muteStreams = arrayOf( + AudioManager.STREAM_ALARM, + AudioManager.STREAM_DTMF, + AudioManager.STREAM_MUSIC, + AudioManager.STREAM_NOTIFICATION, + AudioManager.STREAM_RING, + AudioManager.STREAM_SYSTEM, + AudioManager.STREAM_VOICE_CALL, + ) + + init { + initMuteSettings() + } + + /** + * Starts the recording with the given config. + */ @Throws(Exception::class) override fun start(config: RecordConfig) { this.config = config recorderThread = RecordThread(config, this) recorderThread!!.startRecording() + + if (config.muteAudio) { + muteAudio(true) + } } override fun stop(stopCb: ((path: String?) -> Unit)?) { @@ -86,6 +115,10 @@ class AudioRecorder( } override fun onStop() { + if (config?.muteAudio == true) { + muteAudio(false) + } + stopCb?.invoke(config?.path) stopCb = null @@ -100,4 +133,26 @@ class AudioRecorder( override fun onAudioChunk(chunk: ByteArray) { recorderRecordStreamHandler.sendRecordChunkEvent(chunk) } + + private fun muteAudio(mute: Boolean) { + val audioManager = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager + + val muteValue = -100 // AudioManager.ADJUST_MUTE + val unmuteValue = 100 // AudioManager.ADJUST_UNMUTE + + muteStreams.forEach { stream -> + val volumeLevel = if (mute) muteValue else (muteSettings[stream] ?: unmuteValue) + audioManager.setStreamVolume(stream, volumeLevel, 0) + } + } + + private fun initMuteSettings() { + muteSettings.clear() + + val audioManager = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager + + muteStreams.forEach { stream -> + muteSettings[stream] = audioManager.getStreamVolume(stream) + } + } } \ No newline at end of file diff --git a/record_platform_interface/lib/src/types/record_config.dart b/record_platform_interface/lib/src/types/record_config.dart index 938a74d5..5aca0dcf 100644 --- a/record_platform_interface/lib/src/types/record_config.dart +++ b/record_platform_interface/lib/src/types/record_config.dart @@ -78,7 +78,7 @@ class RecordConfig { } } -/// Android specific configuration +/// Android specific configuration for recording. class AndroidRecordConfig { /// Uses Android MediaRecorder if [true]. /// @@ -86,13 +86,25 @@ class AndroidRecordConfig { /// by default. final bool useLegacy; + /// If [true], this will mute all audio streams like alarms, music, ring, ... + /// + /// This is useful when you want to record audio without any background noise. + /// + /// The streams are restored to their previous state after recording is stopped + /// and will stay at current state on pause/resume. + /// + /// Use at your own risks! + final bool muteAudio; + const AndroidRecordConfig({ this.useLegacy = false, + this.muteAudio = false, }); Map toMap() { return { 'useLegacy': useLegacy, + 'muteAudio': muteAudio, }; } }