上传音频文件
思路
1、自定义Upload
重点:<input ref={inputRef} type="file" accept={accept} onClick={e => e.stopPropagation()} onChange={uploadFile} multiple={multiple}/>
使用input标签设置type是file,将input元素通过forwardRef暴露给父组件,使父组件可以通过useImperativeHandle透传的resetValue方法在外部控制input的value值,
2、自定义AudioUpload
通过区分iOS和Android设置不同的accept来解决格式的兼容性问题
上传流程:文件格式校验——音频时长校验——获取upload token——上传文件——获取到ossUrl
3、使用 AudioUpload组件
.mp3, .wav, .m4a 和 audio/*
.mp3, .wav, 和 .m4a 是具体的音频文件格式
audio/* 是一个 MIME 类型,它表示所有音频文件类型
- MP3 比较流行,有损压缩的音频格式
- wav 无损未压缩的,文件较大
- m4a 通常用于Apple设备
- audio/*支持大多数音频
总结:需要处理特定格式的音频文件用前者;希望支持多种音频格式用后者;
1、自定义Upload
Upload/index.module.css
.tongyi-upload {outline: 0;
}
Upload/index.tsx
import React, { ReactNode, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import classnames from 'classnames';
import styles from './index.module.css';interface UploadChangeParam {file: File;fileList: File[];event?: { percent: number };
}interface UploadProps {className?: string;style?: React.CSSProperties;accept?: string;multiple?: boolean;withCredentials?: boolean;children: ReactNode;// 限制大小maxSize?: number;// onChange?: (info: UploadChangeParam) => void;customRequest: (info: { file: File }) => Promise<void>;onError?: (e: any) => void;
}export default forwardRef((props: UploadProps, ref: any) => {const { className, accept, maxSize, multiple = false, customRequest, onError, children } = props;const inputRef = useRef<HTMLInputElement>(null);const uploadFile = (e: React.ChangeEvent<HTMLInputElement>) => {const { files } = e.target;if (!files) {return;}const originFiles = [...files] as File[];originFiles.forEach((file: File) => {if (maxSize && file.size > maxSize) {onError && onError({code: 'FILE_EXCEEDS_SIZE'});return;}return customRequest && customRequest({file});});};useImperativeHandle(ref, () => {return {resetValue: () => {if (inputRef.current) {inputRef.current.value = '';}}}});return (<divclassName={classnames(styles['tongyi-upload'], className)}onClick={() => inputRef.current?.click()}style={props.style}><inputtype="file"ref={inputRef}style={{ display: 'none' }}accept={accept}onClick={e => e.stopPropagation()}onChange={uploadFile}multiple={multiple}/>{children}</div>)
})
2、自定义AudioUpload
AudioUpload.tsx
import { getUploadToken, uploadFile } from '@/services/file';
import Upload from './Upload';
import { getDeviceType } from '@/utils';
import { getAudioDuration } from '@/utils/audioFile';
import React, {forwardRef,ReactNode,useImperativeHandle,useRef,
} from 'react';interface UploadTokenData {accessId: string;policy: string;signature: string;dir: string;host: string;expire: number;bucketName: string;key: string;
}interface UploadProps {className?: string;accept?: string;multiple?: boolean;withCredentials?: boolean;style?: React.CSSProperties;children: ReactNode;beforeUpload?: () => void;onChange?: (ossUrl: string) => void;onError?: (err: any) => void;
}// 文件大小限制
const maxSize = 10 * 1024 * 1024;
const audioFileType = ['mp3', 'aac', 'wav', 'flac', 'ogg', 'm4a'];export default forwardRef((props: UploadProps, ref: any) => {const uploadStatusRef = useRef<any>({});const uploadRef = useRef<any>({});const proxyFn = (fn: () => Promise<any>) => {if (uploadStatusRef.current.status !== 'cancel') {return fn();}return Promise.reject('cancel');};const customRequest = async (info: { file: File }) => {uploadStatusRef.current.status = 'ready';if (!info.file?.type.startsWith('audio/')) {props.onError &&props.onError({code: 'FILE_TYPE_ERROR',message: '',});return Promise.reject('FILE_TYPE_ERROR');}const duration: number = await getAudioDuration(info.file);console.log('获取到的时长', duration);if (duration < 10 || duration > 30) {props.onError &&props.onError({code: 'FILE_DURATION_ERROR',message: '',});return Promise.reject('FILE_DURATION_ERROR');}props.beforeUpload && props.beforeUpload();console.log('文件信息=========', info.file);return (// proxyFn(() => getAudioUploadToken(info.file.name))proxyFn(() => getUploadToken()).then((data: UploadTokenData) => {console.log('getAudioUploadToken的结果', data);return proxyFn(() => uploadFile(data, info.file)).then((uploadRes) => {console.log('uploadFile成功了', uploadRes);props.onChange && props.onChange(uploadRes?.ossUrl);}).catch((e) => {console.log('uploadFile报错了===========', e);props.onError &&props.onError({code: 'UNKNOW',message: e.errorMsg,});});}).catch((e) => {console.log('e=========', e);const { errorMsg } = e;if (e !== 'cancel') {props.onError &&props.onError({code: 'UPLOAD_ERROR',});}}));};useImperativeHandle(ref,() => ({cancel: () => {uploadStatusRef.current.status = 'cancel';uploadRef.current.resetValue();clearTimeout(uploadStatusRef.current.clock);},}),[],);const accept = getDeviceType() ? '.mp3, .wav, .m4a' : 'audio/*';return (<UploadmaxSize={maxSize}accept={accept}{...props}ref={uploadRef}customRequest={customRequest}>{props.children}</Upload>);
});
3、使用 AudioUpload组件
// 上传组件
const uploadRef = useRef<any>();
// 上传状态
const[uploadStatus, setUploadStatus] = useState<string>('default');
// 上传定时器
const uploadTimer = useRef<any>();
// 合成进度
const [percent, setPercent] = useState<number>(0);/*** 开始上传*/const startUpload = () => {console.log('上传中');setUploadStatus('processing');setPercent(0);const fn = () => {const i = Math.ceil(Math.random() * 3);uploadTimer.current = setTimeout(() => {fn();}, 1 * 1000);setPercent((pre) => {let current = pre + i;if (current >= 100) {current = 100;clearTimeout(uploadTimer.current);}return current;});};clearTimeout(uploadTimer.current);uploadTimer.current = setTimeout(() => {fn();}, 1 * 1000);};
/*** 上传失败* @param e*/const onError = (e: any) => {console.log('上传失败了');onChange('');setUploadStatus('error');clearTimeout(uploadTimer.current);uploadRef.current.cancel();setPercent(0);let msg = '';switch (e.code) {case 'FILE_DURATION_ERROR':msg = '请上传10-30秒音频文件';break;case 'FILE_EXCEEDS_SIZE':msg = '请上传10M以下的文件';break;case 'FILE_TYPE_ERROR':msg = '抱歉,请上传音频类型的文件';break;case 'SEC_RESULT':msg = e.message || `${e.code}抱歉,出错了,请换一个文件试试!`;break;case 'UPLOAD_ERROR':msg = '抱歉,上传失败,请重新上传';break;default:msg = `${e.code}抱歉,出错了,请换一个文件试试!`;break;}Toast.show({type: 'error',content: msg,});};/*** 上传成功*/const onUploaded = (ossUrl: string) => {console.log('上传成功获取到ossUrl', ossUrl);clearTimeout(uploadTimer.current);setUploadStatus('success');// 上传成功之后调用接口合成数字声音。。。onMergeSound(ossUrl);};
return (<AudioUploadonError={onError}onChange={onUploaded}beforeUpload={startUpload}ref={uploadRef}><div>上传</div></AudioUpload>
)
4、音频时长校验
/*** 异步获取音频文件的时长* @param file 音频文件* @returns 返回音频的时长(秒)*/
export const getAudioDuration = async (file) => {try {const audio = new Audio(URL.createObjectURL(file));await new Promise((resolve) => (audio.onloadedmetadata = resolve));const { duration } = audio;return duration;} catch (error) {console.error('获取音频时长时发生错误:', error);return 0;}
};
5、上传文件
export const uploadFile = (data: UploadTokenData, file: File) => {console.log('uploadFile开始了', data, '====', file);const bodyFormData = new FormData();const url = `${data.host}/${data.dir}${file.name}`;bodyFormData.append('OSSAccessKeyId', data.accessId);bodyFormData.append('policy', data.policy);bodyFormData.append('signature', data.signature);bodyFormData.append('key', `${data.dir}${file.name}`);bodyFormData.append('dir', data.dir);bodyFormData.append('success_action_status', '200');bodyFormData.append('file', file);console.log('uploadFile上传的url: ', url);return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();xhr.onerror = function error(e) {console.log('upload error', e);reject(e);};xhr.onload = async () => {// allow success when 2xx status see https://github.com/react-component/upload/issues/34if (xhr.status < 200 || xhr.status >= 300) {reject('上传异常');}console.log('upload success');resolve({...data,ossUrl: url,});};xhr.open('post', data.host, true);xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');xhr.send(bodyFormData);});
};
相关文章:
上传音频文件
思路 1、自定义Upload 重点:<input ref{inputRef} type"file" accept{accept} onClick{e > e.stopPropagation()} onChange{uploadFile} multiple{multiple}/> 使用input标签设置type是file,将input元素通过forwardRef暴露给父组件&…...
Linux之jdk离线安装
下载地址 一、linux安装jdk8 1、上传,解压 cd /usr/local/java tar -zxvf jdk-10.0.2_linux-x64_bin.tar.gz2、修改配置 vim /etc/profile #在/etc/profile文件后面加上如下配置 export JAVA_HOME/usr/local/java/jdk-10.0.2 export JRE_HOME/usr/local/java/jd…...
JVM结构、架构与生命周期总结
【1】JVM结构 不同厂商的JVM产品 : 厂商JVMOracle-SUNHotspotOracleJRocketIBMJ9 JVM阿里Taobao JVM HotSpot VM是目前市面上高性能虚拟机的代表作之一。它采用解释器与即时编译器并存的架构。 在今天,Java程序的运行性能早已脱胎换骨,已…...
Flink-StarRocks详解:第四部分StarRocks分区管理,数据压缩(第54天)
文章目录 前言2.3.3 管理分区2.3.3.1 增加分区2.3.3.2 删除分区2.3.3.3 恢复分区2.3.3.4 查看分区 2.3.4 设置分桶2.3.4.1 随机分桶(自 v3.1)2.3.4.2 哈希分桶2.3.4.2.1 优点2.3.4.2.2 如何选择分桶键2.3.4.2.3 注意事项 2.3.4.3 确定分桶数量 2.3.5 最佳…...
为什么有时候银行贷款审核会查大数据信用?
在申请银行贷款时,不少人会疑惑为何银行会深入审查申请人的大数据信用信息。这背后,其实是银行风险控制与精准决策的体现。 首先,大数据信用信用能全面反映申请人的信用状况 它不仅仅局限于传统的征信报告,还涵盖了消费行为、社交…...
LoRa无线通讯,让光伏机器人实现无“线”管理
光伏清洁机器人,作为光伏电站运维的新兴关键设备,已跃升为继组件、支架、光伏逆变器之后的第四大核心组件,正逐步成为光伏电站的标准配置。鉴于光伏电站普遍坐落于偏远无人区或地形复杂之地,光伏清洁机器人必须具备远程操控能力、…...
买流量卡要注意什么,这些冷知识你一定要懂!
买流量卡要注意什么?别总盯着价格看,还有一些隐形的冷知识得了解一下,今天这篇文章就是要告诉你一些流量卡中隐藏的冷知识。 一、首先,那些月租9元、19元的流量卡,大概率都是短期卡,虽然他们的资费便宜&a…...
【嵌入式】STM3212864点阵屏使用SimpleGUI单色屏接口库——(2)精简字库
一 开源库简介与移植 最近一个项目需要用12864屏幕呈现一组较为复杂的菜单界面,本着不重复造轮子的原则找到了SimpleGUI开源库。 开源地址:SimpleGUI: 一个面向单色显示屏的开源GUI接口库。 SimpleGUI是一款针对单色显示屏设计的接口库。相比于传统的GUI…...
《计算机网络》(第8版)第1章 概述 复习笔记
第 1 章 概述 一、计算机网络在信息时代中的作用 计算机网络的两个重要功能: 1 .连通性 指互联网上的用户之间是相互连通的。 2 .共享(资源共享) 资源共享可以是信息共享、软件共享,也可以是硬件共享。此…...
银行数据质量保障体系建设实践
引言 在数字化转型浪潮中,数据中台成为企业实现数据驱动决策的关键支撑。它不仅整合了企业内外部的数据资源,还通过数据共享与复用,提升了运营效率和业务创新能力。然而,随着数据量的激增和数据来源的多样化,如何确保…...
笔记小结:《利用Python进行数据分析》二进制数据格式存储与web交互
提示:此节内容仅作了解即可 目录 二进制数据格式 使用HDF5 读取Microsoft Excel文件 二进制数据格式 实现数据的高效二进制格式存储最简单的办法之一是使用Python内置的pickle序列化。 Python 的 pickle 模块是一个用于序列化和反序列化 Python 对象结构的模块…...
电脑桌面图标变白了?3个方法20秒钟轻松解
电脑桌面图标变白了?3个方法20秒钟轻松解 ⚠️电脑桌面图标变白了,3种方法轻松解决 🚸方法一和方法二属于治标不治本的解决方法,但操作较为简单,在不同情况下有不成功的可能,方法三相对复杂一些,…...
数据治理,管什么?
元数据(Metadata):通俗地说就是描述数据的数据,比如数据的名称、属性、分类、字段信息、大小、标签等等。要做好数据的管理,元数据起到了举足轻重的作用。 参考数据(Reference Data)࿱…...
【前端】JavaScript入门及实战121-125
文章目录 121 滚轮事件122 键盘事件123 键盘移动div124 BOM125 History 121 滚轮事件 <!DOCTYPE html> <html> <head> <title></title> <meta charset "utf-8"> <style type"text/css">#box1 {width: 100px;h…...
pytest测试框架之http协议接口测试
1 接口测试 日常测试中接口测试是一项重要的工作,尤其是http协议的接口测试更加普遍,比如一些常用的测试框架或者工具(robotframework框架,testng框架,postman等)都支持http接口的测试,而这节内容主要介绍…...
FFmpeg源码:av_gcd函数分析
一、引言 公约数,是一个能同时整除几个整数的数。如果一个整数同时是几个整数的约数,称这个整数为它们的“公约数”;公约数中最大的称为最大公约数。对任意的若干个正整数,1总是它们的公约数。 公约数与公倍数相反,就…...
springboot物流寄查系统-计算机毕业设计源码95192
目 录 1 绪论 1.1 研究背景 1.2选题背景 1.3论文结构与章节安排 2 springboot物流寄查系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2…...
【秋招笔试】24-07-27-OPPO-秋招笔试题(算法岗)
🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 💡 第一题贪心模拟…...
AUTOSAR实战教程 - 模式管理BswM与其他各模块的交互
近日驻厂某OEM,幸得大块的个人时间, 把BswM这一块的内容从ETAS/ISOLAR工具配置到代码实现做了一个全方位的CT. 2024,希望孜孜内卷的汽车人升职加薪! 博主近期写的一首小诗,也一并送给大家,懂的都懂: 在看不到阳光的冬天/ 我染了风寒/ 白天点灯/ 晚上吃药/ 躺在被窝里才敢…...
经典非比较排序—计数排序的Java实现方式
目录 1.具体思路: 2.代码实现: 3.代码分析 4.示例测试: 测试源码: 测试结果: 计数排序,又被称为鸽巢原理,属于桶排序的一种,其本质是通过哈希映射思想,设定计数数组输入以…...
Linux fanotify vs inotify:如何为你的监控需求选择正确的工具?
Linux文件监控技术选型:fanotify与inotify深度对比与实践指南 在构建需要实时感知文件系统变化的应用程序时,开发者常面临监控工具的选择困境。无论是开发安全扫描工具、持续备份系统还是智能IDE,文件监控都是核心需求。Linux平台提供了inoti…...
告别按钮!用Qt实现STM32小车的键盘与手柄控制方案(附串口通信源码)
超越按钮控制:Qt框架下STM32小车的键盘与手柄交互方案 在嵌入式开发领域,人机交互体验往往被忽视,而实际上它直接影响着用户的操作效率和舒适度。对于STM32遥控小车这类需要实时操控的项目,传统的按钮点击方式存在明显局限——操作…...
现代差旅电力管理实战:从充电安全到设备续航全攻略
1. 一次久违的飞行:无处不在的电力焦虑与科技依赖距离上一次飞行已经过去了整整十七个月。当我上周踏入纽约拉瓜迪亚机场,准备开启后疫情时代的首次旅程时,感觉像是进入了另一个维度。在我缺席的这段时间里,LGA完成了一场彻底的蜕…...
别再只会addItem了!QT QComboBox的5个高级用法与实战场景(含完整代码)
别再只会addItem了!QT QComboBox的5个高级用法与实战场景(含完整代码) 在QT开发中,QComboBox可能是最容易被低估的控件之一。很多开发者仅仅把它当作一个简单的下拉选择框,用addItem()填充几个静态选项就草草了事。但实…...
osModa:基于NixOS与AI智能体的下一代服务器操作系统
1. 项目概述:为AI智能体而生的操作系统如果你和我一样,长期在服务器运维和AI应用部署的一线摸爬滚打,那你一定对这样的场景深有体会:凌晨三点,手机突然响起刺耳的告警,你睡眼惺忪地爬起来,SSH连…...
Dify实战指南:从零构建大模型应用与智能体开发全流程
1. 项目概述:从零到一,构建你的大模型应用开发实战手册如果你对AI应用开发感兴趣,但又觉得从零开始搭建一个能用的智能体(Agent)或者知识库问答系统门槛太高,那么你很可能已经听说过Dify这个名字。作为一个…...
Burp AI Agent:AI驱动的Web安全测试自动化实践
1. 项目概述:当Burp Suite遇上AI,安全测试的范式革新 如果你是一名Web安全测试人员或渗透测试工程师,那么Burp Suite这个工具对你来说,就像外科医生的手术刀一样熟悉。我们用它拦截流量、重放请求、扫描漏洞,日复一日。…...
使用Taotoken后如何清晰观测API用量与成本变化
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用Taotoken后如何清晰观测API用量与成本变化 对于团队管理者或开发者而言,将大模型能力集成到产品中后,资…...
浏览器扩展革命:5分钟解锁微信网页版全功能访问
浏览器扩展革命:5分钟解锁微信网页版全功能访问 【免费下载链接】wechat-need-web 让微信网页版可用 / Allow the use of WeChat via webpage access 项目地址: https://gitcode.com/gh_mirrors/we/wechat-need-web 还在为微信网页版的各种限制而烦恼吗&…...
基于Tauri与Bun的本地多智能体AI助手YouClaw:架构、配置与实战
1. 项目概述:一个桌面端的多智能体AI助手运行时 最近在折腾AI智能体(Agent)的本地化部署和集成,发现了一个挺有意思的开源项目——YouClaw。简单来说,它是一个基于Tauri 2构建的桌面应用,核心是一个支持多…...
