Implement Wake Word Detection Component

Resolved 💬 2 comments Opened Oct 20, 2025 by sanchitmonga22 Closed Oct 20, 2025

Priority

CRITICAL - Production Blocker

Context

Wake word detection is essential for building always-listening voice assistants. The iOS SDK has a complete wake word implementation using Porcupine/OpenWakeWord. The Kotlin SDK is completely missing this component.

Current State

  • Status: Component does not exist
  • Impact: Cannot build always-listening voice features
  • iOS Has: Full implementation with multiple engine support

iOS Reference Implementation

  • Component: sdk/runanywhere-swift/Sources/RunAnywhere/Components/WakeWord/WakeWordComponent.swift
  • Services: sdk/runanywhere-swift/Sources/RunAnywhere/Components/WakeWord/Services/WakeWordService.swift
  • Models: sdk/runanywhere-swift/Sources/RunAnywhere/Components/WakeWord/Models/

iOS Architecture Pattern

public final class WakeWordComponent: ModelBasedComponent {
    private var service: WakeWordService?
    
    public func startListening() async throws
    public func stopListening() async
    public var detectionStream: AsyncStream<WakeWordDetection> { get }
}

public protocol WakeWordService: Sendable {
    func startDetection(audioStream: AsyncStream<Data>) async throws
    var detections: AsyncStream<WakeWordDetection> { get }
}

Implementation Plan

1. Define Common Interfaces (commonMain)

// commonMain/kotlin/com/runanywhere/sdk/components/wakeword/WakeWordComponent.kt
class WakeWordComponent(
    configuration: WakeWordConfiguration,
    serviceContainer: ServiceContainer? = null
) : BaseComponent<WakeWordService>(configuration, serviceContainer) {
    
    suspend fun startListening()
    suspend fun stopListening()
    val detectionStream: Flow<WakeWordDetection>
    
    override suspend fun createService(): WakeWordService {
        val provider = ModuleRegistry.wakeWordProvider(configuration.modelId)
        return provider?.createWakeWordService(configuration)
            ?: throw SDKError.ComponentNotAvailable("No wake word provider")
    }
}

// commonMain/kotlin/com/runanywhere/sdk/components/wakeword/services/WakeWordService.kt
interface WakeWordService {
    suspend fun startDetection(audioStream: Flow<ByteArray>)
    suspend fun stopDetection()
    val detections: Flow<WakeWordDetection>
}

// commonMain/kotlin/com/runanywhere/sdk/components/wakeword/models/WakeWordConfiguration.kt
data class WakeWordConfiguration(
    val modelId: String,
    val keywords: List<String>,
    val sensitivity: Float = 0.5f,
    val audioFormat: AudioFormat = AudioFormat.PCM_16BIT
) : ComponentConfiguration

data class WakeWordDetection(
    val keyword: String,
    val confidence: Float,
    val timestamp: Long
)

2. Platform Implementations

Android (androidMain)
// androidMain/kotlin/com/runanywhere/sdk/components/wakeword/services/AndroidWakeWordService.kt
class AndroidWakeWordService(
    private val context: Context,
    private val configuration: WakeWordConfiguration
) : WakeWordService {
    
    // Use Porcupine or Snowboy for Android
    private var porcupineManager: PorcupineManager? = null
    
    override suspend fun startDetection(audioStream: Flow<ByteArray>) {
        // Initialize wake word engine
        porcupineManager = PorcupineManager.Builder()
            .setKeyword(configuration.keywords.first())
            .setSensitivity(configuration.sensitivity)
            .build(context) { keywordIndex ->
                // Emit detection
            }
        porcupineManager?.start()
    }
    
    override suspend fun stopDetection() {
        porcupineManager?.stop()
        porcupineManager?.delete()
    }
}
JVM (jvmMain)
// jvmMain/kotlin/com/runanywhere/sdk/components/wakeword/services/JvmWakeWordService.kt
class JvmWakeWordService(
    private val configuration: WakeWordConfiguration
) : WakeWordService {
    
    // Use Vosk or similar for JVM
    override suspend fun startDetection(audioStream: Flow<ByteArray>) {
        // Implementation using Vosk keyword spotting
    }
    
    override suspend fun stopDetection() {
        // Cleanup
    }
}

3. Provider Registration

// commonMain/kotlin/com/runanywhere/sdk/core/ModuleRegistry.kt
interface WakeWordServiceProvider {
    suspend fun createWakeWordService(configuration: WakeWordConfiguration): WakeWordService
    fun canHandle(modelId: String?): Boolean
    val name: String
}

object ModuleRegistry {
    private val wakeWordProviders = CopyOnWriteArrayList<WakeWordServiceProvider>()
    
    fun registerWakeWord(provider: WakeWordServiceProvider) {
        wakeWordProviders.add(provider)
    }
    
    fun wakeWordProvider(modelId: String? = null): WakeWordServiceProvider? {
        return wakeWordProviders.firstOrNull { it.canHandle(modelId) }
    }
}

Files to Create/Modify

New Files (commonMain)

  • [ ] components/wakeword/WakeWordComponent.kt
  • [ ] components/wakeword/services/WakeWordService.kt
  • [ ] components/wakeword/models/WakeWordConfiguration.kt
  • [ ] components/wakeword/models/WakeWordDetection.kt
  • [ ] components/wakeword/providers/WakeWordServiceProvider.kt

New Files (androidMain)

  • [ ] components/wakeword/services/AndroidWakeWordService.kt

New Files (jvmMain)

  • [ ] components/wakeword/services/JvmWakeWordService.kt

Files to Modify

  • [ ] core/ModuleRegistry.kt (add wake word provider registration)
  • [ ] events/ComponentEvent.kt (add wake word events)

Dependencies Required

Android

  • Porcupine Wake Word: ai.picovoice:porcupine-android:3.0.0
  • OR Snowboy: Custom integration

JVM

  • Vosk: com.alphacephei:vosk:0.3.45
  • OR OpenWakeWord: Custom integration

Testing Requirements

  • [ ] Unit tests for WakeWordComponent lifecycle
  • [ ] Integration tests with mock audio stream
  • [ ] Android platform tests with Porcupine
  • [ ] JVM platform tests with Vosk
  • [ ] Multi-keyword detection tests
  • [ ] Sensitivity tuning tests
  • [ ] Audio stream interruption handling

Success Criteria

  • [ ] WakeWordComponent can detect configured keywords
  • [ ] Android implementation uses Porcupine
  • [ ] JVM implementation uses Vosk
  • [ ] Detection stream emits events with confidence scores
  • [ ] Proper lifecycle management (start/stop)
  • [ ] Thread-safe provider registration
  • [ ] Low latency detection (<500ms)
  • [ ] Low false positive rate

Estimated Effort

3-4 weeks (150-200 hours)

  • Component architecture: 2-3 days
  • Android implementation (Porcupine integration): 5-7 days
  • JVM implementation (Vosk integration): 5-7 days
  • Testing and tuning: 5-7 days
  • Documentation: 2 days

Dependencies

  • Depends on: #142 (thread safety fixes)
  • Depends on: #147 (privacy-first logging)
  • Depends on: #152 (Audio Capture Infrastructure)
  • Blocks: Voice Agent Component (#151)

Related Issues

  • Part of always-listening voice assistant roadmap
  • Required for hands-free voice activation
  • Aligns with iOS wake word architecture

🤖 Generated with Claude Code

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