- AudioManager 和 Transcriber 通过 props 关联的。在 React 中,props(properties 的缩写)是组件之间传递数据和回调函数的主要方式。它们可以被看作是组件的输入参数,允许父组件向子组件传递信息或功能。具体来说,AudioManager 组件接收一个包含 transcriber 属性的对象作为其 props 参数,这个 transcriber 对象实现了特定接口(即 Transcriber),用于处理音频转录相关的逻辑。
APP.tsx中调用AudioManager
-
const transcriber = useTranscriber();
代码实现了一个自定义的 React 钩子 useTranscriber 来初始化一个 实例transcriber
,用于处理音频转录过程,结合 Web Worker 和后台模型进行推理。
-
<AudioManager transcriber={transcriber} />
通过 useTranscriber() 获取到的 transcriber 对象作为 props 传递给 AudioManager 组件。这意味着 AudioManager 可以访问 transcriber 上的所有方法和属性,从而可以根据用户的交互(如选择文件、点击录音按钮等)来触发相应的音频处理操作。
AudioManager实现
- AudioManager.tsx中实现
AudioManager
export function AudioManager(props: { transcriber: Transcriber }) {
const [progress, setProgress] = useState<number | undefined>(undefined);
const [audioData, setAudioData] = useState<
| {
buffer: AudioBuffer;
url: string;
source: AudioSource;
mimeType: string;
}
| undefined
>(undefined);
const [audioDownloadUrl, setAudioDownloadUrl] = useState<string | undefined>(undefined);
const isAudioLoading = progress !== undefined;
const resetAudio = () => {
setAudioData(undefined);
setAudioDownloadUrl(undefined);
};
const setAudioFromDownload = async (
data: ArrayBuffer,
mimeType: string,
) => {
const audioCTX = new AudioContext({
sampleRate: Constants.SAMPLING_RATE,
});
const blobUrl = URL.createObjectURL(
new Blob([data], { type: "audio/*" }),
);
const decoded = await audioCTX.decodeAudioData(data);
setAudioData({
buffer: decoded,
url: blobUrl,
source: AudioSource.URL,
mimeType: mimeType,
});
};
const setAudioFromRecording = async (data: Blob) => {
resetAudio();
setProgress(0);
const blobUrl = URL.createObjectURL(data);
const fileReader = new FileReader();
fileReader.onprogress = (event) => {
setProgress(event.loaded / event.total || 0);
};
fileReader.onloadend = async () => {
const audioCTX = new AudioContext({
sampleRate: Constants.SAMPLING_RATE,
});
const arrayBuffer = fileReader.result as ArrayBuffer;
const decoded = await audioCTX.decodeAudioData(arrayBuffer);
setProgress(undefined);
setAudioData({
buffer: decoded,
url: blobUrl,
source: AudioSource.RECORDING,
mimeType: data.type,
});
};
fileReader.readAsArrayBuffer(data);
};
const downloadAudioFromUrl = async (
requestAbortController: AbortController,
) => {
if (audioDownloadUrl) {
try {
setAudioData(undefined);
setProgress(0);
const { data, headers } = (await axios.get(audioDownloadUrl, {
signal: requestAbortController.signal,
responseType: "arraybuffer",
onDownloadProgress(progressEvent) {
setProgress(progressEvent.progress || 0);
},
})) as {
data: ArrayBuffer;
headers: { "content-type": string };
};
let mimeType = headers["content-type"];
if (!mimeType || mimeType === "audio/wave") {
mimeType = "audio/wav";
}
setAudioFromDownload(data, mimeType);
} catch (error) {
console.log("Request failed or aborted", error);
} finally {
setProgress(undefined);
}
}
};
useEffect(() => {
if (audioDownloadUrl) {
const requestAbortController = new AbortController();
downloadAudioFromUrl(requestAbortController);
return () => {
requestAbortController.abort();
};
}
}, [audioDownloadUrl]);
return (
<>
{}
<div className='flex flex-col justify-center items-center rounded-lg bg-white shadow-xl shadow-black/5 ring-1 ring-slate-700/10'>
{}
<div className='flex flex-row space-x-2 py-2 w-full px-2'>
{}
<UrlTile
icon={<AnchorIcon />}
text={"From URL"}
onUrlUpdate={(e) => {
props.transcriber.onInputChange();
setAudioDownloadUrl(e);
}}
/>
<VerticalBar />
{}
<FileTile
icon={<FolderIcon />}
text={"From file"}
onFileUpdate={(decoded, blobUrl, mimeType) => {
props.transcriber.onInputChange();
setAudioData({
buffer: decoded,
url: blobUrl,
source: AudioSource.FILE,
mimeType: mimeType,
});
}}
/>
{}
{navigator.mediaDevices && (
<>
<VerticalBar />
<RecordTile
icon={<MicrophoneIcon />}
text={"Record"}
setAudioData={(e) => {
props.transcriber.onInputChange();
setAudioFromRecording(e);
}}
/>
</>
)}
</div>
{}
{
<AudioDataBar
progress={isAudioLoading ? progress : +!!audioData}
/>
}
</div>
{}
{audioData && (
<>
<AudioPlayer
audioUrl={audioData.url}
mimeType={audioData.mimeType}
/>
<div className='relative w-full flex justify-center items-center'>
{}
<TranscribeButton
onClick={() => {
props.transcriber.start(audioData.buffer);
}}
isModelLoading={props.transcriber.isModelLoading}
isTranscribing={props.transcriber.isBusy}
/>
{}
<SettingsTile
className='absolute right-4'
transcriber={props.transcriber}
icon={<SettingsIcon />}
/>
</div>
{}
{props.transcriber.progressItems.length > 0 && (
<div className='relative z-10 p-4 w-full'>
<label>
Loading model files... (only run once)
</label>
{props.transcriber.progressItems.map((data) => (
<div key={data.file}>
<Progress
text={data.file}
percentage={data.progress}
/>
</div>
))}
</div>
)}
</>
)}
</>
);
}