当前位置: 首页 > article >正文

webassembly009 transformers.js 网页端侧推理 whisper-web的AudioManager组件

  • 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>
                    )}
                </>
            )}
        </>
    );
}

http://www.kler.cn/a/548719.html

相关文章:

  • 42 接雨水
  • 【数据结构】 栈和队列
  • 网剧《一念逍遥》正式启动筹备
  • vLLM专题(二):安装-CPU
  • 【Python】Python入门基础——环境搭建
  • Ubuntu20.04部署stable-diffusion-webui环境小记
  • Leetcode100-春招-矩阵题类
  • 【06】泛型
  • Httprint 指纹识别技术:网络安全的关键洞察
  • [高等数学] 分部积分法
  • 大模型开发实战篇5:多模态--文生图模型API
  • Flask中获取请求参数的一些方式总结
  • DeepSeek在linux下的安装部署与应用测试
  • 基于Python的Flask微博话题舆情分析可视化系统
  • Dify+Ollama+DeepSeek部署本地大模型+知识库搭建
  • Typescript class中的方法和函数类型的属性有何不同?
  • 每日一题——47. 全排列 II
  • Linux系统Centos安装部署nginx代理
  • 数字内容体验未来趋势:五大平台横向对比与深度解析
  • 惠普HP Color LaserJet CP1215/1210彩色打印机打印校准方法