如何做网站静态页面/2023年10月疫情恢复
需求:需要录音的文本可分为多段和单个,我们可以进行上传录音和在线录音的形式生成最终的录音。
在这里用到了自定义Audio,Audio代码在我的博客里有,博客地址是:https://blog.csdn.net/qq_40657321/article/details/120329405
其中在线录音用到了js-audio-recorder包,详情链接:https://www.npmjs.com/package/js-audio-recorder/v/0.2.3
在线录音参考文章:
1.在线录音演示地址:https://recorder.zhuyuntao.cn/
2.在线录音代码:https://github.com/2fps/recorder
3.在线录音实现:https://www.jb51.net/article/159849.htm
上传录音
1.1 上传录音(单个)
上传之前
上传后:生成录音并且可删除
flag是表示文件是否上传的标志
{!detailItem?.voiceItems[0]?.ossPath ?<div className={styles.upload_btn}>//上传文件<Uploadaction={`url 上传接口`}headers={{_security_token:((token || Cookie.get('token的key')) as string) ||getState().global.token,}}data={() => ({// 除了file额外的参数id: detail?.id,type: 1,})}showUploadList={false} // 不显示上传后的文件onChange={(data: any) => handleUpload(data, 0)}maxCount={1} // 最大上传个数1个><Button className={styles.btn}>上传文件</Button></Upload><div className={styles.tit}><span>支持拓展名:mp3/wav,大小不要超过10M</span></div></div> : detailItem?.voiceItems[0]?.flag ? <div className={styles.upload_btn}>//上传后//自定义Audio,代码在文章开头的链接里<Audiosrc={path} // 文件路径id={detailItem.id + '12'}type={'delete'}onClick={() => deleteRecord(0)}bg={'#F2F3F5'}/></div> : ''}</div>
1.2 上传录音(多个)
部分上传成功:生成录音并且可删除,其他未上传文件可继续上传
全部上传后:生成录音并且可逐个删除
在线录音
2.1 在线录音(单个)
录音初始状态:点击按钮可开始录音
录音中:点击按钮暂停录音并且生成录音
录音结束后:生成录音并且可删除
<div className={styles.online_btn}>{(!detailItem?.voiceItems[0]?.audioFlag && !detailItem?.voiceItems[0]?.ossPath) ? <div className={styles.upload_btn}><Button className={styles.btn} onClick={() => startRecording(0)}><img src={play} className={styles.btn_img} />开始录音</Button></div> : (detailItem?.voiceItems[0]?.audioFlag && !detailItem?.voiceItems[0]?.ossPath) ? <div className={styles.upload_btn}><Button className={styles.btn} onClick={() => stopRecording(0)}><img src={pause} className={styles.btn_img} ></img>{detailItem?.voiceItems[0].timeValue || '00:00'}</Button></div> : detailItem?.voiceItems[0]?.ossPath ? <div className={styles.upload_btn}><Audiosrc={detailItem?.voiceItems[0]?.ossPath}id={detailItem.id + '11'}type={'delete'}onClick={() => deleteRecord(0)}bg={'#F2F3F5'}/></div> : ''}</div>
2.2 在线录音(多个)
部分录音状态:点击按钮可开始录音,点击删除可删除录音,重新录音
全部录音完成:点击删除按钮,删除当前录音,可重新开始录音
多个分段(数字转汉字)
拿到的数据是一个数组,第0个录音数据,是第一段,依次递增展示。
所以需要把下标+1,并且转成汉字。例子如下:
1=》一
12=》一十二
上代码:
//将小数部分的数字转换为字符串的方法:
var chnNumChar = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
var chnUnitSection = ["", "万", "亿", "万亿", "亿亿"];
var chnUnitChar = ["", "十", "百", "千"];//定义在每个小节的内部进行转化的方法,其他部分则与小节内部转化方法相同const sectionToChinese = (section: number) => {var str = '', chnstr = '', zero = false, count = 0; //zero为是否进行补零, 第一次进行取余由于为个位数,默认不补零while (section > 0) {var v = section % 10; //对数字取余10,得到的数即为个位数if (v == 0) { //如果数字为零,则对字符串进行补零if (zero) {zero = false; //如果遇到连续多次取余都是0,那么只需补一个零即可chnstr = chnNumChar[v] + chnstr;}} else {zero = true; //第一次取余之后,如果再次取余为零,则需要补零str = chnNumChar[v];str += chnUnitChar[count];chnstr = str + chnstr;}count++;section = Math.floor(section / 10);}return chnstr;}//定义整个数字全部转换的方法,需要依次对数字进行10000为单位的取余,然后分成小节,按小节计算,当每个小节的数不足1000时,则需要进行补零const TransformToChinese = (num: number) => {num = Math.floor(num);var unitPos = 0;var strIns = '', chnStr = '';var needZero = false;if (num === 0) {return chnNumChar[0];}while (num > 0) {var section = num % 10000;if (needZero) {chnStr = chnNumChar[0] + chnStr;}strIns = sectionToChinese(section);strIns += (section !== 0) ? chnUnitSection[unitPos] : chnUnitSection[0];chnStr = strIns + chnStr;needZero = (section < 1000) && (section > 0);num = Math.floor(num / 10000);unitPos++;}return chnStr;}
完整代码
import React, { useEffect, useState } from 'react';
import { Modal, Form, Button, Upload, message } from 'antd';
import { getState, } from '@@/store';
import { CONFIG } from '@/services';
import Cookie from 'js-cookie';
import util from '@souche-f2e/souche-util';
import { TVoiceSave, TConfigDetail } from '../types';
import styles from './index.less'
import pause from '@/assets/images/pause.png';
import play from '@/assets/images/play.png';
// import { default as Recorder } from '@/utils/record';
// import { Recorder } from '@/utils';
import Recorder from 'js-audio-recorder';
import Audio from '@/components/Audio';type ISyncConfigModalProps = {visible: boolean;detail: TConfigDetail | null;loading: boolean;uploadType: string;onCancel: () => void;onConfirm: (data: TVoiceSave) => void;
};const token = util.getParams().token;
let recorder: any = null;
//将小数部分的数字转换为字符串的方法:
var chnNumChar = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
var chnUnitSection = ["", "万", "亿", "万亿", "亿亿"];
var chnUnitChar = ["", "十", "百", "千"];const SyncConfigModal: React.FC<ISyncConfigModalProps> = ({visible,loading,onCancel,onConfirm,detail,uploadType
}) => {const [form] = Form.useForm();const [detailItem, setDetailItem] = useState<TConfigDetail | null>(null);const [lastIndex, setLastIndex] = useState<any>(null);useEffect(() => {if (!visible) {form.resetFields();setDetailItem(null);} else {setLastIndex(null);if (visible && detail && detail?.voiceItems?.length > 1) {detail.voiceItems = detail?.voiceItems?.filter(v => !v.paramName)setDetailItem(detail);} else {setDetailItem(detail);}}}, [visible]);// const handleFormSubmit = (values: { groupId: string; syncType: '1' | '2' }) => {// onConfirm({// ...values,// });// };//定义在每个小节的内部进行转化的方法,其他部分则与小节内部转化方法相同const sectionToChinese = (section: number) => {var str = '', chnstr = '', zero = false, count = 0; //zero为是否进行补零, 第一次进行取余由于为个位数,默认不补零while (section > 0) {var v = section % 10; //对数字取余10,得到的数即为个位数if (v == 0) { //如果数字为零,则对字符串进行补零if (zero) {zero = false; //如果遇到连续多次取余都是0,那么只需补一个零即可chnstr = chnNumChar[v] + chnstr;}} else {zero = true; //第一次取余之后,如果再次取余为零,则需要补零str = chnNumChar[v];str += chnUnitChar[count];chnstr = str + chnstr;}count++;section = Math.floor(section / 10);}return chnstr;}//定义整个数字全部转换的方法,需要依次对数字进行10000为单位的取余,然后分成小节,按小节计算,当每个小节的数不足1000时,则需要进行补零const TransformToChinese = (num: number) => {num = Math.floor(num);var unitPos = 0;var strIns = '', chnStr = '';var needZero = false;if (num === 0) {return chnNumChar[0];}while (num > 0) {var section = num % 10000;if (needZero) {chnStr = chnNumChar[0] + chnStr;}strIns = sectionToChinese(section);strIns += (section !== 0) ? chnUnitSection[unitPos] : chnUnitSection[0];chnStr = strIns + chnStr;needZero = (section < 1000) && (section > 0);num = Math.floor(num / 10000);unitPos++;}return chnStr;}const handleUpload = (data: any, index: number) => {if (data.fileList[0].status === 'done') {if (data.file.response.code === '200' && detailItem) {let list = { ...detailItem };const item = { ...list.voiceItems[index] }console.log(item, 'item---')list.voiceItems[index] = { ...item, ...data.file.response.data }|| {};console.log(list.voiceItems[index], 'list.voiceItems[index]---')setDetailItem(list);}}};const transferTime = (time: number) => {let min: number | string = Math.floor(time / 60);if (min < 10) {min = `0${min}`;}let sec: number | string = Math.floor((time) % 60);if (sec < 10) {sec = `0${sec}`;}return `${min}:${sec}`;}// 开始录音const startRecording = (index: number) => {if (detailItem && detailItem?.voiceItems) {let list = { ...detailItem };list.voiceItems[index].audioFlag = true;// 播放其他的停止播放if (lastIndex !== null && lastIndex !== index) {console.log(lastIndex)list.voiceItems[lastIndex].audioFlag = false;list.voiceItems[lastIndex].timeValue = "00:00"; // 时间重置setDetailItem(list);stopRecording(lastIndex); // 销毁}create(index);}setLastIndex(index);}const create = (index: number) => {recorder = new Recorder({sampleBits: 16, // 采样位数,支持 8 或 16,默认是16sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000numChannels: 1,})recorder.start().then(() => {// 开始录音recorder.onprogress = function (params: any) {if (detailItem && detailItem?.voiceItems) {let list = { ...detailItem };list.voiceItems[index].timeValue = transferTime(params?.duration);setDetailItem(list);}}}, (error: any) => {// 出错了console.log(`${error.name} : ${error.message}`);});}// 停止录音const stopRecording = (index: number) => {recorder && recorder.stop()if (detailItem && detailItem?.voiceItems) {let list = { ...detailItem };list.voiceItems[index].audioFlag = false;setDetailItem(list);}createDownloadLink(index)}// 删除录音const deleteRecord = (index: number) => {if (detailItem && detailItem?.voiceItems) {let list = { ...detailItem };list.voiceItems[index].audioFlag = false;list.voiceItems[index].ossPath = '';setDetailItem(list);}}// 生成文件const createDownloadLink = async (index: number) => {const blob = recorder.getWAVBlob()console.log('b;ol', blob);let formDate = new FormData();formDate.append("file", blob, "blob.wav");formDate.append("serviceId", detailItem?.id || '');formDate.append("serviceModel", "1");try {const res = await CONFIG.uploadFile(formDate);if (res) {if (detailItem && detailItem?.voiceItems) {let list = { ...detailItem };const item = { ...list.voiceItems[index] }console.log(item, 'item---')list.voiceItems[index] = { ...item, ...res, duration: undefined } || {};console.log(list.voiceItems[index], 'list.voiceItems[index]---')setDetailItem(list);}}} catch (e) {}}// 保存const handleOk = async () => {const params = {id: detailItem?.id || '',voiceItems: detailItem?.voiceItems || [],// voiceType: uploadType === 'upload'? "UPLOAD" : 'ONLINE'}onConfirm(params);}return (<Modalvisible={visible}title={uploadType === 'upload' ? "上传录音" : "在线录音"}onOk={handleOk}onCancel={onCancel}confirmLoading={loading}width={560}className={styles.modal_height}>{detailItem?.voiceItems?.length === 1 ?<div className={styles.upload_one}><h4>话术内容</h4><p>{detailItem?.voiceItems[0]?.text}</p>{uploadType === 'upload' ? <div>{!detailItem?.voiceItems[0]?.ossPath ? <div className={styles.upload_btn}><Uploadaction={`${process.env.MUJI_APP_CUSTOMER_SYSTEM_SERVER}/speechVoiceController/uploadFile.json`}headers={{_security_token:((token || Cookie.get('_security_token_ai')) as string) ||getState().global.token,}}data={() => ({serviceId: detail?.id,serviceModel: 1,})}showUploadList={false}onChange={(data: any) => handleUpload(data, 0)}maxCount={1}><Button className={styles.btn}>上传文件</Button></Upload><div className={styles.tit}><span>支持拓展名:mp3/wav,大小不要超过10M</span></div></div> : detailItem?.voiceItems[0]?.ossPath ? <div className={styles.upload_btn}><Audiosrc={detailItem?.voiceItems[0]?.ossPath}id={detailItem.id + '12'}type={'delete'}onClick={() => deleteRecord(0)}bg={'#F2F3F5'}/></div> : ''}</div> :<div className={styles.online_btn}>{(!detailItem?.voiceItems[0]?.audioFlag && !detailItem?.voiceItems[0]?.ossPath) ? <div className={styles.upload_btn}><Button className={styles.btn} onClick={() => startRecording(0)}><img src={play} className={styles.btn_img} />开始录音</Button></div> : (detailItem?.voiceItems[0]?.audioFlag && !detailItem?.voiceItems[0]?.ossPath) ? <div className={styles.upload_btn}><Button className={styles.btn} onClick={() => stopRecording(0)}><img src={pause} className={styles.btn_img} ></img>{detailItem?.voiceItems[0].timeValue || '00:00'}</Button></div> : detailItem?.voiceItems[0]?.ossPath ? <div className={styles.upload_btn}><Audiosrc={detailItem?.voiceItems[0]?.ossPath}id={detailItem.id + '11'}type={'delete'}onClick={() => deleteRecord(0)}bg={'#F2F3F5'}/></div> : ''}</div>}</div> :<div>{detailItem?.voiceItems?.map((v: any, i: number) => {return (<div key={i}>{<div className={styles.upload_one} key={i} ><h4>第{TransformToChinese(i + 1)}段</h4><p>{v?.text}</p>{(uploadType === 'upload' && !v?.ossPath) ? <div className={styles.upload_btn}><Uploadaction={`${process.env.MUJI_APP_CUSTOMER_SYSTEM_SERVER}/speechVoiceController/uploadFile.json`}headers={{_security_token:((token || Cookie.get('_security_token_ai')) as string) ||getState().global.token,}}data={() => ({serviceId: detail?.id,serviceModel: 1,})}showUploadList={false}onChange={(data: any) => handleUpload(data, i)}maxCount={1}><Button className={styles.btn}>上传文件</Button></Upload><div className={styles.tit}><p>含有变量的话术需要分段录入</p><p> 支持拓展名:mp3/wav,大小不要超过10M</p></div></div> : (uploadType === 'upload' && v?.ossPath) ? <div className={styles.upload_btn}><Audiosrc={v?.ossPath}id={i}type={'delete'}onClick={() => deleteRecord(i)}bg={'#F2F3F5'}/></div> : ''}{(uploadType === 'online' && !v?.audioFlag && !v?.ossPath) ? <div className={styles.upload_btn}><Button className={styles.btn} onClick={() => startRecording(i)}><img src={play} className={styles.btn_img} />开始录音</Button><div className={styles.tit}><div>含有变量的话术需要分段录入</div></div></div> : (uploadType === 'online' && v?.audioFlag && !v?.ossPath) ? <div className={styles.upload_btn}><Button className={styles.btn} onClick={() => stopRecording(i)}><img src={pause} className={styles.btn_img} ></img>{v.timeValue || '00:00'}</Button><div className={styles.tit}><div>含有变量的话术需要分段录入</div></div></div> :(uploadType === 'online' && v?.ossPath) ? <div className={styles.upload_btn}><Audiosrc={v?.ossPath}id={i}type={'delete'}onClick={() => deleteRecord(i)}bg={'#F2F3F5'}/></div> : ''}</div>}</div>)})}</div>}</Modal >);
};export default SyncConfigModal;