Adding a easy microphone source selector
parent
ddef3359bd
commit
11085739fe
|
@ -73,10 +73,14 @@ async function initializeApp() {
|
||||||
|
|
||||||
aplayer.on_ready(() => aplayer.set_master_volume(settings.getValue(Settings.KEY_SOUND_MASTER) / 100));
|
aplayer.on_ready(() => aplayer.set_master_volume(settings.getValue(Settings.KEY_SOUND_MASTER) / 100));
|
||||||
|
|
||||||
setDefaultRecorder(new RecorderProfile("default"));
|
const recorder = new RecorderProfile("default");
|
||||||
defaultRecorder.initialize().catch(error => {
|
try {
|
||||||
|
await recorder.initialize();
|
||||||
|
} catch (error) {
|
||||||
|
/* TODO: Recover into a defined state? */
|
||||||
logError(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error);
|
logError(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error);
|
||||||
});
|
}
|
||||||
|
setDefaultRecorder(recorder);
|
||||||
|
|
||||||
sound.initialize().then(() => {
|
sound.initialize().then(() => {
|
||||||
logInfo(LogCategory.AUDIO, tr("Sounds initialized"));
|
logInfo(LogCategory.AUDIO, tr("Sounds initialized"));
|
||||||
|
|
|
@ -27,6 +27,8 @@ import {VideoBroadcastType, VideoConnectionStatus} from "tc-shared/connection/Vi
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
import {getVideoDriver} from "tc-shared/video/VideoSource";
|
import {getVideoDriver} from "tc-shared/video/VideoSource";
|
||||||
import {kLocalBroadcastChannels} from "tc-shared/ui/frames/video/Definitions";
|
import {kLocalBroadcastChannels} from "tc-shared/ui/frames/video/Definitions";
|
||||||
|
import {getRecorderBackend, IDevice} from "tc-shared/audio/recorder";
|
||||||
|
import {defaultRecorder, defaultRecorderEvents} from "tc-shared/voice/RecorderProfile";
|
||||||
|
|
||||||
class InfoController {
|
class InfoController {
|
||||||
private readonly mode: ControlBarMode;
|
private readonly mode: ControlBarMode;
|
||||||
|
@ -36,6 +38,7 @@ class InfoController {
|
||||||
private globalEvents: (() => void)[] = [];
|
private globalEvents: (() => void)[] = [];
|
||||||
private globalHandlerRegisteredEvents: {[key: string]: (() => void)[]} = {};
|
private globalHandlerRegisteredEvents: {[key: string]: (() => void)[]} = {};
|
||||||
private handlerRegisteredEvents: (() => void)[] = [];
|
private handlerRegisteredEvents: (() => void)[] = [];
|
||||||
|
private defaultRecorderListener: () => void;
|
||||||
|
|
||||||
constructor(events: Registry<ControlBarEvents>, mode: ControlBarMode) {
|
constructor(events: Registry<ControlBarEvents>, mode: ControlBarMode) {
|
||||||
this.events = events;
|
this.events = events;
|
||||||
|
@ -64,7 +67,13 @@ class InfoController {
|
||||||
this.sendVideoState("camera");
|
this.sendVideoState("camera");
|
||||||
}));
|
}));
|
||||||
events.push(bookmarkEvents.on("notify_bookmarks_updated", () => this.sendBookmarks()));
|
events.push(bookmarkEvents.on("notify_bookmarks_updated", () => this.sendBookmarks()));
|
||||||
events.push(getVideoDriver().getEvents().on("notify_device_list_changed", () => this.sendCameraList()))
|
events.push(getVideoDriver().getEvents().on("notify_device_list_changed", () => this.sendCameraList()));
|
||||||
|
events.push(getRecorderBackend().getDeviceList().getEvents().on("notify_list_updated", () => this.sendMicrophoneList()));
|
||||||
|
events.push(defaultRecorderEvents.on("notify_default_recorder_changed", () => {
|
||||||
|
this.unregisterDefaultRecorderEvents();
|
||||||
|
this.registerDefaultRecorderEvents();
|
||||||
|
this.sendMicrophoneList();
|
||||||
|
}));
|
||||||
if(this.mode === "main") {
|
if(this.mode === "main") {
|
||||||
events.push(server_connections.events().on("notify_active_handler_changed", event => this.setConnectionHandler(event.newHandler)));
|
events.push(server_connections.events().on("notify_active_handler_changed", event => this.setConnectionHandler(event.newHandler)));
|
||||||
}
|
}
|
||||||
|
@ -73,6 +82,8 @@ class InfoController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
|
this.unregisterDefaultRecorderEvents();
|
||||||
|
|
||||||
server_connections.getAllConnectionHandlers().forEach(handler => this.unregisterGlobalHandlerEvents(handler));
|
server_connections.getAllConnectionHandlers().forEach(handler => this.unregisterGlobalHandlerEvents(handler));
|
||||||
this.unregisterCurrentHandlerEvents();
|
this.unregisterCurrentHandlerEvents();
|
||||||
|
|
||||||
|
@ -80,6 +91,21 @@ class InfoController {
|
||||||
this.globalEvents = [];
|
this.globalEvents = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private registerDefaultRecorderEvents() {
|
||||||
|
if(!defaultRecorder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.defaultRecorderListener = defaultRecorder.events.on("notify_device_changed", () => this.sendMicrophoneList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private unregisterDefaultRecorderEvents() {
|
||||||
|
if(this.defaultRecorderListener) {
|
||||||
|
this.defaultRecorderListener();
|
||||||
|
this.defaultRecorderListener = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private registerGlobalHandlerEvents(handler: ConnectionHandler) {
|
private registerGlobalHandlerEvents(handler: ConnectionHandler) {
|
||||||
const events = this.globalHandlerRegisteredEvents[handler.handlerId] = [];
|
const events = this.globalHandlerRegisteredEvents[handler.handlerId] = [];
|
||||||
|
|
||||||
|
@ -219,6 +245,31 @@ class InfoController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sendMicrophoneList() {
|
||||||
|
const deviceList = getRecorderBackend().getDeviceList();
|
||||||
|
const devices = deviceList.getDevices();
|
||||||
|
const defaultDevice = deviceList.getDefaultDeviceId();
|
||||||
|
const selectedDevice = defaultRecorder?.getDeviceId();
|
||||||
|
|
||||||
|
this.events.fire_react("notify_microphone_list", {
|
||||||
|
devices: devices.map(device => {
|
||||||
|
let selected = false;
|
||||||
|
if(selectedDevice === IDevice.DefaultDeviceId && device.deviceId === defaultDevice) {
|
||||||
|
selected = true;
|
||||||
|
} else if(selectedDevice === device.deviceId) {
|
||||||
|
selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: device.name,
|
||||||
|
driver: device.driver,
|
||||||
|
id: device.deviceId,
|
||||||
|
selected: selected
|
||||||
|
};
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public sendSpeakerState() {
|
public sendSpeakerState() {
|
||||||
this.events.fire_react("notify_speaker_state", {
|
this.events.fire_react("notify_speaker_state", {
|
||||||
enabled: !this.currentHandler?.isSpeakerMuted()
|
enabled: !this.currentHandler?.isSpeakerMuted()
|
||||||
|
@ -303,10 +354,6 @@ export function initializePopoutControlBarController(events: Registry<ControlBar
|
||||||
infoHandler.setConnectionHandler(handler);
|
infoHandler.setConnectionHandler(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initializeClientControlBarController(events: Registry<ControlBarEvents>) {
|
|
||||||
initializeControlBarController(events, "main");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initializeControlBarController(events: Registry<ControlBarEvents>, mode: ControlBarMode) : InfoController {
|
export function initializeControlBarController(events: Registry<ControlBarEvents>, mode: ControlBarMode) : InfoController {
|
||||||
const infoHandler = new InfoController(events, mode);
|
const infoHandler = new InfoController(events, mode);
|
||||||
infoHandler.initialize();
|
infoHandler.initialize();
|
||||||
|
@ -318,6 +365,7 @@ export function initializeControlBarController(events: Registry<ControlBarEvents
|
||||||
events.on("query_bookmarks", () => infoHandler.sendBookmarks());
|
events.on("query_bookmarks", () => infoHandler.sendBookmarks());
|
||||||
events.on("query_away_state", () => infoHandler.sendAwayState());
|
events.on("query_away_state", () => infoHandler.sendAwayState());
|
||||||
events.on("query_microphone_state", () => infoHandler.sendMicrophoneState());
|
events.on("query_microphone_state", () => infoHandler.sendMicrophoneState());
|
||||||
|
events.on("query_microphone_list", () => infoHandler.sendMicrophoneList());
|
||||||
events.on("query_speaker_state", () => infoHandler.sendSpeakerState());
|
events.on("query_speaker_state", () => infoHandler.sendSpeakerState());
|
||||||
events.on("query_subscribe_state", () => infoHandler.sendSubscribeState());
|
events.on("query_subscribe_state", () => infoHandler.sendSubscribeState());
|
||||||
events.on("query_host_button", () => infoHandler.sendHostButton());
|
events.on("query_host_button", () => infoHandler.sendHostButton());
|
||||||
|
@ -373,10 +421,24 @@ export function initializeControlBarController(events: Registry<ControlBarEvents
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
events.on("action_toggle_microphone", event => {
|
events.on("action_toggle_microphone", async event => {
|
||||||
/* change the default global setting */
|
/* change the default global setting */
|
||||||
settings.setValue(Settings.KEY_CLIENT_STATE_MICROPHONE_MUTED, !event.enabled);
|
settings.setValue(Settings.KEY_CLIENT_STATE_MICROPHONE_MUTED, !event.enabled);
|
||||||
|
|
||||||
|
if(typeof event.targetDeviceId === "string") {
|
||||||
|
const device = getRecorderBackend().getDeviceList().getDevices().find(device => device.deviceId === event.targetDeviceId);
|
||||||
|
try {
|
||||||
|
if(!device) {
|
||||||
|
throw tr("Target device could not be found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await defaultRecorder?.setDevice(device);
|
||||||
|
} catch (error) {
|
||||||
|
createErrorModal(tr("Failed to change microphone"), tr("Failed to change microphone.\nTarget device could not be found.")).open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const current_connection_handler = infoHandler.getCurrentHandler();
|
const current_connection_handler = infoHandler.getCurrentHandler();
|
||||||
if(current_connection_handler) {
|
if(current_connection_handler) {
|
||||||
current_connection_handler.setMicrophoneMuted(!event.enabled);
|
current_connection_handler.setMicrophoneMuted(!event.enabled);
|
||||||
|
@ -390,6 +452,10 @@ export function initializeControlBarController(events: Registry<ControlBarEvents
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
events.on("action_open_microphone_settings", () => {
|
||||||
|
global_client_actions.fire("action_open_window_settings", { defaultCategory: "audio-microphone" });
|
||||||
|
});
|
||||||
|
|
||||||
events.on("action_toggle_speaker", event => {
|
events.on("action_toggle_speaker", event => {
|
||||||
/* change the default global setting */
|
/* change the default global setting */
|
||||||
settings.setValue(Settings.KEY_CLIENT_STATE_SPEAKER_MUTED, !event.enabled);
|
settings.setValue(Settings.KEY_CLIENT_STATE_SPEAKER_MUTED, !event.enabled);
|
||||||
|
|
|
@ -9,6 +9,7 @@ export type MicrophoneState = "enabled" | "disabled" | "muted";
|
||||||
export type VideoState = "enabled" | "disabled" | "unavailable" | "unsupported" | "disconnected";
|
export type VideoState = "enabled" | "disabled" | "unavailable" | "unsupported" | "disconnected";
|
||||||
export type HostButtonInfo = { title?: string, target?: string, url: string };
|
export type HostButtonInfo = { title?: string, target?: string, url: string };
|
||||||
export type VideoDeviceInfo = { name: string, id: string };
|
export type VideoDeviceInfo = { name: string, id: string };
|
||||||
|
export type MicrophoneDeviceInfo = { name: string, id: string, driver: string, selected: boolean };
|
||||||
|
|
||||||
export interface ControlBarEvents {
|
export interface ControlBarEvents {
|
||||||
action_connection_connect: { newTab: boolean },
|
action_connection_connect: { newTab: boolean },
|
||||||
|
@ -17,19 +18,21 @@ export interface ControlBarEvents {
|
||||||
action_bookmark_manage: {},
|
action_bookmark_manage: {},
|
||||||
action_bookmark_add_current_server: {},
|
action_bookmark_add_current_server: {},
|
||||||
action_toggle_away: { away: boolean, globally: boolean, promptMessage?: boolean },
|
action_toggle_away: { away: boolean, globally: boolean, promptMessage?: boolean },
|
||||||
action_toggle_microphone: { enabled: boolean },
|
action_toggle_microphone: { enabled: boolean, targetDeviceId?: string },
|
||||||
action_toggle_speaker: { enabled: boolean },
|
action_toggle_speaker: { enabled: boolean },
|
||||||
action_toggle_subscribe: { subscribe: boolean },
|
action_toggle_subscribe: { subscribe: boolean },
|
||||||
action_toggle_query: { show: boolean },
|
action_toggle_query: { show: boolean },
|
||||||
action_query_manage: {},
|
action_query_manage: {},
|
||||||
action_toggle_video: { broadcastType: VideoBroadcastType, enable: boolean, quickStart?: boolean, deviceId?: string },
|
action_toggle_video: { broadcastType: VideoBroadcastType, enable: boolean, quickStart?: boolean, deviceId?: string },
|
||||||
action_manage_video: { broadcastType: VideoBroadcastType }
|
action_manage_video: { broadcastType: VideoBroadcastType },
|
||||||
|
action_open_microphone_settings: {},
|
||||||
|
|
||||||
query_mode: {},
|
query_mode: {},
|
||||||
query_connection_state: {},
|
query_connection_state: {},
|
||||||
query_bookmarks: {},
|
query_bookmarks: {},
|
||||||
query_away_state: {},
|
query_away_state: {},
|
||||||
query_microphone_state: {},
|
query_microphone_state: {},
|
||||||
|
query_microphone_list: {},
|
||||||
query_speaker_state: {},
|
query_speaker_state: {},
|
||||||
query_subscribe_state: {},
|
query_subscribe_state: {},
|
||||||
query_query_state: {},
|
query_query_state: {},
|
||||||
|
@ -42,6 +45,7 @@ export interface ControlBarEvents {
|
||||||
notify_bookmarks: { marks: Bookmark[] },
|
notify_bookmarks: { marks: Bookmark[] },
|
||||||
notify_away_state: { state: AwayState },
|
notify_away_state: { state: AwayState },
|
||||||
notify_microphone_state: { state: MicrophoneState },
|
notify_microphone_state: { state: MicrophoneState },
|
||||||
|
notify_microphone_list: { devices: MicrophoneDeviceInfo[] },
|
||||||
notify_speaker_state: { enabled: boolean },
|
notify_speaker_state: { enabled: boolean },
|
||||||
notify_subscribe_state: { subscribe: boolean },
|
notify_subscribe_state: { subscribe: boolean },
|
||||||
notify_query_state: { shown: boolean },
|
notify_query_state: { shown: boolean },
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
ControlBarEvents,
|
ControlBarEvents,
|
||||||
ControlBarMode,
|
ControlBarMode,
|
||||||
HostButtonInfo,
|
HostButtonInfo, MicrophoneDeviceInfo,
|
||||||
MicrophoneState,
|
MicrophoneState,
|
||||||
VideoDeviceInfo,
|
VideoDeviceInfo,
|
||||||
VideoState
|
VideoState
|
||||||
|
@ -316,17 +316,108 @@ const MicrophoneButton = () => {
|
||||||
events.on("notify_microphone_state", event => setState(event.state));
|
events.on("notify_microphone_state", event => setState(event.state));
|
||||||
|
|
||||||
if(state === "muted") {
|
if(state === "muted") {
|
||||||
return <Button switched={true} colorTheme={"red"} autoSwitch={false} iconNormal={ClientIcon.InputMuted} tooltip={tr("Unmute microphone")}
|
return (
|
||||||
onToggle={() => events.fire("action_toggle_microphone", { enabled: true })} key={"muted"} />;
|
<Button switched={true} colorTheme={"red"} autoSwitch={false} iconNormal={ClientIcon.InputMuted} tooltip={tr("Unmute microphone")}
|
||||||
|
onToggle={() => events.fire("action_toggle_microphone", { enabled: true })} key={"muted"}>
|
||||||
|
<DropdownEntry
|
||||||
|
icon={ClientIcon.InputMuted}
|
||||||
|
text={<Translatable>Unmute microphone</Translatable>}
|
||||||
|
onClick={() => events.fire("action_toggle_microphone", { enabled: true })}
|
||||||
|
/>
|
||||||
|
<DropdownEntry
|
||||||
|
icon={ClientIcon.Settings}
|
||||||
|
text={<Translatable>Open microphone settings</Translatable>}
|
||||||
|
onClick={() => events.fire("action_open_microphone_settings", {})}
|
||||||
|
/>
|
||||||
|
<MicrophoneDeviceList />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
} else if(state === "enabled") {
|
} else if(state === "enabled") {
|
||||||
return <Button colorTheme={"red"} autoSwitch={false} iconNormal={ClientIcon.InputMuted} tooltip={tr("Mute microphone")}
|
return (
|
||||||
onToggle={() => events.fire("action_toggle_microphone", { enabled: false })} key={"enabled"} />;
|
<Button colorTheme={"red"} autoSwitch={false} iconNormal={ClientIcon.InputMuted} tooltip={tr("Mute microphone")}
|
||||||
|
onToggle={() => events.fire("action_toggle_microphone", { enabled: false })} key={"enabled"}>
|
||||||
|
<DropdownEntry
|
||||||
|
icon={ClientIcon.InputMuted}
|
||||||
|
text={<Translatable>Mute microphone</Translatable>}
|
||||||
|
onClick={() => events.fire("action_toggle_microphone", { enabled: false })}
|
||||||
|
/>
|
||||||
|
<DropdownEntry
|
||||||
|
icon={ClientIcon.Settings}
|
||||||
|
text={<Translatable>Open microphone settings</Translatable>}
|
||||||
|
onClick={() => events.fire("action_open_microphone_settings", {})}
|
||||||
|
/>
|
||||||
|
<MicrophoneDeviceList />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <Button autoSwitch={false} iconNormal={ClientIcon.ActivateMicrophone} tooltip={tr("Enable your microphone on this server")}
|
return (
|
||||||
onToggle={() => events.fire("action_toggle_microphone", { enabled: true })} key={"disabled"} />;
|
<Button autoSwitch={false} iconNormal={ClientIcon.ActivateMicrophone} tooltip={tr("Enable your microphone on this server")}
|
||||||
|
onToggle={() => events.fire("action_toggle_microphone", { enabled: true })} key={"disabled"}>
|
||||||
|
<DropdownEntry
|
||||||
|
icon={ClientIcon.ActivateMicrophone}
|
||||||
|
text={<Translatable>Enable your microphone</Translatable>}
|
||||||
|
onClick={() => events.fire("action_toggle_microphone", { enabled: true })}
|
||||||
|
/>
|
||||||
|
<DropdownEntry
|
||||||
|
icon={ClientIcon.Settings}
|
||||||
|
text={<Translatable>Open microphone settings</Translatable>}
|
||||||
|
onClick={() => events.fire("action_open_microphone_settings", {})}
|
||||||
|
/>
|
||||||
|
<MicrophoneDeviceList />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This should be above all driver weights */
|
||||||
|
const kDriverWeightSelected = 1000;
|
||||||
|
const kDriverWeights = {
|
||||||
|
"MME": 100,
|
||||||
|
"Windows DirectSound": 80,
|
||||||
|
"Windows WASAPI": 50
|
||||||
|
};
|
||||||
|
|
||||||
|
const MicrophoneDeviceList = React.memo(() => {
|
||||||
|
const events = useContext(Events);
|
||||||
|
const [ deviceList, setDeviceList ] = useState<MicrophoneDeviceInfo[]>(() => {
|
||||||
|
events.fire("query_microphone_list");
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
events.reactUse("notify_microphone_list", event => setDeviceList(event.devices));
|
||||||
|
|
||||||
|
if(deviceList.length <= 1) {
|
||||||
|
/* we don't need a select here */
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const devices: {[key: string]: { weight: number, device: MicrophoneDeviceInfo }} = {};
|
||||||
|
for(const entry of deviceList) {
|
||||||
|
const weight = entry.selected ? kDriverWeightSelected : (kDriverWeights[entry.driver] | 0);
|
||||||
|
if(typeof devices[entry.name] !== "undefined" && devices[entry.name].weight >= weight) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
devices[entry.name] = {
|
||||||
|
weight,
|
||||||
|
device: entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<hr key={"hr"} />
|
||||||
|
{Object.values(devices).map(({ device }) => (
|
||||||
|
<DropdownEntry
|
||||||
|
text={device.name || tr("Unknown device name")}
|
||||||
|
key={"m-" + device.id}
|
||||||
|
icon={device.selected ? ClientIcon.Apply : undefined}
|
||||||
|
onClick={() => events.fire("action_toggle_microphone", { enabled: true, targetDeviceId: device.id })}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const SpeakerButton = () => {
|
const SpeakerButton = () => {
|
||||||
const events = useContext(Events);
|
const events = useContext(Events);
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,8 @@ export type MicrophoneDevice = {
|
||||||
default: boolean
|
default: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MicrophoneSettingsSelectedMicrophone = { type: "default" } | { type: "none" } | { type: "device", deviceId: string };
|
export type SelectedMicrophone = { type: "default" } | { type: "none" } | { type: "device", deviceId: string };
|
||||||
export type MicrophoneSettingsDevices = {
|
export type MicrophoneDevices = {
|
||||||
status: "error",
|
status: "error",
|
||||||
error: string
|
error: string
|
||||||
} | {
|
} | {
|
||||||
|
@ -35,7 +35,7 @@ export type MicrophoneSettingsDevices = {
|
||||||
} | {
|
} | {
|
||||||
status: "success",
|
status: "success",
|
||||||
devices: MicrophoneDevice[]
|
devices: MicrophoneDevice[]
|
||||||
selectedDevice: MicrophoneSettingsSelectedMicrophone;
|
selectedDevice: SelectedMicrophone;
|
||||||
};
|
};
|
||||||
export interface MicrophoneSettingsEvents {
|
export interface MicrophoneSettingsEvents {
|
||||||
"query_devices": { refresh_list: boolean },
|
"query_devices": { refresh_list: boolean },
|
||||||
|
@ -46,10 +46,9 @@ export interface MicrophoneSettingsEvents {
|
||||||
|
|
||||||
"action_help_click": {},
|
"action_help_click": {},
|
||||||
"action_request_permissions": {},
|
"action_request_permissions": {},
|
||||||
"action_set_selected_device": { target: MicrophoneSettingsSelectedMicrophone },
|
"action_set_selected_device": { target: SelectedMicrophone },
|
||||||
"action_set_selected_device_result": {
|
"action_set_selected_device_result": {
|
||||||
status: "success",
|
status: "success",
|
||||||
selectedDevice: MicrophoneSettingsSelectedMicrophone
|
|
||||||
} | {
|
} | {
|
||||||
status: "error",
|
status: "error",
|
||||||
reason: string
|
reason: string
|
||||||
|
@ -65,7 +64,8 @@ export interface MicrophoneSettingsEvents {
|
||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
"notify_devices": MicrophoneSettingsDevices,
|
notify_devices: MicrophoneDevices,
|
||||||
|
notify_device_selected: { device: SelectedMicrophone },
|
||||||
|
|
||||||
notify_device_level: {
|
notify_device_level: {
|
||||||
level: {
|
level: {
|
||||||
|
@ -171,6 +171,17 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
|
||||||
|
|
||||||
/* device list */
|
/* device list */
|
||||||
{
|
{
|
||||||
|
const currentSelectedDevice = (): SelectedMicrophone => {
|
||||||
|
let deviceId = defaultRecorder.getDeviceId();
|
||||||
|
if(deviceId === IDevice.DefaultDeviceId) {
|
||||||
|
return { type: "default" };
|
||||||
|
} else if(deviceId === IDevice.NoDeviceId) {
|
||||||
|
return { type: "none" };
|
||||||
|
} else {
|
||||||
|
return { type: "device", deviceId: deviceId };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
events.on("query_devices", event => {
|
events.on("query_devices", event => {
|
||||||
if (!aplayer.initialized()) {
|
if (!aplayer.initialized()) {
|
||||||
events.fire_react("notify_devices", {
|
events.fire_react("notify_devices", {
|
||||||
|
@ -201,18 +212,6 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
|
||||||
} else {
|
} else {
|
||||||
const devices = deviceList.getDevices();
|
const devices = deviceList.getDevices();
|
||||||
|
|
||||||
let selectedDevice: MicrophoneSettingsSelectedMicrophone;
|
|
||||||
{
|
|
||||||
let deviceId = defaultRecorder.getDeviceId();
|
|
||||||
if(deviceId === IDevice.DefaultDeviceId) {
|
|
||||||
selectedDevice = { type: "default" };
|
|
||||||
} else if(deviceId === IDevice.NoDeviceId) {
|
|
||||||
selectedDevice = { type: "none" };
|
|
||||||
} else {
|
|
||||||
selectedDevice = { type: "device", deviceId: deviceId };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultDeviceId = getRecorderBackend().getDeviceList().getDefaultDeviceId();
|
const defaultDeviceId = getRecorderBackend().getDeviceList().getDefaultDeviceId();
|
||||||
events.fire_react("notify_devices", {
|
events.fire_react("notify_devices", {
|
||||||
status: "success",
|
status: "success",
|
||||||
|
@ -224,7 +223,7 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
|
||||||
default: defaultDeviceId === e.deviceId
|
default: defaultDeviceId === e.deviceId
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
selectedDevice: selectedDevice,
|
selectedDevice: currentSelectedDevice(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -270,13 +269,21 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
|
||||||
}
|
}
|
||||||
|
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
|
/* TODO:
|
||||||
|
* This isn't needed since the defaultRecorder might already fire a device change event which will update our ui.
|
||||||
|
* We only have this since we can't ensure that the recorder does so.
|
||||||
|
*/
|
||||||
|
events.fire_react("notify_device_selected", { device: currentSelectedDevice() });
|
||||||
logTrace(LogCategory.GENERAL, tr("Changed default microphone device to %s"), displayName);
|
logTrace(LogCategory.GENERAL, tr("Changed default microphone device to %s"), displayName);
|
||||||
events.fire_react("action_set_selected_device_result", {status: "success", selectedDevice: event.target });
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
logWarn(LogCategory.AUDIO, tr("Failed to change microphone to device %s: %o"), displayName, error);
|
logWarn(LogCategory.AUDIO, tr("Failed to change microphone to device %s: %o"), displayName, error);
|
||||||
events.fire_react("action_set_selected_device_result", {status: "error", reason: error || tr("lookup the console") });
|
events.fire_react("action_set_selected_device_result", {status: "error", reason: error || tr("lookup the console") });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
events.on("notify_destroy", defaultRecorder.events.on("notify_device_changed", () => {
|
||||||
|
events.fire_react("notify_device_selected", { device: currentSelectedDevice() });
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* settings */
|
/* settings */
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {useEffect, useRef, useState} from "react";
|
||||||
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
||||||
import {Button} from "tc-shared/ui/react-elements/Button";
|
import {Button} from "tc-shared/ui/react-elements/Button";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {MicrophoneDevice, MicrophoneSettingsEvents, MicrophoneSettingsSelectedMicrophone} from "tc-shared/ui/modal/settings/Microphone";
|
import {MicrophoneDevice, MicrophoneSettingsEvents, SelectedMicrophone} from "tc-shared/ui/modal/settings/Microphone";
|
||||||
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
|
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
|
||||||
import {ClientIcon} from "svg-sprites/client-icons";
|
import {ClientIcon} from "svg-sprites/client-icons";
|
||||||
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
||||||
|
@ -174,8 +174,8 @@ const MicrophoneList = (props: { events: Registry<MicrophoneSettingsEvents> }) =
|
||||||
return {type: "loading"};
|
return {type: "loading"};
|
||||||
});
|
});
|
||||||
const [selectedDevice, setSelectedDevice] = useState<{
|
const [selectedDevice, setSelectedDevice] = useState<{
|
||||||
selectedDevice: MicrophoneSettingsSelectedMicrophone,
|
selectedDevice: SelectedMicrophone,
|
||||||
selectingDevice: MicrophoneSettingsSelectedMicrophone | undefined
|
selectingDevice: SelectedMicrophone | undefined
|
||||||
}>();
|
}>();
|
||||||
const [deviceList, setDeviceList] = useState<MicrophoneDevice[]>([]);
|
const [deviceList, setDeviceList] = useState<MicrophoneDevice[]>([]);
|
||||||
|
|
||||||
|
@ -219,16 +219,15 @@ const MicrophoneList = (props: { events: Registry<MicrophoneSettingsEvents> }) =
|
||||||
selectedDevice: selectedDevice?.selectedDevice,
|
selectedDevice: selectedDevice?.selectedDevice,
|
||||||
selectingDevice: undefined
|
selectingDevice: undefined
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
setSelectedDevice({
|
|
||||||
selectedDevice: event.selectedDevice,
|
|
||||||
selectingDevice: undefined
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
props.events.reactUse("notify_device_selected", event => {
|
||||||
|
setSelectedDevice({ selectedDevice: event.device, selectingDevice: undefined });
|
||||||
|
})
|
||||||
|
|
||||||
const deviceSelectState = (device: MicrophoneDevice | "none" | "default"): MicrophoneSelectedState => {
|
const deviceSelectState = (device: MicrophoneDevice | "none" | "default"): MicrophoneSelectedState => {
|
||||||
let selected: MicrophoneSettingsSelectedMicrophone;
|
let selected: SelectedMicrophone;
|
||||||
let mode: MicrophoneSelectedState;
|
let mode: MicrophoneSelectedState;
|
||||||
if(typeof selectedDevice?.selectingDevice !== "undefined") {
|
if(typeof selectedDevice?.selectingDevice !== "undefined") {
|
||||||
selected = selectedDevice.selectingDevice;
|
selected = selectedDevice.selectingDevice;
|
||||||
|
@ -569,7 +568,7 @@ const ThresholdSelector = (props: { events: Registry<MicrophoneSettingsEvents> }
|
||||||
const defaultDeviceId = useRef<string | undefined>();
|
const defaultDeviceId = useRef<string | undefined>();
|
||||||
const [isVadActive, setVadActive] = useState(false);
|
const [isVadActive, setVadActive] = useState(false);
|
||||||
|
|
||||||
const changeCurrentDevice = (selected: MicrophoneSettingsSelectedMicrophone) => {
|
const changeCurrentDevice = (selected: SelectedMicrophone) => {
|
||||||
switch (selected.type) {
|
switch (selected.type) {
|
||||||
case "none":
|
case "none":
|
||||||
setCurrentDevice({ type: "none" });
|
setCurrentDevice({ type: "none" });
|
||||||
|
@ -612,11 +611,7 @@ const ThresholdSelector = (props: { events: Registry<MicrophoneSettingsEvents> }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("action_set_selected_device_result", event => {
|
props.events.reactUse("notify_device_selected", event => changeCurrentDevice(event.device));
|
||||||
if(event.status === "success") {
|
|
||||||
changeCurrentDevice(event.selectedDevice);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let isActive = isVadActive && currentDevice.type === "device";
|
let isActive = isVadActive && currentDevice.type === "device";
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -9,6 +9,7 @@ import * as ppt from "tc-backend/ppt";
|
||||||
import {getRecorderBackend, IDevice} from "../audio/recorder";
|
import {getRecorderBackend, IDevice} from "../audio/recorder";
|
||||||
import {FilterType, StateFilter, ThresholdFilter} from "../voice/Filter";
|
import {FilterType, StateFilter, ThresholdFilter} from "../voice/Filter";
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
|
||||||
export type VadType = "threshold" | "push_to_talk" | "active";
|
export type VadType = "threshold" | "push_to_talk" | "active";
|
||||||
export interface RecorderProfileConfig {
|
export interface RecorderProfileConfig {
|
||||||
|
@ -35,12 +36,25 @@ export interface RecorderProfileConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DefaultRecorderEvents {
|
||||||
|
notify_default_recorder_changed: {}
|
||||||
|
}
|
||||||
|
|
||||||
export let defaultRecorder: RecorderProfile; /* needs initialize */
|
export let defaultRecorder: RecorderProfile; /* needs initialize */
|
||||||
|
export const defaultRecorderEvents: Registry<DefaultRecorderEvents> = new Registry<DefaultRecorderEvents>();
|
||||||
|
|
||||||
export function setDefaultRecorder(recorder: RecorderProfile) {
|
export function setDefaultRecorder(recorder: RecorderProfile) {
|
||||||
defaultRecorder = recorder;
|
defaultRecorder = recorder;
|
||||||
|
(window as any).defaultRecorder = defaultRecorder;
|
||||||
|
defaultRecorderEvents.fire("notify_default_recorder_changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecorderProfileEvents {
|
||||||
|
notify_device_changed: { },
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RecorderProfile {
|
export class RecorderProfile {
|
||||||
|
readonly events: Registry<RecorderProfileEvents>;
|
||||||
readonly name;
|
readonly name;
|
||||||
readonly volatile; /* not saving profile */
|
readonly volatile; /* not saving profile */
|
||||||
|
|
||||||
|
@ -66,6 +80,7 @@ export class RecorderProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(name: string, volatile?: boolean) {
|
constructor(name: string, volatile?: boolean) {
|
||||||
|
this.events = new Registry<RecorderProfileEvents>();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.volatile = typeof(volatile) === "boolean" ? volatile : false;
|
this.volatile = typeof(volatile) === "boolean" ? volatile : false;
|
||||||
|
|
||||||
|
@ -95,6 +110,7 @@ export class RecorderProfile {
|
||||||
/* TODO */
|
/* TODO */
|
||||||
this.input?.destroy();
|
this.input?.destroy();
|
||||||
this.input = undefined;
|
this.input = undefined;
|
||||||
|
this.events.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() : Promise<void> {
|
async initialize() : Promise<void> {
|
||||||
|
@ -109,7 +125,7 @@ export class RecorderProfile {
|
||||||
/* default values */
|
/* default values */
|
||||||
this.config = {
|
this.config = {
|
||||||
version: 1,
|
version: 1,
|
||||||
device_id: undefined,
|
device_id: IDevice.DefaultDeviceId,
|
||||||
volume: 100,
|
volume: 100,
|
||||||
|
|
||||||
vad_threshold: {
|
vad_threshold: {
|
||||||
|
@ -306,10 +322,22 @@ export class RecorderProfile {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceId() : string { return this.config.device_id; }
|
getDeviceId() : string | typeof IDevice.DefaultDeviceId | typeof IDevice.NoDeviceId { return this.config.device_id; }
|
||||||
setDevice(device: IDevice | undefined) : Promise<void> {
|
setDevice(device: IDevice | typeof IDevice.DefaultDeviceId | typeof IDevice.NoDeviceId) : Promise<void> {
|
||||||
this.config.device_id = device ? device.deviceId : IDevice.NoDeviceId;
|
let deviceId;
|
||||||
|
if(typeof device === "object") {
|
||||||
|
deviceId = device.deviceId;
|
||||||
|
} else {
|
||||||
|
deviceId = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.config.device_id === deviceId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.config.device_id = deviceId;
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
|
this.events.fire("notify_device_changed");
|
||||||
return this.input?.setDeviceId(this.config.device_id) || Promise.resolve();
|
return this.input?.setDeviceId(this.config.device_id) || Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue