上传音频文件

avatar
作者
筋斗云
阅读量:0

思路

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 (     <div       className={classnames(styles['tongyi-upload'], className)}       onClick={() => inputRef.current?.click()}       style={props.style}     >       <input         type="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 (     <Upload       maxSize={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 ( 	<AudioUpload 	  onError={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/34       if (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);   }); }; 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!