Adding a easy microphone source selector

master
WolverinDEV 2021-02-15 17:47:04 +01:00
parent ddef3359bd
commit 11085739fe
7 changed files with 252 additions and 57 deletions

View File

@ -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"));

View File

@ -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);

View File

@ -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 },

View File

@ -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);

View File

@ -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 */

View File

@ -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 (

View File

@ -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();
} }