Skip to content

Commit 9ccfb63

Browse files
committed
PoC: Embedded Native <-> WebView audio device selection
1 parent 2880782 commit 9ccfb63

File tree

1 file changed

+47
-5
lines changed
  • features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui

1 file changed

+47
-5
lines changed

features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import android.os.Build
1717
import android.util.Log
1818
import android.view.ViewGroup
1919
import android.webkit.ConsoleMessage
20+
import android.webkit.JavascriptInterface
2021
import android.webkit.PermissionRequest
2122
import android.webkit.WebChromeClient
2223
import android.webkit.WebView
@@ -36,6 +37,7 @@ import androidx.compose.runtime.getValue
3637
import androidx.compose.runtime.mutableStateListOf
3738
import androidx.compose.runtime.mutableStateOf
3839
import androidx.compose.runtime.remember
40+
import androidx.compose.runtime.rememberCoroutineScope
3941
import androidx.compose.runtime.setValue
4042
import androidx.compose.ui.Alignment
4143
import androidx.compose.ui.Modifier
@@ -70,8 +72,11 @@ import io.element.android.libraries.designsystem.theme.components.Text
7072
import io.element.android.libraries.designsystem.theme.components.TextButton
7173
import io.element.android.libraries.designsystem.theme.components.TopAppBar
7274
import io.element.android.libraries.ui.strings.CommonStrings
75+
import kotlinx.coroutines.delay
76+
import kotlinx.coroutines.launch
7377
import timber.log.Timber
7478
import java.util.concurrent.Executors
79+
import kotlin.time.Duration.Companion.seconds
7580

7681
typealias RequestPermissionCallback = (Array<String>) -> Unit
7782

@@ -179,6 +184,7 @@ private fun CallWebView(
179184
Column(modifier = modifier) {
180185
var audioDeviceCallback: AudioDeviceCallback? by remember { mutableStateOf(null) }
181186

187+
val coroutinScope = rememberCoroutineScope()
182188
AndroidView(
183189
modifier = Modifier.fillMaxWidth(),
184190
factory = { context ->
@@ -192,11 +198,47 @@ private fun CallWebView(
192198
if (url is AsyncData.Success && webView.url != url.data) {
193199
webView.loadUrl(url.data)
194200

195-
val audioManager = webView.context.getSystemService<AudioManager>()!!
196-
val devices = audioManager.loadCommunicationAudioDevices()
197-
webView.evaluateJavascript("""
198-
controls.setOutputDevices([${devices.joinToString(",") { "{ 'id': '${it.id}', 'name': '${it.description()}' }" } }])
199-
""".trimIndent(), null)
201+
val setAudioOutputCallback = object {
202+
@JavascriptInterface
203+
fun setOutputDevice(id: String) {
204+
val audioManager = webView.context.getSystemService<AudioManager>()!!
205+
val audioDevices = audioManager.loadCommunicationAudioDevices()
206+
val selectedDevice = audioDevices.find { it.id.toString() == id }
207+
if (selectedDevice != null) {
208+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
209+
audioManager.setCommunicationDevice(selectedDevice)
210+
} else {
211+
when (selectedDevice.type) {
212+
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> {
213+
@Suppress("DEPRECATION")
214+
audioManager.isSpeakerphoneOn = true
215+
}
216+
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> {
217+
@Suppress("DEPRECATION")
218+
audioManager.isSpeakerphoneOn = false
219+
}
220+
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> {
221+
audioManager.isBluetoothScoOn = true
222+
}
223+
else -> Timber.d("Audio device selected but it's not compatible, type: ${selectedDevice.type}")
224+
}
225+
}
226+
}
227+
}
228+
}
229+
230+
webView.addJavascriptInterface(setAudioOutputCallback, "setOutputDeviceCallback")
231+
232+
coroutinScope.launch {
233+
delay(4.seconds)
234+
val audioManager = webView.context.getSystemService<AudioManager>()!!
235+
val devices = audioManager.loadCommunicationAudioDevices()
236+
val setDevicesScript = """
237+
controls.setOutputDevices([${devices.joinToString(",") { "{ 'id': '${it.id}', 'name': '${it.description()}' }" } }]);
238+
controls.onOutputDeviceSelect = (id) => { setOutputDeviceCallback.setOutputDevice(id); };
239+
""".trimIndent()
240+
webView.evaluateJavascript(setDevicesScript, null)
241+
}
200242
}
201243
},
202244
onRelease = { webView ->

0 commit comments

Comments
 (0)