Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9276a92c83 | |||
| d16896c4b4 | |||
| 20050d4077 | |||
| 79760d1b2e | |||
| 13f1103604 | |||
| 73b7a76ea8 |
@@ -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 806
|
versionCode 809
|
||||||
versionName "0.0.8.6"
|
versionName "0.0.8.9"
|
||||||
// Fallback fuer Libraries mit Product Flavors
|
// Fallback fuer Libraries mit Product Flavors
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,9 +78,14 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
|||||||
val encoding = AudioFormat.ENCODING_PCM_16BIT
|
val encoding = AudioFormat.ENCODING_PCM_16BIT
|
||||||
val minBuf = AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding)
|
val minBuf = AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding)
|
||||||
val bytesPerSecond = sampleRate * channels * 2 // 16-bit = 2 bytes
|
val bytesPerSecond = sampleRate * channels * 2 // 16-bit = 2 bytes
|
||||||
// Buffer muss mindestens PREROLL + etwas Spielraum fassen.
|
|
||||||
val prerollTarget = (bytesPerSecond * prerollSec).toInt()
|
val prerollTarget = (bytesPerSecond * prerollSec).toInt()
|
||||||
val bufferSize = (minBuf * 32).coerceAtLeast(prerollTarget * 2)
|
// Buffer entkoppelt von Preroll — fester ~3s-Buffer reicht. Wenn er
|
||||||
|
// an Preroll gekoppelt ist (z.B. 7s bei preroll=3.5s) und nur kurz
|
||||||
|
// gefuettert wird, stallt AudioTrack auf manchen Geraeten (OnePlus
|
||||||
|
// Android 12: pos bleibt 0 obwohl play() lief).
|
||||||
|
// 3s damit Padding bis 2s vor play() noch Headroom hat (write() ist
|
||||||
|
// blocking — wenn Buffer voll ist, deadlockt es vor play()).
|
||||||
|
val bufferSize = (bytesPerSecond * 3).coerceAtLeast(minBuf * 8)
|
||||||
prerollBytes = prerollTarget
|
prerollBytes = prerollTarget
|
||||||
bytesBuffered = 0
|
bytesBuffered = 0
|
||||||
playbackStarted = false
|
playbackStarted = false
|
||||||
@@ -156,16 +161,11 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
|||||||
val data = queue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
|
val data = queue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
if (endRequested) {
|
if (endRequested) {
|
||||||
// Falls wir vor Pre-Roll enden (kurzer Text): trotzdem abspielen
|
// Bei kurzem Text NICHT hier play() callen — erst nach
|
||||||
if (!playbackStarted) {
|
// Trailing-Silence + Padding (siehe Block nach mainLoop),
|
||||||
try {
|
// damit AudioTrack mit komplett gefuelltem Buffer startet.
|
||||||
t.play()
|
// OnePlus A12: AudioTrack startet nicht zuverlaessig wenn
|
||||||
playbackStarted = true
|
// play() bei dünnem Buffer gerufen wird.
|
||||||
Log.i(TAG, "Playback gestartet VOR Pre-Roll (kurzer Text, ${bytesBuffered}B gepuffert)")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "play() fallback failed: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break@mainLoop
|
break@mainLoop
|
||||||
}
|
}
|
||||||
// Underrun-Schutz: Stille reinfuettern wenn der AudioTrack-
|
// Underrun-Schutz: Stille reinfuettern wenn der AudioTrack-
|
||||||
@@ -228,6 +228,30 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
|||||||
}
|
}
|
||||||
bytesBuffered += silence.size
|
bytesBuffered += silence.size
|
||||||
}
|
}
|
||||||
|
// Bei kurzem Text (play() noch nicht gestartet): Buffer auf min.
|
||||||
|
// 2s padden + DANN play(). Auf OnePlus A12 startet AudioTrack
|
||||||
|
// bei einem zu duennen Buffer nicht — pos bleibt auf 0 stehen.
|
||||||
|
if (!playbackStarted && !writerShouldStop) {
|
||||||
|
val minStartBytes = bytesPerSecond * 2
|
||||||
|
if (bytesBuffered < minStartBytes) {
|
||||||
|
val padBytes = (minStartBytes - bytesBuffered.toInt()) and 0x7FFFFFFE
|
||||||
|
val pad = ByteArray(padBytes)
|
||||||
|
var padOff = 0
|
||||||
|
while (padOff < pad.size && !writerShouldStop) {
|
||||||
|
val w = t.write(pad, padOff, pad.size - padOff)
|
||||||
|
if (w <= 0) break
|
||||||
|
padOff += w
|
||||||
|
}
|
||||||
|
bytesBuffered += pad.size
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
t.play()
|
||||||
|
playbackStarted = true
|
||||||
|
Log.i(TAG, "Playback gestartet (kurzer Text, ${bytesBuffered}B komplett gepuffert)")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "play() short-text failed: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Writer-Thread Fehler: ${e.message}")
|
Log.w(TAG, "Writer-Thread Fehler: ${e.message}")
|
||||||
} finally {
|
} finally {
|
||||||
@@ -237,12 +261,21 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex
|
|||||||
val totalFrames = (bytesBuffered / streamBytesPerFrame).toInt()
|
val totalFrames = (bytesBuffered / streamBytesPerFrame).toInt()
|
||||||
var lastPos = -1
|
var lastPos = -1
|
||||||
var stalledCount = 0
|
var stalledCount = 0
|
||||||
|
var retried = false
|
||||||
while (!writerShouldStop) {
|
while (!writerShouldStop) {
|
||||||
val pos = t.playbackHeadPosition
|
val pos = t.playbackHeadPosition
|
||||||
if (pos >= totalFrames) break
|
if (pos >= totalFrames) break
|
||||||
// Safety: wenn Position 2s nicht mehr vorwaerts → AudioTrack hing
|
|
||||||
if (pos == lastPos) {
|
if (pos == lastPos) {
|
||||||
stalledCount++
|
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) {
|
if (stalledCount > 40) {
|
||||||
Log.w(TAG, "playback stalled at $pos/$totalFrames — give up")
|
Log.w(TAG, "playback stalled at $pos/$totalFrames — give up")
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aria-cockpit",
|
"name": "aria-cockpit",
|
||||||
"version": "0.0.8.6",
|
"version": "0.0.8.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -126,13 +126,24 @@ class PhoneCallService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** AudioFocus-Loss = irgendeine andere App hat das Mikro/die Audio-Pipeline
|
/** AudioFocus-Loss = irgendeine andere App hat den Focus uebernommen.
|
||||||
* uebernommen — typisch VoIP-Apps bei eingehendem Anruf, aber auch System-
|
* Das passiert bei VoIP-Anrufen (was wir wollen) ABER auch bei normalen
|
||||||
* Voice-Assistants etc. */
|
* Audio-Playern (anderer Player startet, Notification-Sound, sogar
|
||||||
private _onFocusChanged(type: 'loss' | 'loss_transient' | 'gain'): void {
|
* 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') {
|
if (type === 'loss' || type === 'loss_transient') {
|
||||||
// Schon durch klassischen TelephonyManager pausiert? Dann nichts doppeln.
|
// Schon durch klassischen TelephonyManager pausiert? Dann nichts doppeln.
|
||||||
if (this.lastState === 'ringing' || this.lastState === 'offhook') return;
|
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.interruptedByFocus = true;
|
||||||
this._haltForCall('Anruf erkannt (VoIP) — ARIA pausiert');
|
this._haltForCall('Anruf erkannt (VoIP) — ARIA pausiert');
|
||||||
// Pollen, weil GAIN nicht zuverlaessig kommt (wir releasen den Focus
|
// Pollen, weil GAIN nicht zuverlaessig kommt (wir releasen den Focus
|
||||||
|
|||||||
Reference in New Issue
Block a user