improve: add monitor to client mode

This commit is contained in:
w-okada 2023-08-03 05:15:05 +09:00
parent 2b08e47377
commit 0e878edc1e
9 changed files with 1279 additions and 50 deletions

View File

@ -1 +1,10 @@
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html>
<!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8" />
<title>Voice Changer Client Demo</title>
<script defer src="index.js"></script></head>
<body style="width: 100%; height: 100%; margin: 0px">
<div id="app" style="width: 100%; height: 100%"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,31 +0,0 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

View File

@ -11,8 +11,8 @@
"build:dev": "npm-run-all clean webpack:dev",
"start": "webpack-dev-server --config webpack.dev.js",
"build:mod": "cd ../lib && npm run build:dev && cd - && cp -r ../lib/dist/* node_modules/@dannadori/voice-changer-client-js/dist/",
"build:mod_dos": "cd ../lib && npm run build:dev && cd ../demo && copy ../lib/dist/index.js node_modules/@dannadori/voice-changer-client-js/dist/",
"build:mod_dos2": "copy ../lib/dist/index.js node_modules/@dannadori/voice-changer-client-js/dist/",
"build:mod_dos": "cd ../lib && npm run build:dev && cd ../demo && npm-run-all build:mod_copy",
"build:mod_copy": "XCOPY ..\\lib\\dist\\* .\\node_modules\\@dannadori\\voice-changer-client-js\\dist\\* /s /e /h /y",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [

View File

@ -63,6 +63,7 @@ type GuiStateAndMethod = {
outputAudioDeviceInfo: MediaDeviceInfo[];
audioInputForGUI: string;
audioOutputForGUI: string;
audioMonitorForGUI: string;
fileInputEchoback: boolean | undefined;
shareScreenEnabled: boolean;
audioOutputForAnalyzer: string;
@ -70,6 +71,7 @@ type GuiStateAndMethod = {
setOutputAudioDeviceInfo: (val: MediaDeviceInfo[]) => void;
setAudioInputForGUI: (val: string) => void;
setAudioOutputForGUI: (val: string) => void;
setAudioMonitorForGUI: (val: string) => void;
setFileInputEchoback: (val: boolean) => void;
setShareScreenEnabled: (val: boolean) => void;
setAudioOutputForAnalyzer: (val: string) => void;
@ -106,6 +108,7 @@ export const GuiStateProvider = ({ children }: Props) => {
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([]);
const [audioInputForGUI, setAudioInputForGUI] = useState<string>("none");
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none");
const [audioMonitorForGUI, setAudioMonitorForGUI] = useState<string>("none");
const [fileInputEchoback, setFileInputEchoback] = useState<boolean>(false); //最初のmuteが有効になるように。undefined <-- ??? falseしておけばよさそう。undefinedだとwarningがでる。
const [shareScreenEnabled, setShareScreenEnabled] = useState<boolean>(false);
const [audioOutputForAnalyzer, setAudioOutputForAnalyzer] = useState<string>("default");
@ -270,6 +273,7 @@ export const GuiStateProvider = ({ children }: Props) => {
outputAudioDeviceInfo,
audioInputForGUI,
audioOutputForGUI,
audioMonitorForGUI,
fileInputEchoback,
shareScreenEnabled,
audioOutputForAnalyzer,
@ -277,6 +281,7 @@ export const GuiStateProvider = ({ children }: Props) => {
setOutputAudioDeviceInfo,
setAudioInputForGUI,
setAudioOutputForGUI,
setAudioMonitorForGUI,
setFileInputEchoback,
setShareScreenEnabled,
setAudioOutputForAnalyzer,

View File

@ -2,14 +2,14 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { fileSelectorAsDataURL, useIndexedDB } from "@dannadori/voice-changer-client-js";
import { useGuiState } from "../001_GuiStateProvider";
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../../const";
import { AUDIO_ELEMENT_FOR_PLAY_MONITOR, AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_MONITR, INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../../const";
import { isDesktopApp } from "../../../const";
export type DeviceAreaProps = {};
export const DeviceArea = (_props: DeviceAreaProps) => {
const { setting, serverSetting, audioContext, setAudioOutputElementId, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording } = useAppState();
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, audioOutputForGUI, outputAudioDeviceInfo, shareScreenEnabled, setShareScreenEnabled } = useGuiState();
const { setting, serverSetting, audioContext, setAudioOutputElementId, setAudioMonitorElementId, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording } = useAppState();
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, setAudioMonitorForGUI, audioOutputForGUI, audioMonitorForGUI, outputAudioDeviceInfo, shareScreenEnabled, setShareScreenEnabled } = useGuiState();
const [inputHostApi, setInputHostApi] = useState<string>("ALL");
const [outputHostApi, setOutputHostApi] = useState<string>("ALL");
const [monitorHostApi, setMonitorHostApi] = useState<string>("ALL");
@ -244,10 +244,10 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
audio_echo.volume = 0;
setFileInputEchoback(false);
// original stream to play.
const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement;
audio_org.src = url;
audio_org.pause();
// // original stream to play.
// const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement;
// audio_org.src = url;
// audio_org.pause();
};
const echobackClass = fileInputEchoback ? "config-sub-area-control-field-wav-file-echoback-button-active" : "config-sub-area-control-field-wav-file-echoback-button";
@ -256,7 +256,7 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
<div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-wav-file left-padding-1">
<div className="config-sub-area-control-field-wav-file-audio-container">
<audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls hidden></audio>
{/* <audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls hidden></audio> */}
<audio className="config-sub-area-control-field-wav-file-audio" id={AUDIO_ELEMENT_FOR_TEST_CONVERTED} controls controlsList="nodownload noplaybackrate"></audio>
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK} controls hidden></audio>
</div>
@ -381,7 +381,8 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
const setAudioOutput = async () => {
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
// [AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
const audio = document.getElementById(x) as HTMLAudioElement;
if (audio) {
if (serverSetting.serverSetting.enableServerAudio == 1) {
@ -598,7 +599,88 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
// (6) Monitor
// (6) モニター
useEffect(() => {
const loadCache = async () => {
const key = await getItem(INDEXEDDB_KEY_AUDIO_MONITR);
if (key) {
setAudioMonitorForGUI(key as string);
}
};
loadCache();
}, []);
useEffect(() => {
const setAudioMonitor = async () => {
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
[AUDIO_ELEMENT_FOR_PLAY_MONITOR].forEach((x) => {
const audio = document.getElementById(x) as HTMLAudioElement;
if (audio) {
if (serverSetting.serverSetting.enableServerAudio == 1) {
// Server Audio を使う場合はElementから音は出さない。
audio.volume = 0;
} else if (audioMonitorForGUI == "none") {
// @ts-ignore
audio.setSinkId("");
audio.volume = 0;
} else {
const audioOutputs = mediaDeviceInfos.filter((x) => {
return x.kind == "audiooutput";
});
const found = audioOutputs.some((x) => {
return x.deviceId == audioMonitorForGUI;
});
if (found) {
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。
audio.setSinkId(audioMonitorForGUI);
audio.volume = 1;
} else {
console.warn("No audio output device. use default");
}
}
}
});
};
setAudioMonitor();
}, [audioMonitorForGUI, serverSetting.serverSetting.enableServerAudio]);
// (6-1) クライアント
const clientMonitorRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></>;
}
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">monitor</div>
<div className="config-sub-area-control-field">
<select
className="body-select"
value={audioMonitorForGUI}
onChange={(e) => {
setAudioMonitorForGUI(e.target.value);
setItem(INDEXEDDB_KEY_AUDIO_MONITR, e.target.value);
}}
>
{outputAudioDeviceInfo.map((x) => {
return (
<option key={x.deviceId} value={x.deviceId}>
{x.label}
</option>
);
})}
</select>
</div>
</div>
);
}, [serverSetting.serverSetting.enableServerAudio, outputAudioDeviceInfo, audioMonitorForGUI]);
useEffect(() => {
console.log("initializedRef.current", initializedRef.current);
setAudioMonitorElementId(AUDIO_ELEMENT_FOR_PLAY_MONITOR);
}, [initializedRef.current]);
// (6-2) サーバ
const serverMonitorRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></>;
@ -685,10 +767,12 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
{audioInputScreenRow}
{clientAudioOutputRow}
{serverAudioOutputRow}
{clientMonitorRow}
{serverMonitorRow}
{outputRecorderRow}
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_MONITOR}></audio>
</div>
);
};

