diff --git a/app.go b/app.go index 3ae8307..4dda0ae 100644 --- a/app.go +++ b/app.go @@ -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 diff --git a/changelog.json b/changelog.json index 35ee29b..7c70b8d 100644 --- a/changelog.json +++ b/changelog.json @@ -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." ] }, {