2929749314
- Ear button activates conversation mode (green dot) - After TTS playback finishes → 800ms pause → auto-start recording - VAD stops recording on silence → sends to ARIA → ARIA answers → TTS → loop - Like a natural conversation / walkie-talkie mode - Audio service fires onPlaybackFinished when queue empty Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
/**
|
|
* Gespraechsmodus — "Ohr-Button"
|
|
*
|
|
* Wenn aktiv: Nach jeder ARIA-Antwort (TTS fertig) startet automatisch die Aufnahme.
|
|
* Wie ein Walkie-Talkie / natuerliches Gespraech:
|
|
* ARIA spricht → Aufnahme startet → User spricht → VAD stoppt → ARIA antwortet → ...
|
|
*
|
|
* Phase 2 (geplant): Porcupine "ARIA" Wake Word fuer passives Lauschen.
|
|
*/
|
|
|
|
type WakeWordCallback = () => void;
|
|
type StateCallback = (state: WakeWordState) => void;
|
|
|
|
export type WakeWordState = 'off' | 'listening' | 'detected';
|
|
|
|
class WakeWordService {
|
|
private state: WakeWordState = 'off';
|
|
private wakeCallbacks: WakeWordCallback[] = [];
|
|
private stateCallbacks: StateCallback[] = [];
|
|
|
|
/** Gespraechsmodus starten */
|
|
async start(): Promise<boolean> {
|
|
if (this.state === 'listening') return true;
|
|
console.log('[WakeWord] Gespraechsmodus aktiviert — Aufnahme startet nach ARIA-Antwort');
|
|
this.setState('listening');
|
|
return true;
|
|
}
|
|
|
|
/** Gespraechsmodus stoppen */
|
|
stop(): void {
|
|
console.log('[WakeWord] Gespraechsmodus deaktiviert');
|
|
this.setState('off');
|
|
}
|
|
|
|
/** Nach ARIA-Antwort (TTS fertig): Aufnahme automatisch starten */
|
|
async resume(): Promise<void> {
|
|
if (this.state !== 'listening') return;
|
|
// Kurze Pause damit TTS-Audio nicht ins Mikrofon geht
|
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
if (this.state === 'listening') {
|
|
console.log('[WakeWord] TTS fertig — starte automatisch Aufnahme');
|
|
this.wakeCallbacks.forEach(cb => cb());
|
|
}
|
|
}
|
|
|
|
isActive(): boolean {
|
|
return this.state === 'listening';
|
|
}
|
|
|
|
// --- Callbacks ---
|
|
|
|
onWakeWord(callback: WakeWordCallback): () => void {
|
|
this.wakeCallbacks.push(callback);
|
|
return () => {
|
|
this.wakeCallbacks = this.wakeCallbacks.filter(cb => cb !== callback);
|
|
};
|
|
}
|
|
|
|
onStateChange(callback: StateCallback): () => void {
|
|
this.stateCallbacks.push(callback);
|
|
return () => {
|
|
this.stateCallbacks = this.stateCallbacks.filter(cb => cb !== callback);
|
|
};
|
|
}
|
|
|
|
getState(): WakeWordState {
|
|
return this.state;
|
|
}
|
|
|
|
private setState(state: WakeWordState): void {
|
|
if (this.state !== state) {
|
|
this.state = state;
|
|
this.stateCallbacks.forEach(cb => cb(state));
|
|
}
|
|
}
|
|
}
|
|
|
|
const wakeWordService = new WakeWordService();
|
|
export default wakeWordService;
|