View File

@ -1,13 +1,15 @@
export const AUDIO_ELEMENT_FOR_PLAY_RESULT = "audio-result"
export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original"
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted"
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback"
export const AUDIO_ELEMENT_FOR_PLAY_RESULT = "audio-result" // 変換後の出力用プレイヤー
export const AUDIO_ELEMENT_FOR_PLAY_MONITOR = "audio-monitor" // 変換後のモニター用プレイヤー
export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original" // ??? 使ってないかも。
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted" // ファイルインプットのコントロール
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback" // ファイルインプットのエコーバック
export const AUDIO_ELEMENT_FOR_SAMPLING_INPUT = "body-wav-container-wav-input"
export const AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT = "body-wav-container-wav-output"
export const INDEXEDDB_KEY_AUDIO_OUTPUT = "INDEXEDDB_KEY_AUDIO_OUTPUT"
export const INDEXEDDB_KEY_AUDIO_MONITR = "INDEXEDDB_KEY_AUDIO_MONITOR"
export const INDEXEDDB_KEY_DEFAULT_MODEL_TYPE = "INDEXEDDB_KEY_DEFALT_MODEL_TYPE"

View File

@ -97,6 +97,7 @@ export const ServerSettingKey = {
"serverReadChunkSize": "serverReadChunkSize",
"serverInputAudioGain": "serverInputAudioGain",
"serverOutputAudioGain": "serverOutputAudioGain",
"serverMonitorAudioGain": "serverMonitorAudioGain",
"tran": "tran",
"noiseScale": "noiseScale",
@ -157,6 +158,7 @@ export type VoiceChangerServerSetting = {
serverReadChunkSize: number
serverInputAudioGain: number
serverOutputAudioGain: number
serverMonitorAudioGain: number
tran: number // so-vits-svc
@ -361,6 +363,7 @@ export const DefaultServerSetting: ServerInfo = {
serverReadChunkSize: 256,
serverInputAudioGain: 1.0,
serverOutputAudioGain: 1.0,
serverMonitorAudioGain: 1.0,
// VC Specific
srcId: 0,

View File

@ -47,6 +47,7 @@ export type ClientState = {
clearSetting: () => Promise<void>
// AudioOutputElement 設定
setAudioOutputElementId: (elemId: string) => void
setAudioMonitorElementId: (elemId: string) => void
ioErrorCount: number
resetIoErrorCount: () => void
@ -215,6 +216,18 @@ export const useClient = (props: UseClientProps): ClientState => {
}
}
const setAudioMonitorElementId = (elemId: string) => {
if (!voiceChangerClientRef.current) {
console.warn("[voiceChangerClient] is not ready for set audio output.")
return
}
const audio = document.getElementById(elemId) as HTMLAudioElement
if (audio.paused) {
audio.srcObject = voiceChangerClientRef.current.stream
audio.play()
}
}
// (2-2) 情報リロード
const getInfo = useMemo(() => {
return async () => {
@ -286,6 +299,7 @@ export const useClient = (props: UseClientProps): ClientState => {
// AudioOutputElement 設定
setAudioOutputElementId,
setAudioMonitorElementId,
ioErrorCount,
resetIoErrorCount