Committed by
GitHub
switch to using AudioSwitch library, and have SDK handle audio routing by default (#104)
正在显示
15 个修改的文件
包含
131 行增加
和
16 行删除
| @@ -113,6 +113,7 @@ dependencies { | @@ -113,6 +113,7 @@ dependencies { | ||
| 113 | implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0' | 113 | implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0' |
| 114 | api 'com.github.webrtc-sdk:android:97.4692.04' | 114 | api 'com.github.webrtc-sdk:android:97.4692.04' |
| 115 | api "com.squareup.okhttp3:okhttp:4.9.1" | 115 | api "com.squareup.okhttp3:okhttp:4.9.1" |
| 116 | + implementation "com.twilio:audioswitch:1.1.5" | ||
| 116 | implementation "androidx.annotation:annotation:1.3.0" | 117 | implementation "androidx.annotation:annotation:1.3.0" |
| 117 | implementation "androidx.core:core:${versions.androidx_core}" | 118 | implementation "androidx.core:core:${versions.androidx_core}" |
| 118 | implementation "com.google.protobuf:protobuf-javalite:${versions.protobuf}" | 119 | implementation "com.google.protobuf:protobuf-javalite:${versions.protobuf}" |
| 1 | package io.livekit.android | 1 | package io.livekit.android |
| 2 | 2 | ||
| 3 | +import android.app.Application | ||
| 3 | import android.content.Context | 4 | import android.content.Context |
| 4 | import io.livekit.android.dagger.DaggerLiveKitComponent | 5 | import io.livekit.android.dagger.DaggerLiveKitComponent |
| 5 | import io.livekit.android.dagger.create | 6 | import io.livekit.android.dagger.create |
| @@ -47,6 +48,11 @@ class LiveKit { | @@ -47,6 +48,11 @@ class LiveKit { | ||
| 47 | overrides: LiveKitOverrides = LiveKitOverrides(), | 48 | overrides: LiveKitOverrides = LiveKitOverrides(), |
| 48 | ): Room { | 49 | ): Room { |
| 49 | val ctx = appContext.applicationContext | 50 | val ctx = appContext.applicationContext |
| 51 | + | ||
| 52 | + if (ctx !is Application) { | ||
| 53 | + LKLog.w { "Application context was not found, this may cause memory leaks." } | ||
| 54 | + } | ||
| 55 | + | ||
| 50 | val component = DaggerLiveKitComponent | 56 | val component = DaggerLiveKitComponent |
| 51 | .factory() | 57 | .factory() |
| 52 | .create(ctx, overrides) | 58 | .create(ctx, overrides) |
| 1 | package io.livekit.android | 1 | package io.livekit.android |
| 2 | 2 | ||
| 3 | +import io.livekit.android.audio.AudioHandler | ||
| 4 | +import io.livekit.android.audio.NoAudioHandler | ||
| 3 | import okhttp3.OkHttpClient | 5 | import okhttp3.OkHttpClient |
| 4 | import org.webrtc.VideoDecoderFactory | 6 | import org.webrtc.VideoDecoderFactory |
| 5 | import org.webrtc.VideoEncoderFactory | 7 | import org.webrtc.VideoEncoderFactory |
| @@ -36,4 +38,10 @@ data class LiveKitOverrides( | @@ -36,4 +38,10 @@ data class LiveKitOverrides( | ||
| 36 | * Override the [VideoDecoderFactory] used by the library. | 38 | * Override the [VideoDecoderFactory] used by the library. |
| 37 | */ | 39 | */ |
| 38 | val videoDecoderFactory: VideoDecoderFactory? = null, | 40 | val videoDecoderFactory: VideoDecoderFactory? = null, |
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * Override the default [AudioHandler]. Use [NoAudioHandler] to turn off automatic audio handling. | ||
| 44 | + */ | ||
| 45 | + | ||
| 46 | + val audioHandler: AudioHandler? = null | ||
| 39 | ) | 47 | ) |
| 1 | +package io.livekit.android.audio | ||
| 2 | + | ||
| 3 | +import android.content.Context | ||
| 4 | +import com.twilio.audioswitch.AudioSwitch | ||
| 5 | +import javax.inject.Inject | ||
| 6 | +import javax.inject.Singleton | ||
| 7 | + | ||
| 8 | +@Singleton | ||
| 9 | +class AudioSwitchHandler | ||
| 10 | +@Inject | ||
| 11 | +constructor(context: Context) : AudioHandler { | ||
| 12 | + private val audioSwitch = AudioSwitch(context) | ||
| 13 | + override fun start() { | ||
| 14 | + audioSwitch.start { _, _ -> } | ||
| 15 | + audioSwitch.activate() | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + override fun stop() { | ||
| 19 | + audioSwitch.stop() | ||
| 20 | + } | ||
| 21 | +} |
| 1 | +package io.livekit.android.dagger | ||
| 2 | + | ||
| 3 | +import androidx.annotation.Nullable | ||
| 4 | +import dagger.Module | ||
| 5 | +import dagger.Provides | ||
| 6 | +import io.livekit.android.audio.AudioHandler | ||
| 7 | +import io.livekit.android.audio.AudioSwitchHandler | ||
| 8 | +import javax.inject.Named | ||
| 9 | +import javax.inject.Provider | ||
| 10 | + | ||
| 11 | +@Module | ||
| 12 | +object AudioHandlerModule { | ||
| 13 | + @Provides | ||
| 14 | + fun audioHandler( | ||
| 15 | + audioSwitchHandler: Provider<AudioSwitchHandler>, | ||
| 16 | + @Named(InjectionNames.OVERRIDE_AUDIO_HANDLER) | ||
| 17 | + @Nullable | ||
| 18 | + audioHandlerOverride: AudioHandler? | ||
| 19 | + ): AudioHandler { | ||
| 20 | + return audioHandlerOverride ?: audioSwitchHandler.get() | ||
| 21 | + } | ||
| 22 | +} |
| @@ -30,4 +30,5 @@ object InjectionNames { | @@ -30,4 +30,5 @@ object InjectionNames { | ||
| 30 | internal const val OVERRIDE_JAVA_AUDIO_DEVICE_MODULE_CUSTOMIZER = "override_java_audio_device_module_customizer" | 30 | internal const val OVERRIDE_JAVA_AUDIO_DEVICE_MODULE_CUSTOMIZER = "override_java_audio_device_module_customizer" |
| 31 | internal const val OVERRIDE_VIDEO_ENCODER_FACTORY = "override_video_encoder_factory" | 31 | internal const val OVERRIDE_VIDEO_ENCODER_FACTORY = "override_video_encoder_factory" |
| 32 | internal const val OVERRIDE_VIDEO_DECODER_FACTORY = "override_video_decoder_factory" | 32 | internal const val OVERRIDE_VIDEO_DECODER_FACTORY = "override_video_decoder_factory" |
| 33 | + internal const val OVERRIDE_AUDIO_HANDLER = "override_audio_handler" | ||
| 33 | } | 34 | } |
| @@ -17,6 +17,7 @@ import javax.inject.Singleton | @@ -17,6 +17,7 @@ import javax.inject.Singleton | ||
| 17 | WebModule::class, | 17 | WebModule::class, |
| 18 | JsonFormatModule::class, | 18 | JsonFormatModule::class, |
| 19 | OverridesModule::class, | 19 | OverridesModule::class, |
| 20 | + AudioHandlerModule::class, | ||
| 20 | ] | 21 | ] |
| 21 | ) | 22 | ) |
| 22 | internal interface LiveKitComponent { | 23 | internal interface LiveKitComponent { |
| @@ -34,4 +34,9 @@ class OverridesModule(private val overrides: LiveKitOverrides) { | @@ -34,4 +34,9 @@ class OverridesModule(private val overrides: LiveKitOverrides) { | ||
| 34 | @Nullable | 34 | @Nullable |
| 35 | fun videoDecoderFactory() = overrides.videoDecoderFactory | 35 | fun videoDecoderFactory() = overrides.videoDecoderFactory |
| 36 | 36 | ||
| 37 | + @Provides | ||
| 38 | + @Named(InjectionNames.OVERRIDE_AUDIO_HANDLER) | ||
| 39 | + @Nullable | ||
| 40 | + fun audioHandler() = overrides.audioHandler | ||
| 41 | + | ||
| 37 | } | 42 | } |
| @@ -13,6 +13,7 @@ import dagger.assisted.AssistedInject | @@ -13,6 +13,7 @@ import dagger.assisted.AssistedInject | ||
| 13 | import io.livekit.android.ConnectOptions | 13 | import io.livekit.android.ConnectOptions |
| 14 | import io.livekit.android.RoomOptions | 14 | import io.livekit.android.RoomOptions |
| 15 | import io.livekit.android.Version | 15 | import io.livekit.android.Version |
| 16 | +import io.livekit.android.audio.AudioHandler | ||
| 16 | import io.livekit.android.dagger.InjectionNames | 17 | import io.livekit.android.dagger.InjectionNames |
| 17 | import io.livekit.android.events.BroadcastEventBus | 18 | import io.livekit.android.events.BroadcastEventBus |
| 18 | import io.livekit.android.events.ParticipantEvent | 19 | import io.livekit.android.events.ParticipantEvent |
| @@ -43,6 +44,7 @@ constructor( | @@ -43,6 +44,7 @@ constructor( | ||
| 43 | private val defaultDispatcher: CoroutineDispatcher, | 44 | private val defaultDispatcher: CoroutineDispatcher, |
| 44 | @Named(InjectionNames.DISPATCHER_IO) | 45 | @Named(InjectionNames.DISPATCHER_IO) |
| 45 | private val ioDispatcher: CoroutineDispatcher, | 46 | private val ioDispatcher: CoroutineDispatcher, |
| 47 | + private val audioHandler: AudioHandler, | ||
| 46 | ) : RTCEngine.Listener, ParticipantListener, ConnectivityManager.NetworkCallback() { | 48 | ) : RTCEngine.Listener, ParticipantListener, ConnectivityManager.NetworkCallback() { |
| 47 | 49 | ||
| 48 | private lateinit var coroutineScope: CoroutineScope | 50 | private lateinit var coroutineScope: CoroutineScope |
| @@ -77,7 +79,15 @@ constructor( | @@ -77,7 +79,15 @@ constructor( | ||
| 77 | 79 | ||
| 78 | @FlowObservable | 80 | @FlowObservable |
| 79 | @get:FlowObservable | 81 | @get:FlowObservable |
| 80 | - var state: State by flowDelegate(State.DISCONNECTED) | 82 | + var state: State by flowDelegate(State.DISCONNECTED) { new, old -> |
| 83 | + if (new != old) { | ||
| 84 | + when (new) { | ||
| 85 | + State.CONNECTING -> audioHandler.start() | ||
| 86 | + State.DISCONNECTED -> audioHandler.stop() | ||
| 87 | + else -> {} | ||
| 88 | + } | ||
| 89 | + } | ||
| 90 | + } | ||
| 81 | private set | 91 | private set |
| 82 | 92 | ||
| 83 | @FlowObservable | 93 | @FlowObservable |
livekit-android-sdk/src/test/java/io/livekit/android/mock/dagger/TestAudioHandlerModule.kt
0 → 100644
| 1 | +package io.livekit.android.mock.dagger | ||
| 2 | + | ||
| 3 | +import dagger.Binds | ||
| 4 | +import dagger.Module | ||
| 5 | +import io.livekit.android.audio.AudioHandler | ||
| 6 | +import io.livekit.android.audio.NoAudioHandler | ||
| 7 | + | ||
| 8 | +@Module | ||
| 9 | +interface TestAudioHandlerModule { | ||
| 10 | + @Binds | ||
| 11 | + fun audioHandler(audioHandler: NoAudioHandler): AudioHandler | ||
| 12 | +} |
| @@ -15,6 +15,7 @@ import javax.inject.Singleton | @@ -15,6 +15,7 @@ import javax.inject.Singleton | ||
| 15 | TestCoroutinesModule::class, | 15 | TestCoroutinesModule::class, |
| 16 | TestRTCModule::class, | 16 | TestRTCModule::class, |
| 17 | TestWebModule::class, | 17 | TestWebModule::class, |
| 18 | + TestAudioHandlerModule::class, | ||
| 18 | JsonFormatModule::class, | 19 | JsonFormatModule::class, |
| 19 | ] | 20 | ] |
| 20 | ) | 21 | ) |
| @@ -3,6 +3,7 @@ package io.livekit.android.room | @@ -3,6 +3,7 @@ package io.livekit.android.room | ||
| 3 | import android.content.Context | 3 | import android.content.Context |
| 4 | import android.net.Network | 4 | import android.net.Network |
| 5 | import androidx.test.core.app.ApplicationProvider | 5 | import androidx.test.core.app.ApplicationProvider |
| 6 | +import io.livekit.android.audio.NoAudioHandler | ||
| 6 | import io.livekit.android.coroutines.TestCoroutineRule | 7 | import io.livekit.android.coroutines.TestCoroutineRule |
| 7 | import io.livekit.android.events.EventCollector | 8 | import io.livekit.android.events.EventCollector |
| 8 | import io.livekit.android.events.RoomEvent | 9 | import io.livekit.android.events.RoomEvent |
| @@ -40,7 +41,7 @@ class RoomTest { | @@ -40,7 +41,7 @@ class RoomTest { | ||
| 40 | 41 | ||
| 41 | var eglBase: EglBase = MockEglBase() | 42 | var eglBase: EglBase = MockEglBase() |
| 42 | 43 | ||
| 43 | - val localParticantFactory = object : LocalParticipant.Factory { | 44 | + val localParticipantFactory = object : LocalParticipant.Factory { |
| 44 | override fun create(info: LivekitModels.ParticipantInfo, dynacast: Boolean): LocalParticipant { | 45 | override fun create(info: LivekitModels.ParticipantInfo, dynacast: Boolean): LocalParticipant { |
| 45 | return Mockito.mock(LocalParticipant::class.java) | 46 | return Mockito.mock(LocalParticipant::class.java) |
| 46 | } | 47 | } |
| @@ -52,13 +53,14 @@ class RoomTest { | @@ -52,13 +53,14 @@ class RoomTest { | ||
| 52 | fun setup() { | 53 | fun setup() { |
| 53 | context = ApplicationProvider.getApplicationContext() | 54 | context = ApplicationProvider.getApplicationContext() |
| 54 | room = Room( | 55 | room = Room( |
| 55 | - context, | ||
| 56 | - rtcEngine, | ||
| 57 | - eglBase, | ||
| 58 | - localParticantFactory, | ||
| 59 | - DefaultsManager(), | ||
| 60 | - coroutineRule.dispatcher, | ||
| 61 | - coroutineRule.dispatcher, | 56 | + context = context, |
| 57 | + engine = rtcEngine, | ||
| 58 | + eglBase = eglBase, | ||
| 59 | + localParticipantFactory = localParticipantFactory, | ||
| 60 | + defaultsManager = DefaultsManager(), | ||
| 61 | + defaultDispatcher = coroutineRule.dispatcher, | ||
| 62 | + ioDispatcher = coroutineRule.dispatcher, | ||
| 63 | + audioHandler = NoAudioHandler(), | ||
| 62 | ) | 64 | ) |
| 63 | } | 65 | } |
| 64 | 66 |
| @@ -16,7 +16,6 @@ import io.livekit.android.room.participant.LocalParticipant | @@ -16,7 +16,6 @@ import io.livekit.android.room.participant.LocalParticipant | ||
| 16 | import io.livekit.android.room.participant.Participant | 16 | import io.livekit.android.room.participant.Participant |
| 17 | import io.livekit.android.room.participant.RemoteParticipant | 17 | import io.livekit.android.room.participant.RemoteParticipant |
| 18 | import io.livekit.android.room.track.* | 18 | import io.livekit.android.room.track.* |
| 19 | -import io.livekit.android.sample.audio.AppRTCAudioManager | ||
| 20 | import io.livekit.android.util.flow | 19 | import io.livekit.android.util.flow |
| 21 | import kotlinx.coroutines.ExperimentalCoroutinesApi | 20 | import kotlinx.coroutines.ExperimentalCoroutinesApi |
| 22 | import kotlinx.coroutines.flow.* | 21 | import kotlinx.coroutines.flow.* |
| @@ -72,12 +71,7 @@ class CallViewModel( | @@ -72,12 +71,7 @@ class CallViewModel( | ||
| 72 | private val mutablePermissionAllowed = MutableStateFlow(true) | 71 | private val mutablePermissionAllowed = MutableStateFlow(true) |
| 73 | val permissionAllowed = mutablePermissionAllowed.hide() | 72 | val permissionAllowed = mutablePermissionAllowed.hide() |
| 74 | 73 | ||
| 75 | - private val audioManager = AppRTCAudioManager(application) | ||
| 76 | - | ||
| 77 | init { | 74 | init { |
| 78 | - | ||
| 79 | - audioManager.start(null) | ||
| 80 | - | ||
| 81 | viewModelScope.launch { | 75 | viewModelScope.launch { |
| 82 | launch { | 76 | launch { |
| 83 | error.collect { Timber.e(it) } | 77 | error.collect { Timber.e(it) } |
| @@ -200,7 +194,6 @@ class CallViewModel( | @@ -200,7 +194,6 @@ class CallViewModel( | ||
| 200 | override fun onCleared() { | 194 | override fun onCleared() { |
| 201 | super.onCleared() | 195 | super.onCleared() |
| 202 | room.disconnect() | 196 | room.disconnect() |
| 203 | - audioManager.stop() | ||
| 204 | } | 197 | } |
| 205 | 198 | ||
| 206 | fun setMicEnabled(enabled: Boolean) { | 199 | fun setMicEnabled(enabled: Boolean) { |
-
请 注册 或 登录 后发表评论