feat: While recording a QSO the count is clickable to start the recording again

This commit is contained in:
2026-06-22 21:55:21 +02:00
parent 678787ec62
commit 81c60628c6
5 changed files with 49 additions and 3 deletions
+12
View File
@@ -4237,6 +4237,18 @@ func (a *App) QSOAudioRestart() bool {
return a.qsoRec.Active() return a.qsoRec.Active()
} }
// QSOAudioResetClock restarts the in-progress recording from zero, dropping
// everything captured so far (pre-roll included). Lets the operator click the
// REC timer to record only their own exchange when the station was already in a
// long QSO before they entered the call. Returns whether a recording is active.
func (a *App) QSOAudioResetClock() bool {
if a.qsoRec == nil {
return false
}
a.qsoRec.ResetQSOClock()
return a.qsoRec.Active()
}
// QSOAudioCancel drops the in-progress recording (callsign cleared, QSO // QSOAudioCancel drops the in-progress recording (callsign cleared, QSO
// abandoned without logging). // abandoned without logging).
func (a *App) QSOAudioCancel() { func (a *App) QSOAudioCancel() {
+15 -3
View File
@@ -32,7 +32,7 @@ import {
GetDVKMessages, GetDVKStatus, DVKPlay, DVKStop, GetDVKMessages, GetDVKStatus, DVKPlay, DVKStop,
StartCWDecoder, StopCWDecoder, SetCWDecoderPitch, StartCWDecoder, StopCWDecoder, SetCWDecoderPitch,
ChatAvailable, GetChatHistory, SendChatMessage, GetOnlineOperators, ChatAvailable, GetChatHistory, SendChatMessage, GetOnlineOperators,
QSOAudioBegin, QSOAudioCancel, QSOAudioRestart, QSOAudioBegin, QSOAudioCancel, QSOAudioRestart, QSOAudioResetClock,
GetAwardDefs, GetAwardDefs,
GetUIPref, GetUIPref,
ReportLiveActivity, ReportLiveActivity,
@@ -507,6 +507,12 @@ export default function App() {
if (forCall !== undefined) recordingCallRef.current = forCall.trim().toUpperCase(); if (forCall !== undefined) recordingCallRef.current = forCall.trim().toUpperCase();
QSOAudioRestart().then((active) => { setRecording(active); setRecTick((t) => t + 1); }).catch(() => {}); QSOAudioRestart().then((active) => { setRecording(active); setRecTick((t) => t + 1); }).catch(() => {});
}; };
// Reset the recording to zero (drop everything so far, pre-roll included) —
// bound to clicking the REC timer. Use when the station was already in a long
// QSO and you only want your own exchange in the file.
const resetRecordingClock = () => {
QSOAudioResetClock().then((active) => { setRecording(active); setRecTick((t) => t + 1); }).catch(() => {});
};
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [filterCallsign, setFilterCallsign] = useState(''); const [filterCallsign, setFilterCallsign] = useState('');
// Advanced filter builder (replaces the old band/mode dropdowns). // Advanced filter builder (replaces the old band/mode dropdowns).
@@ -2247,10 +2253,16 @@ export default function App() {
</Label> </Label>
<div className="relative"> <div className="relative">
{recording && RECORDABLE_MODES.has(mode.toUpperCase()) && ( {recording && RECORDABLE_MODES.has(mode.toUpperCase()) && (
<span className="absolute right-2 top-1/2 -translate-y-1/2 z-10 inline-flex items-center gap-1 text-[10px] font-semibold tabular-nums text-red-600 whitespace-nowrap pointer-events-none"> <button
type="button"
onMouseDown={(e) => e.preventDefault()}
onClick={resetRecordingClock}
title="Click to restart the recording from 0 — drops everything captured so far (incl. pre-roll) so the file holds only your exchange"
className="absolute right-2 top-1/2 -translate-y-1/2 z-10 inline-flex items-center gap-1 text-[10px] font-semibold tabular-nums text-red-600 whitespace-nowrap cursor-pointer rounded px-1 hover:bg-red-50"
>
<span className="size-2 rounded-full bg-red-600 animate-pulse" /> <span className="size-2 rounded-full bg-red-600 animate-pulse" />
{String(Math.floor(recSeconds / 60)).padStart(2, '0')}:{String(recSeconds % 60).padStart(2, '0')} {String(Math.floor(recSeconds / 60)).padStart(2, '0')}:{String(recSeconds % 60).padStart(2, '0')}
</span> </button>
)} )}
<Input <Input
ref={callsignRef} ref={callsignRef}
+2
View File
@@ -429,6 +429,8 @@ export function QSOAudioBegin():Promise<boolean>;
export function QSOAudioCancel():Promise<void>; export function QSOAudioCancel():Promise<void>;
export function QSOAudioResetClock():Promise<boolean>;
export function QSOAudioRestart():Promise<boolean>; export function QSOAudioRestart():Promise<boolean>;
export function QuitApp():Promise<void>; export function QuitApp():Promise<void>;
+4
View File
@@ -826,6 +826,10 @@ export function QSOAudioCancel() {
return window['go']['main']['App']['QSOAudioCancel'](); return window['go']['main']['App']['QSOAudioCancel']();
} }
export function QSOAudioResetClock() {
return window['go']['main']['App']['QSOAudioResetClock']();
}
export function QSOAudioRestart() { export function QSOAudioRestart() {
return window['go']['main']['App']['QSOAudioRestart'](); return window['go']['main']['App']['QSOAudioRestart']();
} }
+16
View File
@@ -239,6 +239,22 @@ func (r *Recorder) RestartQSO() {
r.active = true r.active = true
} }
// ResetQSOClock restarts the active accumulation from ZERO — discarding
// everything captured so far INCLUDING the pre-roll. Unlike RestartQSO (which
// re-seeds from the pre-roll ring), this keeps nothing: the saved file will
// contain only audio from this moment onward. Used when the contact you entered
// was already in a long QSO and you want to record just your own exchange.
// No-op if not running; if no take is active it begins one (empty).
func (r *Recorder) ResetQSOClock() {
r.mu.Lock()
defer r.mu.Unlock()
if !r.running {
return
}
r.acc = nil
r.active = true
}
// TakeQSO snapshots the accumulated recording as raw 16 kHz mono PCM bytes and // TakeQSO snapshots the accumulated recording as raw 16 kHz mono PCM bytes and
// stops accumulating — fast, no encoding. The next BeginQSO can safely start a // stops accumulating — fast, no encoding. The next BeginQSO can safely start a
// new take immediately. Pair with WritePCM to encode/write off the hot path so // new take immediately. Pair with WritePCM to encode/write off the hot path so