Compare commits
5 Commits
493cba36a2
...
v0.1.8.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 91760dd2e1 | |||
| 3c2e537420 | |||
| 97b6ea1b3e | |||
| 94ee0455a2 | |||
| 0bf6d49432 |
@@ -79,8 +79,8 @@ android {
|
|||||||
applicationId "com.ariacockpit"
|
applicationId "com.ariacockpit"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 10800
|
versionCode 10802
|
||||||
versionName "0.1.8.0"
|
versionName "0.1.8.2"
|
||||||
// Fallback fuer Libraries mit Product Flavors
|
// Fallback fuer Libraries mit Product Flavors
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aria-cockpit",
|
"name": "aria-cockpit",
|
||||||
"version": "0.1.8.0",
|
"version": "0.1.8.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -312,6 +312,10 @@ class AudioService {
|
|||||||
// lich Chunks einer alten Session in eine neue mischen.
|
// lich Chunks einer alten Session in eine neue mischen.
|
||||||
private streamRequestId: string = '';
|
private streamRequestId: string = '';
|
||||||
private streamAudioRequestId: string = '';
|
private streamAudioRequestId: string = '';
|
||||||
|
// Latch: ist endpointListeners fuer den aktuellen Session-Cycle schon gefeuert
|
||||||
|
// worden? Wird auf false gesetzt beim startStreamingRecording, auf true beim
|
||||||
|
// ersten Endpoint (egal ob via RVS oder Fallback). Verhindert Doppel-Fires.
|
||||||
|
private streamEndpointFired: boolean = false;
|
||||||
// Subscriber-Handles fuer Native-Events + RVS-Listener (cleanup beim stop)
|
// Subscriber-Handles fuer Native-Events + RVS-Listener (cleanup beim stop)
|
||||||
private streamPcmChunkSub: { remove: () => void } | null = null;
|
private streamPcmChunkSub: { remove: () => void } | null = null;
|
||||||
private streamPcmErrorSub: { remove: () => void } | null = null;
|
private streamPcmErrorSub: { remove: () => void } | null = null;
|
||||||
@@ -389,10 +393,8 @@ class AudioService {
|
|||||||
// Wir stoppen die Aufnahme — whisper hat alles was es braucht.
|
// Wir stoppen die Aufnahme — whisper hat alles was es braucht.
|
||||||
// Kein stt_stream_end senden: das Endpoint kam von der Bridge,
|
// Kein stt_stream_end senden: das Endpoint kam von der Bridge,
|
||||||
// sie hat schon finalisiert.
|
// sie hat schon finalisiert.
|
||||||
|
this._fireEndpoint(ev);
|
||||||
this._cleanupStreamLocal('endpoint');
|
this._cleanupStreamLocal('endpoint');
|
||||||
this.endpointListeners.forEach(cb => {
|
|
||||||
try { cb(ev); } catch (e) { console.warn('[Audio] endpoint listener err:', e); }
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t === 'stt_stream_done') {
|
if (t === 'stt_stream_done') {
|
||||||
@@ -979,6 +981,7 @@ class AudioService {
|
|||||||
this.streamRequestId = requestId;
|
this.streamRequestId = requestId;
|
||||||
this.streamAudioRequestId = opts.audioRequestId || '';
|
this.streamAudioRequestId = opts.audioRequestId || '';
|
||||||
this.streamGotPartial = false;
|
this.streamGotPartial = false;
|
||||||
|
this.streamEndpointFired = false;
|
||||||
this.recordingStartTime = Date.now();
|
this.recordingStartTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1066,10 +1069,17 @@ class AudioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Sauberer User-initiated Stop. Sendet stt_stream_end an die Bridge,
|
/** Sauberer User-initiated Stop. Sendet stt_stream_end an die Bridge,
|
||||||
* die noch ihren Final-Transcribe macht. */
|
* die noch ihren Final-Transcribe macht.
|
||||||
|
*
|
||||||
|
* Plus: Fallback-Timer (3s). Wenn die Bridge nicht antwortet (z.B. weil
|
||||||
|
* veraltete Version ohne Streaming-Handler laeuft), feuern wir den
|
||||||
|
* Endpoint-Listener trotzdem mit text='' damit die App-UI nicht in
|
||||||
|
* "wird verarbeitet..." haengt. ChatScreen behandelt das wie den
|
||||||
|
* No-Speech-Fall (Bubble weg + endConversation). */
|
||||||
async stopStreamingRecording(reason: string = 'user'): Promise<void> {
|
async stopStreamingRecording(reason: string = 'user'): Promise<void> {
|
||||||
const reqId = this.streamRequestId;
|
const reqId = this.streamRequestId;
|
||||||
if (!reqId) return;
|
if (!reqId) return;
|
||||||
|
const audioReqId = this.streamAudioRequestId;
|
||||||
try {
|
try {
|
||||||
rvs.send('stt_stream_end' as any, { requestId: reqId, reason });
|
rvs.send('stt_stream_end' as any, { requestId: reqId, reason });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -1078,6 +1088,21 @@ class AudioService {
|
|||||||
// Recorder lokal abschalten — Bridge feuert dann ihrerseits noch
|
// Recorder lokal abschalten — Bridge feuert dann ihrerseits noch
|
||||||
// stt_endpoint + stt_stream_done.
|
// stt_endpoint + stt_stream_done.
|
||||||
this._cleanupStreamLocal(`stop:${reason}`);
|
this._cleanupStreamLocal(`stop:${reason}`);
|
||||||
|
// Fallback-Watchdog: nach 3s noch immer kein Endpoint via RVS angekommen
|
||||||
|
// → _fireEndpoint mit text='' (idempotent via streamEndpointFired-Latch,
|
||||||
|
// d.h. wenn echtes stt_endpoint zwischen jetzt und +3s ankommt feuert
|
||||||
|
// dieser Fallback NICHT).
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.streamEndpointFired) return;
|
||||||
|
console.log('[Audio] stopStreamingRecording: 3s ohne Bridge-Antwort — fallback fire');
|
||||||
|
this._fireEndpoint({
|
||||||
|
audioRequestId: audioReqId,
|
||||||
|
text: '',
|
||||||
|
reason: `stop:${reason}:no-response`,
|
||||||
|
durationS: 0,
|
||||||
|
sttMs: 0,
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Abbruch ohne dass Brain den Text verarbeitet — z.B. wenn der User
|
/** Abbruch ohne dass Brain den Text verarbeitet — z.B. wenn der User
|
||||||
@@ -1095,15 +1120,23 @@ class AudioService {
|
|||||||
} catch {}
|
} catch {}
|
||||||
this._cleanupStreamLocal(`cancel:${reason}`);
|
this._cleanupStreamLocal(`cancel:${reason}`);
|
||||||
// Listener feuern damit ChatScreen reagieren kann (endConversation etc.)
|
// Listener feuern damit ChatScreen reagieren kann (endConversation etc.)
|
||||||
const ev: SttEndpointEvent = {
|
this._fireEndpoint({
|
||||||
audioRequestId: audioReqId,
|
audioRequestId: audioReqId,
|
||||||
text: '',
|
text: '',
|
||||||
reason: `cancel:${reason}`,
|
reason: `cancel:${reason}`,
|
||||||
durationS: 0,
|
durationS: 0,
|
||||||
sttMs: 0,
|
sttMs: 0,
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Feuert den Endpoint-Listener — aber nur einmal pro Session-Cycle.
|
||||||
|
* Wird sowohl vom RVS-stt_endpoint-Pfad als auch vom Fallback-Watchdog
|
||||||
|
* und cancelStreamingRecording aufgerufen. */
|
||||||
|
private _fireEndpoint(ev: SttEndpointEvent): void {
|
||||||
|
if (this.streamEndpointFired) return;
|
||||||
|
this.streamEndpointFired = true;
|
||||||
this.endpointListeners.forEach(cb => {
|
this.endpointListeners.forEach(cb => {
|
||||||
try { cb(ev); } catch (e) { console.warn('[Audio] endpoint listener (cancel) err:', e); }
|
try { cb(ev); } catch (e) { console.warn('[Audio] endpoint listener err:', e); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -390,15 +390,35 @@ class WakeWordService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Nach ARIA-Antwort (TTS fertig): naechste Aufnahme im Conversation-Window starten */
|
/** Nach ARIA-Antwort (TTS fertig): naechste Aufnahme im Conversation-Window starten.
|
||||||
|
*
|
||||||
|
* WICHTIG: setTimeout(800ms) kann im Hintergrund (Display aus) verspaetet
|
||||||
|
* feuern — JS-Thread ist geparkt. Wenn der Timer >2s ueberfaellig ist,
|
||||||
|
* hat der User offensichtlich die App verlassen und kommt erst spaeter
|
||||||
|
* wieder — wir oeffnen das Mikro dann NICHT, sondern beenden die
|
||||||
|
* Konversation. Sonst sieht der User nach dem App-Resume "Mikro plus-
|
||||||
|
* aufnahme laeuft" obwohl er gar nichts gesagt hat → wirkt wie Phantom-
|
||||||
|
* Wake-Word. Klassische Doze-Throttling-Falle wie bei wake.detect frueher. */
|
||||||
async resume(): Promise<void> {
|
async resume(): Promise<void> {
|
||||||
if (this.state !== 'conversing') return;
|
if (this.state !== 'conversing') return;
|
||||||
|
const scheduledAt = Date.now();
|
||||||
// Kurze Pause damit TTS-Audio nicht ins Mikrofon geht
|
// Kurze Pause damit TTS-Audio nicht ins Mikrofon geht
|
||||||
await new Promise(resolve => setTimeout(resolve, 800));
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
if (this.state === 'conversing') {
|
if (this.state !== 'conversing') return;
|
||||||
console.log('[WakeWord] TTS fertig — naechste Aufnahme im Conversation-Window');
|
const delay = Date.now() - scheduledAt;
|
||||||
this.wakeCallbacks.forEach(cb => cb());
|
if (delay > 2800) {
|
||||||
|
// Timer war stark verspaetet — JS-Thread war im Hintergrund geparkt.
|
||||||
|
// Conversation als beendet behandeln statt das Mikro zu oeffnen.
|
||||||
|
console.log('[WakeWord] resume(): %dms statt ~800ms — App war im Background. endConversation statt mic-open', delay);
|
||||||
|
import('./logger').then(m => m.reportAppDebug('wake.resume',
|
||||||
|
`delayed ${delay}ms (>2800) — endConversation statt mic-open`)).catch(()=>{});
|
||||||
|
// Asynchroner Aufruf — endConversation ist async, kein await damit wir
|
||||||
|
// hier nicht in einem Promise-Chain haengen.
|
||||||
|
this.endConversation().catch(() => {});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
console.log('[WakeWord] TTS fertig — naechste Aufnahme im Conversation-Window (delay=%dms)', delay);
|
||||||
|
this.wakeCallbacks.forEach(cb => cb());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True solange das Ohr aktiv ist (armed ODER conversing). */
|
/** True solange das Ohr aktiv ist (armed ODER conversing). */
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ const ALLOWED_TYPES = new Set([
|
|||||||
"xtts_delete_voice",
|
"xtts_delete_voice",
|
||||||
"voice_preload", "voice_ready",
|
"voice_preload", "voice_ready",
|
||||||
"stt_request", "stt_response",
|
"stt_request", "stt_response",
|
||||||
|
// Streaming-STT (Phase 1+2): App schickt PCM live an whisper-bridge,
|
||||||
|
// die feuert stt_endpoint mit dem finalen Text — kein Audio-Roundtrip.
|
||||||
|
"stt_stream_start", "stt_audio_chunk", "stt_stream_end",
|
||||||
|
"stt_partial", "stt_endpoint", "stt_stream_done",
|
||||||
"service_status",
|
"service_status",
|
||||||
"config_request",
|
"config_request",
|
||||||
"flux_request", "flux_response",
|
"flux_request", "flux_response",
|
||||||
|
|||||||
Reference in New Issue
Block a user