import {spawnReactModal} from "tc-shared/ui/react-elements/Modal"; import {InternalModal} from "tc-shared/ui/react-elements/internal-modal/Controller"; import * as React from "react"; import {Translatable} from "tc-shared/ui/react-elements/i18n"; import {EchoTestEventRegistry, EchoTestModal} from "tc-shared/ui/modal/echo-test/Renderer"; import {Registry} from "tc-shared/events"; import {EchoTestEvents, TestState} from "tc-shared/ui/modal/echo-test/Definitions"; import {ConnectionHandler} from "tc-shared/ConnectionHandler"; import {global_client_actions} from "tc-shared/events/GlobalEvents"; import {VoiceConnectionStatus} from "tc-shared/connection/VoiceConnection"; import {Settings, settings} from "tc-shared/settings"; import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration"; import {LogCategory, logError, logTrace, logWarn} from "tc-shared/log"; import {ServerFeature} from "tc-shared/connection/ServerFeatures"; export function spawnEchoTestModal(connection: ConnectionHandler) { const events = new Registry(); initializeController(connection, events); const modal = spawnReactModal(class extends InternalModal { constructor() { super(); } renderBody(): React.ReactElement { return ( ); } title(): string | React.ReactElement { return Voice echo test; } }); events.on("action_close", () => { modal.destroy(); }); modal.events.on("close", () => events.fire_react("notify_close")); modal.events.on("destroy", () => { events.fire("notify_destroy"); events.destroy(); }); modal.show(); } function initializeController(connection: ConnectionHandler, events: Registry) { let testState: TestState = {state: "stopped"}; events.on("action_open_microphone_settings", () => { global_client_actions.fire("action_open_window_settings", {defaultCategory: "audio-microphone"}); }); events.on("action_toggle_tests", event => { settings.setValue(Settings.KEY_VOICE_ECHO_TEST_ENABLED, event.enabled); }); events.on("query_test_state", () => { events.fire_react("notify_tests_toggle", {enabled: settings.getValue(Settings.KEY_VOICE_ECHO_TEST_ENABLED)}); }); events.on("notify_destroy", settings.globalChangeListener(Settings.KEY_VOICE_ECHO_TEST_ENABLED, value => { events.fire_react("notify_tests_toggle", {enabled: value}); })); events.on("action_test_result", event => { if (event.status === "success") { events.fire("action_close"); } else { events.fire_react("action_stop_test"); events.fire_react("notify_test_phase", {phase: "troubleshooting"}); } }); events.on("action_troubleshooting_finished", event => { if (event.status === "aborted") { events.fire("action_close"); } else { events.fire_react("notify_test_phase", {phase: "testing"}); events.fire("action_start_test"); } }); const reportVoiceConnectionState = (state: VoiceConnectionStatus) => { if (state === VoiceConnectionStatus.Connected) { beginTest(); } else { endTest(); } switch (state) { case VoiceConnectionStatus.Connected: events.fire_react("notify_voice_connection_state", {state: "connected"}); break; case VoiceConnectionStatus.Disconnected: case VoiceConnectionStatus.Disconnecting: events.fire_react("notify_voice_connection_state", {state: "disconnected"}); break; case VoiceConnectionStatus.Connecting: events.fire_react("notify_voice_connection_state", {state: "connecting"}); break; case VoiceConnectionStatus.ClientUnsupported: events.fire_react("notify_voice_connection_state", {state: "unsupported-client"}); break; case VoiceConnectionStatus.ServerUnsupported: events.fire_react("notify_voice_connection_state", {state: "unsupported-server"}); break; case VoiceConnectionStatus.Failed: events.fire_react("notify_voice_connection_state", {state: "failed", message: connection.getServerConnection().getVoiceConnection().getFailedMessage() }); break; } }; events.on("notify_destroy", connection.getServerConnection().getVoiceConnection().events.on("notify_connection_status_changed", event => { reportVoiceConnectionState(event.newStatus); })); events.on("query_voice_connection_state", () => reportVoiceConnectionState(connection.getServerConnection().getVoiceConnection().getConnectionState())); events.on("query_test_state", () => { events.fire_react("notify_test_state", {state: testState}); }); events.on("action_start_test", () => { beginTest(); }); events.on("action_unmute", () => { connection.setSpeakerMuted(false, true); connection.setMicrophoneMuted(false, true); beginTest(); }); const setTestState = (state: TestState) => { testState = state; events.fire_react("notify_test_state", {state: state}); } let testId = 0; const beginTest = () => { if (testState.state === "initializing" || testState.state === "running") { return; } else if (!connection.serverFeatures.supportsFeature(ServerFeature.WHISPER_ECHO)) { setTestState({state: "unsupported"}); return; } else if (connection.isSpeakerMuted() || connection.isMicrophoneMuted()) { setTestState({ state: "muted", speaker: connection.isSpeakerMuted(), microphone: connection.isMicrophoneMuted() }); return; } setTestState({state: "initializing"}); const currentTestId = ++testId; connection.startEchoTest().then(() => { if (currentTestId !== testId) { return; } logTrace(LogCategory.VOICE, tr("Echo test started.")); setTestState({state: "running"}); }).catch(error => { logWarn(LogCategory.VOICE, tr("Failed to start echo test: %o"), error); if (currentTestId !== testId) { return; } let message; if (error instanceof CommandResult) { message = error.formattedMessage(); } else if (error instanceof Error) { message = error.message; } else if (typeof error === "string") { message = error; } else { message = tr("lookup the console"); logError(LogCategory.AUDIO, tr("Failed to begin echo testing: %o"), error); } setTestState({state: "start-failed", error: message}); }); } const endTest = () => { setTestState({state: "stopped"}); connection.stopEchoTest(); } events.on(["notify_destroy", "notify_close", "action_stop_test"], endTest); }