Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa54765b03 | |||
| 8929bc99bb | |||
| 0428c06612 |
@@ -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 206
|
versionCode 207
|
||||||
versionName "0.0.2.6"
|
versionName "0.0.2.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.0.2.6",
|
"version": "0.0.2.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -748,7 +748,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
<Text style={styles.sectionTitle}>{'\u00DC'}ber</Text>
|
<Text style={styles.sectionTitle}>{'\u00DC'}ber</Text>
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
<Text style={styles.aboutTitle}>ARIA Cockpit</Text>
|
<Text style={styles.aboutTitle}>ARIA Cockpit</Text>
|
||||||
<Text style={styles.aboutVersion}>Version 0.0.2.6 </Text>
|
<Text style={styles.aboutVersion}>Version 0.0.2.7 </Text>
|
||||||
<Text style={styles.aboutInfo}>
|
<Text style={styles.aboutInfo}>
|
||||||
Stefans Kommandozentrale f{'\u00FC'}r ARIA.{'\n'}
|
Stefans Kommandozentrale f{'\u00FC'}r ARIA.{'\n'}
|
||||||
Gebaut mit React Native + TypeScript.
|
Gebaut mit React Native + TypeScript.
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ class AudioService {
|
|||||||
// Audio-Queue fuer sequentielle TTS-Wiedergabe
|
// Audio-Queue fuer sequentielle TTS-Wiedergabe
|
||||||
private audioQueue: string[] = [];
|
private audioQueue: string[] = [];
|
||||||
private isPlaying: boolean = false;
|
private isPlaying: boolean = false;
|
||||||
|
private preloadedSound: Sound | null = null;
|
||||||
|
private preloadedPath: string = '';
|
||||||
|
|
||||||
// VAD State
|
// VAD State
|
||||||
private vadEnabled: boolean = false;
|
private vadEnabled: boolean = false;
|
||||||
@@ -220,35 +222,62 @@ class AudioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.isPlaying = true;
|
this.isPlaying = true;
|
||||||
const base64Data = this.audioQueue.shift()!;
|
|
||||||
|
|
||||||
try {
|
// Preloaded Sound verwenden wenn verfuegbar, sonst neu laden
|
||||||
const tmpPath = `${RNFS.CachesDirectoryPath}/aria_tts_${Date.now()}.wav`;
|
let sound: Sound;
|
||||||
await RNFS.writeFile(tmpPath, base64Data, 'base64');
|
let soundPath: string;
|
||||||
|
|
||||||
this.currentSound = new Sound(tmpPath, '', (error) => {
|
if (this.preloadedSound) {
|
||||||
if (error) {
|
sound = this.preloadedSound;
|
||||||
console.error('[Audio] Fehler beim Laden:', error);
|
soundPath = this.preloadedPath;
|
||||||
RNFS.unlink(tmpPath).catch(() => {});
|
this.preloadedSound = null;
|
||||||
this._playNext();
|
this.preloadedPath = '';
|
||||||
return;
|
// Daten aus Queue entfernen (wurde schon preloaded)
|
||||||
}
|
this.audioQueue.shift();
|
||||||
this.currentSound?.play((success) => {
|
} else {
|
||||||
if (success) {
|
const base64Data = this.audioQueue.shift()!;
|
||||||
console.log('[Audio] Wiedergabe abgeschlossen');
|
try {
|
||||||
} else {
|
soundPath = `${RNFS.CachesDirectoryPath}/aria_tts_${Date.now()}.wav`;
|
||||||
console.warn('[Audio] Wiedergabe fehlgeschlagen');
|
await RNFS.writeFile(soundPath, base64Data, 'base64');
|
||||||
}
|
sound = await new Promise<Sound>((resolve, reject) => {
|
||||||
this.currentSound?.release();
|
const s = new Sound(soundPath, '', (err) => err ? reject(err) : resolve(s));
|
||||||
this.currentSound = null;
|
|
||||||
RNFS.unlink(tmpPath).catch(() => {});
|
|
||||||
// Naechstes Audio abspielen
|
|
||||||
this._playNext();
|
|
||||||
});
|
});
|
||||||
});
|
} catch (err) {
|
||||||
} catch (err) {
|
console.error('[Audio] Laden fehlgeschlagen:', err);
|
||||||
console.error('[Audio] Wiedergabefehler:', err);
|
this._playNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentSound = sound;
|
||||||
|
|
||||||
|
// Naechstes Audio schon vorbereiten waehrend dieses abspielt
|
||||||
|
this._preloadNext();
|
||||||
|
|
||||||
|
sound.play((success) => {
|
||||||
|
if (!success) console.warn('[Audio] Wiedergabe fehlgeschlagen');
|
||||||
|
sound.release();
|
||||||
|
this.currentSound = null;
|
||||||
|
RNFS.unlink(soundPath).catch(() => {});
|
||||||
this._playNext();
|
this._playNext();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Naechstes Audio im Hintergrund vorladen (verhindert Stottern) */
|
||||||
|
private async _preloadNext(): Promise<void> {
|
||||||
|
if (this.audioQueue.length === 0 || this.preloadedSound) return;
|
||||||
|
|
||||||
|
const base64Data = this.audioQueue[0]; // Nicht shift — bleibt in Queue
|
||||||
|
try {
|
||||||
|
const tmpPath = `${RNFS.CachesDirectoryPath}/aria_tts_pre_${Date.now()}.wav`;
|
||||||
|
await RNFS.writeFile(tmpPath, base64Data, 'base64');
|
||||||
|
this.preloadedSound = await new Promise<Sound>((resolve, reject) => {
|
||||||
|
const s = new Sound(tmpPath, '', (err) => err ? reject(err) : resolve(s));
|
||||||
|
});
|
||||||
|
this.preloadedPath = tmpPath;
|
||||||
|
} catch {
|
||||||
|
this.preloadedSound = null;
|
||||||
|
this.preloadedPath = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +290,12 @@ class AudioService {
|
|||||||
this.currentSound.release();
|
this.currentSound.release();
|
||||||
this.currentSound = null;
|
this.currentSound = null;
|
||||||
}
|
}
|
||||||
|
if (this.preloadedSound) {
|
||||||
|
this.preloadedSound.release();
|
||||||
|
this.preloadedSound = null;
|
||||||
|
if (this.preloadedPath) RNFS.unlink(this.preloadedPath).catch(() => {});
|
||||||
|
this.preloadedPath = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Status & Callbacks ---
|
// --- Status & Callbacks ---
|
||||||
|
|||||||
+29
-12
@@ -100,25 +100,42 @@ async function handleTTSRequest(payload) {
|
|||||||
// Markdown entfernen
|
// Markdown entfernen
|
||||||
const cleanText = text.replace(/\*\*([^*]+)\*\*/g, "$1").trim();
|
const cleanText = text.replace(/\*\*([^*]+)\*\*/g, "$1").trim();
|
||||||
|
|
||||||
// Text in Saetze aufteilen (sequentiell rendern fuer korrekte Reihenfolge)
|
// Text in Saetze aufteilen, dann zu Chunks von 2-3 Saetzen zusammenfassen
|
||||||
const sentences = cleanText.split(/(?<=[.!?])\s+/).map(s => s.trim()).filter(s => s.length > 0);
|
// (mehr Kontext = konsistentere Stimme/Lautstaerke, aber nicht zu lang fuer WebSocket)
|
||||||
if (sentences.length === 0) return;
|
const sentences = cleanText.split(/(?<=[.!?])\s+/)
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(s => s.length > 0)
|
||||||
|
.map(s => s.replace(/[.]+$/, '')); // Punkt am Ende entfernen
|
||||||
|
|
||||||
log(`TTS-Request: "${cleanText.slice(0, 60)}..." (${sentences.length} Saetze, voice: ${voice || "default"}, lang: ${language || "de"})`);
|
const MAX_CHUNK_CHARS = 250; // Max ~250 Zeichen pro Chunk
|
||||||
|
const chunks = [];
|
||||||
|
let currentChunk = '';
|
||||||
|
for (const sentence of sentences) {
|
||||||
|
if (currentChunk && (currentChunk.length + sentence.length + 2) > MAX_CHUNK_CHARS) {
|
||||||
|
chunks.push(currentChunk);
|
||||||
|
currentChunk = sentence;
|
||||||
|
} else {
|
||||||
|
currentChunk = currentChunk ? currentChunk + '. ' + sentence : sentence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentChunk) chunks.push(currentChunk);
|
||||||
|
if (chunks.length === 0) return;
|
||||||
|
|
||||||
|
log(`TTS-Request: "${cleanText.slice(0, 60)}..." (${sentences.length} Saetze → ${chunks.length} Chunks, voice: ${voice || "default"}, lang: ${language || "de"})`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const voiceSample = voice ? path.join(VOICES_DIR, `${voice}.wav`) : null;
|
const voiceSample = voice ? path.join(VOICES_DIR, `${voice}.wav`) : null;
|
||||||
const hasCustomVoice = voiceSample && fs.existsSync(voiceSample);
|
const hasCustomVoice = voiceSample && fs.existsSync(voiceSample);
|
||||||
|
|
||||||
// Jeden Satz sequentiell rendern und sofort senden
|
// Jeden Chunk sequentiell rendern und sofort senden
|
||||||
for (let i = 0; i < sentences.length; i++) {
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
const sentence = sentences[i];
|
const chunk = chunks[i];
|
||||||
try {
|
try {
|
||||||
const audioBuffer = await callXTTSAPI(sentence, language || "de", hasCustomVoice ? voiceSample : null);
|
const audioBuffer = await callXTTSAPI(chunk, language || "de", hasCustomVoice ? voiceSample : null);
|
||||||
|
|
||||||
if (audioBuffer && audioBuffer.length > 100) {
|
if (audioBuffer && audioBuffer.length > 100) {
|
||||||
const base64 = audioBuffer.toString("base64");
|
const base64 = audioBuffer.toString("base64");
|
||||||
log(`TTS [${i + 1}/${sentences.length}]: ${audioBuffer.length} bytes (${(audioBuffer.length / 1024).toFixed(0)}KB) — "${sentence.slice(0, 40)}..."`);
|
log(`TTS [${i + 1}/${chunks.length}]: ${audioBuffer.length} bytes (${(audioBuffer.length / 1024).toFixed(0)}KB) — "${chunk.slice(0, 50)}..."`);
|
||||||
|
|
||||||
sendToRVS({
|
sendToRVS({
|
||||||
type: "xtts_response",
|
type: "xtts_response",
|
||||||
@@ -132,12 +149,12 @@ async function handleTTSRequest(payload) {
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (sentenceErr) {
|
} catch (chunkErr) {
|
||||||
log(`TTS [${i + 1}/${sentences.length}] Fehler: ${sentenceErr.message} — ueberspringe`);
|
log(`TTS [${i + 1}/${chunks.length}] Fehler: ${chunkErr.message} — ueberspringe`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`TTS komplett: ${sentences.length} Saetze gerendert`);
|
log(`TTS komplett: ${chunks.length} Chunks gerendert`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(`TTS Fehler: ${err.message}`);
|
log(`TTS Fehler: ${err.message}`);
|
||||||
sendToRVS({
|
sendToRVS({
|
||||||
|
|||||||
Reference in New Issue
Block a user