feat: record a contact by hand when automatic recording is off
A red dot in the slot the counter will occupy. Click it and the counter starts,
the dot disappears, and logging the QSO writes the file exactly as an automatic
recording would — the save path already keyed off an active take, not off the
setting.
With automatic recording disabled the capture engine is not idle, it is not
running at all, so a manual take starts it. Two consequences handled here:
- It is released again when the take ends — logged, cancelled, or dropped for
being a digital mode. An operator who records one contact does not expect
their microphone held open for the rest of the session. The release is a
deferred call at the top of the save path so every exit goes through it,
rather than one that has to be remembered at each return.
- There is no pre-roll. An automatic recording opens with the seconds BEFORE
the callsign was typed; a manual one can only begin now. The button's
tooltip says so, because otherwise the difference between two files is a
mystery to the person holding them.
The button only appears where it can work: devices configured, automatic
recording off, and a mode whose audio is worth keeping. That state is asked of
the backend rather than inferred from a preference in the interface, so the two
cannot drift apart, and it is re-read when settings are saved.
This commit is contained in:
+30
-1
@@ -44,6 +44,7 @@ import {
|
||||
StartCWDecoder, StopCWDecoder, SetCWDecoderPitch,
|
||||
ChatAvailable, GetChatHistory, SendChatMessage, GetOnlineOperators,
|
||||
QSOAudioBegin, QSOAudioCancel, QSOAudioRestart, QSOAudioResetClock,
|
||||
QSOAudioManualReady, QSOAudioManualStart,
|
||||
GetAwardDefs,
|
||||
GetUIPref, GetActiveProfile, ListProfiles, ActivateProfile, QuitApp,
|
||||
ReportLiveActivity, LiveLastQSOAgeSec,
|
||||
@@ -771,6 +772,19 @@ export default function App() {
|
||||
// Bumped on every (re)start so the timer resets even when `recording` was
|
||||
// already true (jumping spot→spot keeps recording=true but starts a fresh take).
|
||||
const [recTick, setRecTick] = useState(0);
|
||||
// True when the sound devices are configured but automatic recording is OFF —
|
||||
// the only case where a record button makes sense. Asking the backend rather
|
||||
// than reading a preference here keeps the two from drifting apart.
|
||||
const [manualRecReady, setManualRecReady] = useState(false);
|
||||
const refreshManualRecReady = () => { QSOAudioManualReady().then(setManualRecReady).catch(() => {}); };
|
||||
useEffect(() => { refreshManualRecReady(); }, []);
|
||||
const startManualRecording = () => {
|
||||
QSOAudioManualStart().then((active) => {
|
||||
setRecording(active);
|
||||
setRecTick((t) => t + 1);
|
||||
if (!active) setError(t('rec.manualFailed'));
|
||||
}).catch((e: any) => setError(String(e?.message ?? e)));
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!recording) { setRecSeconds(0); return; }
|
||||
const start = Date.now();
|
||||
@@ -3680,6 +3694,21 @@ export default function App() {
|
||||
DUPE
|
||||
</span>
|
||||
)}
|
||||
{/* Not recording, but able to: offer a record button in the slot the
|
||||
counter will occupy. It disappears the moment recording starts, so the
|
||||
two never fight over the space — and it is only offered on modes that
|
||||
produce audio worth keeping. */}
|
||||
{!recording && manualRecReady && RECORDABLE_MODES.has(mode.toUpperCase()) && (
|
||||
<button
|
||||
type="button"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={startManualRecording}
|
||||
title={t('rec.manualStart')}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 z-10 inline-flex items-center justify-center size-4 rounded-full cursor-pointer hover:bg-destructive-muted"
|
||||
>
|
||||
<span className="size-2.5 rounded-full border border-destructive bg-destructive/40 hover:bg-destructive" />
|
||||
</button>
|
||||
)}
|
||||
{recording && RECORDABLE_MODES.has(mode.toUpperCase()) && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -6191,7 +6220,7 @@ export default function App() {
|
||||
<SettingsModal
|
||||
initialSection={settingsSection}
|
||||
onClose={() => { setShowSettings(false); setSettingsSection(undefined); }}
|
||||
onSaved={() => { loadStation(); loadLists(); loadCATCfg(); reloadWk(); }}
|
||||
onSaved={() => { loadStation(); loadLists(); loadCATCfg(); reloadWk(); refreshManualRecReady(); }}
|
||||
onMainPaneChanged={(side, v) => { if (side === 'left') setMainPaneLeft(v as MainPaneKind); else setMainPaneRight(v as MainPaneKind); }}
|
||||
flexAvailable={catState.backend === 'flex'}
|
||||
icomAvailable={catState.backend === 'icom'}
|
||||
|
||||
Reference in New Issue
Block a user