Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cad68db2a2 | |||
| 50b10c8ac0 |
@@ -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 10001
|
versionCode 10002
|
||||||
versionName "0.1.0.1"
|
versionName "0.1.0.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.0.1",
|
"version": "0.1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import {
|
|||||||
TTS_SPEED_MAX,
|
TTS_SPEED_MAX,
|
||||||
TTS_SPEED_STORAGE_KEY,
|
TTS_SPEED_STORAGE_KEY,
|
||||||
} from '../services/audio';
|
} from '../services/audio';
|
||||||
|
import audioService from '../services/audio';
|
||||||
import {
|
import {
|
||||||
isWakeReadySoundEnabled,
|
isWakeReadySoundEnabled,
|
||||||
setWakeReadySoundEnabled,
|
setWakeReadySoundEnabled,
|
||||||
@@ -135,6 +136,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
const [vadSilenceDb, setVadSilenceDb] = useState<number | null>(null);
|
const [vadSilenceDb, setVadSilenceDb] = useState<number | null>(null);
|
||||||
const [showVadInfo, setShowVadInfo] = useState(false);
|
const [showVadInfo, setShowVadInfo] = useState(false);
|
||||||
const [apkCacheInfo, setApkCacheInfo] = useState<{count: number, totalMB: number} | null>(null);
|
const [apkCacheInfo, setApkCacheInfo] = useState<{count: number, totalMB: number} | null>(null);
|
||||||
|
const [ttsCacheInfo, setTtsCacheInfo] = useState<{count: number, totalMB: number} | null>(null);
|
||||||
const [ttsSpeed, setTtsSpeed] = useState<number>(TTS_SPEED_DEFAULT);
|
const [ttsSpeed, setTtsSpeed] = useState<number>(TTS_SPEED_DEFAULT);
|
||||||
const [wakeKeyword, setWakeKeyword] = useState<string>(DEFAULT_KEYWORD);
|
const [wakeKeyword, setWakeKeyword] = useState<string>(DEFAULT_KEYWORD);
|
||||||
const [wakeStatus, setWakeStatus] = useState<string>('');
|
const [wakeStatus, setWakeStatus] = useState<string>('');
|
||||||
@@ -224,6 +226,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
});
|
});
|
||||||
isWakeReadySoundEnabled().then(setWakeReadySound);
|
isWakeReadySoundEnabled().then(setWakeReadySound);
|
||||||
updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {});
|
updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {});
|
||||||
|
audioService.getTtsCacheSize().then(setTtsCacheInfo).catch(() => {});
|
||||||
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
||||||
if (saved) setXttsVoice(saved);
|
if (saved) setXttsVoice(saved);
|
||||||
});
|
});
|
||||||
@@ -1251,6 +1254,38 @@ const SettingsScreen: React.FC = () => {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* === TTS-Cache === */}
|
||||||
|
<Text style={[styles.sectionTitle, {marginTop: 16}]}>TTS-Cache</Text>
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.toggleHint}>
|
||||||
|
Gespeicherte Sprachausgaben (WAV pro Antwort) — werden fuer den
|
||||||
|
Play-Button und Auto-Resume nach Anrufen genutzt. Loeschen
|
||||||
|
unterbricht keine laufende Wiedergabe, alte Antworten lassen sich
|
||||||
|
danach nur nicht mehr abspielen.
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.storageSizeText, {marginTop: 8}]}>
|
||||||
|
{ttsCacheInfo === null ? '...' :
|
||||||
|
ttsCacheInfo.count === 0 ? 'leer' :
|
||||||
|
`${ttsCacheInfo.count} WAV${ttsCacheInfo.count === 1 ? '' : 's'} · ${ttsCacheInfo.totalMB.toFixed(1)}MB`}
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.clearButton, {marginTop: 8, backgroundColor: 'rgba(255,59,48,0.15)'}]}
|
||||||
|
onPress={async () => {
|
||||||
|
const res = await audioService.clearTtsCache();
|
||||||
|
ToastAndroid.show(
|
||||||
|
res.removed === 0
|
||||||
|
? 'TTS-Cache war schon leer'
|
||||||
|
: `${res.removed} WAV${res.removed === 1 ? '' : 's'} geloescht (${res.freedMB.toFixed(1)}MB frei)`,
|
||||||
|
ToastAndroid.SHORT,
|
||||||
|
);
|
||||||
|
const info = await audioService.getTtsCacheSize();
|
||||||
|
setTtsCacheInfo(info);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>TTS-Cache leeren</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
{/* === Logs === */}
|
{/* === Logs === */}
|
||||||
|
|||||||
@@ -301,6 +301,12 @@ class AudioService {
|
|||||||
console.warn('[Audio] PcmPlaybackFinished-Subscription fehlgeschlagen:', err);
|
console.warn('[Audio] PcmPlaybackFinished-Subscription fehlgeschlagen:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// App-Start: orphaned aria_tts_*.wav / aria_recording_*.mp4 aus dem Cache
|
||||||
|
// wegraeumen. Sammeln sich an wenn Sound mid-playback gestoppt wird (Anruf,
|
||||||
|
// Mute, Barge-In) — der completion-callback feuert dann nicht und die Datei
|
||||||
|
// bleibt liegen. 5min-Threshold damit gerade aktiv geschriebene Files sicher
|
||||||
|
// sind. cleanupOnStartup ist async, blockt den Constructor nicht.
|
||||||
|
this._cleanupStaleCacheFiles(5 * 60 * 1000).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** AudioFocus mit kleiner Verzoegerung freigeben — Spotify/YouTube
|
/** AudioFocus mit kleiner Verzoegerung freigeben — Spotify/YouTube
|
||||||
@@ -1249,19 +1255,29 @@ class AudioService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Alte Aufnahme- und TTS-Files aus dem Cache loeschen (>30s alt). */
|
/** Alte Aufnahme- und TTS-Files aus dem Cache loeschen.
|
||||||
private async _cleanupStaleCacheFiles(): Promise<void> {
|
* Default 30s — verwendet beim Mikro-Start (kurze Lebensdauer reicht).
|
||||||
|
* App-Start nutzt 5min damit gerade aktive Files nicht erwischt werden. */
|
||||||
|
private async _cleanupStaleCacheFiles(maxAgeMs: number = 30000): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const files = await RNFS.readDir(RNFS.CachesDirectoryPath);
|
const files = await RNFS.readDir(RNFS.CachesDirectoryPath);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
let removed = 0;
|
||||||
|
let freedBytes = 0;
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
if (!f.isFile()) continue;
|
if (!f.isFile()) continue;
|
||||||
if (!f.name.startsWith('aria_recording_') && !f.name.startsWith('aria_tts_')) continue;
|
if (!f.name.startsWith('aria_recording_') && !f.name.startsWith('aria_tts_')) continue;
|
||||||
const age = now - (f.mtime ? f.mtime.getTime() : 0);
|
const age = now - (f.mtime ? f.mtime.getTime() : 0);
|
||||||
if (age > 30000) {
|
if (age > maxAgeMs) {
|
||||||
|
freedBytes += parseInt(f.size as any, 10) || 0;
|
||||||
await RNFS.unlink(f.path).catch(() => {});
|
await RNFS.unlink(f.path).catch(() => {});
|
||||||
|
removed += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (removed > 0) {
|
||||||
|
console.log('[Audio] Cache-Cleanup: %d Files entfernt, %.1fMB freigegeben',
|
||||||
|
removed, freedBytes / 1024 / 1024);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// silent — cleanup ist best-effort
|
// silent — cleanup ist best-effort
|
||||||
}
|
}
|
||||||
@@ -1288,6 +1304,43 @@ class AudioService {
|
|||||||
// silent
|
// silent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Aktuelle Groesse des TTS-Caches. */
|
||||||
|
async getTtsCacheSize(): Promise<{ count: number; totalMB: number }> {
|
||||||
|
let count = 0;
|
||||||
|
let total = 0;
|
||||||
|
try {
|
||||||
|
const dir = `${RNFS.DocumentDirectoryPath}/tts_cache`;
|
||||||
|
if (await RNFS.exists(dir)) {
|
||||||
|
const files = await RNFS.readDir(dir);
|
||||||
|
for (const f of files) {
|
||||||
|
if (!f.isFile() || !f.name.endsWith('.wav')) continue;
|
||||||
|
count += 1;
|
||||||
|
total += parseInt(f.size as any, 10) || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return { count, totalMB: total / 1024 / 1024 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TTS-Cache komplett leeren (Settings-Button). */
|
||||||
|
async clearTtsCache(): Promise<{ removed: number; freedMB: number }> {
|
||||||
|
let removed = 0;
|
||||||
|
let freed = 0;
|
||||||
|
try {
|
||||||
|
const dir = `${RNFS.DocumentDirectoryPath}/tts_cache`;
|
||||||
|
if (!(await RNFS.exists(dir))) return { removed: 0, freedMB: 0 };
|
||||||
|
const files = await RNFS.readDir(dir);
|
||||||
|
for (const f of files) {
|
||||||
|
if (!f.isFile() || !f.name.endsWith('.wav')) continue;
|
||||||
|
const size = parseInt(f.size as any, 10) || 0;
|
||||||
|
await RNFS.unlink(f.path).catch(() => {});
|
||||||
|
removed += 1;
|
||||||
|
freed += size;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return { removed, freedMB: freed / 1024 / 1024 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton
|
// Singleton
|
||||||
|
|||||||
Reference in New Issue
Block a user