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

上传音频文件

思路

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);});
};
http://www.lryc.cn/news/413387.html

相关文章:

  • Linux之jdk离线安装
  • JVM结构、架构与生命周期总结
  • Flink-StarRocks详解:第四部分StarRocks分区管理,数据压缩(第54天)
  • 为什么有时候银行贷款审核会查大数据信用?
  • LoRa无线通讯,让光伏机器人实现无“线”管理
  • 买流量卡要注意什么,这些冷知识你一定要懂!
  • 【嵌入式】STM3212864点阵屏使用SimpleGUI单色屏接口库——(2)精简字库
  • 《计算机网络》(第8版)第1章 概述 复习笔记
  • 银行数据质量保障体系建设实践
  • 笔记小结:《利用Python进行数据分析》二进制数据格式存储与web交互
  • 电脑桌面图标变白了?3个方法20秒钟轻松解
  • 数据治理,管什么?
  • 【前端】JavaScript入门及实战121-125
  • pytest测试框架之http协议接口测试
  • FFmpeg源码:av_gcd函数分析
  • springboot物流寄查系统-计算机毕业设计源码95192
  • 【秋招笔试】24-07-27-OPPO-秋招笔试题(算法岗)
  • AUTOSAR实战教程 - 模式管理BswM与其他各模块的交互
  • 经典非比较排序—计数排序的Java实现方式
  • 【C++从小白到大牛】栈和队列(优先级队列)
  • Golang之OpenGL(一)
  • 122. Go反射中与结构体相关的常用方法与应用
  • Java入门、进阶、强化、扩展、知识体系完善等知识点学习、性能优化、源码分析专栏分享
  • Spring-bean销毁
  • 【4】BlazorUI库
  • 树与二叉树【下】
  • ElementPlus 中el-select自定义指令实现触底加载请求options数据
  • 基于Selenium实现操作网页及操作windows桌面应用
  • 科普文:linux系列之操作系统内存管理简介
  • 【已解决】关于MyBatis的collection集合中只能取到一条数据的问题