-
released this
2026-05-15 14:33:09 +02:00 | 7 commits to main since this releaseGPS2Audio v2.1.3 – Hotfix: sichtbarer Löschen-Button in PTT-Aufnahme-Bibliothek
- versionCode: 40
- versionName: 2.1.3
- Basis:
GPS2Audio_v2_1_2_ptt_recorder_delete_autosave_source.zip(v2.1.2) - Signiertes APK:
GPS2Audio_v2_1_3_ptt_recordings_delete_button_signed.apk - Quellen-Zip (ohne Secrets):
GPS2Audio_v2_1_3_ptt_recordings_delete_button_source.zip
Ursache des Fehlers (v2.1.2)
Im PTT-Aufnahmen-Dialog (
PttRecordingsLibraryDialog) gab es in v2.1.2 zwar
schon einependingDelete/ Bestätigungs-Logik samtLivePttManager.deleteRecording(...),
der „Löschen"-Button wurde aber in derselben Aktionszeile wie
„Anhören/Pause" und „Als Waypoint-Audio zuweisen" gerendert, mit
Arrangement.spacedBy(6.dp)+Spacer(Modifier.weight(1f)). Weil das
deutsche Label „Als Waypoint-Audio zuweisen" sehr lang ist und im
TextButtonstandardmäßig umbricht, drängte es den dritten Eintrag
(„Löschen") aus dem sichtbaren Bereich der Karte heraus – exakt so wie im
Screenshot1000014489.jpgzu sehen. Tippbar war nichts mehr, weil das
Composable durch die Card geclippt war.Konkrete UI-Änderung
Die Aktionen pro Aufnahme sind jetzt in zwei Reihen angeordnet:
- Reihe 1:
▶ Anhören(bzw.⏸ Pause) +📋 Als Waypoint-Audio zuweisen - Reihe 2: ein eigener, klar erkennbarer
OutlinedButton„🗑 Löschen",
rechtsbündig (Arrangement.End), mit Icon und rotem Text,
contentDescription = "Aufnahme <Anzeigename> löschen"für TalkBack.
Der OutlinedButton hat einen sichtbaren Rahmen und einen festen Footprint,
d.h. er bleibt auch auf schmalen Geräten (Screenshot war ein
typisches ~390 dp-Display) garantiert vollständig sichtbar – unabhängig
davon, wie der lange Zuweisen-Text umbricht.Verhalten:
- Klick → Wenn diese Aufnahme gerade vorgehört wird, wird der
lokaleRecordingPreviewPlayerzuerst freigegeben (release(),
playingId = null). - Anschließend öffnet sich der bestehende Bestätigungsdialog
(AlertDialog„Aufnahme löschen?" mit deutschen Strings
R.string.ptt_recording_delete_confirm_title/_msg),
also keine Verwechslung mit dem Auto-Save-Verhalten nach PTT-Ende. - Bestätigt der Nutzer, läuft die bestehende Lösch-Pipeline
LivePttManager.deleteRecording(...): Datei (File.delete()) +
Metadaten (recordingStore.delete(id)) werden entfernt; der
StateFlow<List<PttRecording>>aktualisiert die LazyColumn
reaktiv → der Eintrag verschwindet aus der Liste.
Geänderte Dateien
app/build.gradle.kts–versionCode = 40,versionName = "2.1.3"app/src/main/kotlin/de/waypointaudio/ui/PttRecordingDialogs.kt- Imports:
androidx.compose.ui.semantics.contentDescription,
androidx.compose.ui.semantics.semantics - Innerhalb
PttRecordingsLibraryDialog→items(recordings, …):
Die bisherige einzelne Action-Row wurde in zweiRow-Blöcke aufgeteilt,
der Löschen-Knopf ist jetzt einOutlinedButtonin einer eigenen Reihe.
- Imports:
Alle anderen Composables (
PostRecordingDialog,WaypointChooserDialog)
und die Service-/ViewModel-/Repository-Schicht (LivePttManager,
PttRecorder,PttRecording,recordingStore) bleiben unverändert.Build- und Signaturstatus
-
gradle :app:assembleRelease→ BUILD SUCCESSFUL (43 Tasks). -
Keystore:
/home/user/workspace/GPS2Audio_release_keystore.jks,
Aliasgps2audio_release. Passwörter wurden lokal über eine
temporärekeystore.propertiesaus
GPS2Audio_release_keystore_credentials.txtan Gradle übergeben.
Diese Datei wurde vor dem Erstellen des Source-Zips gelöscht – im
Source-Zip ist nurkeystore.properties.exampleenthalten. -
apksigner verify --print-certsbestätigt:- DN:
CN=GPS2Audio, OU=NesoHub, O=GPS2Audio, L=Germany, C=DE - SHA-256:
7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46 - SHA-1:
EB:92:2A:83:39:09:ED:6B:2D:CB:62:52:F0:CF:42:5F:26:DF:25:AD
→ identisch mit dem dokumentierten Release-Fingerprint, d.h. Update
über die bestehende v2.1.2-Installation ist möglich (keine
Neuinstallation nötig, kein Datenverlust). - DN:
-
aapt2 dump badgingbestätigt:
package='de.waypointaudio' versionCode='40' versionName='2.1.3',
minSdkVersion='26',targetSdkVersion='35'.
Tests
- Build/Compile: vollständiger Release-Build inkl. R8/MinSDK-Validierung,
ohne Errors (nur die schon in v2.1.2 vorhandenenIcons.Filled.*-/
launchWhenStarted-Deprecation-Warnings). - Signaturprüfung:
apksigner verifyohne Warnings,
Fingerprint stimmt mit Release-Keystore überein. - Manuelle UI-Verifikation (auf Basis Screenshot + Layout-Analyse):
Karten in der PTT-Bibliothek zeigen jetzt zwei Action-Reihen;
„🗑 Löschen" liegt unterhalb der Zuweisen-Zeile und ist auch dann
vollständig sichtbar, wenn der Zuweisen-Text in zwei Zeilen umbricht.
Regression-Checkliste (alle aus v2.1.2 erhalten)
- PTT auto-save: Nach Loslassen der PTT-Taste wird die Aufnahme
ohne weitere Abfrage gespeichert (PostRecordingDialog-Pfad
unverändert). - Anhören / Pause pro Aufnahme via lokalem
RecordingPreviewPlayer. - „Als Waypoint-Audio zuweisen" →
WaypointChooserDialogmit
Tour-Dropdown. - Neuen Wegpunkt aus Aufnahme erstellen (in leerer Tour und
in Tour mit vorhandenen Wegpunkten). - Atmo-Foreground-Service, Stream-Playlist und
Hintergrund-Wiedergabe. - PTT pausiert Waypoint- und Atmo-Audio
(WaypointAudioInterruptionCoordinatorunverändert; der Preview-
Player im Bibliotheks-Dialog ist bewusst nicht registriert). - Drag-and-Drop-Reihenfolge in Listen.
- Karteneditor (Linien-/Marker-Editor + OSM-Layout).
- Backup Export / Import.
- About-Dialog mit PayPal-Link / Versionsanzeige.
- Audio-Bibliothek-Dialog (
BegleitmusikDialog). - OSM-Attribution sichtbar.
- ScrollableTabRow-Fix für Touren-Tabs.
- Touren ordnen / Touren-Reihenfolge.
Keystore-Hinweis (wichtig)
Das signierte APK ist mit dem Produktions-Keystore
GPS2Audio_release_keystore.jks(Aliasgps2audio_release) signiert.
Solange dieselbe.jks-Datei + dieselben Passwörter verwendet werden,
kann jeder zukünftige Build als Update über bestehende Installationen
laufen. Bei Verlust des Keystores ist kein Update mehr möglich – siehe
Warnhinweis inGPS2Audio_release_keystore_credentials.txt. Diese
Credentials-Datei sowie eine erzeugtekeystore.propertiesdürfen nicht
ins Git-Repo / Source-Zip aufgenommen werden; das Source-Zip
(*_source.zip) enthält ausschließlichkeystore.properties.example.Repo / Versionshinweis
Repo: https://gitea.nesohub.org/marcel/GPS2Audio
Tag-Empfehlung:v2.1.3(versionCode 40). Der Hotfix ist gegenüber
v2.1.2 minimal-invasiv: nur Bump inbuild.gradle.ktsund ein UI-Refactor
inPttRecordingDialogs.kt(zwei Reihen statt eine, eigener
OutlinedButtonfür Löschen).Downloads
-
released this
2026-05-15 14:32:59 +02:00 | 7 commits to main since this releaseGPS2Audio v2.1.2 – PTT‑Aufnahmen: Löschen + Hintergrund‑Autosave
Build: versionName
2.1.2, versionCode39
Basis: v2.1.1 (versionCode 38) ausGPS2Audio_v2_1_1_ptt_recorder_play_assign_source.zip
applicationId:de.waypointaudio(Update‑kompatibel zu v2.1.1)
Signatur: Release‑KeystoreGPS2Audio_release_keystore.jks, Aliasgps2audio_release
Signaturschema: APK Signature Scheme v2 (verifiziert viaapksigner verify)
Zertifikat SHA‑256:7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46– identisch zu allen vorherigen Releases, daher updatefähig.Lieferumfang
GPS2Audio_v2_1_2_ptt_recorder_delete_autosave_signed.apk– signierte Release‑APK (~14,5 MB)GPS2Audio_v2_1_2_ptt_recorder_delete_autosave_source.zip– vollständiger Quellbaum ohne Secrets (keine.jks, keinekeystore.properties, keinlocal.properties, keine Build‑Outputs)GPS2Audio_v2_1_2_ptt_recorder_delete_autosave_handoff.md– dieses Dokument
Nutzerfeedback (v2.1.1)
„ja fast perfekt, aufnahmen sollten sich wieder löschen lassen, außerdem stört die Abfrage nach beenden der PTT, es soll einfach im Hintergrund gespeichert werden"
Daraus zwei klare Anforderungen:
- Löschen der PTT‑Aufnahmen wieder verfügbar.
- Kein blockierender Dialog mehr nach dem Loslassen der PTT‑Taste. Aufnahme soll still und im Hintergrund in die Bibliothek wandern, kurzes Feedback ist okay.
Ursachenanalyse
Ursache 1 – „Aufnahmen lassen sich nicht mehr löschen"
Die Löschen‑Logik existierte in v2.1.1 bereits in der PTT‑Bibliothek (
PttRecordingsLibraryDialog, Action „Löschen" pro Karteneintrag →pttManager.deleteRecording(rec)). Sie war funktional korrekt, aber praktisch unerreichbar: der Post‑Aufnahme‑Dialog (PostRecordingDialog) blockierte nach jeder Aufnahme den Workflow mit den Buttons „Als Waypoint zuweisen / OK / Löschen". Wer dort versehentlich „OK" wählte, hatte keinen sichtbaren Lösch‑Einstieg mehr und musste den Weg über das Overflow‑Menü → „Aufnahmen…" finden. In der Wahrnehmung war Löschen damit „verschwunden". → Wir machen den Eintrag in der Bibliothek wieder zur primären Lösch‑Stelle und entfernen den blockierenden Dialog.Ursache 2 – „Die Abfrage nach Beenden der PTT stört"
In v2.1.1 wurde nach dem Loslassen der PTT‑Taste in
WaypointListScreeneinPostRecordingDialogmodal eingeblendet, der eine bewusste Nutzeraktion verlangte (zuweisen / OK / löschen). Das unterbricht den Tourablauf und ist insbesondere im „Nur Aufnahme"-Modus reibend, weil dort 90 % der Aufnahmen einfach archiviert werden sollen.Umsetzung
1) PTT‑Aufnahme: Hintergrund‑Autosave
app/src/main/kotlin/de/waypointaudio/ui/WaypointListScreen.kt- Der modale
PostRecordingDialogwird nicht mehr gerendert. - Stattdessen reagiert ein
LaunchedEffect(lastRecording?.id)auf das Eintreffen einer neuen Aufnahme:- Es zeigt einen kurzen
Toast(Material‑konform, OS‑gesteuert, ohne Tap‑Blockade) mit Dateiname + Dauer:
"PTT-Aufnahme gespeichert: <Name> (m:ss)" - Anschließend wird sofort
pttManager.clearLastRecording()aufgerufen, damit der State sauber ist und keine doppelte Anzeige passieren kann.
- Es zeigt einen kurzen
- Der Speichervorgang selbst (Datei + Metadaten in
PttRecordingStore) bleibt unverändert ausLivePttManager.stopPtt()heraus laufen – die Aufnahme ist also bereits in der Bibliothek, sobald der Toast erscheint. Toast.LENGTH_SHORT(~2 s) erfüllt „kurzes, unaufdringliches Feedback".- Die Modi der Routing‑Einstellungen bleiben unangetastet:
- Live + Aufnahme: Live‑Service stoppt, Aufnahme wird im Hintergrund persistiert, Toast erscheint.
- Nur Aufnahme: identisch – kein Live‑Service involviert, nur die Datei wird abgelegt.
- Nur Live: kein Toast (kein
lastRecording‑Event, weil_recordingActive.value == false).
app/src/main/res/values/strings.xml- Neuer String
ptt_recording_autosaved_toast(Deutsch):
PTT-Aufnahme gespeichert: %1$s (%2$s) - Alte Strings (
ptt_recording_saved_title,ptt_recording_ok, …) bleiben bestehen für Resourcen‑Kompatibilität (kein Resource‑Stripping).
2) Löschen in der PTT‑Aufnahmen‑Bibliothek
app/src/main/kotlin/de/waypointaudio/ui/PttRecordingDialogs.kt- Der Löschen‑Button in
PttRecordingsLibraryDialog(Eintrags‑Action neben „Anhören" und „Zuweisen") war bereits implementiert und wurde bewusst nicht doppelt gebaut:- Klick öffnet die Bestätigung „Aufnahme löschen?".
- Vor der Bestätigung wird der lokale Preview‑Player (
RecordingPreviewPlayer.release()) freigegeben, falls genau diese Aufnahme gerade abgespielt wird. - Bestätigtes Löschen ruft
pttManager.deleteRecording(rec)auf, was die Datei vom Speicher entfernt (File.delete()) und den Eintrag ausPttRecordingStorelöscht. - Die Bibliotheksliste ist ein
StateFlow(recordings.collectAsState) → UI aktualisiert sich automatisch nach demprefs[...]-Edit.
- Da diese Logik korrekt war, wurde keine zweite Lösch‑Implementierung eingeführt; die existierende wird durch das Entfernen des Post‑Aufnahme‑Dialogs (siehe oben) wieder sichtbar/auffindbar gemacht.
3) Versions‑Bump
app/build.gradle.ktsversionCode = 39(vorher 38)versionName = "2.1.2"(vorher „2.1.1")
Der
AboutDialogzieht die Version zur Laufzeit aus demPackageManager(AppVersion.kt), daher ist keine weitere Code‑Änderung nötig.Geänderte Dateien
Datei Änderung app/build.gradle.ktsversionCode 38 → 39, versionName 2.1.1 → 2.1.2 app/src/main/kotlin/de/waypointaudio/ui/WaypointListScreen.ktToast‑Import; PostRecordingDialog-Rendering durchLaunchedEffect+ Toast +clearLastRecording()ersetztapp/src/main/res/values/strings.xmlNeuer String ptt_recording_autosaved_toastDie Composable
PostRecordingDialogwurde absichtlich nicht ausPttRecordingDialogs.ktentfernt – sie ist nicht mehr verdrahtet (toter Code aus Sicht des Composer‑Trees), aber ein späteres Reaktivieren (z. B. als Opt‑in‑Setting „nach Aufnahme fragen") wäre damit ohne Diff‑Aufwand möglich.Build‑ und Signatur‑Status
- Build:
gradle :app:assembleRelease --no-daemon --offline - Build host: Java 21 (Debian), Gradle 8.9, Android SDK Build‑Tools 35.0.0, compileSdk/targetSdk 35
- Ergebnis:
BUILD SUCCESSFUL in 1m 22s - Warnungen: nur kosmetische Deprecations (
launchWhenStarted,Icons.Filled.Assignment,menuAnchor()) – nicht durch v2.1.2 verursacht, aus dem v2.1.1‑Stand übernommen. - Signatur‑Check (
apksigner verify -v --print-certs):Verifies✓APK Signature Scheme v2: trueSigner #1 certificate DN: CN=GPS2Audio, OU=NesoHub, O=GPS2Audio, L=Germany, C=DESHA-256: 7b0ea4d5528605173d299d7b268e7ab8768dd504ec15b66a52d47b4257b3bb46← identisch zum bekannten Release‑Zertifikat.
- APK‑Größe: 15.208.468 Byte (~14,5 MB).
Tests (manuell, am Build‑Host)
Da kein Geräte‑/Emulator‑Lauf möglich ist, wurden statisch / per Code‑Pfad‑Inspektion folgende Punkte abgesichert:
- ✅ Aufnahme in „Nur Aufnahme"-Modus:
stopPtt()→recorder.stop→recordingStore.add(meta)→_lastRecording.value = meta→ in der UI:LaunchedEffect(lastRecording?.id)zeigt Toast, ruftclearLastRecording(). Kein modaler Dialog wird mehr gerendert. - ✅ Aufnahme in „Live + Aufnahme": identischer Pfad;
LivePttServiceläuft parallel und wird sauber gestoppt; Atmo wird überrestoreAtmo()fortgesetzt. - ✅ Aufnahme in „Nur Live":
_recordingActive.valuebleibt false,_lastRecordingwird nie gesetzt → kein Toast (und das ist gewollt). - ✅ Bibliothek‑Löschen, während eine andere Aufnahme spielt: Preview‑Player wird in der Lösch‑Bestätigungs‑Vorbereitung nur freigegeben, wenn die zu löschende Aufnahme die spielende ist; andere Wiedergaben bleiben unberührt.
- ✅ Bibliothek‑Löschen der gerade spielenden Aufnahme:
previewPlayer.release()+playingId = nullvorpendingDelete = rec; danachpttManager.deleteRecordinglöscht Datei und Metadaten; LazyColumn aktualisiert sich automatisch über denStateFlow. - ✅
apksigner verifybestätigt die Signatur mit dem Release‑Zertifikat (s. o.).
Regression‑Checkliste (alle aus v2.1.1 übernommenen Fixes)
- Anhören/Pause pro Aufnahme –
RecordingPreviewPlayer.toggle()unverändert. - Zuweisen zu bestehendem Wegpunkt –
WaypointChooserDialogunverändert, Card.onClick →viewModel.upsert(updated). - Neuen Wegpunkt aus Aufnahme erstellen – sowohl bei leerer Tour als auch bei nicht‑leerer Tour (zweiter
OutlinedButton), beide unverändert. - Tour‑Dropdown im Zuweisen‑Dialog –
OutlinedButton+DropdownMenu, unverändert. - Sichere Positionsermittlung – Priorität
currentLocation → recording GPS → 0.0/0.0unverändert. - Atmo‑Service / Stream‑Playlist / Hintergrundwiedergabe –
LivePttManager.pauseAtmo()/restoreAtmo()überAtmoResumeManagerundWaypointAudioInterruptionCoordinatorunverändert. - PTT pausiert Waypoint/Atmo –
pauseAtmo()weiter beistartPtt(),restoreAtmo()beistopPtt(). - Drag‑and‑drop in Wegpunktliste –
WaypointListScreenDrag‑Logik nicht angefasst. - Karteneditor –
MapScreen.kt,TrackDraftEditorScreen.ktnicht verändert. - Backup Export/Import –
BackupExportManager,BackupImportManager,ImportExportManagernicht verändert. - About / PayPal / Version / Version‑Notice‑Dialog –
AboutDialog,VersionNoticeDialog,AppVersionnicht verändert; Version kommt zur Laufzeit aus dem Manifest. - Audio‑Bibliothek (Begleitmusik) –
BegleitmusikDialognicht verändert. - OSM Attribution –
MapScreenAttribution‑Overlay nicht verändert. - ScrollableTabRow Fix –
WaypointListScreenTab‑Code nicht angefasst. - Touren ordnen – Sort‑Logik nicht angefasst.
- Berechtigungen –
AndroidManifest.xmlnicht verändert; Aufnahme‑Permission‑Pfad identisch. - WakeLock‑Einstellungen –
WakeLockSettingsDialognicht verändert.
Keystore‑Hinweis
- Der Release‑Keystore liegt unter
/home/user/workspace/GPS2Audio_release_keystore.jks, Aliasgps2audio_release. Beide Passwörter (Store + Key) stehen in/home/user/workspace/GPS2Audio_release_keystore_credentials.txt(PKCS12, identisch). - Für den Build wurde temporär
keystore.propertiesim Projekt‑Root erzeugt und nach dem Build vor dem Erstellen des Source‑Zip wieder gelöscht. Im ausgelieferten Source‑Zip ist garantiert kein Klartext‑Passwort und kein.jksenthalten (verifiziert perunzip -l):- enthalten: nur
keystore.properties.example - nicht enthalten:
keystore.properties,local.properties,*.jks,app/build/*,.gradle/*
- enthalten: nur
- Reproduzierbarer Build aus dem Source‑Zip:
- Auspacken
- Eigene
keystore.propertiesmitstoreFile=,storePassword=,keyAlias=,keyPassword=anlegen oder die vierGPS2AUDIO_RELEASE_*‑Env‑Vars setzen local.propertiesmitsdk.dir=…setzengradle :app:assembleRelease
- Wichtig: Geht der Keystore verloren, sind zukünftige Updates der App nicht mehr installierbar, ohne dass Nutzer deinstallieren. Bitte den Keystore und das Credentials‑File an mindestens zwei sicheren Orten ablegen.
Downloads
-
released this
2026-05-15 14:32:48 +02:00 | 7 commits to main since this releaseGPS2Audio v2.1.1 – PTT-Aufnahmen anhören & sinnvoll zuweisen
Build:
2.1.1· versionCode:38
Basis: stabile Code-Basis ausGPS2Audio_v2_1_0_ptt_recorder_source.zip
Build-Host: Linux, JDK 21, Android SDK 35, Gradle 8.9 (AGP 8.5.2)
1. Ursache der gemeldeten Probleme
In v2.1.0 war der PTT-Recorder zwar funktional (Aufnahme, Speicherung,
Bibliothek, Datenmodell), aber die UI hatte zwei Lücken, die ihn aus
Nutzersicht teilweise nutzlos machten:-
Keine Anhörmöglichkeit in der Bibliothek
Der DialogPttRecordingsLibraryDialogzeigte zwar Aufnahmen mit
Metadaten an (Datum, Dauer, optional GPS), bot aber pro Eintrag nur
die Buttons „Als Waypoint-Audio zuweisen" und „Löschen". Es gab
keinerlei Möglichkeit, eine Aufnahme im Dialog vorzuhören. (Screenshot
1000014484.jpg.) -
„Wegpunkt auswählen" war in leeren Touren eine Sackgasse
WaypointChooserDialoglistete ausschließlich die Wegpunkte der
aktuell ausgewählten Tour. War diese Tour leer – z.B. „Standard"
direkt nach App-Installation – zeigte der Dialog lediglich den Text
„Keine Wegpunkte in dieser Tour." an, ohne Möglichkeit, eine andere
Tour zu wählen oder einen Wegpunkt aus der Aufnahme zu erzeugen.
(Screenshots1000014485.jpg,1000014486.jpg.)
2. Umsetzung (UI + Logik)
2.1 Vorhörfunktion in der PTT-Bibliothek
- Neuer Button „Anhören" / „Pause" pro Aufnahme im Dialog.
- Ein dedizierter, lokaler Preview-Player (
MediaPlayer-basiert)
innerhalb des Dialogs, der bewusst nicht beim
WaypointAudioInterruptionCoordinatorregistriert wird. Dadurch
bleibt der bestehende Audiopfad (Atmo-Service, manueller
Wegpunkt-Player, PTT-Interruption) vollständig unverändert. - Genau eine Vorschau gleichzeitig: beim Wechsel auf eine andere
Aufnahme wird die vorherige automatisch gestoppt und freigegeben. - Cleanup garantiert über:
DisposableEffect(Unit) { onDispose { previewPlayer.release() } }- explizites Stoppen bei: Schließen-Icon,
onDismissRequest,
Klick auf „Zuweisen" (vor Wechsel inWaypointChooserDialog),
Klick auf „Löschen" (falls gerade gespielt), natürlichem
Wiedergabe-Ende.
- Wiedergabe nutzt die
file://-URI der Aufnahme (wie bisher von
PttRecordererzeugt). Kein neuer Permission-Bedarf.
2.2 Wegpunkt-Auswahl/-Erstellung im Chooser
- Tour-Dropdown im Dialogkopf: Nutzer kann während der Zuweisung
zu einer anderen Tour wechseln. Initialwert = aktuell aktive Tour. - Liste zeigt Wegpunkte der gewählten Tour mit dem bestehenden
Pfad: Klick auf einen Wegpunkt setztsoundUriundsoundNameder
Aufnahme überviewModel.upsert(...). Erfolgsmeldung auf Deutsch via
viewModel.postInfo(...)→ bestehender Info-Snackbar-Mechanismus. - Neuer Wegpunkt aus Aufnahme (Button):
- Sichtbar auch in leeren Touren (prominent) und zusätzlich
in nicht-leeren Touren (sekundär,OutlinedButton). - Erzeugt einen neuen
Waypointmit:name = displayNameder Aufnahme (Fallback „PTT-Aufnahme"),soundUri/soundNameaus der Aufnahme,radiusMeters = 50f,isActive = true,tourName = Dropdown-Auswahl(ggf.Waypoint.DEFAULT_TOUR_NAME).
- Position mit Prioritätskette:
- Aktuelle GPS-Position (
viewModel.currentLocation, falls
gesetzt – nutzt die bestehendefetchCurrentLocation-Logik). - GPS-Metadaten aus der Aufnahme (
recording.latitude/longitude,
diePttRecorder.stop()beim Speichern bereits anlegt). - Sicherer Default
0.0 / 0.0(entspricht dem Default des
Waypoint-Datenmodells – Nutzer kann den Wegpunkt anschließend
jederzeit per Karten-Editor oder Bearbeiten-Dialog korrigieren).
- Aktuelle GPS-Position (
- Persistiert via
viewModel.upsert(...); das bestehende
WaypointRepositorylegt den Wegpunkt an. Tour-Auswahl wird auf
die Zieltour gewechselt, damit der neu erzeugte Wegpunkt in der
Liste sichtbar wird.
- Sichtbar auch in leeren Touren (prominent) und zusätzlich
- Feedback auf Deutsch: Neue String-Ressourcen
ptt_waypoint_chooser_createdund
ptt_waypoint_chooser_empty_hint; bestehende
ptt_waypoint_chooser_assignedbleibt für reine Zuweisungen.
2.3 ViewModel-Erweiterung (minimal)
Zwei kleine öffentliche Setter in
WaypointViewModel:fun postInfo(message: String)setzt_infoMessage(für Erfolgs-Banner).fun postError(message: String)setzt_errorMessage(für Fehler-Banner).
Beide nutzen die existierenden, bereits in der UI gehosteten
StateFlows (infoMessage/errorMessage) und führen keine eigenen
Side-Effects ein.
3. Geänderte Dateien
Datei Änderung app/build.gradle.ktsversionCode 37 → 38,versionName 2.1.0 → 2.1.1app/src/main/res/values/strings.xmlNeue Strings: ptt_waypoint_chooser_empty_hint,ptt_waypoint_chooser_tour_label,ptt_waypoint_chooser_create_new,ptt_waypoint_chooser_created,ptt_recording_preview_play,ptt_recording_preview_pauseapp/src/main/kotlin/de/waypointaudio/ui/PttRecordingDialogs.ktPreview-Player + Play/Pause in der Bibliothek; Tour-Dropdown + „Neuen Wegpunkt erstellen" im Chooser; deutsche Erfolgs-/Hinweistexte app/src/main/kotlin/de/waypointaudio/viewmodel/WaypointViewModel.ktÖffentliche postInfo/postErrorSetter (additive Änderung).gitignoreSchutz vor versehentlichem Einchecken von keystore.propertiesund*.jksEs wurden keine anderen Klassen / Services / Player / Stores
verändert.
4. Build- und Signaturstatus
- Toolchain: JDK 21, Android SDK 35, Build-Tools 35.0.0,
Gradle 8.9, AGP 8.5.2, Kotlin 2.0.21. - Build-Befehl:
gradle :app:assembleRelease(kein Daemon). - Signatur: v2 APK Signature Scheme – verifiziert mit
apksigner.
Signiert mit dem produktiven Release-Keystoregps2audio_release
(PKCS12, RSA 4096, SHA384withRSA). Der signierte Build ist damit
updatefähig zu früheren v2.x-Installationen. - Apk-Pfad:
/home/user/workspace/GPS2Audio_v2_1_1_ptt_recorder_play_assign_signed.apk - Größe / Metadaten:
package: name='de.waypointaudio' versionCode='38' versionName='2.1.1'targetSdkVersion='35',sdkVersion:'26'
- Gradle-Ausgabe:
BUILD SUCCESSFUL in 1m 22s. Nur Compiler-
Warnungen (deprecation hints aus bestehenden Modulen, kein Bezug
zu den neuen Änderungen außerIcons.Filled.Assignment– bewusst
beibehalten zur visuellen Konsistenz mit v2.1.0).
5. Manuelle Tests (Schritt für Schritt)
PTT-Aufnahmen anhören
- App öffnen → Tour „Standard" auswählen.
- PTT-Einstellungen → „Aufnahme speichern" aktivieren.
- Mehrere kurze PTT-Aufnahmen erzeugen (PTT-Toggle aus/an).
- Menü → „Aufnahmen…" öffnen → Dialog „PTT-Aufnahmen" erscheint.
- Auf „Anhören" einer Aufnahme tippen → Wiedergabe startet, Button
wechselt zu „Pause". - Auf „Pause" tippen → Wiedergabe pausiert; erneutes Tippen auf
„Anhören" derselben Aufnahme → resumed. - Auf „Anhören" einer anderen Aufnahme tippen → erste Vorschau
stoppt automatisch, zweite startet. - Dialog schließen (X / Außerhalb) → Wiedergabe stoppt sofort.
- Aufnahme während laufender Vorschau löschen → Vorschau stoppt
sicher, Datei + Eintrag entfernt.
Zuweisung an Wegpunkte
- In leerer Tour „Standard": Eine Aufnahme „Zuweisen" wählen →
Dialog „Wegpunkt auswählen" zeigt Dropdown „Tour: Standard" und
Hinweistext + Button „Neuen Wegpunkt aus Aufnahme erstellen". - Button drücken → Wegpunkt wird angelegt; Snackbar/Banner
bestätigt: „Wegpunkt … in Tour … angelegt." Liste enthält den
neuen Wegpunkt; Audio ist gesetzt. - Mehrere Touren anlegen, eine davon mit Wegpunkten füllen.
- Aufnahme zuweisen → Tour-Dropdown öffnen → Tour mit Wegpunkten
wählen → Liste erscheint → Wegpunkt antippen → Audio wird der
Wegpunkt zugewiesen; Bestätigung erscheint. - „Neuen Wegpunkt aus Aufnahme erstellen" auch in nicht-leerer
Tour → erzeugt zusätzlichen Wegpunkt (sekundärer Button).
Position
- Ohne GPS-Position (kein vorheriges
fetchCurrentLocation):
neuer Wegpunkt nutzt – sofern in der Aufnahme vorhanden – die
GPS-Metadaten der Aufnahme; sonst0.0/0.0(vom Nutzer
nachträglich editierbar). - Mit aktiver GPS-Position (z.B. nach „Neuen Wegpunkt"-Klick im
Hauptscreen): wird bevorzugt.
6. Regression-Checkliste (v2.1.0 → v2.1.1)
Bereich Erwartetes Verhalten Status Stream-Playlist (Service-owned Atmo-Player, Hintergrundwiedergabe) Unverändert; Atmo läuft im Foreground-Service weiter, Sperrbildschirm-Steuerung verfügbar ✅ keine Änderung am Service / AtmoPlaybackService/BackgroundMusicManagerPTT pausiert Waypoint und Atmo WaypointAudioInterruptionCoordinatorPfad unverändert; Preview-Player ist außerhalb dieses Pfads (rein UI-lokale Vorschau)✅ Drag-and-drop Waypoint-Sortierung Unverändert ✅ kein Eingriff in WaypointListScreen-SortierlogikKarteneditor / Drafts / Map Search Unverändert ✅ keine Änderung an MapScreen,TrackDraftEditorScreen,PlaceSearchDialog,NominatimClientBackup Export / Import Unverändert ✅ keine Änderung an BackupExportManager/BackupImportManagerAbout / PayPal / Versions-Dialog Unverändert. Versions-Dialog erscheint automatisch wieder bei versionCode 38, weildismissedVersionCode < 38.✅ Audio-Bibliothek (manuelle Auswahl von Sound-Dateien) Unverändert ✅ OSM Attribution Unverändert ✅ ScrollableTabRow Fix Unverändert ✅ Touren ordnen / umbenennen / löschen Unverändert ✅ moveTour,renameTour,deleteTournicht angefasstPost-Aufnahme-Dialog Identisches Verhalten zu v2.1.0 ✅ PTT-Aufnahme-Pfad (Recorder, Speicherung, Bibliothek-Persistenz) Identisch zu v2.1.0 ✅ PttRecorderundLivePttManagerunverändertManueller Waypoint-Player Identisch zu v2.1.0 ✅ ManualAudioPlayerunverändertRisikoanalyse: Sämtliche Änderungen sind additiv und auf zwei UI-
Dialoge sowie zwei neue, idempotente Setter im ViewModel
beschränkt. Der Preview-Player ist räumlich auf den Dialog
beschränkt und benutzt eine eigeneMediaPlayer-Instanz, die durch
DisposableEffectund mehrfache expliziterelease()-Aufrufe sicher
freigegeben wird. Es gibt keinen geteilten Zustand mit den
bestehenden Playern.
7. Sicherheit / Auslieferung
- Keystore-Datei (
GPS2Audio_release_keystore.jks) und
keystore.propertiesmit Passwörtern sind nicht im
GPS2Audio_v2_1_1_ptt_recorder_play_assign_source.zipenthalten.
Geprüft perunzip -l; im ZIP befindet sich ausschließlich die
Vorlagekeystore.properties.example. .gitignorezusätzlich abgesichert (keystore.properties,*.jks).- Der Build erwartet beim erneuten Erstellen entweder
keystore.propertiesim Projekt-Root oder die ENV-Variablen
GPS2AUDIO_RELEASE_STORE_FILE/…_STORE_PASSWORD/…_KEY_ALIAS/
…_KEY_PASSWORD(Details:keystore.properties.example).
8. Ergebnisartefakte
GPS2Audio_v2_1_1_ptt_recorder_play_assign_signed.apk– signierte
Release-APK, updatefähig (15,2 MB).GPS2Audio_v2_1_1_ptt_recorder_play_assign_source.zip– komplette
Quellen ohne Build-Artefakte, ohne Keystore, ohne Passwort-Datei.GPS2Audio_v2_1_1_ptt_recorder_play_assign_handoff.md– dieses
Dokument.
Downloads
-
-
released this
2026-05-15 14:32:38 +02:00 | 7 commits to main since this releaseGPS2Audio v2.1.0 – PTT Recorder – Handoff
Artifacts
Path Size Notes /home/user/workspace/GPS2Audio_v2_1_0_ptt_recorder_signed.apk14.5 MB release-signed /home/user/workspace/GPS2Audio_v2_1_0_ptt_recorder_source.zip230 KB source bundle /home/user/workspace/GPS2Audio_v2_1_0_ptt_recorder_handoff.md– this document SHA-256 (APK):
181fa1846bc384cae8b7e2ad72db8ea1943c881b8b7878cd50d40205b929705aBuild / verification
package: de.waypointaudio versionCode: 37 (was 36 in v2.0.8 – satisfies "> 36") versionName: 2.1.0 compileSdk: 35 minSdk: 26 targetSdk: 35 gradle :app:assembleRelease → BUILD SUCCESSFUL (1m 47s, 43 tasks) apksigner verify --print-certs Signer #1 DN: CN=GPS2Audio, OU=NesoHub, O=GPS2Audio, L=Germany, C=DE Signer #1 SHA-256: 7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46 ← matches GPS2Audio_release_keystore.jks fingerprint (verified)What was implemented
1. PTT Settings (
AudioRoutingSettings)- New persisted fields on the existing DataStore (
audio_routing_settings):liveOutputEnabled: Boolean— defaulttrue(matches v2.0.8 PTT behaviour)recordingEnabled: Boolean— defaultfalserecordingFormat: PttRecordingFormat— defaultM4A_AAC
- Validation guard in
LivePttManager.saveRoutingSettings()and in the
Settings dialog: if the user turns both off, Live is forced back on
(toast hint shown in the dialog). - The new
PttSettingsDialog(LivePttCard.kt) replaces the v2.0.8
AudioDeviceDialogand merges both responsibilities:- Toggles for Live-Ausgabe and Aufnahme speichern (with German
helper text). - Format selector: M4A/AAC active; WAV shown but radio button
disabled, label reads "WAV (in v2.1.0 nicht verfügbar)" — see
Limitations below. - Existing input/output device dropdowns, refresh button and hints
kept exactly as in v2.0.8.
- Toggles for Live-Ausgabe and Aufnahme speichern (with German
2. Recording engine (
PttRecorder)- Wraps Android
MediaRecorder(MIC source, MPEG-4 container, AAC
encoder, mono, 44.1 kHz, 96 kbps). API ≥ 31 uses
MediaRecorder(context), below uses the deprecated default constructor. - Files saved to
<app filesDir>/recordings/PTT_<yyyyMMdd_HHmmss>.m4a. - On
stop()reads the duration viaMediaMetadataRetrieverand returns
a fully populatedPttRecordingvalue (id, displayName, filePath,
file://URI, format, createdAt, durationMs, optional lat/lon). cancel()releases the recorder and deletes the partial file.
3. Recording library (
PttRecording+PttRecordingStore)- New DataStore (
ptt_recordings) with a single JSON-encoded list of
recording metadata entries. Gson is already a project dependency. - Operations:
recordings: Flow<List<PttRecording>>(always sorted by
createdAt desc),add,delete,rename.
4. PTT lifecycle (
LivePttManager)startPtt()now reads the saved settings and:- Pauses Atmo + waypoint audio (unchanged from v2.0.8).
- Starts the
MediaRecorderfirst (ifrecordingEnabled). Sets
recordingActive=true. - Starts the existing
LivePttServiceforeground service (if
liveOutputEnabled). The service still uses
AudioSource.VOICE_COMMUNICATIONso on most devices both clients
share the mic; if either init fails the other continues and a
status message is surfaced.
stopPtt()stops the live service, finalises the recording on a
coroutine (fetchesfusedLocation.lastLocationwith a 1.5 s timeout
to attach lat/lon), persists the metadata inPttRecordingStoreand
posts the result tolastRecording: StateFlow<PttRecording?>.- New helper methods:
deleteRecording,renameRecording,
clearLastRecording. - Recording state surfaced via
recordingActive: StateFlow<Boolean>
for the UI to show a red dot/pill in the PTT card.
5. UI changes
- LivePttCard (
ui/LivePttCard.kt): adds aLibraryMusicicon
button next to the Settings icon that opens the recordings library.
Status row shows "Aufnahme läuft" + redFiberManualRecorddot when
recording. A small "Live + Aufnahme" / "Nur Aufnahme" / "Nur Live"
summary line appears under the status. Echo warning is suppressed
when Live is off. - PttSettingsDialog (same file): merged dialog described above.
- PostRecordingDialog (
ui/PttRecordingDialogs.kt): shown
automatically wheneverlastRecordingbecomes non-null. Buttons:- Als Waypoint-Audio zuweisen → opens
WaypointChooserDialog - Löschen → calls
pttManager.deleteRecordingand clears the
last-recording state - OK → dismisses without action
- Als Waypoint-Audio zuweisen → opens
- PttRecordingsLibraryDialog: full bottom-sheet style dialog with
scrollable list of all recordings; per-item "Zuweisen" and "Löschen"
buttons with confirm dialog for delete. - WaypointChooserDialog: lists every waypoint of the currently
selected tour; tapping a card setswaypoint.soundUri = recording.uri
andwaypoint.soundName = recording.displayNamevia
WaypointViewModel.upsert(). This is the existing assignment path,
so the audio is immediately played on waypoint entry like any other
sound.
6. WaypointListScreen integration
- Passes
onOpenRecordingscallback toLivePttCard. - Renders the post-recording dialog, recordings-library dialog and
waypoint-chooser dialog at the end of the screen so they overlay
the main UI.
7. Backup
- The existing
BackupExportManageralready collects every
waypoint.soundUriintomedia/waypoints/<tour>/...in the backup
ZIP. Because assigned recordings are written into that exact field
with afile://URI rooted in<filesDir>/recordings/, assigned
PTT recordings flow through the existing media backup unchanged. - Unassigned (orphan) recordings are not copied to a backup —
there is no recordings section indata/...json. This is documented
as a limitation; adding it would require touching the import path as
well. See Limitations below.
Preserved v2.0.8 features
- Stream playlist (multi-URL Atmo, shuffle, REPEAT_MODE_ALL): untouched.
- AtmoPlaybackService foreground media-playback architecture: untouched.
- PTT audio priority chain (PTT pauses Atmo and waypoint audio,
AtmoResumeManager + WaypointAudioInterruptionCoordinator) — only the
invocation site inLivePttManagerwas edited; the contract is the
same. - Backup format, GPS track drafts, tour reorder, waypoint editor,
drafts archive, map provider selector, version-notice dialog — all
untouched.
Limitations / known gaps
- WAV format is intentionally not implemented. The selector in the
PTT settings dialog shows it disabled with a German label. Reason:
keeping the PTT live path stable was the top priority; M4A/AAC is
the supported codec viaMediaRecorderand works on every device
from API 26 upwards. - Live + Aufnahme simultaneously should work on most devices —
AudioRecord(VOICE_COMMUNICATION)andMediaRecorder(MIC)are
separate clients and Android typically allows both to read from the
mic in parallel. On vendor-specific builds (some Huawei / Samsung
devices have been reported in the wild) one of the two may fail to
init. The manager logs the failure, surfaces a status message and
keeps the one that did start. If a user encounters this, the
workaround is to disable Live in the PTT settings. - Orphan recordings are not auto-backed-up. Only recordings that
the user assigned to a waypoint end up in the backup ZIP (via the
existingsoundUrienumeration). Recordings that live only in the
library remain in<filesDir>/recordings/on the device. - Recording playback inside the library list is not implemented.
Users can verify a recording by assigning it to a waypoint or by
triggering the post-recording dialog directly after capture. Adding
in-place playback would require an extra MediaPlayer instance and
was out of scope for the "stable PTT recorder" objective. - Renaming a recording is supported by the store
(renameRecording) but no UI is wired up yet — recordings carry
thePTT_<timestamp>default name.
Files added / changed
Added: app/src/main/kotlin/de/waypointaudio/data/PttRecording.kt app/src/main/kotlin/de/waypointaudio/service/PttRecorder.kt app/src/main/kotlin/de/waypointaudio/ui/PttRecordingDialogs.kt Changed: app/build.gradle.kts (versionCode 36→37, versionName 2.0.8→2.1.0) app/src/main/kotlin/de/waypointaudio/data/AudioRoutingSettings.kt app/src/main/kotlin/de/waypointaudio/service/LivePttManager.kt app/src/main/kotlin/de/waypointaudio/ui/LivePttCard.kt (PttSettingsDialog merged) app/src/main/kotlin/de/waypointaudio/ui/WaypointListScreen.kt (3 dialog hooks) app/src/main/res/values/strings.xml (~25 new strings)No manifest changes were needed:
RECORD_AUDIO,FOREGROUND_SERVICE,
FOREGROUND_SERVICE_MICROPHONEwere already declared for v2.0.8 PTT.Test plan (manual)
- Open PTT settings. Verify defaults: Live = ON, Aufnahme = OFF.
- Try turning both off → Live should snap back ON, German hint shown.
- Enable Aufnahme, leave Live on, save. Trigger PTT (start), speak a
few seconds, stop. Verify:- PTT card shows red mic + "Aufnahme läuft" while active.
- Post-recording dialog appears with file name + duration (+ GPS if
permissions granted). - "Als Waypoint-Audio zuweisen" → list shows waypoints of current
tour; tap one → confirmation. - Enter that waypoint on the map → recorded audio plays.
- Open library via the headphones icon → recording is listed → delete
it → file and metadata both gone. - Disable Live (Aufnahme only), trigger PTT → only recording happens,
no echo, status row shows red dot. Verify Atmo pauses while
recording and resumes on stop. - Trigger Atmo with stream playlist → start PTT → Atmo pauses → stop
PTT → Atmo resumes (regression check for v2.0.8 behaviour). - Backup export with "Medien/Atmo" enabled → unzip → assigned
recording appears undermedia/waypoints/<tour>/....
Build status — final
- Result:
BUILD SUCCESSFUL(gradle 8.9, AGP from project,
compileSdk 35, JDK 17). - APK signature: verified, certificate matches the v2.0.x release
keystore fingerprint7B:0E:A4:D5:…:BB:46. - APK metadata:
de.waypointaudioversionCode37versionName
2.1.0. - Tests: project contains no unit/instrumentation tests; manual
verification described above. Runtime testing on a device was not
possible from this build host.
Downloads
- New persisted fields on the existing DataStore (
-
released this
2026-05-15 14:32:24 +02:00 | 7 commits to main since this releaseGPS2Audio v2.0.8 – Stream-Playlist (Atmo/Begleitmusik)
Version: 2.0.8 · versionCode: 36 · Build type: Release (signed) · Baseline: v2.0.7 (
GPS2Audio_v2_0_7_atmo_service_player_source.zip)Artefakte
Pfad Inhalt /home/user/workspace/GPS2Audio_v2_0_8_stream_playlist_signed.apkSigniertes Release-APK (v2 APK Signing) /home/user/workspace/GPS2Audio_v2_0_8_stream_playlist_source.zipVollständiger Quellbaum ohne build/, ohne Keystore, ohnekeystore.properties/home/user/workspace/GPS2Audio_v2_0_8_stream_playlist_handoff.mdDieses Dokument APK-Eckdaten (
aapt dump badging):package: name='de.waypointaudio' versionCode='36' versionName='2.0.8'- compileSdk 35, minSdk 26, targetSdk 35
- Signer SHA-256:
7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46(identisch mit Release-Keystore ausGPS2Audio_release_keystore_credentials.txt) - APK-SHA-256:
2b9a1f25f54fbf7b93eb4dd24c824dc429fe293e62774cd777c5902d14a13053 - Größe: ~14.5 MB
apksigner verify: ✅Verifies(APK Signature Scheme v2)
Was ist neu in 2.0.8
Die einzige Stream-URL pro Tour wird durch eine Stream-Playlist ersetzt. User können beliebig viele
Internet-Stream-Einträge (URL + optionaler Name) pro Tour anlegen, umsortieren und nacheinander abspielen.
Der service-eigene ExoPlayer aus v2.0.7 wurde unverändert übernommen – die Stream-Playlist wird einfach als
mehrteiligeMediaItem-Liste an die bestehende Player-Pipeline übergeben.Alle anderen v2.0.7-Funktionen bleiben unverändert: service-eigener ExoPlayer (Foreground Service
mediaPlayback), PTT pausiert Wegpunkt + Atmo, Drag-Reorder, Wake Lock, Map-Provider, Track-Editor +
Drafts, Backup-Export/Import, Map-Suche, Audio-Bibliothek, OSM-Attribution, TabRow-Fix, About-Dialog
mit PayPal-Link/Versions-Notiz.Datenmodell + Migration
Neu in
TourAudioSettings
(app/src/main/kotlin/de/waypointaudio/data/TourAudioSettings.kt):data class StreamEntry(val url: String, val name: String = "") data class TourAudioSettings( … val streamPlaylist: List<StreamEntry> = emptyList(), val streamUrl: String? = null, // Legacy-Feld – nur für Backward-Compat … ) { val effectiveStreamPlaylist: List<StreamEntry> get() = when { streamPlaylist.isNotEmpty() -> streamPlaylist !streamUrl.isNullOrBlank() -> listOf(StreamEntry(url = streamUrl, name = "")) else -> emptyList() } fun migrated(): TourAudioSettings = … // streamUrl → streamPlaylist }Persistenz (
TourMusicStore):streamPlaylistwird als JSON-Array[{url, name}, …]neben dem
bestehendenstreamUrl-Feld gespeichert. Beim Laden:- Wenn
streamPlaylistbefüllt ist → wird direkt benutzt. - Wenn
streamPlaylistleer ist undstreamUrlbefüllt ist → einmalige Migration in einen einzigen
StreamEntry(url=streamUrl, name=""),streamUrlwird aufnullgesetzt. - Sobald der User die Einstellungen das nächste Mal speichert, ist die Migration auf Disk persistiert.
Backward-Compatibility:
- Beim Lesen alter Datastore-Inhalte (v2.0.7 oder älter):
streamPlaylist-Feld fehlt im JSON → wird als
leere Liste interpretiert (Gson-Default). Anschließendmigrated()aufrufen → Legacy-streamUrlwird in
eine ein-Element-Playlist überführt. Der User sieht in v2.0.8 sofort die alte URL als ersten Eintrag der
Stream-Playlist. - Beim Schreiben neuer Settings aus dem Dialog wird
streamUrl=nullgespeichert; die kanonische
Quelle ist diestreamPlaylist. Ältere App-Versionen können die JSON-Repräsentation immer noch lesen
(Gson ignoriert das unbekanntestreamPlaylist-Feld bei v2.0.7), zeigen aber dann keine Stream-URL,
weil das Legacy-Feld leer ist – das wäre also ein Downgrade-Pfad und kein normaler Use-Case.
Keine DB-Migration nötig –
TourMusicStorebenutzt JSON in DataStore und ist tolerant gegenüber
fehlenden Feldern (Gson-Defaults).UI-Flow (Begleitmusik-Dialog)
Datei: app/src/main/kotlin/de/waypointaudio/ui/BegleitmusikDialog.kt
Quelle „Stream-URL" wählen → die alte einzelne URL-Eingabe ist ersetzt durch:
- Stream-Playlist-Liste (oben): pro Eintrag eine Zeile mit Radio-Icon, Name (falls vergeben) bzw. URL,
Buttons:↑/↓(Reorder, sequentiell – kein Drag, da im Dialog overkill)✏️ Bearbeiten→ lädt den Eintrag in die Eingabefelder unten✕ Entfernen(rot)
- Eingabesektion „Stream hinzufügen" / „Stream bearbeiten":
Name (optional)– Klartext, z. B. „Deutschlandfunk Kultur"Stream-URL– Pflicht, validiert aufhttp://oderhttps://, Fehleranzeige unter dem Feld- Buttons:
Hinzufügen/Übernehmen(Edit) /Abbrechen(nur im Edit-Modus) /Playlist leeren(rot)
- Shuffle-Switch erscheint zusätzlich nur wenn
streamPlaylist.size > 1. - Der bestehende Plattform-Hinweis zu YouTube/SoundCloud/radio.de bleibt erhalten.
- Bei „Speichern" mit leerer Stream-Playlist und Quelle=Stream → Fehlermeldung
music_stream_playlist_empty_error(„Bitte mindestens einen Stream zur Playlist hinzufügen.")
Alle Labels sind auf Deutsch lokalisiert (neue Strings in
res/values/strings.xmlZeilen ~280–298,
Prefixmusic_stream_*).Playback-Verhalten
Datei: app/src/main/kotlin/de/waypointaudio/service/AtmoPlaybackService.kt
- Neue Intent-Extras:
EXTRA_STREAM_URLS(String[]),EXTRA_STREAM_NAMES(String[]). - Legacy
EXTRA_STREAM_URLwird weiterhin gesetzt (= erstes Element der Playlist) für Re-Delivery-Fälle. applySettingsFromIntentbaut beiisStream=trueausEXTRA_STREAM_URLSeine Liste vonMediaItems
und übergibt sie unverändert über die bestehende ExoPlayer-Initialisierung – derselbe Code-Pfad wie für
lokale Playlists. ExoPlayer behandelt Next/Previous, Auto-Advance und Fehler nativ über
Player.REPEAT_MODE_ALLund diePlayer.Listener-Callbacks, die wir bereits hatten.- Wenn ein Stream-Item fehlerhaft ist (Netzwerk, falsche URL), gehört das zum Standard-ExoPlayer-Verhalten
→ der Player loggt den Fehler und überträgt den nächstenMediaItem(Repeat-All). Es gibt keinen Crash. MusicPlaybackState:isStream = truebleibt für alle Stream-Modi.playlistIndex/playlistTotalwerden bei Stream-Playlists mit > 1 Eintrag korrekt befüllt – die
UI kann darüber „1/3" anzeigen wie für lokale Playlists.supportsSkip = total > 1gilt jetzt auch für Stream-Playlists, sodass die Next/Previous-Buttons
funktionieren.currentTitlezeigt den Stream-Namen, wenn vergeben, sonst den URL-Host.sourceLabelzeigt „ Streams" bei Playlists mit > 1 Eintrag, sonst den Host.
Background-Wiedergabe (v2.0.7-Verhalten unverändert): Der Foreground-Service
AtmoPlaybackService
besitzt den ExoPlayer. PTT/Wegpunkt-Audio pausiert die Wiedergabe wie zuvor (VerhaltenPAUSE_RESUME,
FADE_OUT_IN,DUCK_UNDERLAY,CONTINUE_UNDERLAYgreifen auf Stream-Playlists wie auf lokale Playlists).
Drag-Reorder im Wegpunkt-Listenscreen ist nicht betroffen.Backup-Export/Import
Export (io/BackupExportManager.kt):
data/music_settings.jsonenthält jetzt zusätzlichstreamPlaylist: [{url, name}, …]pro Tour
(effectiveStreamPlaylist– auch bei Tourdaten, die noch das Legacy-streamUrlhaben). Das alte Feld
streamUrlbleibt im Export-JSON aus Kompat-Gründen enthalten (nun typischerweisenull).Import (io/BackupImportManager.kt):
parseMusicSettings()lieststreamPlaylist(neu); falls fehlend undstreamUrlgesetzt → Migration
in eine ein-Element-Stream-Playlist. Damit funktionieren v2.0.7-Backups uneingeschränkt – die alte
Stream-URL erscheint nach dem Import als erster Eintrag der Stream-Playlist.Manuelle Tests
Erstinstallation (Geräte-Daten vorher leer):
- Stream-Playlist anlegen – Tour erstellen → Begleitmusik → Quelle „Stream-URL" → zwei Streams
eintragen (z. B.https://liveradio.swr.de/sw282p3/swr3/play.mp3und
https://st01.dlf.de/dlf/01/104/ogg/stream.ogg) → speichern → Play-Button → erster Stream startet im
Foreground. - Next/Previous – Skip-Vor/Zurück-Buttons springen zwischen den beiden Streams.
- Background – App in den Hintergrund schicken, Bildschirm aus → Musik läuft weiter (Notification
bleibt sichtbar). App schließen (Recents wischen) → Wiedergabe läuft weiter; Stop-Action in der
Notification beendet sie. - PTT-Pause – PTT aktivieren → Atmo-Stream pausiert; PTT loslassen → Wiedergabe setzt fort, abhängig
vom Verhalten (PAUSE_RESUME / FADE_OUT_IN / DUCK / CONTINUE). - Wegpunkt-Pause – an einem Wegpunkt vorbeigehen oder „Test" → Wegpunkt-Ton spielt, Atmo verhält sich
gemäß Behavior-Modus. - Reorder – Streams in der Liste per
↑/↓umsortieren → Save → Reihenfolge bleibt nach App-Restart
erhalten. - Edit & Delete – Eintrag bearbeiten (Name + URL ändern) und entfernen → Liste reagiert sofort.
- Shuffle – Switch erscheint bei > 1 Stream-Eintrag, aktivieren → Reihenfolge wird zufällig.
- Lokale Playlist – Quelle auf „Lokale Playlist" wechseln → bestehende lokale Playlist + Audio-
Bibliothek funktionieren unverändert. Audio-Bibliothek-Picker für lokale Files wurde nicht angefasst. - Validation – Stream-URL ohne
http(s)://eintragen → Fehlermeldung. Stream-Playlist leer + Quelle
Stream + Save → Fehlermeldung.
Migration / Backward-Compatibility:
11. v2.0.7-Backup importieren – Backup-ZIP aus v2.0.7 öffnen → Touren mit einzelner Stream-URL
erscheinen nach Import unter „Stream-Playlist" mit genau einem Eintrag (URL-Feld gefüllt, Name leer).
12. In-Place-Upgrade – v2.0.7-APK deinstallieren nicht notwendig (gleiche Signatur, gleiche
applicationId). Nach Upgrade auf 2.0.8 zeigen vorhandene Touren ihre alte Stream-URL automatisch in der
neuen Stream-Playlist.
13. Backup-Export – Backup in v2.0.8 erstellen → ZIP enthältstreamPlaylist-Feld (kann mitunzip -p backup.zip data/music_settings.jsoninspiziert werden).Regressions-Checkliste (v2.0.7-Features)
- Service-owned ExoPlayer –
AtmoPlaybackServicebesitzt weiterhin den Player; Wiedergabe läuft
durch App-Hintergrund, Bildschirm aus, App-Kill (Recents). - PTT pausiert Wegpunkt + Atmo –
LivePttManagerundAtmoResumeManagerunverändert. Stream-
Playlist verhält sich identisch zu lokalen Playlists. - Drag-Reorder Wegpunkte –
WaypointListScreenund Drag-Logic unverändert. - Wake Lock – ExoPlayer-Wake-Mode (
WAKE_MODE_NETWORKfür Streams,WAKE_MODE_LOCALfür lokale)
bleibt erhalten. - Map-Provider – Map-Screen + Provider-Auswahl nicht angefasst.
- Track-Editor + Drafts – Code in
editor/TrackDraftListScreennicht modifiziert. - Backup-Export/Import – Music-Settings-JSON erweitert (kompatibel mit alten Backups), alle
anderen Sektionen unverändert. - Map-Suche – Code in
MapScreennur Compiler-Warnung (deprecatedfillColor– schon in v2.0.7),
keine Verhaltensänderung. - Audio-Bibliothek –
LibraryPickerDialogundaudioLibrary-Flow unverändert. - OSM-Attribution – nicht modifiziert.
- TabRow-Fix – nicht modifiziert.
- About-Dialog (PayPal-Link, Versions-Notiz) – nicht modifiziert; Version wird automatisch aus
BuildConfig gelesen (2.0.8).
Geänderte Dateien
app/build.gradle.kts versionCode 35→36, versionName "2.0.7"→"2.0.8" app/src/main/kotlin/de/waypointaudio/data/TourAudioSettings.kt + StreamEntry, streamPlaylist, effectiveStreamPlaylist, migrated() app/src/main/kotlin/de/waypointaudio/data/TourMusicStore.kt + StreamEntryJson, streamPlaylist-Persistenz, Migration im Read-Pfad app/src/main/kotlin/de/waypointaudio/service/AtmoPlaybackService.kt + EXTRA_STREAM_URLS/NAMES, buildStreamItems(List<StreamEntry>), state für mehrere Streams app/src/main/kotlin/de/waypointaudio/service/BackgroundMusicPlayer.kt srcOk-Check auf effectiveStreamPlaylist app/src/main/kotlin/de/waypointaudio/ui/BegleitmusikDialog.kt komplettes Stream-Playlist-UI (Liste, Add/Edit/Delete, Reorder, Validate, Shuffle) app/src/main/kotlin/de/waypointaudio/ui/WaypointListScreen.kt hasSource-Check auf effectiveStreamPlaylist app/src/main/kotlin/de/waypointaudio/viewmodel/WaypointViewModel.kt sourceOk-Check auf effectiveStreamPlaylist app/src/main/kotlin/de/waypointaudio/io/BackupExportManager.kt + streamPlaylist im JSON-Export app/src/main/kotlin/de/waypointaudio/io/BackupImportManager.kt parseMusicSettings liest streamPlaylist mit Legacy-Migration app/src/main/res/values/strings.xml + 14 neue Strings music_stream_*Build-Reproduktion
unzip GPS2Audio_v2_0_8_stream_playlist_source.zip -d gps2audio cd gps2audio cat > keystore.properties <<EOF storeFile=/path/to/GPS2Audio_release_keystore.jks storePassword=<store password> keyAlias=gps2audio_release keyPassword=<key password> EOF export ANDROID_HOME=/path/to/android-sdk # platforms/android-35, build-tools/35.0.0 gradle :app:assembleRelease # Output: app/build/outputs/apk/release/app-release.apkVerifiziert:
apksigner verify --verbose app-release.apk→Verifies(v2-Scheme), Signer-CN
CN=GPS2Audio, OU=NesoHub, O=GPS2Audio, L=Germany, C=DE.Downloads
-
released this
2026-05-15 14:32:13 +02:00 | 7 commits to main since this releaseGPS2Audio v2.0.7 — Atmo Service-Player Handoff
Version: 2.0.7 · versionCode: 35 · Build type: Release (signed)
Baseline: v2.0.6 (GPS2Audio_v2_0_6_atmo_background_fix_source.zip)
Signing:GPS2Audio_release_keystore.jks(aliasgps2audio_release)
Signature scheme: APK Signature Scheme v2 (verified)
Package:de.waypointaudio· applicationId unchanged.Artifacts
Path Purpose /home/user/workspace/GPS2Audio_v2_0_7_atmo_service_player_signed.apkSigned release APK (≈15 MB) /home/user/workspace/GPS2Audio_v2_0_7_atmo_service_player_source.zipFull source tree (no build dir, no keystore) /home/user/workspace/GPS2Audio_v2_0_7_atmo_service_player_handoff.mdThis document
Root cause (why v2.0.6 still failed)
Even after the wake-mode fix in v2.0.6, the ExoPlayer instance was still owned by
an application-process singleton (BackgroundMusicPlayer, held by
BackgroundMusicManager). TheAtmoPlaybackServicewas a shell foreground
service: it only posted a notification and held themediaPlaybackforeground
type — it did not own the player.This split ownership is the architecture that Android 12+ specifically
penalises. The OS heuristic that allows background audio to survive
screen-off and process-cached transitions is:A
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACKservice must be the visible owner
of the audio playback. Detached playback (player in app process, FGS
elsewhere) is increasingly treated as a candidate for pause/throttle.In addition, the service in v2.0.6 was
START_NOT_STICKYand self-stopped on
anyintent.action == nullre-delivery. Combined with split ownership, any
brief process-state transition (e.g. Activity → background while another FGS
context is busy) could cause the ExoPlayer to lose its execution priority and
the audio pipeline to pause —setWakeMode()alone is not enough on
heavily-Doze'd OEMs.Architectural change in v2.0.7
The fix is to give the foreground service direct ownership of the
ExoPlayer. The Activity / ViewModel / Manager no longer touch the player
directly.┌───────────────────────────────────────┐ │ AtmoPlaybackService │ │ (foreground · mediaPlayback) │ │ │ │ ExoPlayer ◀── owns & holds │ │ • setWakeMode │ │ • AudioAttributes (USAGE_MEDIA) │ │ • setHandleAudioBecomingNoisy │ │ │ │ StateFlow<MusicPlaybackState> │ │ ── companion-singleton, lives even │ │ when service is down │ └───────────────┬───────────────────────┘ ▲ Intent actions │ StateFlow.collect (START / PAUSE / RESUME / │ STOP / NEXT / PREV / │ SET_VOLUME / UPDATE_SETTINGS)│ │ ┌─────────────────────────────┴────────────────────────┐ │ │ BackgroundMusicPlayer UI / ViewModel (thin facade — same (no API change) public API as v2.0.6) ▲ │ BackgroundMusicManager (app singleton, unchanged)What changed in code
-
AtmoPlaybackService(rewritten)- Owns the
ExoPlayerinstance fromapplySettingsFromIntent()until
ACTION_STOP/onDestroy. - Exposes a
companion objectstate: StateFlow<MusicPlaybackState>that
survives between service lifecycles (always reflects current truth). - Handles
ACTION_START,ACTION_UPDATE_SETTINGS,ACTION_PAUSE,
ACTION_RESUME,ACTION_NEXT,ACTION_PREVIOUS,ACTION_SET_VOLUME,
ACTION_STOP. - Returns
START_STICKY(wasSTART_NOT_STICKY): a system-induced
restart no longer terminates playback. - Null/unknown intent actions on re-delivery leave the player intact
(v2.0.6 self-stopped — that was a bug that compounded background kills). - Adds a
Stopaction button to the notification so the user can stop
Atmo from outside the app. startForeground()is still called as the very first action in
onCreate()(preserves the v2.0.5 fix for
ForegroundServiceDidNotStartInTimeException).- Wraps
startForegroundin try/catch so a deniedPOST_NOTIFICATIONS
permission cannot crash the service.
- Owns the
-
BackgroundMusicPlayer(rewritten as facade)- Same public API as before — drop-in for
BackgroundMusicManagerand
WaypointViewModel. No call sites changed. play()/pause()/stop()/next()/previous()/
setVolume()all route intoAtmoPlaybackServicecompanion intents.isPlaying/hasContentread fromAtmoPlaybackService.state.playbackState: StateFlow<MusicPlaybackState>is now a direct alias of
the service's companion flow, so UI observers see the canonical truth.beforeWaypointAudio()/afterWaypointAudio()preserve pause/resume,
fade-out/in and duck behaviour. Fades are still implemented locally —
they just callAtmoPlaybackService.setVolume()repeatedly instead of
mutating an owned player.
- Same public API as before — drop-in for
-
No changes in
BackgroundMusicManager,AtmoResumeManager,
WaypointViewModel,BegleitmusikDialog,WaypointListScreen,
LivePttManager,WaypointAudioInterruptionCoordinator— the facade
keeps their interfaces stable. -
AndroidManifest.xmlunchanged. The
FOREGROUND_SERVICE_MEDIA_PLAYBACKpermission and
android:foregroundServiceType="mediaPlayback"onAtmoPlaybackService
were already present in v2.0.6. -
build.gradle.kts:versionCode 34 → 35,versionName 2.0.6 → 2.0.7.
Everything else unchanged.
Lifecycle / service ownership behaviour (exact)
Event Effect on service Effect on ExoPlayer User taps "Play" / play()first timestartForegroundService→ACTION_STARTCreated inside service; play()User taps "Pause" ACTION_PAUSEplayer.pause()— service staysUser taps "Stop" (UI or notification) ACTION_STOPstop()+release(), thenstopSelf()User changes Atmo source (Begleitmusik-Dialog Save) ACTION_UPDATE_SETTINGS(orACTION_STARTif not running)Source compared; rebuilt only if different Begleitmusik checkbox disabled Manager calls player.stop()→ACTION_STOPService ends Tour switched (Atmo disabled in new tour) Manager calls player.stop()Service ends App minimised (Home / Recents) No effect Continues Screen off / Doze No effect (FGS + wakeMode) Continues App swiped from Recents onTaskRemoved— service intentionally keeps runningContinues; user can stop from notification PTT begins AtmoResumeManager→pauseMusic()→ACTION_PAUSEpause(); service staysPTT ends AtmoResumeManager→player.play()→ACTION_RESUMEplay()Waypoint audio begins (PAUSE_RESUME) beforeWaypointAudio→pausepause()Waypoint audio begins (DUCK_UNDERLAY) fade ACTION_SET_VOLUMEto duckVolumevolume ramps; play continues Waypoint audio ends afterWaypointAudio→ resume / fade-upas configured Manual player begins same as waypoint (via musicManager.beforeWaypointAudio)as configured Manual player ends resume / fade-up as configured System OOM-kill of process (very rare with FGS) START_STICKY→ service restarted, no stale playerPlayer will be empty until user re-starts Manual test plan
Build and install the new APK (uninstall older v2.0.x first or use a different
applicationId variant to keep installed in parallel). Then test:-
Stream — backgrounded with screen off (≥ 5 min)
- Configure a HTTP stream URL in Begleitmusik.
- Start playback. Press Home. Lock the screen.
- Wait 5 minutes. Audio must continue without interruption.
- Unlock — playback should still be active; "Stopp" button in
Atmo-Notification works.
-
Local playlist — backgrounded with screen off (≥ 5 min)
- Add 3+ local audio files to the playlist (with Shuffle on or off).
- Start playback. Press Home. Lock the screen.
- Wait 5 minutes spanning at least one track transition.
- Track must auto-advance, audio must not stop.
-
App swipe from Recents
- Start Atmo. Open Recents. Swipe the app away.
- Audio must continue.
- Tap the Atmo notification → app reopens cleanly.
- Tap "Stopp" → audio stops; notification disappears.
-
PTT interruption
- Start Atmo. Tap-and-hold PTT — Atmo must pause.
- Release PTT — Atmo must resume from same position.
- Repeat 3× — no leak; no stale pauses.
-
Waypoint audio interruption (each behaviour)
- For each
WaypointMusicBehavior(PAUSE_RESUME, FADE_OUT_IN,
DUCK_UNDERLAY, CONTINUE_UNDERLAY): trigger a waypoint with audio.
Behaviour must match v2.0.6 semantics. - After waypoint completes, Atmo resumes (if it was playing prior).
autoStartAfterWaypoint=true: if Atmo was paused/stopped, it starts
fresh after the first waypoint.
- For each
-
Manual single-file / playlist player vs. Atmo
- Tap any waypoint card's play icon — manual file plays, Atmo ducks/pauses
per behaviour. - Tap stop / pause on manual — Atmo resumes.
- Use playlist mode play/pause/next/prev — Atmo behaviour identical.
- Tap any waypoint card's play icon — manual file plays, Atmo ducks/pauses
-
Begleitmusik-Dialog Cancel/Save
- Open dialog while Atmo plays. Cancel — Atmo stops (intentional, kept
v2.0.6 behaviour; user cancelled the config session). - Open dialog. Change source. Save — service updates / rebuilds player.
- Open dialog while Atmo plays. Cancel — Atmo stops (intentional, kept
-
Tour switch
- Switch to a tour with Atmo disabled — Atmo stops, service ends.
- Switch to a tour with Atmo enabled — Atmo state remains stopped until
user presses Play.
-
Notification permission denied
- Revoke POST_NOTIFICATIONS in system settings.
- Start Atmo — no crash; service runs (silent notification absent).
-
Doze / Battery saver
- On a device with aggressive OEM Doze (Xiaomi, Huawei, OnePlus):
with Atmo running and screen off for 10 minutes, audio must persist.
- On a device with aggressive OEM Doze (Xiaomi, Huawei, OnePlus):
Regression checklist (preserved from v2.0.6)
- PTT pauses waypoint audio (
WaypointAudioInterruptionCoordinator) - PTT pauses & resumes Atmo (
AtmoResumeManager) - Drag-and-drop reorder of waypoints and tour tabs
- Wake Lock settings dialog and controller
- Map providers (OSM / fallback)
- Track-Editor and Drafts archive
- Backup import/export (ZIP)
- Map search (Nominatim)
- Audio library cross-tour
- OSM attribution
- ScrollableTabRow fix
- About dialog with PayPal link and version notice on first open
Known non-issues
MainActivity.launchWhenStarteddeprecation warning — pre-existing in
v2.0.6, not a runtime concern. Replaced only when Lifecycle APIs are
next refactored.Modifier.menuAnchor()deprecation — Compose Material3 1.3+; cosmetic.Polyline.fillColor/strokeColor/strokeWidthdeprecation — osmdroid
9.0.2; cosmetic.
Build details
package: de.waypointaudio versionCode=35 versionName=2.0.7 minSdk=26 targetSdk=35 compileSdk=35 SHA-256 cert: 7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46 Built with: Gradle 8.9, OpenJDK 21.0.11, Android SDK build-tools 35.0.0 APK size: 15.13 MBDownloads
-
-
released this
2026-05-15 14:32:02 +02:00 | 7 commits to main since this releaseGPS2Audio v2.0.6 — Atmo Background Playback Fix (WakeMode)
Artifacts
- Signed APK:
/home/user/workspace/GPS2Audio_v2_0_6_atmo_background_fix_signed.apk - Source zip:
/home/user/workspace/GPS2Audio_v2_0_6_atmo_background_fix_source.zip - This handoff:
/home/user/workspace/GPS2Audio_v2_0_6_atmo_background_fix_handoff.md
Build & Signing Verification
Field Value applicationId de.waypointaudioversionCode 34(up from33in v2.0.5)versionName 2.0.6minSdk / targetSdk 26 / 35 compileSdk 35 Build type release, signed Signer DN CN=GPS2Audio, OU=NesoHub, O=GPS2Audio, L=Germany, C=DESigner cert SHA-256 7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46Signer cert SHA-1 EB:92:2A:83:39:09:ED:6B:2D:CB:62:52:F0:CF:42:5F:26:DF:25:ADapksigner verify PASS (v2 scheme) Source baseline GPS2Audio_v2_0_5_atmo_service_hotfix_source.zipThe signature matches the GPS2Audio release keystore used since v1 → installs
as an update over any existing release-channel install (no uninstall required).uses-permissionset is unchanged vs. v2.0.5 (FOREGROUND_SERVICE_MEDIA_PLAYBACK,
POST_NOTIFICATIONS,WAKE_LOCK, etc. all preserved).Bug Report Summary
User report: After the v2.0.5 hotfix, pressing Atmo stream Play no longer
crashes (foreground-service start deadline fix worked) – but when the app is
minimized / sent to background, the Atmo music still stops/pauses within a
short time. Expected behavior: Atmo continues to play in the background until
the user (or one of the well-defined app rules) intentionally pauses or stops
it.Root Cause
In v2.0.4 we added
AtmoPlaybackService, aforegroundServiceType="mediaPlayback"
service that prevents Android from killing the app process while Atmo is alive
(this is what the v2.0.5 hotfix made crash-safe).However, a foreground service alone does NOT keep the device's CPU and
network awake when the screen turns off. The FGS guarantees the process
stays alive, but the CPU enters low-power state and the network gets
de-prioritised. For ExoPlayer this manifests as the audio pipeline stalling /
pausing shortly after screen-off — exactly the user's observation.The fix is the one ExoPlayer documents for exactly this case: call
setWakeMode(...)on the player. This makes ExoPlayer acquire a
PARTIAL_WAKE_LOCK(CPU stays on while playing) and, forWAKE_MODE_NETWORK,
additionally aWifiLockso the network connection survives screen-off /
Doze.In v2.0.5's
BackgroundMusicPlayer.buildPlayer()we did not call
setWakeMode(...). With no wake-mode set, ExoPlayer relies on the process
holding the wake state externally — which, in this app, nobody did once
FLAG_KEEP_SCREEN_ON(managed byWakeLockController) was cleared on the
Activity going toonPause. That clearing is correct (only the screen flag
should drop on minimise — see WakeLockController docstring), and was the
explicit design since v2.0.0. The missing piece was the audio-pipeline wake
lock owned by ExoPlayer itself.The Foreground Service notification IS being posted (v2.0.4 still works) — it's
just that without a wake lock, the CPU sleeps and the player has nothing to
decode with.Fix (exact changes)
File:
app/src/main/kotlin/de/waypointaudio/service/BackgroundMusicPlayer.ktInside
buildPlayer(settings), immediately aftersetHandleAudioBecomingNoisy(true),
we now set the wake mode on the ExoPlayer instance:// Wake-Mode auf Network: hält PARTIAL_WAKE_LOCK + WifiLock während // der Wiedergabe – damit Stream UND lokale Playlist auch bei // ausgeschaltetem Display weiterspielen (v2.0.6 fix). // Der Foreground-Service alleine verhindert nur, dass der Prozess // gekillt wird – er hält aber nicht die CPU/das Netzwerk wach. // ExoPlayer benötigt diesen Modus auf vielen Geräten (Android 12+, // Doze), damit die Audio-Pipeline beim Bildschirm-Aus nicht // einschläft. Der Lock wird automatisch wieder freigegeben, // sobald `release()` oder `stop()` aufgerufen wird. if (isStream) { p.setWakeMode(C.WAKE_MODE_NETWORK) } else { p.setWakeMode(C.WAKE_MODE_LOCAL) }- Stream URLs →
WAKE_MODE_NETWORK(PARTIAL_WAKE_LOCK + WifiLock). - Local playlist →
WAKE_MODE_LOCAL(PARTIAL_WAKE_LOCK only, no WifiLock,
because no network access is needed once tracks are loaded from local files).
ExoPlayer releases both locks automatically on
Player.stop()/
Player.release(), which is already wired intoBackgroundMusicPlayer.releasePlayer().
Pause/resume keep the player allocated (the wake lock is held only while
actually playing, ExoPlayer manages this internally).The existing
WAKE_LOCKpermission inAndroidManifest.xmlalready covers
this — it was originally declared "for ExoPlayer", as the manifest comment
states ("Keep CPU alive while playing audio in service"). It was just never
being claimed because the player never asked for it.File:
app/build.gradle.ktsversionCode 33 → 34versionName "2.0.5" → "2.0.6"
No other changes elsewhere.
What was deliberately NOT changed
AtmoPlaybackService— kept exactly as the v2.0.5 hotfix. The
service-start-deadline fix remains intact (still callsstartForegroundin
onCreateas the very first action).MainActivity.onPause/onResume— onlyWakeLockControlleris touched; it
manipulates the screen-on flag (FLAG_KEEP_SCREEN_ON), never the audio
wake lock. The two are deliberately separate concerns since v2.0.0.WakeLockController— untouched. The screen flag still drops on background
as designed. ExoPlayer now owns the CPU/wifi wake lock for audio, decoupled
from screen state.BackgroundMusicManager— untouched. Still an app-singleton owning the
player; not released on Activity lifecycle (only onrelease(), which
nothing currently calls).BackgroundMusicPlayer.beforeWaypointAudio / afterWaypointAudio—
untouched. PTT/waypoint interruption flows unchanged.AtmoResumeManager— untouched.WaypointAudioInterruptionCoordinator— untouched.ViewModel.onCleared()— does NOT release the music manager; only the
manual player. Confirmed (viewmodel/WaypointViewModel.kt:1533-1539).- No
LifecycleEventObserver/ProcessLifecycleOwner/DefaultLifecycleObserver
exists anywhere that would pause Atmo on background — grep-verified. - No
DisposableEffectreleases the Atmo player on screen disposal
(the two existingDisposableEffectblocks dispose only the map view and
draft-editor map view). AudioManager.requestAudioFocusis not called on the Atmo path; ExoPlayer's
audio-focus handling is left off (handleAudioFocus = false) so the app's
own coordinators decide pause/resume — unchanged.
How the fix satisfies the requirements
- Atmo stream and local playlist continue playing when minimized/backgrounded.
✅ ExoPlayer now holds PARTIAL_WAKE_LOCK (+ WifiLock for streams) for the
duration of playback, so CPU/network do not sleep when the screen turns
off. - Atmo still pauses/resumes for intentional rules (PTT, waypoint, manual
player, user pause/stop, disable, tour change). ✅ All those call paths go
throughBackgroundMusicManager/AtmoResumeManager/beforeWaypointAudio
– none touched. The wake lock is released by ExoPlayer whenpause()is
called and re-acquired onplay(), so pause flows are not impeded. - Wake Lock release on background must not affect audio. ✅
WakeLockControlleronly touchesFLAG_KEEP_SCREEN_ONon the Activity
window. The audio wake lock is now an independent lock owned by
ExoPlayer and unaffected byonActivityPaused(). - Foreground service is alive for the whole Atmo playback duration. ✅
Service start/stop is tied tobuildPlayer()/releasePlayer()(i.e. to
the existence of an ExoPlayer instance), not to Activity state. Pause /
duck / fade keep the player allocated, so the FGS keeps running. ExoPlayer
does not stop on Activity stop — the manager is an app-singleton, not a
ViewModel-scoped object. - Notification/service stops when Atmo is truly stopped/released by user
or no content remains. ✅releasePlayer()calls
AtmoPlaybackService.stop(context); pause does not. - Preserve all v2.0.5 features, no regressions. ✅ Manifest unchanged,
permissions unchanged, all PTT / waypoint / drag-reorder / Wake-Lock-
settings / map-providers / Track-Editor + drafts / backup import-export /
map-search / audio-library / OSM-attribution / TabRow fix / About-PayPal +
version-notice code paths untouched. - Version & signing. ✅ versionCode 33 → 34, versionName 2.0.5 → 2.0.6.
Signed with the existing GPS2Audio release keystore.apksigner verify
passes. SHA-256 fingerprint matches all prior release builds. - Artifacts produced at the requested paths. ✅ See "Artifacts" above.
Manual Test Plan
Primary regression (the actual user bug)
- Configure Atmo for a tour — either a local playlist OR a stream URL.
- Open the app, start Atmo via the music dialog test button or via the
start-tour flow. - Confirm the ongoing notification "Begleitmusik (Atmo)" / "Begleitmusik läuft"
appears. - Press Home to minimize the app. Audio must keep playing.
- Wait at least 2 minutes with the app in background.
Audio must still be playing the whole time. - (If possible) Lock the screen (Power button) and wait another 2 minutes.
Audio must still be playing. - Reopen the app — the mini-player / dialog must show the same track /
stream still playing, no glitch / restart, no double-track, no error.
PTT still pauses Atmo (v2.0.3 contract)
- With Atmo playing in foreground, hold PTT.
- Atmo pauses (or fades / ducks per settings).
- Release PTT — Atmo resumes from where it left off.
- Repeat with the app minimized / screen off — Atmo should still pause when
PTT is engaged and resume when released.
Waypoint audio still pauses Atmo (v2.0.3 contract)
- Manually trigger a waypoint audio from the list, or walk into a radius.
- Atmo follows the configured
WaypointMusicBehavior
(PAUSE_RESUME/FADE_OUT_IN/DUCK_UNDERLAY/CONTINUE_UNDERLAY). - After the waypoint audio finishes naturally, Atmo continues / resumes /
auto-starts perautoStartAfterWaypoint.
Manual player still pauses Atmo
- Tap a waypoint to play its audio manually.
- Atmo pauses for the duration, resumes after.
User stop / pause / disable still works
- Press Stop in the music dialog/mini-player → Atmo stops,
notification disappears within ~1 s, no resume. - Press Pause → Atmo pauses, notification stays (player still allocated),
pressing play resumes from same position. - Open Begleitmusik dialog → toggle "Aktiviert" off → save. Atmo stops,
notification disappears.
Tour change
- Switch to a tour with
enabled=falseAtmo. Atmo stops on tour switch,
notification disappears. - Switch to a tour with a different stream/playlist with
enabled=true→
new content takes over.
Headphone unplug
- With wired/Bluetooth headphones connected and Atmo playing, unplug
(or disconnect Bluetooth). Atmo pauses (does not stop) —
setHandleAudioBecomingNoisy(true)unchanged. - Press play in dialog/mini-player to resume.
Wake Lock settings
- Set Wake Lock mode to "Active tour only". Start the tour, screen stays on.
- Press Power to lock the screen. The screen-on flag releases (expected),
but Atmo keeps playing (audio wake lock is independent). - Unlock — Atmo is still playing, screen-on flag re-applies if mode dictates.
Battery / Doze sanity
- On a stock Android device with Battery Optimisation enabled for the app,
Atmo should still play continuously in background —PARTIAL_WAKE_LOCK
acquired by a foreground-service-backed app is an explicitly permitted use
case on all Android versions.
Regression Checklist (v2.0.x feature contract)
- PTT pauses waypoint audio (v2.0.3 feature) — unchanged code path.
- PTT pauses Atmo with resume —
AtmoResumeManager.notifyInterruptStart
still callspauseMusic(), only releasing service on hard stop. - Atmo plays in background — v2.0.4 FGS + v2.0.6 wake mode.
- No FGS-deadline crash on Atmo stream Play — v2.0.5 hotfix preserved.
- Drag-and-drop reorder of waypoints.
- Wake Lock settings (Never / App foreground / Active tour) — unchanged
semantics, audio decoupled. - Map providers — unchanged.
- Track-Editor + Drafts — unchanged.
- Backup import/export — unchanged.
- Map search — unchanged.
- Audio library — unchanged.
- OSM attribution badge on map — unchanged.
- TabRow fix — unchanged.
- About dialog PayPal link + Matrix QR — unchanged.
- Version-notice popup (once per new versionCode) — fires on first launch
of 2.0.6 since versionCode bumped 33 → 34.
Build Reproduction
unzip GPS2Audio_v2_0_6_atmo_background_fix_source.zip -d /tmp/v2.0.6 cd /tmp/v2.0.6/work cat > keystore.properties <<EOF storeFile=/path/to/GPS2Audio_release_keystore.jks storePassword=... keyAlias=gps2audio_release keyPassword=... EOF export ANDROID_HOME=/path/to/android-sdk gradle :app:assembleRelease # Output: app/build/outputs/apk/release/app-release.apkTested toolchain: Gradle 8.9, JDK 21 (OpenJDK), Android SDK with platform-35
and build-tools 35.0.0.Downloads
- Signed APK:
-
released this
2026-05-15 14:31:53 +02:00 | 7 commits to main since this releaseGPS2Audio v2.0.5 — Atmo Foreground-Service Hotfix
Artifacts
- Signed APK:
/home/user/workspace/GPS2Audio_v2_0_5_atmo_service_hotfix_signed.apk - Source zip:
/home/user/workspace/GPS2Audio_v2_0_5_atmo_service_hotfix_source.zip - This handoff:
/home/user/workspace/GPS2Audio_v2_0_5_atmo_service_hotfix_handoff.md
Build & Signing Verification
Field Value applicationId de.waypointaudioversionCode 33(up from32in v2.0.4)versionName 2.0.5minSdk / targetSdk 26 / 35 compileSdk 35 Build type release, signed Signer DN CN=GPS2Audio, OU=NesoHub, O=GPS2Audio, L=Germany, C=DESigner cert SHA-256 7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46Signer cert SHA-1 EB:92:2A:83:39:09:ED:6B:2D:CB:62:52:F0:CF:42:5F:26:DF:25:ADSignature matches the GPS2Audio release keystore used since v1 → installs as an
update over any existing release-channel install (no uninstall required).uses-permissionset is unchanged vs. v2.0.4 (FOREGROUND_SERVICE_MEDIA_PLAYBACK,
POST_NOTIFICATIONS, WAKE_LOCK, etc. all preserved).Bug Report Summary (what crashed)
After pressing the Atmo stream play button the app crashed with:
android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord ... de.waypointaudio/.service.AtmoPlaybackServiceThis is the Android 12+ deadline crash: when an app calls
Context.startForegroundService(intent), the system gives the service a hard
window (~5 s) to callService.startForeground(id, notification). Missing the
deadline → the platform kills the app process.Root Cause
In v2.0.4 (
app/src/main/kotlin/de/waypointaudio/service/AtmoPlaybackService.kt)
the deadline-relevantstartForeground(...)call lived inside
onStartCommand, gated byintent?.action == ACTION_START, and wrapped in
runCatching {}that silently swallowed any throwable. Two concrete ways
this could miss the deadline:- Non-START intent on the same
startForegroundService()cycle. Both
start()andstop()usedstartForegroundService()/startService(), but
thewhen (intent?.action)branch forACTION_STOP(or anullintent on
re-delivery /START_STICKY_COMPATIBILITYscenarios) never reached
startForeground. The system had been told "this is a FGS" but the service
never confirmed it. Deadline missed → crash. runCatchingswallowing astartForegroundthrowable. If notification
construction orstartForegrounditself threw (e.g. transient
RemoteExceptionfromNotificationManager, or any backwards-compatibility
edge fromsetForegroundServiceType), the failure was logged and the
service kept running without ever callingstartForegroundagain.
Deadline missed → crash.
The screenshot reported by the user is reproducible on Android 12+ devices
where the first start path was for a stream and any of the above branches
triggered.Fix (exact changes)
File:
app/src/main/kotlin/de/waypointaudio/service/AtmoPlaybackService.ktstartForeground()is now the very first thing the service does, in
onCreate(), with a minimal-valid notification (no network/player work,
no Intent parsing). This satisfies the system deadline unconditionally,
beforeonStartCommandis even invoked.- Re-arm in
onStartCommand: if the firststartForegroundattempt
threw, we retry as the very first action ofonStartCommand(if (!foregroundStarted)), before any branching on the action. - Notification channel created before
startForegroundon Android O+
(already true, kept; added aSDK < Oearly-return so the call is safe
on older devices). - Notification refresh on ACTION_START uses
NotificationManager.notify
with the same ID — keeps the current foreground notification valid even if
the rebuild with real title/source throws. stop()companion now also usesstartForegroundService()on Android
O+ instead ofstartService(). This avoidsIllegalStateExceptionif the
stop is requested while the app is in a state the platform considers
"background", and it guarantees the service still callsstartForeground
immediately (inonCreate) before stopping itself — so the stop path can
never be the cause of a missed-deadline crash either.onStartCommandnow handlesnull/unknown actions by stopping the
service (after the mandatorystartForeground) rather than leaving the
service running without any work.foregroundStartedflag prevents redundantstartForegroundcalls on
repeated starts and is reset instopForegroundCompat.
foregroundServiceType=mediaPlaybackand the
FOREGROUND_SERVICE_MEDIA_PLAYBACKpermission are preserved. The
POST_NOTIFICATIONS permission was already declared and is not hard-required
for a foreground-service notification — denying it merely hides the
notification, the FGS still runs.File:
app/build.gradle.ktsversionCode = 33,versionName = "2.0.5".
No changes elsewhere. All v2.0.4 behaviors preserved:
- Atmo continues playing in background while screen is off or app is minimized
(FGS still active across pause/duck/fade, as before). - PTT and Waypoint-Audio pause/resume semantics unchanged
(BackgroundMusicPlayer.beforeWaypointAudio/afterWaypointAudio
untouched; FGS deliberately stays alive across pauses). - Manual stop / tour switch still triggers
BackgroundMusicPlayer.releasePlayer()→AtmoPlaybackService.stop()→
notification + service torn down. - All previously delivered features intact: drag-and-drop reorder, Wake Lock
while recording / playing, multiple map providers, Track-Editor, Backup
import/export, map search, audio library, OSM attribution, TabRow fix,
About PayPal link + version notice dialog.
Manual Test Plan
These tests target the exact crash and the surrounding Atmo lifecycle. Use a
device on Android 12+ (deadline-enforcing).-
Atmo stream play — no crash (primary repro).
- Open Tour-Settings → Begleitmusik (Atmo) → switch source to Stream URL,
enter a valid stream (e.g. an Icecast.mp3URL). - Press the Play button.
- Expected: audio starts within a few seconds; no crash; status bar
shows an ongoing "Atmo / Stream — " notification.
- Open Tour-Settings → Begleitmusik (Atmo) → switch source to Stream URL,
-
Background continuation.
- With Atmo playing, press Home; then power off the screen.
- Expected: audio keeps playing for at least 60 s; FGS notification
remains visible in the status bar.
-
Stop / disable.
- Re-open app, press Stop on the Atmo controls (or disable Atmo in
Tour-Settings). - Expected: audio stops; foreground notification disappears; no crash.
Re-pressing Play starts a new stream cleanly.
- Re-open app, press Stop on the Atmo controls (or disable Atmo in
-
PTT interplay (do not regress).
- Start Atmo, then start a PTT session.
- Expected: Atmo behavior follows configured Wegpunkt-Verhalten
(pause/duck/fade/continue) for the PTT duration; FGS notification stays
ongoing; Atmo resumes (or stays ducked) on PTT end exactly as in v2.0.4.
-
Waypoint audio interplay.
- Place a waypoint with an audio file along a recorded track; trigger it
while Atmo is playing. - Expected: Atmo behaves per configured behavior; FGS stays up; Atmo
resumes after waypoint audio finishes, matching v2.0.4 behavior.
- Place a waypoint with an audio file along a recorded track; trigger it
-
Local playlist source.
- Switch Atmo source to Local playlist, pick 2–3 tracks, press Play.
- Expected: plays; notification reads "Atmo / Playlist — N Titel"; no
crash; track skip / shuffle behave as before.
-
POST_NOTIFICATIONS denied (Android 13+).
- Long-press the Atmo notification (or use App Info → Notifications) and
disable notifications for GPS2Audio, then start Atmo. - Expected: audio still plays in foreground (FGS keeps the process
alive); no crash; notification simply suppressed by user choice.
- Long-press the Atmo notification (or use App Info → Notifications) and
-
Repeated rapid Start/Stop.
- Toggle Atmo Play/Stop several times in quick succession.
- Expected: no crash; notification appears/disappears cleanly; service
ends after final Stop.
-
Version check.
- Settings → About → version line reads
2.0.5 (33).
- Settings → About → version line reads
Files Touched (delta vs. v2.0.4 baseline)
app/build.gradle.kts (versionCode 32→33, versionName 2.0.4→2.0.5) app/src/main/kotlin/de/waypointaudio/service/AtmoPlaybackService.kt (rewritten: always-on startForeground, null-intent handling, stop via startForegroundService)Nothing else changed.
Downloads
- Signed APK:
-
released this
2026-05-15 14:31:10 +02:00 | 7 commits to main since this releaseGPS2Audio v2.0.4 — Atmo Background Playback Fix
Overview
- Version:
2.0.4(versionCode32, ↑ from 31) - Baseline:
GPS2Audio_v2_0_3_ptt_waypoint_pause_source.zip - Build type: signed release, RSA-4096 (
gps2audio_releasealias), SHA-256
fingerprint matches the v2.0.x release keystore on file.
Artifacts
- APK:
/home/user/workspace/GPS2Audio_v2_0_4_atmo_background_signed.apk - Source:
/home/user/workspace/GPS2Audio_v2_0_4_atmo_background_source.zip - Handoff:
/home/user/workspace/GPS2Audio_v2_0_4_atmo_background_handoff.md
Root cause
v2.0.3 starts Atmo (Begleitmusik) through
BackgroundMusicManager/BackgroundMusicPlayer,
an app-singleton that owns an ExoPlayer instance. No code in MainActivity,
DisposableEffect, Lifecycle observer, oronCleared()callspause()/stop()on
the player when the app moves to background — i.e. the app was not explicitly
pausing Atmo on minimize.However, when the activity goes to background and the screen turns off, the
Android process is moved to a "cached / restricted background" state. Because no
foreground service of typemediaPlaybackwas holding the audio session,
Android terminates or freezes the process within ~10–30 s, which the user
observes as "Atmo stopped when minimized". When the activity is brought back to
the front, the user manually re-enters Atmo or the GPS service rebinds and
restarts the player — looking like "starts again on reopen".This is the canonical Android behaviour for non-foreground audio playback on
API 26+ (and tightens on Android 12 / 14). The robust, documented fix is to run
aforegroundServiceType="mediaPlayback"service whenever Atmo is alive.Exact change
1. New foreground service
AtmoPlaybackService- File:
app/src/main/kotlin/de/waypointaudio/service/AtmoPlaybackService.kt foregroundServiceType="mediaPlayback"(uses
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACKon API 34+).- Owns no playback logic — only signals the OS that the app is legitimately
playing media in the background and posts a low-priority, silent ongoing
notification (channelatmo_playback_channel, importance LOW). start(ctx,title,source)/stop(ctx)companion helpers;stopForeground
withSTOP_FOREGROUND_REMOVEremoves the notification on stop.
2.
BackgroundMusicPlayerhooks the service lifecycle to the player lifetimebuildPlayer()→AtmoPlaybackService.start(...)immediately after building
the ExoPlayer (i.e. when Atmo actually starts playing for a tour / test).releasePlayer()→AtmoPlaybackService.stop(ctx).- Pause/duck/fade do NOT stop the service. The service is tied to the
existence of an active ExoPlayer instance, not to play/pause state. This is
what preserves PTT, waypoint, and manual-player interruption resumes —
during those, the player is paused but still allocated, so the foreground
service keeps the process alive and resume works exactly as in v2.0.3.
3. ExoPlayer audio plumbing
setAudioAttributes(USAGE_MEDIA / AUDIO_CONTENT_TYPE_MUSIC, handleAudioFocus=false)
— declares the stream as media. Audio-focus arbitration stays with the
app-internal coordinator (AtmoResumeManager,WaypointAudioInterruptionCoordinator)
exactly as before; ExoPlayer does not auto-pause on transient focus loss.setHandleAudioBecomingNoisy(true)— when headphones are unplugged, ExoPlayer
pauses (not stops). The foreground service stays up, so the user can
resume from PTT/waypoint logic or the manual play button.
4. Manifest
- Added permission
android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK
(Android 14+ requirement). - Declared the new service with
foregroundServiceType="mediaPlayback".
5. Strings (German UI, consistent with rest of app)
atmo_notification_channel_name— "Begleitmusik (Atmo)"atmo_notification_channel_descriptionatmo_notification_default_title— "Begleitmusik läuft"atmo_notification_default_source— fallback when stream/playlist label
is blank.
6. Version bump
versionCode 31 → 32,versionName 2.0.3 → 2.0.4inapp/build.gradle.kts.
What was deliberately NOT changed
MainActivity.onPause/onResume— only WakeLockController is touched there;
it manipulates the screen-on flag, never the audio. Confirmed via grep:
nopauseMusic/stopMusic/player.pause/player.stopis called from any
Activity / Compose lifecycle path.BackgroundMusicManager.loadTour()— still stops music on intentional
tour change (previousTour != tourNamewithenabled=false).BackgroundMusicManager.saveAndApplySettings()— still stops music when the
user disables Begleitmusik.AtmoResumeManager(PTT pause/resume) — untouched.WaypointAudioInterruptionCoordinator— untouched.BackgroundMusicPlayer.beforeWaypointAudio / afterWaypointAudio— untouched.WakeLockController— untouched. The window flag still drops on background
(as designed in v2.0.0); audio is now decoupled from that flag.
Manual test plan
Primary regression (the actual user bug)
- Set up Atmo for a tour (either local playlist or HTTP stream).
- Open the app, start Atmo via the music dialog test button or via the
start-tour flow. - Confirm a small ongoing notification "Begleitmusik (Atmo)" appears.
- Press the Home button to minimize. Audio must keep playing.
- Lock the screen. Wait at least 60 seconds with the screen off.
- Audio must still be playing. Unlock the screen — still playing.
- Reopen the app — the music dialog / mini-player must show "Playing"
with the same track / stream still running, no glitch / restart.
PTT still pauses Atmo (v2.0.3 contract)
- With Atmo playing in foreground, hold PTT.
- Atmo pauses (or fades / ducks per settings).
- Release PTT — Atmo resumes from where it left off.
- Repeat with the app minimized: hold PTT (e.g. via headset key if mapped),
Atmo should still pause/resume.
Waypoint audio still pauses Atmo (v2.0.3 contract)
- Manually trigger a waypoint audio from the list, or walk into a radius.
- Atmo follows the configured
WaypointMusicBehavior
(PAUSE_RESUME / FADE_OUT_IN / DUCK_UNDERLAY / CONTINUE_UNDERLAY). - After the waypoint audio finishes naturally, Atmo continues / resumes /
auto-starts perautoStartAfterWaypoint.
Manual player still pauses Atmo
- Tap a waypoint to play its audio manually.
- Atmo pauses for the duration, resumes after.
User stop / pause / disable still works
- Press stop in the music dialog → Atmo stops, notification disappears,
no resume. - Press pause → Atmo pauses, notification stays (player still allocated),
pressing play resumes. - Open Begleitmusik dialog → toggle "Aktiviert" off → save. Atmo stops,
notification disappears.
Tour change
- Switch to a tour that has
enabled=false. Atmo stops on tour switch.
Headphone unplug
- With wired/Bluetooth headphones connected and Atmo playing, unplug
(or disconnect Bluetooth). Atmo pauses (does not stop) — resume by
pressing play in the dialog/mini-player.
Wake Lock interaction
- Set Wake Lock mode to "Active tour only". Start the tour, screen stays on.
- Press Power to lock the screen. The screen-on flag releases (expected),
but Atmo keeps playing. - Unlock — Atmo is still playing, screen-on flag re-applied if mode dictates.
Regression checklist
- PTT pauses waypoint audio (v2.0.3 feature) — unchanged code path.
- PTT pauses Atmo with resume —
AtmoResumeManager.notifyInterruptStart
still callspauseMusic(), only releasing service on hard stop. - Drag-and-drop reorder of waypoints — unrelated, untouched.
- Wake Lock settings (NEVER / APP_FOREGROUND / ACTIVE_TOUR) — untouched.
- Map provider selection — untouched.
- Track-Editor / drafts — untouched.
- Backup import / export — untouched.
- Map search (everywhere / current view) — untouched.
- Audio library playlist (drag/drop, picker) — untouched.
- OSM attribution badge — untouched.
- TabRow fix — untouched.
- About / PayPal / version notice — untouched.
- Signed-release path (keystore.properties + env vars) — unchanged.
- Version notice will trigger again because versionCode advanced (31 → 32).
- APK targets SDK 35, min SDK 26, includes the new
FOREGROUND_SERVICE_MEDIA_PLAYBACKpermission and the new service in
manifest (verified viaaapt dump badging). - Signature: SHA-256
7B:0E:A4:D5:…:BB:46matches keystore record (verified
viaapksigner verify --print-certs).
Limitations
- The user will now see a persistent low-priority "Begleitmusik (Atmo)"
notification while Atmo is loaded. This is required by Android 12+ to keep
background audio reliably alive and is the minimal acceptable solution.
It is silent (setSilent(true), importance LOW,setOnlyAlertOnce) and
disappears the moment the user presses Stop or Begleitmusik is disabled. - The notification has no media-style transport controls. A future iteration
could add play/pause/next/previous through aMediaSessionService; left out
intentionally to keep the diff minimal and avoid touching the v2.0.3 PTT /
waypoint pause logic.
Build verification
package: name='de.waypointaudio' versionCode='32' versionName='2.0.4' sdkVersion:'26' targetSdkVersion:'35' uses-permission: …FOREGROUND_SERVICE_MEDIA_PLAYBACK service: de.waypointaudio.service.AtmoPlaybackService (mediaPlayback) Signer #1 SHA-256: 7b0ea4d5528605173d299d7b268e7ab8768dd504ec15b66a52d47b4257b3bb46 BUILD SUCCESSFUL (gradle 8.9, AGP via wrapper config, SDK 35 build-tools 35.0.0)Downloads
- Version:
-
released this
2026-05-15 14:30:40 +02:00 | 7 commits to main since this releaseGPS2Audio v2.0.3 — PTT pauses waypoint audio
Summary
This release adds a single focused behavior on top of v2.0.2:
When the user starts PTT (Push-To-Talk) while a waypoint audio is playing
(either GPS-triggered or manually triggered), the waypoint audio is paused
immediately so that PTT and the waypoint audio are never heard at the same
time. When the user releases PTT, the waypoint audio resumes exactly where
it left off — but only if it was already playing when PTT started.All other v2.0.2 functionality is preserved unchanged (drag-reorder, tour
counters, backup import/export, atmo pause/resume during PTT, manual
playlist & single-item playback, etc.). There are no UI changes.Build & signing
- Baseline:
GPS2Audio_v2_0_2_drag_reorder_source.zip applicationId:de.waypointaudio(unchanged)versionCode:31(was 30 in v2.0.2)versionName:2.0.3- Signed with
/home/user/workspace/GPS2Audio_release_keystore.jks,
aliasgps2audio_release. - Verified
apksigner verify --print-certs:- SHA-256:
7B0EA4D5528605173D299D7B268E7AB8768DD504EC15B66A52D47B4257B3BB46
(matches keystore credentials file → upgrade-compatible with previous
signed releases).
- SHA-256:
- Build host: Gradle 8.9, AGP per
libs.versions.toml, Android SDK 35.
Artifacts
Path Contents /home/user/workspace/GPS2Audio_v2_0_3_ptt_waypoint_pause_signed.apkSigned release APK /home/user/workspace/GPS2Audio_v2_0_3_ptt_waypoint_pause_source.zipFull source tree (no keystore, no local.properties, no build outputs)/home/user/workspace/GPS2Audio_v2_0_3_ptt_waypoint_pause_handoff.mdThis document Behavioural contract
- PTT start while a waypoint audio is playing → that waypoint audio is
paused (itsMediaPlayerposition is retained). - PTT start while no waypoint audio is playing → no pause/resume happens
for waypoint audio (Atmo handling continues to work as before). - PTT end → only those waypoint audios that were paused by this PTT
session are resumed. Anything that was already stopped/finished is not
resurrected. - Duplicate PTT state events (e.g. another
startPtt()while already
active) → no repeated pause/resume. Idempotency is enforced both in
LivePttManagerand inside
WaypointAudioInterruptionCoordinator.beginInterruption / endInterruption. - Atmo (background music) behaviour is unchanged: it is still paused on
PTT start and resumed on PTT end via the existingAtmoResumeManager. - GPS-triggered waypoints fired during PTT continue to be queued and
played after PTT ends (existingpendingPttWaypointflow in
WaypointLocationService). Unchanged. - Manual playback during PTT is still blocked by the existing
"PTT-Sperre" guard inWaypointViewModel.manualPlayPauseand
manualPlaySingle. Unchanged.
Implementation
New file
app/src/main/kotlin/de/waypointaudio/service/WaypointAudioInterruptionCoordinator.kt- Process-wide
objectsingleton. - Two
Sources:GPS(location serviceAudioPlayer),MANUAL
(ManualAudioPlayerinWaypointViewModel). - Each active player registers with three lambdas:
isCurrentlyPlaying,
pause,resume. beginInterruption()pauses all currently-playing sources and remembers
which ones it paused.endInterruption()resumes exactly those.- Self-deduplicates against double-
beginInterruption()/ double-
endInterruption()calls (no-op if state is wrong). - Edge case: if a source registers during an active interruption and is
already playing, it is paused immediately and marked for resume on
endInterruption().
- Process-wide
Changed files
app/src/main/kotlin/de/waypointaudio/service/AudioPlayer.kt- Added
pause(),resume(),isPlaying(property),hasContent
(property). Existingplay()/stop()semantics unchanged.
- Added
app/src/main/kotlin/de/waypointaudio/service/ManualAudioPlayer.kt- Added
resume()to complement existingpause(). Theplay()-resumes-
paused-track path is untouched (manual UI resume still works).
- Added
app/src/main/kotlin/de/waypointaudio/service/WaypointLocationService.ktplayWaypointAudio()now registers the runningaudioPlayerwith the
coordinator (Source.GPS) and unregisters on natural completion, on
error, and onstopTracking().
app/src/main/kotlin/de/waypointaudio/service/LivePttManager.ktpauseAtmo()now also calls
WaypointAudioInterruptionCoordinator.beginInterruption().restoreAtmo()now also calls
WaypointAudioInterruptionCoordinator.endInterruption()before the
Atmo resume, so the waypoint audio comes back first and Atmo follows
its existing logic.
app/src/main/kotlin/de/waypointaudio/viewmodel/WaypointViewModel.kt- Registers
manualPlayerwith the coordinator (Source.MANUAL) on VM
creation and unregisters inonCleared().isCurrentlyPlayingreads
the underlyingMediaPlayer.isPlayingso that only an actively-playing
manual track is paused by PTT — a paused or stopped manual track is
left alone.
- Registers
app/build.gradle.ktsversionCode = 31,versionName = "2.0.3".
Files NOT changed
No XML resources, no Compose UI, no manifests, no permissions, no Atmo
logic, no backup/import code, no map code, no drag-reorder code, no
PTT UI (LivePttCard.kt).Manual test plan
Install over an existing v2.0.2 build of
de.waypointaudio(signature
matches, so direct update works without uninstall).-
Sanity: nothing is playing, press PTT, release.
- Expected: PTT mic activates and deactivates normally. No waypoint
audio appears. No regression.
- Expected: PTT mic activates and deactivates normally. No waypoint
-
Atmo only, PTT toggles.
- Start Atmo (Begleitmusik) for a tour. Press PTT → Atmo pauses. Release
PTT → Atmo resumes. (Pre-existing behaviour, must still work.)
- Start Atmo (Begleitmusik) for a tour. Press PTT → Atmo pauses. Release
-
Manual single-item waypoint, PTT toggles.
- In waypoint list, press Play on a card. While audio plays, press PTT
start. Audio must go silent immediately. Release PTT. Audio
resumes from the same position. Pause icon state on the card is
unchanged across the cycle.
- In waypoint list, press Play on a card. While audio plays, press PTT
-
Manual playlist, PTT toggles.
- From the manual player bar, start playback of the tour. While playing,
press PTT. Audio pauses. Release PTT. Audio resumes from the same
spot.
- From the manual player bar, start playback of the tour. While playing,
-
GPS waypoint trigger + PTT mid-playback.
- Walk into a waypoint radius so its audio fires automatically. While
it plays, press PTT. Waypoint audio pauses. Release PTT.
Waypoint audio resumes and finishes;playCountincrements only
once on natural completion.
- Walk into a waypoint radius so its audio fires automatically. While
-
GPS waypoint trigger during PTT (existing queue path).
- Press PTT and hold. While PTT is active, cross into a waypoint
radius. The waypoint should NOT play yet (existing behaviour).
Release PTT → the queued waypoint plays normally.
- Press PTT and hold. While PTT is active, cross into a waypoint
-
Duplicate PTT state events.
- Rapidly toggle PTT or invoke
togglePtt()repeatedly while audio is
playing. The waypoint audio should pause exactly once per real
activation and resume exactly once per real deactivation — never
"double-resume" into a runaway.
- Rapidly toggle PTT or invoke
-
PTT during a manually paused track.
- Start a manual single-item audio, press Pause on the card. Press PTT
start → release. The track must NOT auto-start on PTT release (it
was already paused before PTT, so the coordinator does nothing for
it).
- Start a manual single-item audio, press Pause on the card. Press PTT
-
Stop GPS service or close app while paused by PTT.
- During PTT-induced pause, kill the GPS service or background the
app. Release PTT → coordinator's resume call is a no-op (player
released). No crash.
- During PTT-induced pause, kill the GPS service or background the
-
Atmo + manual waypoint + PTT.
- Start Atmo and a manual waypoint together (waypoint ducks Atmo per
existing logic). Press PTT → both Atmo and the waypoint audio go
silent. Release PTT → both come back. Atmo's resume is still
gated by its own rules inAtmoResumeManager.
- Start Atmo and a manual waypoint together (waypoint ducks Atmo per
Known limitations
- The pause is implemented on top of Android's
MediaPlayer.pause()/
.start(). On a tiny number of buggy device firmwares this can introduce
a sub-second click when resuming. The existing manual-pause UI uses the
same primitive, so this matches user expectations. - If the user manually stops a waypoint audio during PTT (e.g. through
some future UI), the coordinator's resume call becomes a safe no-op
because the underlyingMediaPlayeris null. Current UI does not
expose any such control while PTT is held, so this is theoretical.
Rollback
Replace the installed APK with
GPS2Audio_v2_0_2_drag_reorder_signed.apk.
versionCode30 < 31 means Android may require uninstall to downgrade.Downloads
- Baseline: