request audio focus when recording voice messages
Signed-off-by: vmfunc <celeste@linux.com>
This commit is contained in:
@@ -53,6 +53,7 @@ dependencies {
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.recentemojis.api)
|
||||
implementation(projects.libraries.roomselect.api)
|
||||
implementation(projects.libraries.audio.api)
|
||||
implementation(projects.libraries.voiceplayer.api)
|
||||
implementation(projects.libraries.voicerecorder.api)
|
||||
implementation(projects.libraries.mediaplayer.api)
|
||||
@@ -95,6 +96,7 @@ dependencies {
|
||||
testImplementation(projects.libraries.mediapickers.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.audio.test)
|
||||
testImplementation(projects.libraries.voicerecorder.test)
|
||||
testImplementation(projects.libraries.mediaplayer.test)
|
||||
testImplementation(projects.libraries.mediaviewer.test)
|
||||
|
||||
@@ -30,6 +30,8 @@ import io.element.android.features.messages.api.MessageComposerContext
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerPresenter
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
|
||||
import io.element.android.libraries.audio.api.AudioFocus
|
||||
import io.element.android.libraries.audio.api.AudioFocusRequester
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
@@ -58,6 +60,7 @@ class DefaultVoiceMessageComposerPresenter(
|
||||
@Assisted private val timelineMode: Timeline.Mode,
|
||||
private val voiceRecorder: VoiceRecorder,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val audioFocus: AudioFocus,
|
||||
mediaSenderFactory: MediaSenderFactory,
|
||||
private val player: VoiceMessageComposerPlayer,
|
||||
private val messageComposerContext: MessageComposerContext,
|
||||
@@ -246,8 +249,10 @@ class DefaultVoiceMessageComposerPresenter(
|
||||
|
||||
private fun CoroutineScope.startRecording() = launch {
|
||||
try {
|
||||
audioFocus.requestAudioFocus(AudioFocusRequester.VoiceMessage) {}
|
||||
voiceRecorder.startRecord()
|
||||
} catch (e: SecurityException) {
|
||||
audioFocus.releaseAudioFocus()
|
||||
Timber.e(e, "Voice message error")
|
||||
analyticsService.trackError(VoiceMessageException.PermissionMissing("Expected permission to record but none", e))
|
||||
}
|
||||
@@ -255,10 +260,12 @@ class DefaultVoiceMessageComposerPresenter(
|
||||
|
||||
private fun CoroutineScope.finishRecording() = launch {
|
||||
voiceRecorder.stopRecord()
|
||||
audioFocus.releaseAudioFocus()
|
||||
}
|
||||
|
||||
private fun CoroutineScope.cancelRecording() = launch {
|
||||
voiceRecorder.stopRecord(cancelled = true)
|
||||
audioFocus.releaseAudioFocus()
|
||||
}
|
||||
|
||||
private fun CoroutineScope.deleteRecording() = launch {
|
||||
|
||||
@@ -19,12 +19,15 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer.
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
|
||||
import io.element.android.features.messages.impl.messagecomposer.aReplyMode
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.audio.api.AudioFocus
|
||||
import io.element.android.libraries.audio.api.AudioFocusRequester
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.mediaplayer.test.FakeAudioFocus
|
||||
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.impl.DefaultMediaSender
|
||||
@@ -80,6 +83,12 @@ class DefaultVoiceMessageComposerPresenterTest {
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
mediaOptimizationConfigProvider = { MediaOptimizationConfig(compressImages = true, videoCompressionPreset = VideoCompressionPreset.STANDARD) },
|
||||
)
|
||||
private val requestAudioFocusResult = lambdaRecorder<AudioFocusRequester, () -> Unit, Unit> { _, _ -> }
|
||||
private val releaseAudioFocusResult = lambdaRecorder<Unit> { }
|
||||
private val audioFocus: AudioFocus = FakeAudioFocus(
|
||||
requestAudioFocusResult = requestAudioFocusResult,
|
||||
releaseAudioFocusResult = releaseAudioFocusResult,
|
||||
)
|
||||
private val messageComposerContext = FakeMessageComposerContext()
|
||||
|
||||
companion object {
|
||||
@@ -159,6 +168,37 @@ class DefaultVoiceMessageComposerPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - recording requests audio focus and releases on stop`() = runTest {
|
||||
val presenter = createDefaultVoiceMessageComposerPresenter()
|
||||
presenter.test {
|
||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
||||
val recordingState = awaitItem()
|
||||
requestAudioFocusResult.assertions().isCalledOnce()
|
||||
releaseAudioFocusResult.assertions().isNeverCalled()
|
||||
|
||||
recordingState.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop))
|
||||
awaitItem()
|
||||
releaseAudioFocusResult.assertions().isCalledOnce()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - cancelling recording releases audio focus`() = runTest {
|
||||
val presenter = createDefaultVoiceMessageComposerPresenter()
|
||||
presenter.test {
|
||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Cancel))
|
||||
awaitItem()
|
||||
requestAudioFocusResult.assertions().isCalledOnce()
|
||||
releaseAudioFocusResult.assertions().isCalledOnce()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - abort recording`() = runTest {
|
||||
val presenter = createDefaultVoiceMessageComposerPresenter()
|
||||
@@ -653,6 +693,7 @@ class DefaultVoiceMessageComposerPresenterTest {
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
voiceRecorder = voiceRecorder,
|
||||
analyticsService = analyticsService,
|
||||
audioFocus = audioFocus,
|
||||
mediaSenderFactory = { mediaSender },
|
||||
player = VoiceMessageComposerPlayer(FakeMediaPlayer(), this),
|
||||
messageComposerContext = messageComposerContext,
|
||||
|
||||
@@ -17,6 +17,7 @@ android {
|
||||
dependencies {
|
||||
api(projects.features.messages.impl)
|
||||
implementation(projects.libraries.matrix.test)
|
||||
implementation(projects.libraries.audio.test)
|
||||
implementation(projects.libraries.mediaplayer.test)
|
||||
implementation(projects.libraries.mediaupload.test)
|
||||
implementation(projects.libraries.mediaviewer.api)
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.element.android.features.messages.impl.voicemessages.composer.VoiceMes
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.mediaplayer.test.FakeAudioFocus
|
||||
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
|
||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
||||
import io.element.android.libraries.mediaupload.impl.DefaultMediaSender
|
||||
@@ -38,6 +39,10 @@ class FakeDefaultVoiceMessageComposerPresenterFactory(
|
||||
timelineMode = timelineMode,
|
||||
voiceRecorder = FakeVoiceRecorder(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
audioFocus = FakeAudioFocus(
|
||||
requestAudioFocusResult = { _, _ -> },
|
||||
releaseAudioFocusResult = { },
|
||||
),
|
||||
mediaSenderFactory = { mediaSender },
|
||||
player = VoiceMessageComposerPlayer(
|
||||
mediaPlayer = FakeMediaPlayer(),
|
||||
|
||||
Reference in New Issue
Block a user