diff --git a/.changeset/cuddly-moose-try.md b/.changeset/cuddly-moose-try.md new file mode 100644 index 000000000..892910035 --- /dev/null +++ b/.changeset/cuddly-moose-try.md @@ -0,0 +1,5 @@ +--- +"client-sdk-android": patch +--- + +Update audio handling to use AudioManager communication device APIs on S and above diff --git a/.changeset/great-zoos-boil.md b/.changeset/great-zoos-boil.md new file mode 100644 index 000000000..3eb5eee11 --- /dev/null +++ b/.changeset/great-zoos-boil.md @@ -0,0 +1,5 @@ +--- +"client-sdk-android": patch +--- + +Implement changing preferred audio device list on AudioSwitchHandler mid-call diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db7b97033..2fb829ece 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ androidx-camera = "1.4.2" androidx-core = "1.13.1" androidx-fragment = "1.5.1" androidx-lifecycle = "2.8.0" -audioswitch = "89582c47c9a04c62f90aa5e57251af4800a62c9a" +audioswitch = "70efa204dda4f468f989b4b3e1b5ecb6ffe56ceb" autoService = '1.0.1' coroutines = "1.6.0" dagger = "2.46" diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioSwitchHandler.kt b/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioSwitchHandler.kt index 19c457d05..9f8d50fc9 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioSwitchHandler.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioSwitchHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 LiveKit, Inc. + * Copyright 2023-2026 LiveKit, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import com.twilio.audioswitch.AbstractAudioSwitch import com.twilio.audioswitch.AudioDevice import com.twilio.audioswitch.AudioDeviceChangeListener import com.twilio.audioswitch.AudioSwitch +import com.twilio.audioswitch.CommDeviceAudioSwitch import com.twilio.audioswitch.LegacyAudioSwitch import io.livekit.android.room.Room import io.livekit.android.util.LKLog @@ -48,7 +49,7 @@ constructor(private val context: Context) : AudioHandler { /** * Toggle whether logging is enabled for [AudioSwitch]. By default, this is set to false. */ - var loggingEnabled = false + var loggingEnabled = true /** * Listen to changes in the available and active audio devices. @@ -96,6 +97,9 @@ constructor(private val context: Context) : AudioHandler { } } + @Volatile + private var preferredDeviceListBacking: List>? = null + /** * The preferred priority of audio devices to use. The first available audio device will be used. * @@ -104,8 +108,27 @@ constructor(private val context: Context) : AudioHandler { * 2. WiredHeadset * 3. Speakerphone * 4. Earpiece + * + * Changes to this value after [start] has been called will still be applied + * to the underlying [AbstractAudioSwitch] instance. */ - var preferredDeviceList: List>? = null + var preferredDeviceList: List>? + get() = preferredDeviceListBacking + set(value) { + preferredDeviceListBacking = value + val list = value ?: defaultPreferredDeviceList + val h = handler + val sw = audioSwitch + if (h != null && sw != null) { + if (Looper.myLooper() == h.looper) { + sw.setPreferredDeviceList(list) + } else { + h.post { + audioSwitch?.setPreferredDeviceList(list) + } + } + } + } /** * When true, AudioSwitchHandler will request audio focus on start and abandon on stop. @@ -199,7 +222,14 @@ constructor(private val context: Context) : AudioHandler { handler?.removeCallbacksAndMessages(null) handler?.postAtFrontOfQueue { val switch = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + CommDeviceAudioSwitch( + context = context, + loggingEnabled = loggingEnabled, + audioFocusChangeListener = onAudioFocusChangeDispatcher, + preferredDeviceList = preferredDeviceList ?: defaultPreferredDeviceList, + ) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { AudioSwitch( context = context, loggingEnabled = loggingEnabled, diff --git a/sample-app-compose/src/main/java/io/livekit/android/composesample/CallActivity.kt b/sample-app-compose/src/main/java/io/livekit/android/composesample/CallActivity.kt index 16a284a34..82523a6be 100644 --- a/sample-app-compose/src/main/java/io/livekit/android/composesample/CallActivity.kt +++ b/sample-app-compose/src/main/java/io/livekit/android/composesample/CallActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 LiveKit, Inc. + * Copyright 2023-2026 LiveKit, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import android.os.Parcelable import android.view.WindowManager import android.widget.Toast import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.background @@ -35,6 +36,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyRow @@ -107,6 +109,7 @@ class CallActivity : AppCompatActivity() { @OptIn(ExperimentalMaterialApi::class) override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -198,6 +201,7 @@ class CallActivity : AppCompatActivity() { ConstraintLayout( modifier = Modifier .fillMaxSize() + .safeDrawingPadding() .background(MaterialTheme.colors.background), ) { val (speakerView, audienceRow, buttonBar) = createRefs() diff --git a/sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt b/sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt index 5f9fff8bd..5942b6d31 100644 --- a/sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt +++ b/sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 LiveKit, Inc. + * Copyright 2023-2026 LiveKit, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,12 @@ import android.os.Parcelable import android.view.WindowManager import android.widget.EditText import android.widget.Toast +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -70,11 +73,19 @@ class CallActivity : AppCompatActivity() { @androidx.camera.camera2.interop.ExperimentalCamera2Interop override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) binding = CallActivityBinding.inflate(layoutInflater) setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets -> + val bars = windowInsets.getInsets( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout(), + ) + v.setPadding(bars.left, bars.top, bars.right, bars.bottom) + windowInsets + } // Audience row setup val audienceAdapter = GroupieAdapter()