im-ui-mobile
Version:
A Vue3.0 + Typescript instant messaging component library for Uniapp
374 lines (326 loc) • 8.93 kB
JavaScript
/**
* 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();
*/