js录制音频
创始人
2024-11-15 04:34:03
0次

整体思路

1、监听用户onTouchStart事件,设置一个定时器记录是否长按,然后调用JSBridge开始录制事件
2、通过JSBridge监听录音过程,拿到录音的数据,前端用数组变量存放
3、监听用户onTouchEnd松开事件,清除定时器,处理录音数组转换成一个文件上传到oss

难点

难点一:将base64录音片段转为WAV文件
首先将一系列Base64编码的音频段合并成一个PCM数据流;
然后创建一个WAV文件的头部信息;
最后合并WAV文件头和PCM数据

难点二:TypedArray数据的合并
TypedArray: 存储的是固定类型的数值数据,如整数或浮点数。
Array: 可以存储任何类型的数据,包括数字、字符串、对象等

开始录音

  /**    * 开始录音    */   const handleTouchStart = (event) => {     event.preventDefault();     timerId = setTimeout(() => {       setLongPress(true);       console.log('handleTouchStart 长按了');       JSBridge(XX.startRecording', {         numberOfChannels: 1, // 声道数         // sampleRate: 16000, // 采样率         sampleRate: 44100, // 更改采样率为 44100 Hz         bitsPerChannel: 16, // 位深         format: 'PCM',       }).then(() => {         setRecordStatus('dialog_listening');       });     }, 100); // 长按时长,这里设置为100ms   }; 

监听录音过程

 const onRecordChange = (event) => {     console.log(event);      const { error, param } = event || {};     const { pcm } = param || {};     const { errorCode, errorMsg } = error || {};      if (errorCode) {       Toast.show({         type: 'error',         content: `录制失败,${errorMsg}`,       });       baseArrayRef.current = [];     } else {       baseArrayRef.current.push(pcm);     }   };    useEffect(() => {     document.addEventListener('RecordingDataBufferTransfer', onRecordChange);      return () => {       // 清除长按定时器       if (timerId !== null) clearTimeout(timerId);     };   }, []); 

结束录制

/**    * 结束录音    * @returns    */   const handleTouchEnd = (event) => {     if (timerId !== null) {       clearTimeout(timerId)       timerId = null     }     if (!longPress) return;     setLongPress(false);     console.log('handleTouchEnd 松开了');     JSBridge('XX.stopRecording').then(() => {       // 移除事件监听器       document.removeEventListener(         'RecordingDataBufferTransfer',         onRecordChange,       );       setRecordStatus('dialog_sleep');       onMerge();     });   }; 

音频波动动画

VoiceAnimation/index.tsx

import cls from 'classnames'; import debounce from 'lodash/debounce'; import { useLayoutEffect, useMemo, useRef } from 'react'; import styles from './index.module.less';  interface IProps {   status: string; } export default function (props: IProps) {   const { status = 'dialog_sleep' } = props;   const list = useMemo(() => new Array(5).fill(true), []);    return (     
{list.map((_, index) => ( ))}
); } function getTransationByStatus(status: string, index?) { return { dialog_sleep: { transition: 'all 0.3s', height: '8px', transform: 'translateY(0)', }, dialog_idle: { transition: 'all 0.3s', height: '8px', transform: 'translateY(0)', }, dialog_listening: { transition: 'all 0.3s', height: '24px', transform: index % 2 ? 'translateY(8px)' : 'translateY(-8px)', onTransitionEnd: debounce( (event) => { if ( event.target.parentElement.className.indexOf('dialog_listening') === -1 ) return; event.target.style.transitionDuration = '0.5s'; event.target.style.height = '24px'; event.target.style.transform = event.target.style.transform === 'translateY(8px)' ? 'translateY(-8px)' : 'translateY(8px)'; }, { leading: true, trailing: false, }, ), }, dialog_thinking: { transition: 'all 0.3s', height: `${[52, 44, 36, 28, 24][index]}px`, transform: 'translateY(0)', onTransitionEnd: debounce( (event) => { if ( event.target.parentElement.className.indexOf('dialog_thinking') === -1 ) return; event.target.style.transitionDuration = '0.5s'; event.target.style.height = { '52px': '24px', '44px': '28px', '36px': '32px', '32px': '36px', '28px': '44px', '24px': '52px', }[event.target.style.height]; }, { leading: true, trailing: false, }, ), }, dialog_responding: { transition: 'all 0.2s', height: `${Math.random() * (index + 1) * 10 + 24}px`, transform: 'translateY(0)', onTransitionEnd: debounce( (event) => { if ( event.target.parentElement.className.indexOf( 'dialog_responding', ) === -1 ) return; event.target.style.transitionDuration = '0.15s'; event.target.style.height = `${Math.random() * (index + 1) * 10 + 24}px`; }, { leading: true, trailing: false, }, ), }, }[status]; } function AnimationItem({ status, index }: { status: string; index?: number }) { const div = useRef(); useLayoutEffect(() => { const container = div.current as HTMLDivElement; function reset() { container.ontransitionend = (e) => {}; container.style.transition = 'all .1s'; container.style.height = '24px'; container.style.transform = 'translateY(0)'; } reset(); const { onTransitionEnd = () => {}, ...style } = getTransationByStatus(status, index) || {}; container.ontransitionend = onTransitionEnd; for (let prop in style) { container.style[prop] = style[prop]; } return () => {}; }, [status]); return (
{ width: 24, height: 24 }} /> ); }

VoiceAnimation/index.module.less

.voice {   display: flex;   justify-content: center;   align-items: center;   height: 56px;    .item {     // width: 24px;     // height: 24px;     background-color: var(--TY-Text-Brand-1);     border-radius: 20px;     margin: 0 4px;     transform: translateY(0);   } }  .loop(@n, @i: 0) when (@i <= @n) {   &:nth-child(@{i}) {     animation-delay: (@i * 0.2s);   }   .loop(@n, (@i + 1)); } 

一个完整的音频录制——播放的例子

            pcmtowav      
getUserMedia需要https,使用localhost或127.0.0.1时,可用http。

相关内容

热门资讯

实测交流透视挂!宝玛大厅ai智... 实测交流透视挂!宝玛大厅ai智能透视挂教程,宝玛大厅破解软件经验(本来有挂辅助)1、每一步都需要思考...
总算了解辅助器!朱雀吴江三缺一... 总算了解辅助器!朱雀吴江三缺一ai智能透视辅助挂技巧,朱雀吴江三缺一好像是有挂(有辅助挂细节)朱雀吴...
今日公布透视!九五至尊大厅ai... 今日公布透视!九五至尊大厅ai智能透视插件,九五至尊大厅有没有透视挂窍门(切实有挂技术)1、在九五至...
玩家必看科普开挂!聚闲麻将ai... 玩家必看科普开挂!聚闲麻将ai智能透视辅助挂软件,聚闲麻将总是真的有挂(有辅助挂方式)聚闲麻将能透视...
据报道开挂!旅游娱乐ai智能开... 据报道开挂!旅游娱乐ai智能开挂工具,旅游娱乐辅助工具演示(一直有挂教学)1、全新机制【旅游娱乐ai...
一分钟揭秘开挂!决胜朋来麻将a... 一分钟揭秘开挂!决胜朋来麻将ai智能透视辅助挂教程,决胜朋来麻将真是真的是有挂(有辅助挂总结)1、首...
如何分辨真伪透视!红牛ai智能... 如何分辨真伪透视!红牛ai智能透视攻略,红牛是不是有挂经验(真是有挂技术)1、操作简单,无需红牛手机...
2分钟细说辅助器!江湖大庆麻将... 2分钟细说辅助器!江湖大庆麻将ai智能透视辅助挂攻略,江湖大庆麻将竟然是真的有挂(有辅助挂头条)1、...
总算清楚透视挂!老夫子厅ai智... 总算清楚透视挂!老夫子厅ai智能透视挂技巧,老夫子厅怎么开挂课程(好像发现有挂)老夫子厅破解侠是真的...
解密关于辅助!菜鸟麻将ai智能... 解密关于辅助!菜鸟麻将ai智能透视辅助挂脚本,菜鸟麻将总是存在有挂(有辅助挂存在)1、用户打开应用后...