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

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

在这里插入图片描述实现一个react音频上传的大致逻辑如下:

1、上方提示语

<Alertmessage={$t('支持%s,音频大小需为%d以内,音频采样率为%e').replace('%s', AUDIO_TYPES.join()).replace('%d', `${AUDIO_MAX_SIZE}KB`).replace('%e', '16K')}type='info'showIconstyle={{ 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内部参数含义

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

5.5 accept属性

文件选择阶段

<Uploadaccept={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 ? (<Icontype='play3'className='audio-btn'onClick={() => handlePlayAudio(record)}/>) : (<Icontype='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 文件内容的传输机制

自动传输过程

<Uploadname='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传输到服务器 → 服务器存储
http://www.xdnf.cn/news/20083.html

相关文章:

  • 服务器安装vnc服务端
  • jenkins安装和配置流程
  • 深度学习——CNN实例手写数字
  • 归一化的定义与作用
  • ip地址是硬件自带的还是被分配的
  • 《单链表经典问题全解析:5 大核心题型(移除元素 / 反转 / 找中点 / 合并 / 回文判断)实现与详解》
  • 面试高频问题总结
  • 基于 Socket 和多线程的简单 Echo 服务器实现
  • [UT]记录uvm_config_db的错误:get中的第二个参数设置为this
  • 小企业环境-火山方舟和扣子
  • 【FPGA】DDS信号发生器
  • 【C++】Vector核心实现:类设计到迭代器陷阱
  • < 自用文 主机 USC 记录:> 发现正在被攻击 后的自救
  • 天然苏打水生产的原水抽取与三重除菌的3D模拟开发实战
  • AI大模型对决:谁是最强智能?
  • MySQL 清空表实战:TRUNCATE 与 DELETE 的核心差异与正确用法
  • 小白成长之路-develops -jenkins部署lnmp平台
  • 淘宝京东拼多多爬虫实战:反爬对抗、避坑技巧与数据安全要点
  • EDVAC:现代计算机体系的奠基之作
  • JMeter下载安装及使用入门
  • MySQL 行转列 (Pivot) 的 N 种实现方式:静态、动态与 GROUP_CONCAT 详解
  • linux0.12 head.s代码解析
  • Langchain4j 整合MongoDB 实现会话持久化存储详解
  • Day34 UDP套接字编程 可靠文件传输与实时双向聊天系统
  • HTML5圣诞网站源码
  • Python基础(①①Ctypes)
  • Web安全——JWT
  • 厦门创客匠人靠谱嘛?从内容交付能力看其核心优势
  • el-tree 点击父节点无效,只能选中子节点
  • [BUUCTF-OGeek2019]babyrop详解(包含思考过程)