fix: say why playing a recording on the air did nothing

"I click play and nothing happens" is what an operator sees when a function
returns an error into a promise the interface swallows. Every refusal in the
playback path is now logged as well as returned, and each names its own cause —
audio not initialised, the take still running, the take empty, the device
rejecting the file.

One of those was not even checked: playing back needs an OUTPUT device INTO the
radio, which is a different setting from the recorder's input and is very likely
unset on a station that has only ever recorded. It now refuses with that
sentence instead of failing somewhere inside the player.
This commit is contained in:
2026-07-30 23:27:30 +02:00
parent 5a4ad800b3
commit f4956a63bb
2 changed files with 22 additions and 3 deletions
+18 -1
View File
@@ -7710,16 +7710,32 @@ func (a *App) QSOAudioResume() bool {
// loop. Stopping is the operator's statement that the take is finished, and it
// is one click they have already made.
func (a *App) QSOAudioPlayOnAir() error {
// Every refusal below is LOGGED as well as returned. "I click play and
// nothing happens" is what an operator sees when a function returns an error
// into a promise the interface swallowed — and the difference between "no
// output device configured" and "the take was empty" cannot be guessed from
// the outside.
if a.qsoRec == nil || a.audioMgr == nil {
applog.Printf("qso-rec: play refused — audio subsystem not initialised")
return fmt.Errorf("audio not initialized")
}
if !a.qsoRec.Paused() {
applog.Printf("qso-rec: play refused — the recording is still running (stop it first)")
return fmt.Errorf("stop the recording before playing it on the air")
}
pcm, err := a.qsoRec.PeekQSO()
if err != nil {
applog.Printf("qso-rec: play refused — %v", err)
return err
}
cfgEarly, _ := a.GetAudioSettings()
if strings.TrimSpace(cfgEarly.ToRadio) == "" {
// The recorder needs an INPUT from the radio; playing back needs an
// OUTPUT into it, which is a different device and may well be unset on a
// station that only ever recorded.
applog.Printf("qso-rec: play refused — no output device to the radio configured (Settings → Audio)")
return fmt.Errorf("no audio output to the radio is configured — set it in Settings → Audio")
}
// A plain WAV in the data dir, overwritten each time: the player takes a
// path, and MP3 encoding would add seconds to something the operator is
// waiting on with the transmitter keyed.
@@ -7728,7 +7744,7 @@ func (a *App) QSOAudioPlayOnAir() error {
return fmt.Errorf("prepare playback: %w", err)
}
cfg, _ := a.GetAudioSettings()
cfg := cfgEarly
if err := a.pttKey(cfg); err != nil {
applog.Printf("qso-rec: PTT on failed before playback: %v", err)
// Keep going — the audio still reaches the rig and the operator may use VOX.
@@ -7738,6 +7754,7 @@ func (a *App) QSOAudioPlayOnAir() error {
a.pttMu.Unlock()
}
if err := a.audioMgr.Play(cfg.ToRadio, path, cfg.TXGain); err != nil {
applog.Printf("qso-rec: playback on %q failed: %v", cfg.ToRadio, err)
a.pttMu.Lock()
keyed := a.dvkPttKeyed
gen := a.pttGen
+4 -2
View File
@@ -4,11 +4,13 @@
"date": "",
"en": [
"FlexRadio: in split, OpsLog stays on the receive slice instead of following the transmitter.",
"QSO recording works on CW again. Your own sidetone is not in the file, the other station is."
"QSO recording works on CW again. Your own sidetone is not in the file, the other station is.",
"Playing a recording on the air now says why when it cannot — most often no audio output to the radio is configured."
],
"fr": [
"FlexRadio : en split, OpsLog reste sur la slice de réception au lieu de suivre l'émission.",
"L'enregistrement des QSO fonctionne à nouveau en CW. Votre propre signal d'écoute n'est pas dans le fichier, celui du correspondant l'est."
"L'enregistrement des QSO fonctionne à nouveau en CW. Votre propre signal d'écoute n'est pas dans le fichier, celui du correspondant l'est.",
"L'émission d'un enregistrement indique désormais pourquoi elle échoue — le plus souvent aucune sortie audio vers la radio n'est configurée."
]
},
{