Merge pull request #6609 from element-hq/feature/valere/call/clean_up_call_controls
audio: Let EC decide alone what communication device to use
This commit is contained in:
commit
18d98020a9
2 changed files with 21 additions and 75 deletions
|
|
@ -64,6 +64,11 @@ class WebViewAudioManager(
|
||||||
*/
|
*/
|
||||||
private val isWebViewAudioEnabled = AtomicBoolean(true)
|
private val isWebViewAudioEnabled = AtomicBoolean(true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the device id requested by EC, and re-set it if something try to switch (only android S+).
|
||||||
|
*/
|
||||||
|
private var ecRequestedDeviceId: String? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of device types that are considered as communication devices, sorted by likelihood of it being used for communication.
|
* The list of device types that are considered as communication devices, sorted by likelihood of it being used for communication.
|
||||||
*/
|
*/
|
||||||
|
|
@ -113,22 +118,12 @@ class WebViewAudioManager(
|
||||||
@get:RequiresApi(Build.VERSION_CODES.S)
|
@get:RequiresApi(Build.VERSION_CODES.S)
|
||||||
private val commsDeviceChangedListener by lazy {
|
private val commsDeviceChangedListener by lazy {
|
||||||
AudioManager.OnCommunicationDeviceChangedListener { device ->
|
AudioManager.OnCommunicationDeviceChangedListener { device ->
|
||||||
if (device != null && device.id == expectedNewCommunicationDeviceId) {
|
Timber.d("Audio device changed, type: ${device?.id}")
|
||||||
expectedNewCommunicationDeviceId = null
|
val wantedDevice = this.ecRequestedDeviceId
|
||||||
Timber.d("Audio device changed, type: ${device.type}")
|
if (wantedDevice != null && this.ecRequestedDeviceId != device?.id?.toString()) {
|
||||||
updateSelectedAudioDeviceInWebView(device.id.toString())
|
// We want to ensure that we stick to what EC selected even if it was changed outside
|
||||||
} else if (device != null && device.id != expectedNewCommunicationDeviceId) {
|
Timber.d("Audio device changed to unwanted device ${device?.id}, enforce using the expected device $wantedDevice")
|
||||||
// We were expecting a device change but it didn't happen, so we should retry
|
audioManager.selectAudioDevice(wantedDevice)
|
||||||
val expectedDeviceId = expectedNewCommunicationDeviceId
|
|
||||||
if (expectedDeviceId != null) {
|
|
||||||
// Remove the expected id so we only retry once
|
|
||||||
expectedNewCommunicationDeviceId = null
|
|
||||||
audioManager.selectAudioDevice(expectedDeviceId.toString())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Timber.d("Audio device cleared")
|
|
||||||
expectedNewCommunicationDeviceId = null
|
|
||||||
audioManager.selectAudioDevice(null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,40 +139,15 @@ class WebViewAudioManager(
|
||||||
// We need to calculate the available devices ourselves, since calling `listAudioDevices` will return an outdated list
|
// We need to calculate the available devices ourselves, since calling `listAudioDevices` will return an outdated list
|
||||||
val audioDevices = (listAudioDevices() + validNewDevices).distinctBy { it.id }.sortedWith(audioDeviceComparator)
|
val audioDevices = (listAudioDevices() + validNewDevices).distinctBy { it.id }.sortedWith(audioDeviceComparator)
|
||||||
setAvailableAudioDevices(audioDevices.map(SerializableAudioDevice::fromAudioDeviceInfo))
|
setAvailableAudioDevices(audioDevices.map(SerializableAudioDevice::fromAudioDeviceInfo))
|
||||||
// This should automatically switch to a new device if it has a higher priority than the current one
|
|
||||||
selectDefaultAudioDevice(audioDevices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
|
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
|
||||||
// Update the available devices
|
// Update the available devices
|
||||||
|
// Element Call will then decide to switch devices if needed
|
||||||
setAvailableAudioDevices()
|
setAvailableAudioDevices()
|
||||||
|
|
||||||
// Unless the removed device is the current one, we don't need to do anything else
|
|
||||||
val removedCurrentDevice = removedDevices.orEmpty().any { it.id == currentDeviceId }
|
|
||||||
if (!removedCurrentDevice) return
|
|
||||||
|
|
||||||
val previousDevice = previousSelectedDevice
|
|
||||||
if (previousDevice != null) {
|
|
||||||
previousSelectedDevice = null
|
|
||||||
// If we have a previous device, we should select it again
|
|
||||||
audioManager.selectAudioDevice(previousDevice.id.toString())
|
|
||||||
} else {
|
|
||||||
// If we don't have a previous device, we should select the default one
|
|
||||||
selectDefaultAudioDevice()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The currently used audio device id.
|
|
||||||
*/
|
|
||||||
private var currentDeviceId: Int? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a new audio device is selected but not yet set as the communication device by the OS, this id is used to check if the device is the expected one.
|
|
||||||
*/
|
|
||||||
private var expectedNewCommunicationDeviceId: Int? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Previously selected device, used to restore the selection when the selected device is removed.
|
* Previously selected device, used to restore the selection when the selected device is removed.
|
||||||
*/
|
*/
|
||||||
|
|
@ -263,6 +233,7 @@ class WebViewAudioManager(
|
||||||
val webViewAudioDeviceSelectedCallback = AndroidWebViewAudioBridge(
|
val webViewAudioDeviceSelectedCallback = AndroidWebViewAudioBridge(
|
||||||
onAudioDeviceSelected = { selectedDeviceId ->
|
onAudioDeviceSelected = { selectedDeviceId ->
|
||||||
previousSelectedDevice = listAudioDevices().find { it.id.toString() == selectedDeviceId }
|
previousSelectedDevice = listAudioDevices().find { it.id.toString() == selectedDeviceId }
|
||||||
|
this.ecRequestedDeviceId = selectedDeviceId
|
||||||
audioManager.selectAudioDevice(selectedDeviceId)
|
audioManager.selectAudioDevice(selectedDeviceId)
|
||||||
},
|
},
|
||||||
onAudioPlaybackStarted = {
|
onAudioPlaybackStarted = {
|
||||||
|
|
@ -330,34 +301,6 @@ class WebViewAudioManager(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects the default audio device based on the sorted available devices.
|
|
||||||
*
|
|
||||||
* @param availableDevices The list of available audio devices to select from. If not provided, it will use the current list of audio devices.
|
|
||||||
*/
|
|
||||||
private fun selectDefaultAudioDevice(availableDevices: List<AudioDeviceInfo> = listAudioDevices()) {
|
|
||||||
val selectedDevice = availableDevices.firstOrNull()
|
|
||||||
expectedNewCommunicationDeviceId = selectedDevice?.id
|
|
||||||
audioManager.selectAudioDevice(selectedDevice)
|
|
||||||
|
|
||||||
selectedDevice?.let {
|
|
||||||
updateSelectedAudioDeviceInWebView(it.id.toString())
|
|
||||||
} ?: run {
|
|
||||||
Timber.w("Audio: unable to select default audio device")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the WebView's UI to reflect the selected audio device.
|
|
||||||
*
|
|
||||||
* @param deviceId The id of the selected audio device.
|
|
||||||
*/
|
|
||||||
private fun updateSelectedAudioDeviceInWebView(deviceId: String) {
|
|
||||||
coroutineScope.launch(Dispatchers.Main) {
|
|
||||||
webView.evaluateJavascript("controls.setOutputDevice('$deviceId');", null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects the audio device on the OS based on the provided device id.
|
* Selects the audio device on the OS based on the provided device id.
|
||||||
*
|
*
|
||||||
|
|
@ -381,14 +324,14 @@ class WebViewAudioManager(
|
||||||
*
|
*
|
||||||
* @param device The info of the audio device to select, or none to clear the selected device.
|
* @param device The info of the audio device to select, or none to clear the selected device.
|
||||||
*/
|
*/
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
private fun AudioManager.selectAudioDevice(device: AudioDeviceInfo?) {
|
private fun AudioManager.selectAudioDevice(device: AudioDeviceInfo?) {
|
||||||
currentDeviceId = device?.id
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
Timber.d("Setting communication device: ${device.id} - ${deviceName(device.type, device.productName.toString())}")
|
Timber.d("Setting communication device: ${device.id} - ${deviceName(device.type, device.productName.toString())}")
|
||||||
setCommunicationDevice(device)
|
if (!setCommunicationDevice(device)) {
|
||||||
|
Timber.w("Failed to setCommunication device")
|
||||||
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it, "Could not set communication device.")
|
Timber.e(it, "Could not set communication device.")
|
||||||
}
|
}
|
||||||
|
|
@ -410,16 +353,16 @@ class WebViewAudioManager(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setAudioEnabled(true)
|
setAudioEnabled(true)
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
isSpeakerphoneOn = device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
|
isSpeakerphoneOn = device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
|
||||||
isBluetoothScoOn = device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
|
isBluetoothScoOn = device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
|
||||||
} else {
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
isSpeakerphoneOn = false
|
isSpeakerphoneOn = false
|
||||||
isBluetoothScoOn = false
|
isBluetoothScoOn = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedNewCommunicationDeviceId = null
|
|
||||||
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
proximitySensorMutex.withLock {
|
proximitySensorMutex.withLock {
|
||||||
@Suppress("WakeLock", "WakeLockTimeout")
|
@Suppress("WakeLock", "WakeLockTimeout")
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.libraries.audio.api.AudioFocus
|
import io.element.android.libraries.audio.api.AudioFocus
|
||||||
import io.element.android.libraries.audio.api.AudioFocusRequester
|
import io.element.android.libraries.audio.api.AudioFocusRequester
|
||||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultAudioFocus(
|
class DefaultAudioFocus(
|
||||||
|
|
@ -38,9 +39,11 @@ class DefaultAudioFocus(
|
||||||
when (it) {
|
when (it) {
|
||||||
AudioManager.AUDIOFOCUS_GAIN -> {
|
AudioManager.AUDIOFOCUS_GAIN -> {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
Timber.d("AudioFocus: AUDIOFOCUS_GAIN")
|
||||||
}
|
}
|
||||||
AudioManager.AUDIOFOCUS_LOSS -> {
|
AudioManager.AUDIOFOCUS_LOSS -> {
|
||||||
// Permanent focus loss (e.g., phone call) — always stop/pause.
|
// Permanent focus loss (e.g., phone call) — always stop/pause.
|
||||||
|
Timber.d("AudioFocus: AUDIOFOCUS_LOSS")
|
||||||
onFocusLost()
|
onFocusLost()
|
||||||
}
|
}
|
||||||
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT,
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue