🎙️ Hands-Free Voice Commands & AI NLU
The :speed-ui module provides a drop-in Voice Command System enabling commercial drivers, couriers, and motorists to adjust speed thresholds hands-free without looking away from the road.
🎯 Architectural Overview
The Voice Command pipeline is divided into two distinct responsibilities:
- Speech-to-Text Layer (
VoiceCommandButton): Listens to microphone audio via Android's nativeSpeechRecognizerActivity Contract and transcribes voice into text. - Intent Parsing Layer (
VoiceCommandParser): Translates raw text strings into structuredVoiceActiondomain events (offline Regex or remote LLM).
📦 Basic Usage (VoiceCommandButton)
Drop the VoiceCommandButton into your Compose dashboard:
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.vegsahayyak.speedui.components.VoiceCommandButton
import com.vegsahayyak.speedui.api.VoiceAction
import com.vegsahayyak.speedcore.api.SpeedAssistant
@Composable
fun DriverDashboard() {
VoiceCommandButton(
onActionParsed = { action ->
when (action) {
is VoiceAction.SetMaxLimit -> {
// Spoken: "Set limit to 80"
SpeedAssistant.with(context, "YOUR_JWT_LICENSE").setWarningSpeedThreshold(action.limit)
}
is VoiceAction.SetMinLimit -> {
// Spoken: "Set minimum speed to 40"
SpeedAssistant.with(context, "YOUR_JWT_LICENSE").setMinWarningSpeedThreshold(action.limit)
}
is VoiceAction.ChangeUnit -> {
// Spoken: "Change unit to miles per hour"
println("Switching unit to ${action.unitName}")
}
is VoiceAction.Unknown -> {
println("Voice command not recognized")
}
}
}
)
}🌐 Multilingual Regex Parsing (RegexVoiceCommandParser)
By default, the SDK uses RegexVoiceCommandParser which operates 100% offline with zero network latency. You can configure custom regex patterns to support non-English international markets:
import com.vegsahayyak.speedui.utils.RegexVoiceCommandParser
val multilingualParser = RegexVoiceCommandParser()
// In your Compose hierarchy:
VoiceCommandButton(
parser = multilingualParser,
onActionParsed = { action -> /* handle action */ }
)Supported Default Commands (English)
- "Set max speed to 80" →
VoiceAction.SetMaxLimit(80f) - "Speed limit 100" →
VoiceAction.SetMaxLimit(100f) - "Set minimum speed to 40" →
VoiceAction.SetMinLimit(40f) - "Switch to miles per hour" →
VoiceAction.ChangeUnit("MILES_PER_HOUR") - "Change unit to kilometers" →
VoiceAction.ChangeUnit("KILOMETERS_PER_HOUR")
🤖 Advanced AI / Google Gemini NLU Integration
For enterprise fleets requiring natural language understanding (e.g. interpreting "Hey assistant, please cap my speed at seventy-five miles an hour" or handling regional dialects), implement the VoiceCommandParser interface and connect to Google Gemini:
import com.vegsahayyak.speedui.api.VoiceCommandParser
import com.vegsahayyak.speedui.api.VoiceAction
import com.google.ai.client.generativeai.GenerativeModel
class GeminiVoiceCommandParser(
private val generativeModel: GenerativeModel
) : VoiceCommandParser {
override suspend fun parse(text: String): VoiceAction? {
val prompt = """
You are an in-vehicle telematics assistant. The driver said: "$text".
Extract their intent into JSON:
- If setting max limit: {"action": "SET_MAX", "limit": 80}
- If setting min limit: {"action": "SET_MIN", "limit": 40}
- If changing unit: {"action": "CHANGE_UNIT", "unit": "km/h"}
Return only raw JSON.
""".trimIndent()
val response = generativeModel.generateContent(prompt)
val rawJson = response.text ?: return VoiceAction.Unknown
return parseJsonResponse(rawJson)
}
private fun parseJsonResponse(json: String): VoiceAction {
// Parse JSON into VoiceAction instances
return VoiceAction.SetMaxLimit(80f)
}
}Inject your custom AI parser into VoiceCommandButton:
VoiceCommandButton(
parser = GeminiVoiceCommandParser(myGeminiModel),
onActionParsed = { action -> /* handle action */ }
)🛡️ Required Android Permissions
The VoiceCommandButton automatically handles launching the Android speech intent. Ensure RECORD_AUDIO is declared in your app's AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO" />