recorder.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /****** H5录音 ******/
  2. let recorderManager = null; // 录音管理器实例(用于非H5平台)
  3. let innerAudioContext = null; // 音频播放管理器实例
  4. let recordedFilePath = ""; // 录音文件路径
  5. let mediaRecorder = null; // H5平台的录音器实例
  6. let recordedChunks = []; // 存储H5录音片段
  7. let audioBlob = null; // H5录音文件的 Blob 对象
  8. // 需要引入 MP3 转换库,例如 `lamejs`
  9. import Lame from 'lamejs'; // 在实际应用中需要引入 Lame.js 库
  10. /**
  11. * 初始化录音管理器
  12. */
  13. function initRecorderManager() {
  14. if (process.env.UNI_PLATFORM === 'h5') {
  15. if (window.location.origin.indexOf("https") === -1) {
  16. console.error("H5录音功能:请在 https 环境中使用插件recorder.js。");
  17. return;
  18. }
  19. if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
  20. console.error("当前浏览器不支持 getUserMedia API");
  21. return;
  22. }
  23. if (typeof MediaRecorder === "undefined") {
  24. console.error("当前浏览器不支持 MediaRecorder API");
  25. return;
  26. }
  27. if (!mediaRecorder) {
  28. navigator.mediaDevices.getUserMedia({ audio: true })
  29. .then(stream => {
  30. try {
  31. mediaRecorder = new MediaRecorder(stream);
  32. mediaRecorder.ondataavailable = (event) => {
  33. if (event.data.size > 0) {
  34. recordedChunks.push(event.data);
  35. }
  36. };
  37. console.log("H5 录音管理器已初始化");
  38. } catch (e) {
  39. console.error("MediaRecorder 初始化错误:", e.message);
  40. }
  41. })
  42. .catch(err => {
  43. console.error("H5 录音初始化错误:", err.message);
  44. });
  45. }
  46. } else {
  47. if (!recorderManager) {
  48. recorderManager = uni.getRecorderManager();
  49. console.log("非 H5 录音管理器已初始化");
  50. }
  51. }
  52. }
  53. /**
  54. * 初始化音频管理器
  55. */
  56. function initAudioContext() {
  57. if (!innerAudioContext) {
  58. innerAudioContext = uni.createInnerAudioContext();
  59. }
  60. }
  61. /**
  62. * 开始录音
  63. * @param {object} options - 录音参数选项,包含录音时长、采样率等参数
  64. */
  65. function startRecording(options = {}) {
  66. // 确保录音管理器已初始化
  67. initRecorderManager();
  68. if (process.env.UNI_PLATFORM === 'h5') {
  69. if (mediaRecorder) {
  70. mediaRecorder.start();
  71. console.log("H5 开始录音...");
  72. } else {
  73. console.error("无法开始录音:mediaRecorder 不存在或浏览器不支持录音");
  74. }
  75. } else {
  76. const defaultOptions = {
  77. duration: 60000, // 录音时长(毫秒)
  78. sampleRate: 44100, // 采样率
  79. numberOfChannels: 1, // 声道数
  80. encodeBitRate: 96000, // 编码比特率
  81. format: "mp3", // 录音格式
  82. };
  83. recorderManager.start({
  84. ...defaultOptions,
  85. ...options,
  86. });
  87. console.log("开始录音...");
  88. }
  89. }
  90. /**
  91. * 暂停录音
  92. */
  93. function pauseRecording() {
  94. if (process.env.UNI_PLATFORM === 'h5') {
  95. if (mediaRecorder && mediaRecorder.state === 'recording') {
  96. mediaRecorder.pause();
  97. console.log("H5 暂停录音");
  98. }
  99. } else {
  100. if (recorderManager) {
  101. recorderManager.pause();
  102. console.log("暂停录音");
  103. }
  104. }
  105. }
  106. /**
  107. * 继续录音
  108. */
  109. function resumeRecording() {
  110. if (process.env.UNI_PLATFORM === 'h5') {
  111. if (mediaRecorder && mediaRecorder.state === 'paused') {
  112. mediaRecorder.resume();
  113. console.log("H5 继续录音");
  114. }
  115. } else {
  116. if (recorderManager) {
  117. recorderManager.resume();
  118. console.log("继续录音");
  119. }
  120. }
  121. }
  122. /**
  123. * 停止录音
  124. * @param {function} callback - 停止录音后返回录音文件路径的回调函数
  125. */
  126. function stopRecording(callback) {
  127. if (process.env.UNI_PLATFORM === 'h5') {
  128. if (mediaRecorder) {
  129. mediaRecorder.stop();
  130. console.log("H5 停止录音");
  131. // 将录音文件路径抛出
  132. if (typeof callback === 'function') {
  133. mediaRecorder.onstop = async () => {
  134. // 先生成 Blob 对象
  135. audioBlob = new Blob(recordedChunks, { type: 'audio/webm' });
  136. recordedChunks = [];
  137. // 转换为 MP3 格式
  138. const mp3Blob = await convertToMp3(audioBlob);
  139. recordedFilePath = URL.createObjectURL(mp3Blob);
  140. console.log("H5 录音停止,文件保存路径:", recordedFilePath);
  141. callback(recordedFilePath);
  142. };
  143. }
  144. } else {
  145. console.error("无法停止录音:mediaRecorder 不存在或浏览器不支持录音");
  146. }
  147. } else {
  148. if (recorderManager) {
  149. recorderManager.stop();
  150. console.log("停止录音");
  151. // 非 H5 平台录音停止后将文件路径抛出
  152. if (typeof callback === 'function') {
  153. recorderManager.onStop((res) => {
  154. recordedFilePath = res.tempFilePath;
  155. console.log("录音停止,文件保存路径:", recordedFilePath);
  156. callback(recordedFilePath);
  157. });
  158. }
  159. }
  160. }
  161. }
  162. /**
  163. * 转换 Blob 对象为 MP3 格式
  164. * @param {Blob} audioBlob - 原始音频 Blob 对象
  165. * @returns {Promise<Blob>} - MP3 格式的 Blob 对象
  166. */
  167. async function convertToMp3(audioBlob) {
  168. // 使用 Lame.js 库将音频 Blob 转换为 MP3 格式
  169. // 需要在实际应用中引入 Lame.js 库
  170. return new Promise((resolve, reject) => {
  171. // 检查 Lame.js 是否存在
  172. if (typeof Lame === "undefined") {
  173. reject(new Error("Lame.js 库未加载"));
  174. return;
  175. }
  176. const reader = new FileReader();
  177. reader.onload = function(event) {
  178. const audioData = event.target.result;
  179. const mp3Encoder = new Lame.Mp3Encoder(1, 44100, 128);
  180. const mp3Data = [];
  181. // 使用 Lame.js 库将音频数据编码为 MP3
  182. const samples = new Int16Array(audioData);
  183. mp3Data.push(mp3Encoder.encodeBuffer(samples));
  184. mp3Data.push(mp3Encoder.flush());
  185. // 生成 MP3 Blob 对象
  186. const mp3Blob = new Blob(mp3Data, { type: 'audio/mp3' });
  187. resolve(mp3Blob);
  188. };
  189. reader.onerror = function(error) {
  190. reject(error);
  191. };
  192. reader.readAsArrayBuffer(audioBlob);
  193. });
  194. }
  195. /**
  196. * 播放音频
  197. * @param {string} audioUrl - 音频文件 URL
  198. * @param {function} PlayCallback - 开始播放
  199. * @param {function} progressCallback - 播放进度的回调函数 (currentTime, duration)
  200. * @param {function} errorCallback - 播放错误的回调函数 (errorMsg)
  201. * @param {function} endCallback - 播放完毕的回调函数 ()
  202. *
  203. * playAudio(
  204. * audioUrl,
  205. * ()=>{
  206. * console.log("开始播放");
  207. * },
  208. * (currentTime, duration) => {
  209. * console.log(`当前时间: ${currentTime}s, 总时长: ${duration}s`);
  210. * // 更新UI进度条
  211. * },
  212. * (errorMsg) => {
  213. * console.error("播放错误:", errorMsg);
  214. * // 显示错误信息
  215. * },
  216. * () => {
  217. * console.log("音频播放结束");
  218. * // 重置 UI 状态
  219. * }
  220. *);
  221. */
  222. const playAudio = (audioUrl, PlayCallback, progressCallback, errorCallback, endCallback) => {
  223. if (audioUrl) {
  224. initAudioContext();
  225. innerAudioContext.src = audioUrl;
  226. innerAudioContext.play();
  227. console.log("播放外部音频:", audioUrl);
  228. innerAudioContext.onPlay(() => {
  229. console.log('开始播放');
  230. PlayCallback()
  231. });
  232. innerAudioContext.onTimeUpdate(() => {
  233. progressCallback(innerAudioContext.currentTime, innerAudioContext.duration);
  234. });
  235. innerAudioContext.onError((error) => {
  236. const errorMsg = error.errMsg || "播放错误";
  237. console.error("播放错误:", errorMsg);
  238. errorCallback(errorMsg);
  239. });
  240. innerAudioContext.onEnded(() => {
  241. endCallback();
  242. });
  243. } else {
  244. const errorMsg = "音频 URL 不能为空";
  245. console.error(errorMsg);
  246. errorCallback(errorMsg);
  247. }
  248. };
  249. /**
  250. * 暂停音频播放
  251. */
  252. function pausePlaying() {
  253. if (innerAudioContext && innerAudioContext.paused === false) {
  254. innerAudioContext.pause();
  255. console.log("暂停播放音频");
  256. }
  257. }
  258. /**
  259. * 停止音频播放
  260. */
  261. function stopPlaying() {
  262. if (innerAudioContext) {
  263. innerAudioContext.stop();
  264. console.log("停止播放音频");
  265. }
  266. }
  267. // #ifdef H5
  268. // 初始化录音管理器
  269. initRecorderManager();
  270. // #endif
  271. // 导出所有录音相关的工具函数
  272. export {
  273. startRecording,
  274. pauseRecording,
  275. resumeRecording,
  276. stopRecording,
  277. playAudio,
  278. pausePlaying,
  279. stopPlaying,
  280. };