import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.webkit.PermissionRequest
import android.webkit.WebChromeClient
import android.webkit.WebView
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.compose.runtime.remember
class MainActivity : ComponentActivity() {
private val recordAudioPermissionResult =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
println("Microphone permission granted.")
// You might want to reload the WebView or trigger the ElevenLabs initialization here
} else {
println("Microphone permission denied.")
// Handle the case where the user denies permission (e.g., show a message)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ... Compose UI setup ...
}
fun checkAndRequestAudioPermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
) {
println("Microphone permission already granted.")
// Proceed with WebView initialization or ElevenLabs setup
} else {
recordAudioPermissionResult.launch(Manifest.permission.RECORD_AUDIO)
}
}
// ... (Inside your Compose UI where you set up the WebView) ...
val conversationText = remember { mutableStateOf("") }
AndroidView(factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
webViewClient = WebViewClient() // You might have your own WebViewClient
webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
if (request.resources.contains(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
request.grant(request.resources.toTypedArray())
} else {
request.deny()
}
}
}
addJavascriptInterface(WebAppInterface(conversationText), "Android")
loadUrl("your_html_file_path_or_url")
}
})
// ...
}
class WebAppInterface(private val conversationText: MutableState<String>) {
@JavascriptInterface
fun updateConversation(text: String, type: String, isFinal: Boolean) {
// ... your implementation ...
}
}
<elevenlabs-convai agent-id="iwFxFjZC0ZE6GBn5PwrI"></elevenlabs-convai>
<script>
const convaiWidget = document.querySelector('elevenlabs-convai');
convaiWidget.addEventListener('message', (event) => {
const message = event.detail.message;
// Process the message text here
console.log('Message:', message);
});
</script>
<script src="https://elevenlabs.io/convai-widget/index.js" async type="text/javascript"></script>
https://github.com/Kisheo/Kotlin-Webview-Example/tree/master
https://gist.github.com/wheeliechamp/5d5d96e0dcd345ab537195080b0c5398
document.addEventListener('DOMContentLoaded', async () => {
const widget = document.querySelector('elevenlabs-convai');
if (widget) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
console.log('Microphone access granted in JavaScript.');
stream.getTracks().forEach(track => track.stop()); // Stop the stream as we only needed to check permission
// Now you can safely start the ElevenLabs session
const conversation = await Conversation.startSession({
agentId: 'YOUR_AGENT_ID',
onMessage: (message) => {
// ... your onMessage logic ...
},
// ... other callbacks ...
});
} catch (error) {
console.error('Error accessing microphone:', error);
// Display a user-friendly message indicating that microphone access is needed
alert('Microphone access is required for the ElevenLabs widget.');
}
}
});
https://github.com/Kisheo/Kotlin-Webview-Example/tree/master
https://gist.github.com/wheeliechamp/5d5d96e0dcd345ab537195080b0c5398