Implement Voice Capability Service for Platform Voice Feature Detection
Resolved 💬 2 comments Opened Oct 20, 2025 by sanchitmonga22 Closed Oct 20, 2025
Priority
CRITICAL - Production Blocker
Context
The Voice Capability Service is responsible for detecting which voice features are available on the current platform (TTS engines, STT support, microphone access, audio playback, etc.). The iOS SDK has a comprehensive VoiceCapabilityService that checks platform capabilities. The Kotlin SDK is missing this critical service.
Current State
- Status: Service does not exist
- Impact: Cannot determine which voice features are available at runtime
- iOS Has: Complete capability detection for all voice-related features
iOS Reference Implementation
- Service:
sdk/runanywhere-swift/Sources/RunAnywhere/Capabilities/VoiceCapability/Services/VoiceCapabilityService.swift - Models:
sdk/runanywhere-swift/Sources/RunAnywhere/Capabilities/VoiceCapability/Models/
iOS Architecture Pattern
public final class VoiceCapabilityService: CapabilityService {
public func checkCapability() async -> VoiceCapability
public struct VoiceCapability {
public let ttsAvailable: Bool
public let sttAvailable: Bool
public let microphoneAccess: Bool
public let audioPlaybackAvailable: Bool
public let availableTTSVoices: [String]
public let supportedLanguages: [String]
}
}
Implementation Plan
1. Define Common Interfaces (commonMain)
// commonMain/kotlin/com/runanywhere/sdk/capabilities/voice/VoiceCapabilityService.kt
interface VoiceCapabilityService {
suspend fun checkCapability(): VoiceCapability
suspend fun requestMicrophonePermission(): Boolean
val isVoiceSupported: Boolean
}
// commonMain/kotlin/com/runanywhere/sdk/capabilities/voice/models/VoiceCapability.kt
data class VoiceCapability(
val ttsAvailable: Boolean,
val sttAvailable: Boolean,
val microphoneAccess: Boolean,
val audioPlaybackAvailable: Boolean,
val availableTTSVoices: List<String>,
val supportedLanguages: List<String>,
val wakeWordSupported: Boolean = false,
val platform: Platform
)
enum class Platform {
ANDROID, JVM, IOS, MACOS, LINUX, WINDOWS
}
2. Platform Implementations
Android (androidMain)
// androidMain/kotlin/com/runanywhere/sdk/capabilities/voice/AndroidVoiceCapabilityService.kt
class AndroidVoiceCapabilityService(
private val context: Context
) : VoiceCapabilityService {
override suspend fun checkCapability(): VoiceCapability {
val packageManager = context.packageManager
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
// Check TTS availability
val ttsEngines = TextToSpeech.Engine().getDefaultEngine()
val ttsAvailable = ttsEngines != null
// Check STT availability
val sttAvailable = SpeechRecognizer.isRecognitionAvailable(context)
// Check microphone permission
val microphoneAccess = ContextCompat.checkSelfPermission(
context,
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
// Check audio playback
val audioPlaybackAvailable = packageManager.hasSystemFeature(
PackageManager.FEATURE_AUDIO_OUTPUT
)
// Get available TTS voices
val tts = TextToSpeech(context, null)
val voices = tts.voices.map { it.name }
tts.shutdown()
return VoiceCapability(
ttsAvailable = ttsAvailable,
sttAvailable = sttAvailable,
microphoneAccess = microphoneAccess,
audioPlaybackAvailable = audioPlaybackAvailable,
availableTTSVoices = voices,
supportedLanguages = Locale.getAvailableLocales().map { it.language },
wakeWordSupported = true, // Porcupine available on Android
platform = Platform.ANDROID
)
}
override suspend fun requestMicrophonePermission(): Boolean {
// Request permission via Activity
return withContext(Dispatchers.Main) {
// Implementation requires Activity context
false
}
}
override val isVoiceSupported: Boolean
get() = true // Android always supports voice
}
JVM (jvmMain)
// jvmMain/kotlin/com/runanywhere/sdk/capabilities/voice/JvmVoiceCapabilityService.kt
class JvmVoiceCapabilityService : VoiceCapabilityService {
override suspend fun checkCapability(): VoiceCapability {
// Check if MaryTTS or similar is available
val ttsAvailable = checkTTSLibrary()
// Check if Vosk or CMU Sphinx is available
val sttAvailable = checkSTTLibrary()
// Check microphone access (Java Sound API)
val microphoneAccess = checkMicrophoneAccess()
// Audio playback always available via javax.sound
val audioPlaybackAvailable = true
return VoiceCapability(
ttsAvailable = ttsAvailable,
sttAvailable = sttAvailable,
microphoneAccess = microphoneAccess,
audioPlaybackAvailable = audioPlaybackAvailable,
availableTTSVoices = getTTSVoices(),
supportedLanguages = listOf("en", "es", "fr", "de"),
wakeWordSupported = checkWakeWordLibrary(),
platform = detectPlatform()
)
}
override suspend fun requestMicrophonePermission(): Boolean {
// JVM doesn't have runtime permissions
return checkMicrophoneAccess()
}
override val isVoiceSupported: Boolean
get() = true
private fun checkMicrophoneAccess(): Boolean {
return try {
val mixer = AudioSystem.getTargetDataLine(null)
mixer != null
} catch (e: Exception) {
false
}
}
private fun detectPlatform(): Platform {
val os = System.getProperty("os.name").lowercase()
return when {
os.contains("win") -> Platform.WINDOWS
os.contains("mac") -> Platform.MACOS
os.contains("nux") -> Platform.LINUX
else -> Platform.JVM
}
}
}
3. Service Registration
// commonMain/kotlin/com/runanywhere/sdk/foundation/ServiceContainer.kt
class ServiceContainer {
val voiceCapabilityService: VoiceCapabilityService by lazy {
createVoiceCapabilityService()
}
private fun createVoiceCapabilityService(): VoiceCapabilityService {
// expect/actual for platform-specific implementation
return platformVoiceCapabilityService()
}
}
// expect/actual pattern
expect fun platformVoiceCapabilityService(): VoiceCapabilityService
Files to Create/Modify
New Files (commonMain)
- [ ]
capabilities/voice/VoiceCapabilityService.kt - [ ]
capabilities/voice/models/VoiceCapability.kt - [ ]
capabilities/voice/models/Platform.kt
New Files (androidMain)
- [ ]
capabilities/voice/AndroidVoiceCapabilityService.kt
New Files (jvmMain)
- [ ]
capabilities/voice/JvmVoiceCapabilityService.kt
Files to Modify
- [ ]
foundation/ServiceContainer.kt(add voice capability service)
Testing Requirements
- [ ] Unit tests for VoiceCapability data class
- [ ] Android platform tests (TTS, STT, microphone checks)
- [ ] JVM platform tests (library detection)
- [ ] Permission request tests (Android)
- [ ] Capability caching tests
- [ ] Error handling tests (missing permissions)
Success Criteria
- [ ] Service detects all voice capabilities correctly on Android
- [ ] Service detects all voice capabilities correctly on JVM
- [ ] Microphone permission requests work on Android
- [ ] TTS voice enumeration works
- [ ] Platform detection is accurate
- [ ] Service is integrated into ServiceContainer
- [ ] Components can query capabilities before initialization
Estimated Effort
1-2 weeks (80-100 hours)
- Common interface design: 1-2 days
- Android implementation: 3-4 days
- JVM implementation: 3-4 days
- Testing: 2-3 days
- Documentation: 1 day
Dependencies
- Depends on: #147 (privacy-first logging)
- Blocks: #149 (TTS Component)
- Blocks: #150 (Wake Word Detection)
- Blocks: #151 (Voice Agent Component)
Related Issues
- Part of capability detection infrastructure
- Required for runtime feature availability checks
- Aligns with iOS VoiceCapabilityService architecture
🤖 Generated with Claude Code
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