Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d08c06720 | |||
| f066a2a555 | |||
| b55b0e7c42 | |||
| 70f806ef80 | |||
| 0773d9496d | |||
| 1a4857ed62 | |||
| 962d814318 | |||
| 9276a92c83 | |||
| d16896c4b4 | |||
| 20050d4077 | |||
| 79760d1b2e | |||
| 13f1103604 | |||
| 73b7a76ea8 |
@@ -79,8 +79,8 @@ android {
|
||||
applicationId "com.ariacockpit"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 806
|
||||
versionName "0.0.8.6"
|
||||
versionCode 902
|
||||
versionName "0.0.9.2"
|
||||
// Fallback fuer Libraries mit Product Flavors
|
||||
missingDimensionStrategy 'react-native-camera', 'general'
|
||||
}
|
||||
|
||||
@@ -78,9 +78,12 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||
val encoding = AudioFormat.ENCODING_PCM_16BIT
|
||||
val minBuf = AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding)
|
||||
val bytesPerSecond = sampleRate * channels * 2 // 16-bit = 2 bytes
|
||||
// Buffer muss mindestens PREROLL + etwas Spielraum fassen.
|
||||
val prerollTarget = (bytesPerSecond * prerollSec).toInt()
|
||||
val bufferSize = (minBuf * 32).coerceAtLeast(prerollTarget * 2)
|
||||
// Buffer entkoppelt von Preroll — fester ~4s-Buffer. OnePlus A12
|
||||
// mit USAGE_ASSISTANT laeuft AudioTrack erst ab ~3s gepufferter
|
||||
// Daten an. Wir padden Kurztexte vor play() auf 3s (siehe Block
|
||||
// nach mainLoop), Buffer braucht ~1s Headroom weil write() blockt.
|
||||
val bufferSize = (bytesPerSecond * 4).coerceAtLeast(minBuf * 8)
|
||||
prerollBytes = prerollTarget
|
||||
bytesBuffered = 0
|
||||
playbackStarted = false
|
||||
@@ -156,15 +159,11 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||
val data = queue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
|
||||
if (data == null) {
|
||||
if (endRequested) {
|
||||
// Falls wir vor Pre-Roll enden (kurzer Text): trotzdem abspielen
|
||||
// Falls play() noch gar nicht lief (Stream ohne data
|
||||
// ueberhaupt — sehr seltene Edge-Case): jetzt anstossen
|
||||
// damit das finally{}-Wait nicht endlos blockt.
|
||||
if (!playbackStarted) {
|
||||
try {
|
||||
t.play()
|
||||
playbackStarted = true
|
||||
Log.i(TAG, "Playback gestartet VOR Pre-Roll (kurzer Text, ${bytesBuffered}B gepuffert)")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "play() fallback failed: ${e.message}")
|
||||
}
|
||||
try { t.play(); playbackStarted = true } catch (_: Exception) {}
|
||||
}
|
||||
break@mainLoop
|
||||
}
|
||||
@@ -196,12 +195,16 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||
}
|
||||
idleMs = 0L
|
||||
|
||||
// Pre-Roll Check: play() erst wenn genug gepuffert
|
||||
if (!playbackStarted && bytesBuffered + data.size >= prerollBytes) {
|
||||
// play() beim ALLERERSTEN data-chunk aufrufen — egal wie wenig
|
||||
// Daten da sind. Sonst stallt AudioTrack auf OnePlus A12 wenn
|
||||
// play() erst gerufen wird nachdem der Buffer komplett gefuellt
|
||||
// ist. Pre-Roll als "Vorrat aufbauen" passiert dann waehrend
|
||||
// der Track schon spielt — Underrun-Schutz fuettert ggf. Stille.
|
||||
if (!playbackStarted) {
|
||||
try {
|
||||
t.play()
|
||||
playbackStarted = true
|
||||
Log.i(TAG, "Playback gestartet nach Pre-Roll ${bytesBuffered + data.size} Bytes")
|
||||
Log.i(TAG, "Playback gestartet beim 1. Chunk (${bytesBuffered}B leading + ${data.size}B data)")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "play() failed: ${e.message}")
|
||||
}
|
||||
@@ -237,12 +240,21 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
||||
val totalFrames = (bytesBuffered / streamBytesPerFrame).toInt()
|
||||
var lastPos = -1
|
||||
var stalledCount = 0
|
||||
var retried = false
|
||||
while (!writerShouldStop) {
|
||||
val pos = t.playbackHeadPosition
|
||||
if (pos >= totalFrames) break
|
||||
// Safety: wenn Position 2s nicht mehr vorwaerts → AudioTrack hing
|
||||
if (pos == lastPos) {
|
||||
stalledCount++
|
||||
// Nach 500ms Stillstand: AudioTrack-Quirk auf manchen
|
||||
// Geraeten (OnePlus A12) — play() nochmal anstossen.
|
||||
if (stalledCount == 10 && pos == 0 && !retried) {
|
||||
retried = true
|
||||
Log.w(TAG, "playback nicht angefahren — retry play()")
|
||||
try { t.play() } catch (e: Exception) {
|
||||
Log.w(TAG, "retry play() failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
if (stalledCount > 40) {
|
||||
Log.w(TAG, "playback stalled at $pos/$totalFrames — give up")
|
||||
break
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "aria-cockpit",
|
||||
"version": "0.0.8.6",
|
||||
"version": "0.0.9.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1098,14 +1098,15 @@ class AudioService {
|
||||
if (this.preloadedPath) RNFS.unlink(this.preloadedPath).catch(() => {});
|
||||
this.preloadedPath = '';
|
||||
}
|
||||
// PCM-Stream ebenfalls hart stoppen (Cancel/Abbruch)
|
||||
if (this.pcmStreamActive) {
|
||||
PcmStreamPlayer?.stop().catch(() => {});
|
||||
this.pcmStreamActive = false;
|
||||
this.pcmBuffer = [];
|
||||
this.pcmBytesCollected = 0;
|
||||
this.pcmMessageId = '';
|
||||
}
|
||||
// PCM-Stream ebenfalls hart stoppen (Cancel/Abbruch).
|
||||
// pcmStreamActive wird beim isFinal-Chunk schon false gesetzt — der
|
||||
// AudioTrack spielt aber noch sekundenlang aus seinem Buffer ab. Daher
|
||||
// IMMER stop() aufrufen, ohne den Flag zu pruefen (ist idempotent).
|
||||
PcmStreamPlayer?.stop().catch(() => {});
|
||||
this.pcmStreamActive = false;
|
||||
this.pcmBuffer = [];
|
||||
this.pcmBytesCollected = 0;
|
||||
this.pcmMessageId = '';
|
||||
// Audio-Focus sofort freigeben — User hat explizit abgebrochen
|
||||
this._cancelDeferredFocusRelease();
|
||||
AudioFocus?.release().catch(() => {});
|
||||
|
||||
@@ -126,13 +126,24 @@ class PhoneCallService {
|
||||
}
|
||||
}
|
||||
|
||||
/** AudioFocus-Loss = irgendeine andere App hat das Mikro/die Audio-Pipeline
|
||||
* uebernommen — typisch VoIP-Apps bei eingehendem Anruf, aber auch System-
|
||||
* Voice-Assistants etc. */
|
||||
private _onFocusChanged(type: 'loss' | 'loss_transient' | 'gain'): void {
|
||||
/** AudioFocus-Loss = irgendeine andere App hat den Focus uebernommen.
|
||||
* Das passiert bei VoIP-Anrufen (was wir wollen) ABER auch bei normalen
|
||||
* Audio-Playern (anderer Player startet, Notification-Sound, sogar
|
||||
* unsere eigenen Sound-Calls beim Play-Button). Daher checken wir den
|
||||
* AudioMode — nur IN_CALL (2) oder IN_COMMUNICATION (3) zaehlt als Anruf. */
|
||||
private async _onFocusChanged(type: 'loss' | 'loss_transient' | 'gain'): Promise<void> {
|
||||
if (type === 'loss' || type === 'loss_transient') {
|
||||
// Schon durch klassischen TelephonyManager pausiert? Dann nichts doppeln.
|
||||
if (this.lastState === 'ringing' || this.lastState === 'offhook') return;
|
||||
// Mode pruefen — nur echte Anrufe behandeln.
|
||||
let mode = -1;
|
||||
try { mode = await (NativeModules.AudioFocus as any)?.getMode?.(); } catch {}
|
||||
if (mode !== 2 && mode !== 3) {
|
||||
// NORMAL-Mode → kein Anruf (Stefan hat z.B. Play-Button gedrueckt
|
||||
// oder Spotify hat sich neu reingedraengelt). Keine Toasts.
|
||||
console.log('[PhoneCall] FOCUS_LOSS ignoriert (AudioMode=%d, kein Call)', mode);
|
||||
return;
|
||||
}
|
||||
this.interruptedByFocus = true;
|
||||
this._haltForCall('Anruf erkannt (VoIP) — ARIA pausiert');
|
||||
// Pollen, weil GAIN nicht zuverlaessig kommt (wir releasen den Focus
|
||||
|
||||
Reference in New Issue
Block a user