Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 710e7c88d8 | |||
| b6ee5552f0 | |||
| 570eb031e0 | |||
| e9615d987e | |||
| 5e95eacd11 | |||
| ece08f0f2f |
@@ -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 10804
|
versionCode 10807
|
||||||
versionName "0.1.8.4"
|
versionName "0.1.8.7"
|
||||||
// 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.4",
|
"version": "0.1.8.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -497,6 +497,49 @@ const SettingsScreen: React.FC = () => {
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Datei-Manager: Einzel-Datei-Download. ChatScreen subscribet auch auf
|
||||||
|
// file_response — der versucht aber nur Chat-Bubble-Attachments zu
|
||||||
|
// patchen und macht nix wenn die requestId nicht zu einer Nachricht
|
||||||
|
// passt. Hier behandeln wir die Manager-initiierten Downloads
|
||||||
|
// (requestId-Praefix 'single-' aus bulkDownload). Schreibt nach
|
||||||
|
// ~/Download/ wie der ZIP-Pfad.
|
||||||
|
if (message.type === ('file_response' as any)) {
|
||||||
|
const p: any = message.payload || {};
|
||||||
|
const reqId = (p.requestId as string) || '';
|
||||||
|
if (!reqId.startsWith('single-')) return; // nicht unsere Anfrage
|
||||||
|
if (p.error) {
|
||||||
|
ToastAndroid.show('Download fehlgeschlagen: ' + p.error, ToastAndroid.LONG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const b64 = (p.base64 as string) || '';
|
||||||
|
if (!b64) return;
|
||||||
|
const fileName = (p.name as string) ||
|
||||||
|
(p.serverPath as string || '').split('/').pop() ||
|
||||||
|
'aria-download';
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const dir = RNFS.DownloadDirectoryPath;
|
||||||
|
const filePath = `${dir}/${fileName}`;
|
||||||
|
// Falls Datei schon existiert: Suffix anhaengen damit nichts
|
||||||
|
// ueberschrieben wird.
|
||||||
|
let target = filePath;
|
||||||
|
let i = 1;
|
||||||
|
while (await RNFS.exists(target)) {
|
||||||
|
const dot = fileName.lastIndexOf('.');
|
||||||
|
const base = dot > 0 ? fileName.slice(0, dot) : fileName;
|
||||||
|
const ext = dot > 0 ? fileName.slice(dot) : '';
|
||||||
|
target = `${dir}/${base} (${i})${ext}`;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
await RNFS.writeFile(target, b64, 'base64');
|
||||||
|
const sizeKb = Math.round(((b64.length * 0.75)) / 1024);
|
||||||
|
ToastAndroid.show(`Gespeichert: ${target.split('/').pop()} (${sizeKb} KB)`, ToastAndroid.LONG);
|
||||||
|
} catch (e: any) {
|
||||||
|
ToastAndroid.show('Speichern fehlgeschlagen: ' + e.message, ToastAndroid.LONG);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
// Voice wurde gespeichert → Liste neu laden + ggf. auswaehlen
|
// Voice wurde gespeichert → Liste neu laden + ggf. auswaehlen
|
||||||
if (message.type === ('xtts_voice_saved' as any)) {
|
if (message.type === ('xtts_voice_saved' as any)) {
|
||||||
const name = (message.payload as any).name as string;
|
const name = (message.payload as any).name as string;
|
||||||
|
|||||||
@@ -341,8 +341,21 @@ class AudioService {
|
|||||||
try {
|
try {
|
||||||
const emitter = new NativeEventEmitter(NativeModules.PcmStreamPlayer as any);
|
const emitter = new NativeEventEmitter(NativeModules.PcmStreamPlayer as any);
|
||||||
emitter.addListener('PcmPlaybackFinished', () => {
|
emitter.addListener('PcmPlaybackFinished', () => {
|
||||||
console.log('[Audio] PcmPlaybackFinished — Focus jetzt freigeben');
|
console.log('[Audio] PcmPlaybackFinished — AudioTrack drained');
|
||||||
this._releaseFocusDeferred();
|
this._releaseFocusDeferred();
|
||||||
|
// Erst HIER playbackFinished-Listener feuern — nicht schon beim
|
||||||
|
// Empfang des letzten PCM-Chunks (siehe handlePcmChunk). AudioTrack
|
||||||
|
// braucht nach end() noch 1-2s zum Drainen seines Hardware-Buffers.
|
||||||
|
// Wenn wir die Listener zu frueh feuern, re-armt OpenWakeWord
|
||||||
|
// waehrend ARIA noch hoerbar spricht → ARIAs Stimme verwirrt die
|
||||||
|
// Wake-Word-Detection (kein gemeinsames AEC zwischen AudioTrack-
|
||||||
|
// und AudioRecord-Session). Stefan-Reproduktion: nach jeder ARIA-
|
||||||
|
// Antwort schluckte das Wake-Word den naechsten Trigger.
|
||||||
|
import('./logger').then(m => m.reportAppDebug('audio.playback',
|
||||||
|
'PcmPlaybackFinished native event → fire listeners')).catch(()=>{});
|
||||||
|
this.playbackFinishedListeners.forEach(cb => {
|
||||||
|
try { cb(); } catch (e) { console.warn('[Audio] playbackFinished cb err:', e); }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[Audio] PcmPlaybackFinished-Subscription fehlgeschlagen:', err);
|
console.warn('[Audio] PcmPlaybackFinished-Subscription fehlgeschlagen:', err);
|
||||||
@@ -1368,12 +1381,13 @@ class AudioService {
|
|||||||
// releasen den AudioFocus NICHT hier — der writer braucht u.U. noch
|
// releasen den AudioFocus NICHT hier — der writer braucht u.U. noch
|
||||||
// 30+ Sekunden bis der Buffer wirklich abgespielt ist. Den release
|
// 30+ Sekunden bis der Buffer wirklich abgespielt ist. Den release
|
||||||
// triggert das native Event "PcmPlaybackFinished" wenn AudioTrack
|
// triggert das native Event "PcmPlaybackFinished" wenn AudioTrack
|
||||||
// wirklich am Ende ist (siehe ensurePlaybackFinishedListener).
|
// wirklich am Ende ist (siehe Constructor-PcmPlaybackFinished-Handler).
|
||||||
|
//
|
||||||
|
// playbackFinishedListeners feuern AUCH erst dort — frueher feuerten
|
||||||
|
// sie hier (beim Eintreffen des letzten Chunks), das fuehrte zu
|
||||||
|
// einem Race: OpenWakeWord re-armte waehrend AudioTrack noch hoerbar
|
||||||
|
// ARIAs Stimme abspielte → naechstes Wake-Word ging unter.
|
||||||
try { await PcmStreamPlayer!.end(); } catch {}
|
try { await PcmStreamPlayer!.end(); } catch {}
|
||||||
// playbackFinished-Listener informieren (UI-Logik)
|
|
||||||
this.playbackFinishedListeners.forEach(cb => {
|
|
||||||
try { cb(); } catch (e) { console.warn('[Audio] playbackFinished cb err:', e); }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
this.pcmStreamActive = false;
|
this.pcmStreamActive = false;
|
||||||
|
|
||||||
|
|||||||
@@ -346,19 +346,34 @@ class WakeWordService {
|
|||||||
* Ohne: zurueck zu 'off'.
|
* Ohne: zurueck zu 'off'.
|
||||||
*/
|
*/
|
||||||
async endConversation(): Promise<void> {
|
async endConversation(): Promise<void> {
|
||||||
if (this.state !== 'conversing') return;
|
if (this.state !== 'conversing') {
|
||||||
|
// Nicht in conversing — typ. nach App-Resume bevor Streaming endete.
|
||||||
|
// Trotzdem loggen damit wir's im Diagnostic sehen.
|
||||||
|
import('./logger').then(m => m.reportAppDebug('wake.end',
|
||||||
|
`endConversation called but state=${this.state} → noop`)).catch(()=>{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
import('./logger').then(m => m.reportAppDebug('wake.end',
|
||||||
|
`endConversation called, nativeReady=${this.nativeReady}, calling OpenWakeWord.start()`)).catch(()=>{});
|
||||||
if (this.nativeReady && OpenWakeWord) {
|
if (this.nativeReady && OpenWakeWord) {
|
||||||
try {
|
try {
|
||||||
await OpenWakeWord.start();
|
await OpenWakeWord.start();
|
||||||
console.log('[WakeWord] Konversation zu Ende — zurueck zu armed');
|
console.log('[WakeWord] Konversation zu Ende — zurueck zu armed');
|
||||||
|
import('./logger').then(m => m.reportAppDebug('wake.end',
|
||||||
|
`OpenWakeWord.start() OK → state=armed, keyword=${this.keyword}`)).catch(()=>{});
|
||||||
ToastAndroid.show(`Lausche wieder auf "${KEYWORD_LABELS[this.keyword]}"`, ToastAndroid.SHORT);
|
ToastAndroid.show(`Lausche wieder auf "${KEYWORD_LABELS[this.keyword]}"`, ToastAndroid.SHORT);
|
||||||
this.setState('armed');
|
this.setState('armed');
|
||||||
return;
|
return;
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
console.warn('[WakeWord] re-arm fehlgeschlagen:', err);
|
console.warn('[WakeWord] re-arm fehlgeschlagen:', err);
|
||||||
|
import('./logger').then(m => m.reportAppDebug('wake.end',
|
||||||
|
`OpenWakeWord.start() FAIL: ${err?.message || err} → state=off`,
|
||||||
|
)).catch(()=>{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('[WakeWord] Konversation zu Ende — Ohr aus');
|
console.log('[WakeWord] Konversation zu Ende — Ohr aus');
|
||||||
|
import('./logger').then(m => m.reportAppDebug('wake.end',
|
||||||
|
`fallback: nativeReady=${this.nativeReady} → state=off`)).catch(()=>{});
|
||||||
ToastAndroid.show('Mikro aus', ToastAndroid.SHORT);
|
ToastAndroid.show('Mikro aus', ToastAndroid.SHORT);
|
||||||
this.setState('off');
|
this.setState('off');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user