React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React 音频上传与试听

React实现音频文件上传与试听

作者:山雀~

本文主要介绍了React实现音频文件上传与试听,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

实现一个react音频上传的大致逻辑如下:

1、上方提示语

<Alert
                message={$t('支持%s,音频大小需为%d以内,音频采样率为%e')
                    .replace('%s', AUDIO_TYPES.join())
                    .replace('%d', `${AUDIO_MAX_SIZE}KB`)
                    .replace('%e', '16K')}
                type='info'
                showIcon
                style={{ width: '50%', marginBottom: 16 }}
            />

其中AUDIO_TYPES是我们定义的['MP3']类型数组集合,AUDIO_MAX_SIZE100,后续可修改

2、封装上传组件

<AudioUpload>
    <Button icon={<UploadOutlined />}>上传</Button>
</AudioUpload>

可以封装一个AudioUpload组件,组件内部大概是

const AudioUpload = props => {
    const { children } = props;
    
    return (
        <Upload>
            {children}  {/* 自定义上传按钮 */}
        </Upload>
    );
};

当然并不会这么简单,大致解释一下上传的事件监听机制

// 不是事件冒泡,而是组件嵌套关系
<Upload>           {/* 父组件 */}
    <Button>       {/* 子组件 */}
        上传
    </Button>
</Upload>

具体的流程

首先

// 用户点击这个按钮
<Button icon={<UploadOutlined />}>
    {$t('com.Upload')}
</Button>

AudioUpload组件中的Upload 组件捕获点击
Upload 组件内部的事件监听器被触发
不是事件冒泡,而是 Upload 组件主动监听子元素的点击

// Upload 组件内部自动执行
const hiddenInput = document.querySelector('input[type="file"]');
hiddenInput.click(); // 触发文件选择对话框

3、Ant Design Upload 的实现原理

事件委托模式

// Upload 组件内部的伪代码逻辑
class Upload extends React.Component {
    componentDidMount() {
        // 监听整个上传区域的点击事件
        this.uploadArea.addEventListener('click', (e) => {
            // 检查点击的是否是子元素
            if (e.target.closest('.ant-upload-select-button')) {
                // 触发文件选择
                this.triggerFileSelect();
            }
        });
    }
    
    triggerFileSelect() {
        // 显示文件选择对话框
        this.fileInput.click();
    }
}

4、{children} 的核心作用

提供可点击的 UI 元素

<Upload>
    {children}  {/* 这里需要一个可点击的元素来触发文件选择 */}
</Upload>
// 如果 Upload 组件没有 children
<Upload>
    {/* 空的,没有可点击的元素 */}
</Upload>

// 结果:用户无法点击任何地方来触发文件选择
// Upload 组件不知道应该监听哪个元素的点击事件

5、Upload内部参数含义

<Upload
            name='file'
            headers={}
            action={file =>
                Promise.resolve(
           			 `地址 ${file.name}`
                )
            }
            accept={AUDIO_TYPES.map(item => `.${item}`).join()}
            showUploadList={false}
            beforeUpload={beforeUpload}
            onSuccess={refresh}>
            {children}
        </Upload>

5.1 name

name 属性指定了文件在表单数据中的字段名,服务器端通过这个字段名来获取上传的文件。

服务器端接收:

// 服务器端会这样获取文件
const uploadedFile = req.files.file;  // 通过 'file' 字段名获取

name 的其他可能值

// 根据文件类型命名
name='audio'           // 音频文件
name='image'           // 图片文件
name='document'        // 文档文件
name='video'           // 视频文件

// 根据业务功能命名
name='profile-picture' // 头像
name='background-music' // 背景音乐
name='notification-sound' // 通知音效

5.2 headers

headers 的作用
headers 属性用于设置 HTTP 请求的请求头,通常用于:
身份认证
跨域请求
自定义请求信息
服务器端识别

headers 的常见用途

// 认证相关
headers={{
    'Authorization': 'Bearer ' + token,
    'X-API-Key': apiKey,
    'User-Token': userToken
}}

