UNPKG

im-ui-mobile

Version:

A Vue3.0 + Typescript instant messaging component library for Uniapp

374 lines (326 loc) 8.93 kB
/** * H5录音管理器类(基于Web API) */ class RecorderH5 { /** * 构造函数 * @param {Object} options - 配置选项 * @param {Function} options.getToken - 获取token的函数 * @param {string} options.uploadUrl - 上传URL */ constructor(options = {}) { // 配置参数 this.getToken = options.getToken || (() => ''); this.uploadUrl = options.uploadUrl || ''; // 录音相关变量 this.rc = null; // MediaRecorder 实例 this.duration = 0; // 录音时长 this.chunks = []; // 录音数据块 this.stream = null; // 媒体流 this.startTime = null; // 开始时间 // 绑定事件处理器 this.setupEventHandlers(); } /** * 设置事件处理器 * @private */ setupEventHandlers() { // 可以在这里设置全局事件处理器 } /** * 设置配置 * @param {Object} options - 配置选项 */ setOptions(options) { if (options.getToken) { this.getToken = options.getToken; } if (options.uploadUrl) { this.uploadUrl = options.uploadUrl; } } /** * 获取配置 * @returns {Object} 当前配置 */ getOptions() { return { getToken: this.getToken, uploadUrl: this.uploadUrl }; } /** * 检查是否支持录音功能 * @returns {boolean} 是否支持录音 */ checkIsEnable() { // 获取当前来源 const origin = window.location.origin; // 检查 HTTPS 环境 if ( origin.indexOf("https") === -1 && origin.indexOf("localhost") === -1 && origin.indexOf("127.0.0.1") === -1 ) { uni.showToast({ title: "请在https环境中使用录音功能", icon: "error", }); return false; } // 检查浏览器支持 if (!navigator.mediaDevices || !window.MediaRecorder) { uni.showToast({ title: "当前浏览器不支持录音", icon: "error", }); return false; } return true; } /** * 开始录音 * @returns {Promise<void>} 录音开始Promise */ async start() { try { const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true }); this.startTime = new Date().getTime(); this.chunks = []; this.stream = audioStream; this.rc = new MediaRecorder(this.stream); // 设置数据可用回调 this.rc.ondataavailable = (e) => { this.chunks.push(e.data); }; this.rc.start(); } catch (error) { console.error("获取麦克风权限失败:", error); throw new Error("无法访问麦克风,请检查权限设置"); } } /** * 停止录音并清理资源 */ close() { // 停止媒体流 if (this.stream) { this.stream.getTracks().forEach((track) => { track.stop(); }); this.stream = null; } // 停止MediaRecorder if (this.rc && this.rc.state !== "inactive") { this.rc.stop(); } // 重置状态 this.reset(); } /** * 上传录音文件 * @returns {Promise<Object>} 上传结果Promise */ upload() { return new Promise((resolve, reject) => { if (!this.rc) { reject(new Error("录音未开始")); return; } // 设置录音停止回调 this.rc.onstop = () => { this.handleRecordingStop(resolve, reject); }; // 如果录音还在进行中,停止它 if (this.rc.state === "recording") { this.rc.stop(); } else { // 如果已经停止,直接处理 this.handleRecordingStop(resolve, reject); } }); } /** * 处理录音停止 * @private * @param {Function} resolve - Promise resolve函数 * @param {Function} reject - Promise reject函数 */ handleRecordingStop(resolve, reject) { try { // 检查是否有录音数据 if (this.chunks.length === 0 || !this.chunks[0].size) { this.cleanup(); reject(new Error("没有录音数据")); return; } // 计算录音时长 if (!this.startTime) { this.cleanup(); reject(new Error("录音开始时间未记录")); return; } this.duration = (new Date().getTime() - this.startTime) / 1000; // 创建 Blob 和 File 对象 const newBlob = new Blob(this.chunks, { type: "audio/mpeg" }); const name = new Date().getTime() + ".mp3"; // 使用时间戳作为文件名 const file = new File([newBlob], name, { type: "audio/mpeg" }); // 获取令牌 const accessToken = this.getToken(); if (!accessToken) { this.cleanup(); reject(new Error("未找到访问令牌")); return; } // 检查上传Url if (!this.uploadUrl) { this.cleanup(); reject(new Error("未配置上传Url")); return; } // 上传文件 uni.uploadFile({ url: this.uploadUrl, header: { accessToken: accessToken, }, file: file, name: "file", success: (res) => { try { const r = JSON.parse(res.data); if (r.code !== 200) { console.log("上传失败:", res); reject(new Error(r.message || "上传失败")); } else { const data = { duration: Math.round(this.duration), url: r.data, }; resolve(data); } } catch (error) { console.error(error); reject(new Error("解析响应数据失败")); } finally { this.cleanup(); } }, fail: (e) => { reject(new Error(e.errMsg || "上传失败")); this.cleanup(); }, }); } catch (error) { this.cleanup(); reject(error); } } /** * 获取当前录音时长 * @returns {number|null} 录音时长(秒),如果未开始录音则返回null */ getDuration() { if (!this.startTime) { return null; } return (new Date().getTime() - this.startTime) / 1000; } /** * 获取录音状态 * @returns {string} 录音状态 */ getState() { if (!this.rc) { return 'inactive'; } return this.rc.state; } /** * 重置录音状态 */ reset() { this.chunks = []; this.startTime = null; this.duration = 0; } /** * 清理所有资源 */ cleanup() { this.close(); this.reset(); this.rc = null; } /** * 销毁录音管理器 */ destroy() { this.cleanup(); // 移除所有事件处理器 if (this.rc) { this.rc.ondataavailable = null; this.rc.onstop = null; } } /** * 获取录音数据大小 * @returns {number} 录音数据总大小(字节) */ getDataSize() { return this.chunks.reduce((total, chunk) => total + chunk.size, 0); } /** * 静态方法:创建H5录音管理器实例 * @param {Function} getToken - 获取token的函数 * @param {string} uploadUrl - 上传Url * @returns {RecorderH5} H5录音管理器实例 */ static create(getToken, uploadUrl) { return new RecorderH5({ getToken, uploadUrl }); } } // 导出类 export default RecorderH5; // 使用示例: /* import RecorderH5 from './recorderH5.js'; import { getToken } from './auth.js'; // 创建实例 const recorder = new RecorderH5({ getToken: getToken, uploadUrl: 'https://api.example.com/file/upload' }); // 或使用静态方法创建 const recorder2 = RecorderH5.create(getToken, 'https://api.example.com/file/upload'); // 使用录音功能 async function recordAudio() { try { // 检查支持 if (!recorder.checkIsEnable()) { console.log('设备不支持录音'); return; } // 开始录音 await recorder.start(); console.log('录音开始,状态:', recorder.getState()); // 录音中... await new Promise(resolve => setTimeout(resolve, 3000)); // 获取录音时长 console.log('录音时长:', recorder.getDuration(), '秒'); console.log('录音数据大小:', recorder.getDataSize(), '字节'); // 停止并上传 const result = await recorder.upload(); console.log('录音上传成功:', result); } catch (error) { console.error('录音失败:', error); } } // 手动停止录音(不保存) recorder.close(); // 重置录音状态 recorder.reset(); // 销毁录音管理器 recorder.destroy(); */