UNPKG

mstf-kit

Version:

一个现代化的 JavaScript/TypeScript 工具库,提供了丰富的常用工具函数

2 lines (1 loc) 15.2 kB
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e={SPEECH:16e3,STANDARD:22050,CD:44100,HD:48e3},t={MONO:1,STEREO:2},n={WAV:"wav",MP3:"mp3",WEBM:"webm",OGG:"ogg"};class a extends Error{constructor(e){super(e),this.name="AudioProcessingError"}}function o(t){return t?t<e.SPEECH?(console.warn(`采样率 ${t}Hz 低于最小值,已调整为 ${e.SPEECH}Hz`),e.SPEECH):t>e.HD?(console.warn(`采样率 ${t}Hz 高于最大值,已调整为 ${e.HD}Hz`),e.HD):t:e.SPEECH}function r(e,t,n){if(n)try{n.disconnect()}catch(a){}if(t)try{t.disconnect()}catch(a){}if(e)try{e.close()}catch(a){}}function i(e){const t=function(e){return{[n.WAV]:"audio/wav",[n.MP3]:"audio/mpeg",[n.OGG]:"audio/ogg;codecs=opus",[n.WEBM]:"audio/webm;codecs=opus"}[e]||"audio/webm;codecs=opus"}(e);if(MediaRecorder.isTypeSupported(t))return t;const a=["audio/webm;codecs=opus","audio/webm","audio/ogg;codecs=opus","audio/mp4","audio/mpeg",""];for(const n of a)if(""===n||MediaRecorder.isTypeSupported(n))return console.warn(`格式 ${e} 不受支持,使用 ${n||"浏览器默认"} 替代`),n;return""}async function s(e,t){const{format:r,sampleRate:s,channelCount:l,bitRate:u}=t,d=o(s),f=new AudioContext;try{const t=await e.arrayBuffer(),a=await f.decodeAudioData(t),o=a.sampleRate,s=a.numberOfChannels;let p,w=a;return d!==o&&(w=await async function(e,t,n){const a=e.duration,o=Math.ceil(a*t),r=new OfflineAudioContext(n,o,t),i=r.createBufferSource();i.buffer=e,i.connect(r.destination),i.start(0);const s=await r.startRendering();return console.info(`重采样完成: ${e.sampleRate}Hz -> ${t}Hz`),s}(a,d,s)),l!==s&&(w=function(e,t,n){const a=e.numberOfChannels;if(a===t)return e;const o=n.createBuffer(t,e.length,e.sampleRate);if(1===t&&a>1){const t=o.getChannelData(0);for(let n=0;n<e.length;n++){let o=0;for(let t=0;t<a;t++)o+=e.getChannelData(t)[n];t[n]=o/a}}else if(t>a)for(let r=0;r<t;r++){const t=r%a;o.getChannelData(r).set(e.getChannelData(t))}else for(let r=0;r<t;r++)o.copyToChannel(e.getChannelData(r),r);return console.info(`通道调整完成: ${a} -> ${t}`),o}(w,l,f)),p=r===n.WAV?function(e){const t=e.numberOfChannels,n=e.sampleRate,a=[];for(let i=0;i<t;i++)a.push(e.getChannelData(i));let o;if(2===t)o=function(e,t){const n=e.length+t.length,a=new Float32Array(n);let o=0;for(let r=0;r<n;)a[r++]=e[o],a[r++]=t[o],o++;return a}(a[0],a[1]);else if(1===t)o=a[0];else{o=new Float32Array(e.length*t);for(let n=0;n<e.length;n++)for(let e=0;e<t;e++)o[n*t+e]=a[e][n]}const r=function(e,t){const{sampleRate:n,numChannels:a,bitsPerSample:o}=t,r=o/8,i=a*r,s=e.length*r,l=new ArrayBuffer(44+s),u=new DataView(l);return c(u,0,"RIFF"),u.setUint32(4,36+s,!0),c(u,8,"WAVE"),c(u,12,"fmt "),u.setUint32(16,16,!0),u.setUint16(20,1,!0),u.setUint16(22,a,!0),u.setUint32(24,n,!0),u.setUint32(28,n*i,!0),u.setUint16(32,i,!0),u.setUint16(34,o,!0),c(u,36,"data"),u.setUint32(40,s,!0),16===o?function(e,t,n){for(let a=0;a<n.length;a++,t+=2){const o=Math.max(-1,Math.min(1,n[a]));e.setInt16(t,o<0?32768*o:32767*o,!0)}}(u,44,e):8===o&&function(e,t,n){for(let a=0;a<n.length;a++,t++){const o=Math.max(-1,Math.min(1,n[a])),r=o<0?128*o:127*o;e.setInt8(t,r+128)}}(u,44,e),u}(o,{sampleRate:n,numChannels:t,bitsPerSample:16});return new Blob([r],{type:"audio/wav"})}(w):await async function(e,t,n){const a=new AudioContext,o=a.createBufferSource();o.buffer=e;const r=a.createMediaStreamDestination();o.connect(r);const s=i(t),c={};let l;s&&(c.mimeType=s),n&&(c.audioBitsPerSecond=1e3*n);try{l=new MediaRecorder(r.stream,c)}catch(d){console.warn(`创建编码器失败,使用默认设置: ${d}`),l=new MediaRecorder(r.stream)}const u=[];return l.ondataavailable=e=>{e.data&&e.data.size>0&&u.push(e.data)},new Promise(((n,r)=>{l.onstop=()=>{if(o.disconnect(),a.close(),0===u.length)return void r(new Error("编码过程中没有数据生成"));const e=new Blob(u,{type:s||`audio/${t}`});n(e)},l.start(),o.start(0);const i=1e3*e.duration;setTimeout((()=>{try{l.stop()}catch(d){r(d)}}),i+500)}))}(w,r,u),f.close(),p}catch(p){throw f.close(),console.error("音频处理失败:",p),new a(`音频处理失败: ${p instanceof Error?p.message:String(p)}`)}}function c(e,t,n){for(let a=0;a<n.length;a++)e.setUint8(t+a,n.charCodeAt(a))}function l(e){const t=URL.createObjectURL(e),n=new Audio(t);return n.onended=()=>{URL.revokeObjectURL(t)},n.play(),n}async function u(e,t){try{const n=e instanceof Response?e.body:e;if(!n)throw new Error("无效的流数据");const a=n.getReader(),o=[];let r=0;for(;;){const{done:e,value:t}=await a.read();if(e)break;o.push(t),r+=t.length}const i=new Uint8Array(r);let s=0;for(const e of o)i.set(e,s),s+=e.length;return t||(t=d(i)),new Blob([i],{type:t})}catch(n){throw console.error("转换音频流到 Blob 失败:",n),n}}function d(e){return e.length>12&&82===e[0]&&73===e[1]&&70===e[2]&&70===e[3]&&87===e[8]&&65===e[9]&&86===e[10]&&69===e[11]?"audio/wav":(e.length>3&&(73===e[0]&&68===e[1]&&51===e[2]||255===e[0]&&e[1]),"audio/mp3")}async function f(e,t,n){let a=new Audio,o=!1,r=!1,i=!0,s=0,c=null,l=null,u=null,d=null,f=null,p=null,w=null,h=!1,m=[],y=0;const g=128*n.bufferSize*1024,b=e=>{w?w(e):console.error("流式音频播放器错误:",e)},v=e.getReader(),R=async()=>{try{const{value:e,done:t}=await v.read();return t?(U(),!0):(m.push(e),y+=e.length,A(y/(y+524288)),!h&&n.autoPlay&&n.playWhenEnoughData&&y>=g&&E(),!1)}catch(e){return b(new Error(`读取流失败: ${e}`)),!0}},A=e=>{s=e,f&&f(s)},E=()=>{h=!0;const e=C(m),n=new Blob([e],{type:t});if(a){const e=URL.createObjectURL(n);a.src=e,a.oncanplaythrough=()=>{B(),a.oncanplaythrough=null},S(a),u&&URL.revokeObjectURL(u),u=e}},U=()=>{const e=C(m);if(l=new Blob([e],{type:t}),a){const e=a.currentTime,t=o&&!r;u&&URL.revokeObjectURL(u),u=URL.createObjectURL(l),a.src=u,a.currentTime=e,S(a),t&&B()}A(1)},S=e=>{e.addEventListener("ended",(()=>{o=!1,d&&d()})),e.addEventListener("timeupdate",(()=>{p&&p(e.currentTime,e.duration||0)})),e.addEventListener("error",(()=>{var t;b(new Error(`音频元素错误: ${(null==(t=e.error)?void 0:t.message)||"未知错误"}`))}))},C=e=>{let t=0;e.forEach((e=>{t+=e.length}));const n=new Uint8Array(t);let a=0;return e.forEach((e=>{n.set(e,a),a+=e.length})),n},B=()=>{if(!o&&a){const e=a.play();void 0!==e&&e.catch((e=>{console.warn("自动播放失败:",e)})),o=!0,r=!1,i=!1}};if(a&&(a.volume=n.volume,a.loop=n.loop,S(a)),null!==c&&clearInterval(c),c=window.setInterval((()=>{a&&(()=>{if(!a||a.duration===1/0||isNaN(a.duration))return;const e=a.buffered;if(e.length>0){const t=e.end(e.length-1);s=Math.min(1,t/a.duration),f&&f(s)}})()}),n.bufferUpdateInterval),(async()=>{let e=!1;for(;!e;)e=await R()})(),n.autoPlay&&!n.playWhenEnoughData){const e=new Blob([new Uint8Array(1)],{type:t});a&&(u=URL.createObjectURL(e),a.src=u,B())}const D={play(){m.length>0&&!o&&(h?B():E())},pause(){o&&!r&&a&&(a.pause(),r=!0,o=!1)},resume(){r&&B()},stop(){if(!i&&a){a.pause(),a.currentTime=0,o=!1,r=!1,i=!0,null!==c&&(clearInterval(c),c=null),u&&(URL.revokeObjectURL(u),u=null);try{v.cancel()}catch(e){}}},setVolume(e){a&&(a.volume=Math.max(0,Math.min(1,e)))},getState:()=>o?"playing":r?"paused":"stopped",getDuration:()=>a&&!isNaN(a.duration)?a.duration:0,getCurrentTime:()=>a?a.currentTime:0,seek(e){a&&!isNaN(a.duration)&&e>=0&&e<=a.duration&&(a.currentTime=e)},getBufferedProgress:()=>s,onEnded:null,onBufferUpdate:null,onTimeUpdate:null,onError:null};return Object.defineProperties(D,{onEnded:{get:()=>d,set:e=>{d=e}},onBufferUpdate:{get:()=>f,set:e=>{f=e}},onTimeUpdate:{get:()=>p,set:e=>{p=e}},onError:{get:()=>w,set:e=>{w=e}}}),D}exports.AUDIO_FORMATS=n,exports.CHANNEL_COUNTS=t,exports.SAMPLE_RATES=e,exports.audioBlobToBase64=function(e){return new Promise(((t,n)=>{const o=new FileReader;o.onloadend=()=>t(o.result),o.onerror=e=>n(new a(`转换Blob到Base64失败: ${e}`)),o.readAsDataURL(e)}))},exports.base64ToAudioBlob=function(e,t){const n=e.includes(",")?e.split(",")[1]:e;try{const e=atob(n),a=new ArrayBuffer(e.length),o=new Uint8Array(a);for(let t=0;t<e.length;t++)o[t]=e.charCodeAt(t);return new Blob([a],{type:t})}catch(o){throw console.error("Base64转换失败:",o),new a("无效的Base64音频数据")}},exports.blobToAudioBuffer=async function(e,t){const n=t||new(window.AudioContext||window.webkitAudioContext),a=await e.arrayBuffer();return await n.decodeAudioData(a)},exports.createRecorder=async function(e={}){if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia)throw new a("浏览器不支持录音功能");const c=o(e.sampleRate),l=void 0!==e.isStreaming?e.isStreaming:!(!e.onDataAvailable&&!e.timeslice),u={format:e.format||n.WEBM,sampleRate:c,channelCount:e.channelCount||t.MONO,bitRate:e.bitRate||128,echoCancellation:void 0===e.echoCancellation||e.echoCancellation,noiseSuppression:void 0===e.noiseSuppression||e.noiseSuppression,autoGainControl:void 0===e.autoGainControl||e.autoGainControl,timeslice:e.timeslice||100,onDataAvailable:e.onDataAvailable,onAudioProcess:e.onAudioProcess,isStreaming:l};let d;try{d=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:u.echoCancellation,noiseSuppression:u.noiseSuppression,autoGainControl:u.autoGainControl,channelCount:u.channelCount}})}catch(C){throw console.error("获取麦克风权限失败:",C),new a(`无法访问麦克风: ${C instanceof Error?C.message:String(C)}`)}let f=null,p=null,w=null,h=new Uint8Array(0),m=!1;try{f=new(window.AudioContext||window.webkitAudioContext);const e=f.sampleRate;p=f.createMediaStreamSource(d),w=f.createAnalyser(),w.fftSize=2048,h=new Uint8Array(w.frequencyBinCount),p.connect(w),m=!0,u.sampleRate!==e&&console.info(`系统采样率为 ${e}Hz,将在录音结束后重采样为 ${u.sampleRate}Hz`)}catch(C){console.warn("初始化音频分析器失败:",C),r(f,p,w),f=null,p=null,w=null,m=!1}const y=i(u.format);let g;try{g=new MediaRecorder(d,{mimeType:y,audioBitsPerSecond:1e3*u.bitRate})}catch(C){console.warn(`创建MediaRecorder失败,使用默认配置: ${C}`);try{g=new MediaRecorder(d)}catch(B){throw r(f,p,w),d.getTracks().forEach((e=>e.stop())),new a(`无法创建录音器: ${B}`)}}let b=[],v=0,R=0,A=0,E=null;function U(e,t,n,a){null!==E&&clearInterval(E),E=window.setInterval((()=>{if(n&&t)try{t.getByteFrequencyData(a),e(a.slice(0))}catch(o){e(new Uint8Array(0))}else e(new Uint8Array(0))}),50)}function S(){null!==E&&(clearInterval(E),E=null)}return g.ondataavailable=e=>{e.data&&e.data.size>0&&(b.push(e.data),u.isStreaming&&u.onDataAvailable&&u.onDataAvailable(e.data))},{async start(){if("inactive"===g.state){b=[],v=Date.now(),A=0;const e=u.isStreaming?u.timeslice:1e3;g.start(e),u.onAudioProcess&&U(u.onAudioProcess,w,m,h)}},pause(){"recording"===g.state&&(g.pause(),R=Date.now(),S())},resume(){"paused"===g.state&&(g.resume(),A+=Date.now()-R,u.onAudioProcess&&null===E&&U(u.onAudioProcess,w,m,h))},stop:async()=>new Promise((e=>{const t=async()=>{if(S(),0===b.length)return void e(new Blob([],{type:y}));const t=new Blob(b,{type:y});try{const n=await s(t,{format:u.format,sampleRate:u.sampleRate,channelCount:u.channelCount,bitRate:u.bitRate});e(n)}catch(C){console.error("音频处理失败:",C),e(t)}};if("inactive"!==g.state){if(g.onstop=t,"requestData"in g&&"recording"===g.state)try{g.requestData()}catch(n){}g.stop()}else t()})),cancel(){"inactive"!==g.state&&g.stop(),S(),b=[]},getState:()=>g.state,getDuration:()=>0===v?0:"paused"===g.state?(R-v-A)/1e3:(Date.now()-v-A)/1e3,getAudioData(){if(!m||!w)return new Uint8Array(0);try{return w.getByteFrequencyData(h),h.slice(0)}catch(e){return new Uint8Array(0)}},async getCurrentAudio(){if(0===b.length)return new Blob([],{type:y});const e=new Blob(b.slice(0),{type:y});try{return await s(e,{format:u.format,sampleRate:u.sampleRate,channelCount:u.channelCount,bitRate:u.bitRate})}catch(C){return console.error("处理当前音频失败:",C),e}},release(){S(),r(f,p,w),d.getTracks().forEach((e=>e.stop()))}}},exports.createStreamingAudioPlayer=async function(e,t,n={}){const a={autoPlay:!1,volume:1,loop:!1,bufferSize:3,useMediaSource:!0,playWhenEnoughData:!0,bufferUpdateInterval:250,...n};let o=e instanceof Response?e.body:e;if(!o)throw new Error("无效的流数据");let r=t,i=null;if(!r){const e=o.getReader(),{value:t,done:n}=await e.read();if(n||!t)throw new Error("流为空或已结束");i=t,r=d(i),o=new ReadableStream({start(e){e.enqueue(i)},async pull(t){try{const{value:n,done:a}=await e.read();a?t.close():t.enqueue(n)}catch(n){t.error(n)}}})}const s=r.includes("mp3")||r.includes("mpeg"),c=r.includes("webm"),l=r.includes("mp4");return(s||c||l)&&a.useMediaSource&&"undefined"!=typeof MediaSource?async function(e,t,n){let a=t;if(t.includes("mp3")||t.includes("mpeg"))a="audio/mpeg";else if(t.includes("wav"))throw new Error("MediaSource API 不支持 WAV 格式,请使用 MP3/WebM/MP4 格式");const o=new MediaSource,r=URL.createObjectURL(o),i=new Audio;i.volume=n.volume,i.loop=n.loop,i.src=r;let s=null,c=!1,l=!1,u=!0,d=[],p=!1,w=null,h=!1,m=null,y=0,g=null,b=null,v=null,R=null;if(await new Promise((e=>{o.addEventListener("sourceopen",(()=>{try{s=o.addSourceBuffer(a),s.addEventListener("updateend",E),e()}catch(t){S(new Error(`创建 SourceBuffer 失败: ${t}`)),e()}}),{once:!0})})),!s)return URL.revokeObjectURL(r),f(e,t,n);const A=()=>{if(!i||i.duration===1/0||isNaN(i.duration))return;const e=i.buffered;if(e.length>0){const t=e.end(e.length-1);y=Math.min(1,t/i.duration),b&&b(y)}},E=async()=>{if(s&&!p&&0!==d.length){p=!0;try{const e=d.shift();s.appendBuffer(e),h||(h=!0,n.autoPlay&&n.playWhenEnoughData&&U()),A()}catch(e){S(new Error(`缓冲数据失败: ${e}`))}}},U=()=>{if(!c){const e=i.play();void 0!==e&&e.catch((e=>{console.warn("自动播放失败:",e)})),c=!0,l=!1,u=!1}},S=e=>{R?R(e):console.error("流式音频播放器错误:",e)};i.addEventListener("ended",(()=>{c=!1,g&&g()})),i.addEventListener("timeupdate",(()=>{v&&v(i.currentTime,i.duration||0)})),i.addEventListener("error",(()=>{var e;S(new Error(`音频元素错误: ${(null==(e=i.error)?void 0:e.message)||"未知错误"}`))})),null!==m&&clearInterval(m),m=window.setInterval((()=>{i&&s&&A()}),n.bufferUpdateInterval),(async()=>{try{for(w=e.getReader();;){const{value:e,done:n}=await w.read();if(n){if("open"===o.readyState)try{o.endOfStream()}catch(t){console.warn("媒体源关闭失败:",t)}break}d.push(e),p||E()}}catch(t){S(new Error(`流读取失败: ${t}`))}})();const C={play(){U()},pause(){c&&!l&&(i.pause(),l=!0,c=!1)},resume(){l&&U()},stop(){if(!u){if(i.pause(),i.currentTime=0,c=!1,l=!1,u=!0,w){try{w.cancel()}catch(e){}w=null}null!==m&&(clearInterval(m),m=null),URL.revokeObjectURL(r)}},setVolume(e){i.volume=Math.max(0,Math.min(1,e))},getState:()=>c?"playing":l?"paused":"stopped",getDuration:()=>isNaN(i.duration)?0:i.duration,getCurrentTime:()=>i.currentTime,seek(e){!isNaN(i.duration)&&e>=0&&e<=i.duration&&(i.currentTime=e)},getBufferedProgress:()=>y,onEnded:null,onBufferUpdate:null,onTimeUpdate:null,onError:null};return Object.defineProperties(C,{onEnded:{get:()=>g,set:e=>{g=e}},onBufferUpdate:{get:()=>b,set:e=>{b=e}},onTimeUpdate:{get:()=>v,set:e=>{v=e}},onError:{get:()=>R,set:e=>{R=e}}}),n.autoPlay&&!n.playWhenEnoughData&&U(),C}(o,r,a):f(o,r,a)},exports.playAudioBlob=l,exports.playAudioStream=async function(e,t){return l(await u(e,t))},exports.streamToAudioBlob=u;