davidliu
Committed by GitHub

Add some audio convenience functions (#408)

* Add some audio convenience functions

* Fix tests
@@ -57,6 +57,7 @@ import kotlinx.serialization.Serializable @@ -57,6 +57,7 @@ import kotlinx.serialization.Serializable
57 import livekit.LivekitModels 57 import livekit.LivekitModels
58 import livekit.LivekitRtc 58 import livekit.LivekitRtc
59 import livekit.org.webrtc.* 59 import livekit.org.webrtc.*
  60 +import livekit.org.webrtc.audio.AudioDeviceModule
60 import javax.inject.Named 61 import javax.inject.Named
61 62
62 class Room 63 class Room
@@ -78,6 +79,7 @@ constructor( @@ -78,6 +79,7 @@ constructor(
78 val audioProcessingController: AudioProcessingController, 79 val audioProcessingController: AudioProcessingController,
79 val lkObjects: LKObjects, 80 val lkObjects: LKObjects,
80 networkCallbackManagerFactory: NetworkCallbackManagerFactory, 81 networkCallbackManagerFactory: NetworkCallbackManagerFactory,
  82 + private val audioDeviceModule: AudioDeviceModule,
81 ) : RTCEngine.Listener, ParticipantListener { 83 ) : RTCEngine.Listener, ParticipantListener {
82 84
83 private lateinit var coroutineScope: CoroutineScope 85 private lateinit var coroutineScope: CoroutineScope
@@ -778,6 +780,30 @@ constructor( @@ -778,6 +780,30 @@ constructor(
778 fun create(context: Context): Room 780 fun create(context: Context): Room
779 } 781 }
780 782
  783 + // ------------------------------------- General Utility functions -------------------------------------//
  784 +
  785 + /**
  786 + * Control muting/unmuting all audio output.
  787 + *
  788 + * Note: using this mute will only mute local audio output, and will still receive the data from
  789 + * remote audio tracks. Consider turning off [ConnectOptions.autoSubscribe] and manually unsubscribing
  790 + * if you need more fine-grained control over the data usage.
  791 + **/
  792 + fun setSpeakerMute(muted: Boolean) {
  793 + audioDeviceModule.setSpeakerMute(muted)
  794 + }
  795 +
  796 + /**
  797 + * Control muting/unmuting the audio input.
  798 + *
  799 + * Note: using this mute will only zero the microphone audio data, and will still send data to
  800 + * remote participants (although they will not hear anything). Consider using [TrackPublication.muted]
  801 + * for more fine-grained control over the data usage.
  802 + */
  803 + fun setMicrophoneMute(muted: Boolean) {
  804 + audioDeviceModule.setMicrophoneMute(muted)
  805 + }
  806 +
781 // ------------------------------------- NetworkCallback -------------------------------------// 807 // ------------------------------------- NetworkCallback -------------------------------------//
782 private val networkCallbackManager = networkCallbackManagerFactory.invoke( 808 private val networkCallbackManager = networkCallbackManagerFactory.invoke(
783 object : NetworkCallback() { 809 object : NetworkCallback() {
@@ -26,6 +26,5 @@ abstract class AudioTrack( @@ -26,6 +26,5 @@ abstract class AudioTrack(
26 /** 26 /**
27 * The underlying WebRTC audio track. 27 * The underlying WebRTC audio track.
28 */ 28 */
29 - override val rtcTrack: AudioTrack  
30 -) :  
31 - Track(name, Kind.AUDIO, rtcTrack) 29 + override val rtcTrack: AudioTrack,
  30 +) : Track(name, Kind.AUDIO, rtcTrack)
@@ -20,17 +20,18 @@ import android.Manifest @@ -20,17 +20,18 @@ import android.Manifest
20 import android.content.Context 20 import android.content.Context
21 import android.content.pm.PackageManager 21 import android.content.pm.PackageManager
22 import androidx.core.content.ContextCompat 22 import androidx.core.content.ContextCompat
  23 +import io.livekit.android.room.participant.LocalParticipant
23 import io.livekit.android.webrtc.peerconnection.executeBlockingOnRTCThread 24 import io.livekit.android.webrtc.peerconnection.executeBlockingOnRTCThread
24 import livekit.org.webrtc.MediaConstraints 25 import livekit.org.webrtc.MediaConstraints
25 import livekit.org.webrtc.PeerConnectionFactory 26 import livekit.org.webrtc.PeerConnectionFactory
26 import livekit.org.webrtc.RtpSender 27 import livekit.org.webrtc.RtpSender
27 import livekit.org.webrtc.RtpTransceiver 28 import livekit.org.webrtc.RtpTransceiver
28 -import java.util.* 29 +import java.util.UUID
29 30
30 /** 31 /**
31 * Represents a local audio track (generally using the microphone as input). 32 * Represents a local audio track (generally using the microphone as input).
32 * 33 *
33 - * This class should not be constructed directly, but rather through [LocalParticipant] 34 + * This class should not be constructed directly, but rather through [LocalParticipant.createAudioTrack].
34 */ 35 */
35 class LocalAudioTrack( 36 class LocalAudioTrack(
36 name: String, 37 name: String,
@@ -49,4 +49,18 @@ class RemoteAudioTrack( @@ -49,4 +49,18 @@ class RemoteAudioTrack(
49 rtcTrack.removeSink(sink) 49 rtcTrack.removeSink(sink)
50 } 50 }
51 } 51 }
  52 +
  53 + /**
  54 + * Sets the volume.
  55 + *
  56 + * @param volume a gain value in the range 0 to 10.
  57 + * * 0 will mute the track
  58 + * * 1 will play the track normally
  59 + * * values greater than 1 will boost the volume of the track.
  60 + */
  61 + fun setVolume(volume: Double) {
  62 + executeBlockingOnRTCThread {
  63 + rtcTrack.setVolume(volume)
  64 + }
  65 + }
52 } 66 }
  1 +/*
  2 + * Copyright 2024 LiveKit, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package io.livekit.android.test.mock
  18 +
  19 +import livekit.org.webrtc.audio.AudioDeviceModule
  20 +
  21 +class MockAudioDeviceModule : AudioDeviceModule {
  22 + override fun getNativeAudioDeviceModulePointer(): Long {
  23 + return 1
  24 + }
  25 +
  26 + override fun release() {
  27 + }
  28 +
  29 + override fun setSpeakerMute(muted: Boolean) {
  30 + }
  31 +
  32 + override fun setMicrophoneMute(muted: Boolean) {
  33 + }
  34 +}
@@ -23,9 +23,15 @@ import dagger.Provides @@ -23,9 +23,15 @@ import dagger.Provides
23 import io.livekit.android.audio.AudioProcessingController 23 import io.livekit.android.audio.AudioProcessingController
24 import io.livekit.android.dagger.CapabilitiesGetter 24 import io.livekit.android.dagger.CapabilitiesGetter
25 import io.livekit.android.dagger.InjectionNames 25 import io.livekit.android.dagger.InjectionNames
  26 +import io.livekit.android.test.mock.MockAudioDeviceModule
26 import io.livekit.android.test.mock.MockAudioProcessingController 27 import io.livekit.android.test.mock.MockAudioProcessingController
27 import io.livekit.android.test.mock.MockEglBase 28 import io.livekit.android.test.mock.MockEglBase
28 -import livekit.org.webrtc.* 29 +import livekit.org.webrtc.EglBase
  30 +import livekit.org.webrtc.MediaStreamTrack
  31 +import livekit.org.webrtc.MockPeerConnectionFactory
  32 +import livekit.org.webrtc.PeerConnectionFactory
  33 +import livekit.org.webrtc.WebRTCInitializer
  34 +import livekit.org.webrtc.audio.AudioDeviceModule
29 import javax.inject.Named 35 import javax.inject.Named
30 import javax.inject.Singleton 36 import javax.inject.Singleton
31 37
@@ -48,6 +54,12 @@ object TestRTCModule { @@ -48,6 +54,12 @@ object TestRTCModule {
48 54
49 @Provides 55 @Provides
50 @Singleton 56 @Singleton
  57 + fun audioDeviceModule(): AudioDeviceModule {
  58 + return MockAudioDeviceModule()
  59 + }
  60 +
  61 + @Provides
  62 + @Singleton
51 fun peerConnectionFactory( 63 fun peerConnectionFactory(
52 appContext: Context, 64 appContext: Context,
53 ): PeerConnectionFactory { 65 ): PeerConnectionFactory {
@@ -23,13 +23,17 @@ import androidx.test.core.app.ApplicationProvider @@ -23,13 +23,17 @@ import androidx.test.core.app.ApplicationProvider
23 import io.livekit.android.audio.NoAudioHandler 23 import io.livekit.android.audio.NoAudioHandler
24 import io.livekit.android.audio.NoopCommunicationWorkaround 24 import io.livekit.android.audio.NoopCommunicationWorkaround
25 import io.livekit.android.e2ee.E2EEManager 25 import io.livekit.android.e2ee.E2EEManager
26 -import io.livekit.android.events.* 26 +import io.livekit.android.events.DisconnectReason
  27 +import io.livekit.android.events.EventListenable
  28 +import io.livekit.android.events.ParticipantEvent
  29 +import io.livekit.android.events.RoomEvent
27 import io.livekit.android.memory.CloseableManager 30 import io.livekit.android.memory.CloseableManager
28 import io.livekit.android.room.network.NetworkCallbackManagerImpl 31 import io.livekit.android.room.network.NetworkCallbackManagerImpl
29 import io.livekit.android.room.participant.LocalParticipant 32 import io.livekit.android.room.participant.LocalParticipant
30 import io.livekit.android.test.assert.assertIsClassList 33 import io.livekit.android.test.assert.assertIsClassList
31 import io.livekit.android.test.coroutines.TestCoroutineRule 34 import io.livekit.android.test.coroutines.TestCoroutineRule
32 import io.livekit.android.test.events.EventCollector 35 import io.livekit.android.test.events.EventCollector
  36 +import io.livekit.android.test.mock.MockAudioDeviceModule
33 import io.livekit.android.test.mock.MockAudioProcessingController 37 import io.livekit.android.test.mock.MockAudioProcessingController
34 import io.livekit.android.test.mock.MockEglBase 38 import io.livekit.android.test.mock.MockEglBase
35 import io.livekit.android.test.mock.MockLKObjects 39 import io.livekit.android.test.mock.MockLKObjects
@@ -43,7 +47,10 @@ import kotlinx.coroutines.test.advanceUntilIdle @@ -43,7 +47,10 @@ import kotlinx.coroutines.test.advanceUntilIdle
43 import kotlinx.coroutines.test.runTest 47 import kotlinx.coroutines.test.runTest
44 import livekit.LivekitRtc.JoinResponse 48 import livekit.LivekitRtc.JoinResponse
45 import livekit.org.webrtc.EglBase 49 import livekit.org.webrtc.EglBase
46 -import org.junit.Assert.* 50 +import org.junit.Assert.assertEquals
  51 +import org.junit.Assert.assertFalse
  52 +import org.junit.Assert.assertNull
  53 +import org.junit.Assert.assertTrue
47 import org.junit.Before 54 import org.junit.Before
48 import org.junit.Rule 55 import org.junit.Rule
49 import org.junit.Test 56 import org.junit.Test
@@ -51,7 +58,12 @@ import org.junit.runner.RunWith @@ -51,7 +58,12 @@ import org.junit.runner.RunWith
51 import org.mockito.Mock 58 import org.mockito.Mock
52 import org.mockito.Mockito 59 import org.mockito.Mockito
53 import org.mockito.junit.MockitoJUnit 60 import org.mockito.junit.MockitoJUnit
54 -import org.mockito.kotlin.* 61 +import org.mockito.kotlin.any
  62 +import org.mockito.kotlin.anyOrNull
  63 +import org.mockito.kotlin.doReturn
  64 +import org.mockito.kotlin.doSuspendableAnswer
  65 +import org.mockito.kotlin.stub
  66 +import org.mockito.kotlin.whenever
55 import org.robolectric.RobolectricTestRunner 67 import org.robolectric.RobolectricTestRunner
56 68
57 @ExperimentalCoroutinesApi 69 @ExperimentalCoroutinesApi
@@ -112,6 +124,7 @@ class RoomTest { @@ -112,6 +124,7 @@ class RoomTest {
112 networkCallbackManagerFactory = { networkCallback: NetworkCallback -> 124 networkCallbackManagerFactory = { networkCallback: NetworkCallback ->
113 NetworkCallbackManagerImpl(networkCallback, networkCallbackRegistry) 125 NetworkCallbackManagerImpl(networkCallback, networkCallbackRegistry)
114 }, 126 },
  127 + audioDeviceModule = MockAudioDeviceModule(),
115 ) 128 )
116 } 129 }
117 130