// 跨域相关
headers={{
    'Access-Control-Allow-Origin': '*',
    'Content-Type': 'multipart/form-data'
}}

// 自定义标识
headers={{
    'X-Request-ID': generateRequestId(),
    'X-Client-Version': '1.0.0',
    'X-Platform': 'web'
}}

// 业务相关
headers={{
    'Device-Id': deviceId,
    'Session-Id': sessionId,
    'Request-Source': 'audio-upload'
}}

5.3 action 属性

action 的基本作用

1.定义上传地址

action 属性指定了文件上传的目标 URL,告诉 Upload 组件将文件发送到哪个服务器地址。

2. 触发上传流程

当用户选择文件并通过验证后,Upload 组件会自动向 action 指定的地址发送 HTTP POST 请求,将文件数据上传到服务器。

action 的不同配置方式

1.静态 URL

// 最简单的配置
<Upload action="/api/upload">
    <Button>上传</Button>
</Upload>

// 完整的 URL
<Upload action="https://api.example.com/upload">
    <Button>上传</Button>
</Upload>

2.动态 URL

// 根据文件信息动态构建
action={file => `/upload/${file.name}`}

// 根据环境动态选择
action={file => 
    process.env.NODE_ENV === 'production' 
        ? 'https://api.prod.com/upload' 
        : 'http://localhost:3000/upload'
}

// 根据文件类型动态选择
action={file => {
    if (file.type.startsWith('audio/')) {
        return '/api/upload/audio';
    }
    if (file.type.startsWith('image/')) {
        return '/api/upload/image';
    }
    return '/api/upload/file';
}}

action 的执行时机

// 1. 用户选择文件
// 2. beforeUpload 验证通过
// 3. action 函数被调用,获取上传地址
// 4. 向该地址发送文件数据
// 5. 触发 onSuccess 或 onError 回调

使用 Promise.resolve

1、兼容性考虑

// Ant Design Upload 组件期望 action 返回一个 Promise
// 即使我们返回的是同步的字符串,也需要包装成 Promise

// 正确的方式
action={file => Promise.resolve(uploadUrl)}

// 也可以这样写
action={file => new Promise(resolve => resolve(uploadUrl))}

// 或者使用 async/await
action={async file => uploadUrl}

5.4 分块上传实现简单方案

1. 基本思路

// 在 action 中判断文件大小,大文件走分块上传
action={file => {
    if (file.size > 10 * 1024 * 1024) { // 大于10MB
        // 分块上传
        return '/api/upload/chunk';
    } else {
        // 普通上传
        return '/api/upload/normal';
    }
}}

具体是

const AudioUpload = props => {
    const [isChunked, setIsChunked] = useState(false);
    
    const handleChunkedUpload = async (file) => {
        // 分块上传逻辑
        const chunkSize = 1024 * 1024; // 1MB每块
        const totalChunks = Math.ceil(file.size / chunkSize);
        
        for (let i = 0; i < totalChunks; i++) {
            const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
            const formData = new FormData();
            formData.append('chunk', chunk);
            formData.append('chunkIndex', i);
            formData.append('totalChunks', totalChunks);
            
            await fetch('/api/upload/chunk', {
                method: 'POST',
                body: formData
            });
        }
        
        // 合并文件
        await fetch('/api/upload/merge', {
            method: 'POST',
            body: JSON.stringify({ fileName: file.name, totalChunks })
        });
    };

    const beforeUpload = file => {
        // 大文件使用分块上传
        if (file.size > 10 * 1024 * 1024) {
            setIsChunked(true);
            handleChunkedUpload(file);
            return false; // 阻止默认上传
        }
        return true; // 小文件正常上传
    };

    return (
        <Upload
            action={file => {
                if (isChunked) {
                    return '/api/upload/chunk'; // 分块上传地址
                }
                // 原有的上传地址
                return `地址+${file.name}`;
            }}
            beforeUpload={beforeUpload}
            onSuccess={refresh}>
            {children}
        </Upload>
    );
};

5.5 accept属性

文件选择阶段

<Upload
    accept={AUDIO_TYPES.map(item => `.${item}`).join()}  // 只显示 .MP3 文件
    // ...
