- 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
// 导出名为 AudioManager 的 React 函数组件,该组件接收一个包含 transcriber 属性的对象作为 props。
export function AudioManager(props: { transcriber: Transcriber }) {
// 使用 useState Hook 初始化状态变量 progress,用于跟踪音频加载进度。初始值为 undefined。
const [progress, setProgress] = useState<number | undefined>(undefined);
// 初始化状态变量 audioData,用于存储音频数据(包括解码后的缓冲区、URL、来源类型及 MIME 类型)。初始值为 undefined。
const [audioData, setAudioData] = useState<
| {
buffer: AudioBuffer;
url: string;
source: AudioSource;
mimeType: string;
}
| undefined
>(undefined);
// 初始化状态变量 audioDownloadUrl,用于存储正在下载的音频文件 URL。初始值为 undefined。
const [audioDownloadUrl, setAudioDownloadUrl] = useState<string | undefined>(undefined);
// 判断当前是否有音频正在加载或下载中。如果 progress 不是 undefined,则返回 true。
const isAudioLoading = progress !== undefined;
// 定义 resetAudio 方法,用于清除当前的音频数据和下载链接。
const resetAudio = () => {
setAudioData(undefined);
setAudioDownloadUrl(undefined);
};
// 定义 setAudioFromDownload 方法,用于处理从下载的数据设置音频信息。
const setAudioFromDownload = async (
data: ArrayBuffer,
mimeType: string,
) => {
// 创建一个新的 AudioContext 实例,指定采样率。
const audioCTX = new AudioContext({
sampleRate: Constants.SAMPLING_RATE,
});
// 使用下载的数据创建一个 Blob 对象,并生成一个可访问该 Blob 的 URL。
const blobUrl = URL.createObjectURL(
new Blob([data], { type: "audio/*" }),
);
// 解码下载的音频数据为 AudioBuffer 格式。
const decoded = await audioCTX.decodeAudioData(data);
// 更新状态,设置新的音频数据。
setAudioData({
buffer: decoded,
url: blobUrl,
source: AudioSource.URL,
mimeType: mimeType,
});
};
// 定义 setAudioFromRecording 方法,用于处理录音数据并更新音频信息。
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,
});
};
// 开始以 Array Buffer 格式读取文件。
fileReader.readAsArrayBuffer(data);
};
// 定义 downloadAudioFromUrl 方法,用于从给定的 URL 下载音频文件。
const downloadAudioFromUrl = async (
requestAbortController: AbortController,
) => {
if (audioDownloadUrl) {
try {
setAudioData(undefined);
setProgress(0);
// 发起 GET 请求下载音频文件。
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 Hook 监听 audioDownloadUrl 变化,当 URL 变化时自动尝试下载音频文件。
useEffect(() => {
if (audioDownloadUrl) {
const requestAbortController = new AbortController();
downloadAudioFromUrl(requestAbortController);
// 返回一个清理函数,在组件卸载时取消任何正在进行的请求。
return () => {
requestAbortController.abort();
};
}
}, [audioDownloadUrl]);
// 返回 JSX 代码,定义组件 UI 结构。
return (
<>
{/* 组件的主要 UI 布局 */}
<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'>
{/* URL 输入 */}
<UrlTile
icon={<AnchorIcon />}
text={"From URL"}
onUrlUpdate={(e) => {
props.transcriber.onInputChange(); // 触发输入变化回调
setAudioDownloadUrl(e); // 设置音频下载 URL
}}
/>
<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>
)}
</>
)}
</>
);
}
网页端whisper-web的AudioManager组件解析

被折叠的 条评论
为什么被折叠?



