Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions src/renderer/src/components/radio/audio-hardware-summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import clsx from 'clsx';
import { AudioDevice } from 'trackaudio-afv';
import { Configuration } from '../../../../shared/config.type';
Comment thread
pierr3 marked this conversation as resolved.

export const AUDIO_CONFIG_CHANGED_EVENT = 'trackaudio-audio-config-changed';

type DeviceStatus =
| { state: 'loading' }
| { state: 'unset' }
| { state: 'unavailable' }
| { state: 'set'; name: string };

const resolveDeviceStatus = (devices: AudioDevice[], deviceId: string): DeviceStatus => {
if (!deviceId) {
return { state: 'unset' };
}
const match = devices.find((device) => device.id === deviceId);
return match ? { state: 'set', name: match.name } : { state: 'unavailable' };
};

interface DeviceLineProps {
label: string;
status: DeviceStatus;
}

const DeviceLine: React.FC<DeviceLineProps> = ({ label, status }) => {
const isProblem = status.state === 'unset' || status.state === 'unavailable';
const isLoading = status.state === 'loading';

let text: string;
if (status.state === 'set') {
text = status.name;
} else if (status.state === 'unavailable') {
text = 'Unavailable';
} else if (status.state === 'unset') {
text = 'Not configured';
} else {
text = '…';
}

return (
<div className="d-flex justify-content-center gap-2">
<span className="text-muted">{label}:</span>
<span
className={clsx(isProblem && 'text-danger fw-bold', isLoading && 'text-muted')}
title={status.state === 'set' ? status.name : undefined}
>
{text}
</span>
</div>
);
};

const AudioHardwareSummary: React.FC = () => {
const [inputStatus, setInputStatus] = useState<DeviceStatus>({ state: 'loading' });
const [headsetStatus, setHeadsetStatus] = useState<DeviceStatus>({ state: 'loading' });
const [speakerStatus, setSpeakerStatus] = useState<DeviceStatus>({ state: 'loading' });
const isActive = useRef(true);
const requestId = useRef(0);

const refresh = useCallback(() => {
const thisRequest = ++requestId.current;
const isStale = (): boolean => !isActive.current || thisRequest !== requestId.current;

window.api
.getConfig()
.then((config: Configuration) => {
if (isStale()) {
return;
}

if (config.audioApi < 0) {
setInputStatus({ state: 'unset' });
setHeadsetStatus({ state: 'unset' });
setSpeakerStatus({ state: 'unset' });
return;
}

window.api
.getAudioInputDevices(config.audioApi)
.then((devices: AudioDevice[]) => {
if (isStale()) {
return;
}
setInputStatus(resolveDeviceStatus(devices, config.audioInputDeviceId));
})
.catch((err: unknown) => {
console.error(err);
});
Comment thread
pierr3 marked this conversation as resolved.

window.api
.getAudioOutputDevices(config.audioApi)
.then((devices: AudioDevice[]) => {
if (isStale()) {
return;
}
setHeadsetStatus(resolveDeviceStatus(devices, config.headsetOutputDeviceId));
setSpeakerStatus(resolveDeviceStatus(devices, config.speakerOutputDeviceId));
})
.catch((err: unknown) => {
console.error(err);
});
Comment thread
pierr3 marked this conversation as resolved.
})
.catch((err: unknown) => {
console.error(err);
});
Comment thread
pierr3 marked this conversation as resolved.
}, []);

useEffect(() => {
isActive.current = true;
refresh();

window.addEventListener('focus', refresh);
window.addEventListener(AUDIO_CONFIG_CHANGED_EVENT, refresh);

return () => {
isActive.current = false;
window.removeEventListener('focus', refresh);
window.removeEventListener(AUDIO_CONFIG_CHANGED_EVENT, refresh);
};
}, [refresh]);

return (
<div className="d-flex justify-content-center radio-sub-text mt-3">
<div className="d-flex flex-column gap-0.5">
Comment thread
pierr3 marked this conversation as resolved.
<DeviceLine label="Microphone" status={inputStatus} />
<DeviceLine label="Headset" status={headsetStatus} />
<DeviceLine label="Speaker" status={speakerStatus} />
</div>
</div>
);
};

export default AudioHardwareSummary;
2 changes: 2 additions & 0 deletions src/renderer/src/components/radio/radio-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useUtilStore from '@renderer/store/utilStore';
import ExpandedRxInfo from './expanded-rx-info';
import AddStation from '../sidebar/add-station';
import AddFrequency from '../sidebar/add-frequency';
import AudioHardwareSummary from './audio-hardware-summary';

const EXCLUDED_FREQUENCIES = [0];
const UNICOM_EXCLUDED_FREQUENCIES = [122.8e6, 121.5e6];
Expand All @@ -30,6 +31,7 @@ const WaitingForConnectionMessage = () => (
<div className="d-flex justify-content-center radio-sub-text text-muted text-center">
Click the connect button to establish a connection to the VATSIM audio network.
</div>
<AudioHardwareSummary />
</div>
);

Expand Down
5 changes: 5 additions & 0 deletions src/renderer/src/components/settings-modal/settings-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useUtilStore from '../../store/utilStore';
import AudioApis from './audio-apis';
import AudioInput from './audio-input';
import AudioOutputs from './audio-outputs';
import { AUDIO_CONFIG_CHANGED_EVENT } from '../radio/audio-hardware-summary';
Comment thread
pierr3 marked this conversation as resolved.
import { Info } from 'lucide-react';
import { Nav, OverlayTrigger, Tab, Tooltip } from 'react-bootstrap';
export interface SettingsModalProps {
Expand Down Expand Up @@ -177,27 +178,31 @@ const SettingsModal: React.FC<SettingsModalProps> = ({ closeModal }) => {
.catch(() => {
setAudioInputDevices([]);
});
window.dispatchEvent(new Event(AUDIO_CONFIG_CHANGED_EVENT));
setChangesSaved(SaveStatus.Saved);
Comment thread
pierr3 marked this conversation as resolved.
};

const setInputDevice = (deviceId: string) => {
setChangesSaved(SaveStatus.Saving);
void window.api.setAudioInputDevice(deviceId);
setConfig({ ...config, audioInputDeviceId: deviceId });
window.dispatchEvent(new Event(AUDIO_CONFIG_CHANGED_EVENT));
setChangesSaved(SaveStatus.Saved);
Comment thread
pierr3 marked this conversation as resolved.
};

const setHeadsetDevice = (deviceId: string) => {
setChangesSaved(SaveStatus.Saving);
void window.api.setHeadsetOutputDevice(deviceId);
setConfig({ ...config, headsetOutputDeviceId: deviceId });
window.dispatchEvent(new Event(AUDIO_CONFIG_CHANGED_EVENT));
setChangesSaved(SaveStatus.Saved);
Comment thread
pierr3 marked this conversation as resolved.
};

const setSpeakerDevice = (deviceId: string) => {
setChangesSaved(SaveStatus.Saving);
void window.api.setSpeakerOutputDevice(deviceId);
setConfig({ ...config, speakerOutputDeviceId: deviceId });
window.dispatchEvent(new Event(AUDIO_CONFIG_CHANGED_EVENT));
setChangesSaved(SaveStatus.Saved);
Comment thread
pierr3 marked this conversation as resolved.
};

Expand Down