>

浏览器原生文件选择器
通过 accept 属性过滤,只显示 MP3 文件
用户选择文件后,浏览器将文件对象传递给组件

5.6 beforeUpload属性

const beforeUpload = file => {
    const { name, size } = file;  // 从 File 对象获取文件信息
    
    // 执行各种验证...
    
    // 返回 false 阻止上传,返回 true 允许上传
};

5.7 onSuccess属性

一般是执行刷新操作,重新请求服务器中上传文件列表,展示到table中

6、音频播放控制功能分析

主要是在Table中每一行音频文件的最后放一个图标

播放音频

{playUid !== record.uid ? (
                                    <Icon
                                        type='play3'
                                        className='audio-btn'
                                        onClick={() => handlePlayAudio(record)}
                                    />
                                ) : (
                                    <Icon
                                        type='pause2'
                                        className='audio-btn'
                                        onClick={() => handlePauseAudio()}
                                    />
                                )}

其中一开始

const [playUid, setPlayUid] = useState(-1);

那么开始前就是播放图标

执行流程

const handlePlayAudio = record => {
    // 1. 提取音频信息
    const { uid, url } = record;
    
    // 2. 设置播放状态
    setPlayUid(uid);
    
    // 3. 获取环境配置
    const urlParamsString = localStorage.getItem('_urlParams');
    const urlParams = urlParamsString ? JSON.parse(urlParamsString) : {};
    const { Prefix, UserToken, DeviceId } = urlParams;
    
    // 4. 构建文件加载地址
    const preFix = 地址;
    
    // 5. 先停止上一个音频
    if (lastAudio.current) {
        lastAudio.current.pause();
    }
    
    // 6. 根据部署模式选择播放方式
    if (CLOUDWEB) {
        // 云端模式:先下载再播放
        // ...
    } else {
        // 本地模式:直接播放
        // ...
    }
};

本地模式处理

else {
    // 直接使用文件路径创建音频对象
    lastAudio.current = new Audio(preFix + url);
    
    // 开始播放
    lastAudio.current.play();
    
    // 设置播放结束和错误处理
    lastAudio.current.onended = () => {
        setPlayUid(-1);
    };
    lastAudio.current.onerror = () => {
        setPlayUid(-1);
    };
}

暂停当前正在播放的音频

const handlePauseAudio = () => {
    // 1. 暂停音频播放
    if (lastAudio.current) {
        lastAudio.current.pause();
    }
    
    // 2. 重置播放状态
    setPlayUid(-1);
};

7、文件内容处理机制详解

7.1 文件对象结构

const file = {
    name: 'audio.mp3',           // 文件名
    size: 51200,                 // 文件大小(字节)
    type: 'audio/mpeg',          // MIME类型
    lastModified: 1234567890,    // 最后修改时间
    // 文件的实际二进制内容存储在内存中,但前端不直接读取
};

7.2 文件内容存储位置

前端: 只获取文件的元数据(名称、大小、类型等)
实际内容: 存储在浏览器的内存中,作为 File 对象的一部分
传输: 通过 HTTP 请求自动传输到服务器

7.3 文件内容的传输机制

自动传输过程

<Upload
    name='file'                    // 表单字段名
    action={file => Promise.resolve(uploadUrl)}  // 上传地址
    // ...
/>

传输流程:

浏览器自动创建 FormData 对象
将 File 对象作为 file 字段添加到表单
发送 POST 请求到服务器
文件内容作为请求体的一部分自动传输

服务器端接收

// 服务器端接收到的数据格式:
// Content-Type: multipart/form-data
// 
// --boundary
// Content-Disposition: form-data; name="file"; filename="audio.mp3"
// Content-Type: audio/mpeg
// 
// [二进制音频文件内容]
// --boundary--

上传文件生命周期

用户选择文件 → 文件存储在浏览器内存 → 通过HTTP传输到服务器 → 服务器存储

到此这篇关于React实现音频文件上传与试听的文章就介绍到这了,更多相关React 音频上传与试听内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文