音频h5录制开发

class VoiceRecorder {

constructor(options = {}) {

this.targetElement = options.targetElement;

this.maxDuration = options.maxDuration ?? 60000;

this.cancelDistance = options.cancelDistance ?? 80;

this.onStart = options.onStart;

this.onStop = options.onStop;

this.onCancel = options.onCancel;

this.onComplete = options.onComplete;

this.onError = options.onError;

this.stream = null;

this.recorder = null;

this.chunks = \[\];

this.isRecording = false;

this.isCancel = false;

this.startTime = 0;

this.timer = null;

this.startY = 0;

const check = this.check();

if (!check.respstate) {

throw new Error(check.result.message);

}

this.bind();

}

/**

* 参数及浏览器能力检查

*

* @returns {{

* respstate: boolean,

* result: Object

* }}

*/

check() {

if (!(this.targetElement instanceof HTMLElement)) {

return {

respstate: false,

result: {

code: "INVALID_PARAM",

message: "targetElement必须是HTMLElement"

}

};

}

if (!window.isSecureContext) {

return {

respstate: false,

result: {

code: "UNSAFE_CONTEXT",

message: "当前环境不支持麦克风,请使用HTTPS访问"

}

};

}

if (

!navigator.mediaDevices ||

!navigator.mediaDevices.getUserMedia

) {

return {

respstate: false,

result: {

code: "NOT_SUPPORTED",

message: "当前浏览器不支持getUserMedia"

}

};

}

if (typeof MediaRecorder === "undefined") {

return {

respstate: false,

result: {

code: "NOT_SUPPORTED",

message: "当前浏览器不支持MediaRecorder"

}

};

}

const mimeType = this.getMimeType();

if (!mimeType) {

return {

respstate: false,

result: {

code: "MIME_NOT_SUPPORTED",

message: "当前浏览器没有支持的音频录制格式"

}

};

}

return {

respstate: true,

result: {

mimeType

}

};

}

/**

* 获取浏览器支持的录音格式

*/

getMimeType() {

const types = [

"audio/mp4",

"audio/webm;codecs=opus",

"audio/webm",

"audio/ogg;codecs=opus"

];

for (const type of types) {

try {

if (MediaRecorder.isTypeSupported(type)) {

return type;

}

} catch (e) {

// 某些浏览器检测MIME时可能直接抛异常

}

}

return "";

}

/**

* 绑定事件

*/

bind() {

this.targetElement.addEventListener(

"pointerdown",

this.start

);

this.targetElement.addEventListener(

"pointermove",

this.move

);

this.targetElement.addEventListener(

"pointerup",

this.stop

);

this.targetElement.addEventListener(

"pointercancel",

this.cancel

);

this.targetElement.style.touchAction = "none";

this.targetElement.style.userSelect = "none";

}

/**

* 开始录音

*/

start = async (event) => {

event.preventDefault();

if (this.isRecording) {

return;

}

this.startY = event.clientY;

this.isCancel = false;

try {

if (!this.stream) {

this.stream =

await navigator.mediaDevices.getUserMedia({

audio: true,

video: false

});

}

const mimeType = this.getMimeType();

this.chunks = \[\];

this.recorder = new MediaRecorder(

this.stream,

mimeType

? { mimeType }

: undefined

);

this.recorder.ondataavailable = (event) => {

if (event.data && event.data.size > 0) {

this.chunks.push(event.data);

}

};

this.recorder.onstop = () => {

this.complete();

};

this.recorder.onerror = (event) => {

this.isRecording = false;

this.clearTimer();

this.onError?.(

event.error ||

new Error("录音发生异常")

);

};

this.recorder.start();

this.isRecording = true;

this.startTime = Date.now();

// 最大录音时间

this.timer = setTimeout(() => {

if (this.isRecording) {

// 超时属于正常结束

this.stop();

}

}, this.maxDuration);

this.onStart?.();

} catch (error) {

this.isRecording = false;

this.clearTimer();

this.onError?.(error);

}

};

/**

* 手指/鼠标移动

*

* 向上滑超过cancelDistance,取消录音

*/

move = (event) => {

if (!this.isRecording) {

return;

}

const distance = this.startY - event.clientY;

if (distance >= this.cancelDistance) {

this.cancel();

}

};

/**

* 正常停止录音

*/

stop = (event) => {

event?.preventDefault();

if (!this.isRecording || !this.recorder) {

return;

}

if (this.isCancel) {

return;

}

this.isRecording = false;

this.clearTimer();

if (this.recorder.state !== "inactive") {

this.recorder.stop();

}

this.onStop?.();

};

/**

* 取消录音

*/

cancel = () => {

if (!this.isRecording || !this.recorder) {

return;

}

this.isCancel = true;

this.isRecording = false;

this.clearTimer();

if (this.recorder.state !== "inactive") {

this.recorder.stop();

}

// 告诉业务:用户取消了录音

this.onCancel?.();

};

/**

* 录音完成

*/

complete() {

if (this.isCancel) {

this.chunks = \[\];

this.isCancel = false;

return;

}

if (!this.chunks.length) {

this.onError?.(

new Error("未获取到录音数据")

);

return;

}

const duration = Date.now() - this.startTime;

const type =

this.recorder?.mimeType ||

this.chunks0?.type ||

"audio/webm";

const blob = new Blob(this.chunks, {

type

});

this.chunks = \[\];

this.onComplete?.({

blob,

type: blob.type,

size: blob.size,

duration

});

}

/**

* 清除超时定时器

*/

clearTimer() {

if (this.timer) {

clearTimeout(this.timer);

this.timer = null;

}

}

/**

* 销毁

*/

destroy() {

this.clearTimer();

if (

this.recorder &&

this.recorder.state !== "inactive"

) {

try {

this.recorder.stop();

} catch (e) {

// ignore

}

}

if (this.stream) {

this.stream.getTracks().forEach(track => {

track.stop();

});

}

this.targetElement.removeEventListener(

"pointerdown",

this.start

);

this.targetElement.removeEventListener(

"pointermove",

this.move

);

this.targetElement.removeEventListener(

"pointerup",

this.stop

);

this.targetElement.removeEventListener(

"pointercancel",

this.cancel

);

this.stream = null;

this.recorder = null;

this.chunks = \[\];

this.isRecording = false;

this.isCancel = false;

}

}

相关推荐
扶风ff5 小时前
新品知识更新太快?用练题簿在线刷题,安排企业培训的小测与复盘
前端·学习·小程序
对空六课5 小时前
支持注意力分析的热力图工具有哪些?
前端·数据库·数据分析
Csvn6 小时前
Vue3 响应式与编译:依赖收集如何升级为节点级靶向更新
前端
星栈7 小时前
pnpm 12 升级实测
前端·javascript
Sirens.7 小时前
Java并发锁详解:六类锁策略与 synchronized 底层原理
java·前端·算法
特创数字科技9 小时前
一个纯本地运行的图片处理工具:压缩 / 裁剪 / 九宫格 / 圆角 / 滤镜 / 拼图 / 水印,终生使用
前端
IT_陈寒10 小时前
Vue的v-if和v-for混用居然是个天坑
前端·人工智能·后端
天衍四九-10 小时前
【无标题】
前端·spring boot·mysql·nginx·docker
广州华水科技10 小时前
2026年单北斗GNSS变形监测系统推荐榜单,解锁GNSS位移监测新高度
前端
xcyxiner10 小时前
DicomViewer24 修复编译bug(window test 失败)
前端·qt