IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 录音、上传、播放音频微信小程序实践 -> 正文阅读

[移动开发]录音、上传、播放音频微信小程序实践

录音、上传、播放音频微信小程序实践

最近上线了一款智能外呼机器人产品,需要开发一款录音、上传、播放音频功能的
微信小程序给录音师配置外呼话术真人录音。

代码已开源,数据均已本地化处理。适合新手参考学习的完整原生微信小程序小项目。

实践分析

依赖接口

主要使用以下 api

  1. wx.getRecorderManager :获取全局唯一的录音管理器 RecorderManager
  2. wx.createInnerAudioContext : 创建内部 audio 上下文 InnerAudioContext 对象

PS.

  1. 默认 audio 组件样式不符合需求,目前只需播放进度条,InnerAudioContext 配合 process 组件实现
  2. InnerAudioContext 退出小程序自动停止播放,需要退出小程序依然可播放请使用背景音频 BackgroundAudioManager 代替

为什么要声明全局变量:

  1. 录音本身就是唯一全局
  2. 语音播放,如果每次离开、进入页面动态生成、销毁(好像有 bug),会有多条音频同时播放,为避免这个问题,使用全局唯一对象管理
const recorderManager: WechatMiniprogram.RecorderManager = wx.getRecorderManager();
const innerAudioContext: WechatMiniprogram.InnerAudioContext = wx.createInnerAudioContext();

录音

  • 录音开始配置
const recordOptions = {
  duration: 10 * 60 * 1000, // 最多录音时长 10 分钟
  sampleRate: 8000, // 采样率
  numberOfChannels: 1, // 1 个录音通道即可
  format: 'wav', // 服务端指定格式
};
recorderManager.start(recordOptions)
  • 初始状态
录音
  • 录音检测是否收到声音,本想利用 RecorderManager.onFrameRecorded 来感知是否收到声音,
    展示波形图,但该事件不支持 wav 格式文件。目前监听到开始事件即显示录音计时。
// 监听已录制完指定帧大小的文件事件。如果设置了 frameSize,则会回调此事件。
recorderManager.onFrameRecorded(({ frameBuffer, isLastFrame }) => {
  console.log('frameBuffer.byteLength: ', frameBuffer.byteLength)
  console.log('isLastFrame: ', isLastFrame);
});
  • 监听录音开始事件,设置录音进行中状态,并展示录音计时器
recorderManager.onStart(() => {
  console.log('recorder start');
  this.startClock();
  this.setData({
    ...recordingData,
  });
});
录音中
  • 停止录音事件,可以接收到本地录音文件地址、录音时长信息。一般上传文件至 CDN ,然后把地址存储到业务服务器,接着试听播放。
recorderManager.stop();
// 停止录音事件
recorderManager.onStop(async (res) => {
  console.log('recorder stop', res)
  // 停止后立即更新状态,以免异常
  this.stopClock();
  this.setData({
    ...initRData,
  });
  if (isError) {
    isError = false;
    return;
  }
  const { tempFilePath, duration } = res;
  console.log('tempFilePath', tempFilePath);
  const url = await uploadFile({ filePath: tempFilePath });

  // 快速开始时,获取的都是未录音,会冲掉当前上传试听,这里手动设置一下
  if (innerAudioContext.currentTime) {
    innerAudioContext.stop();
  }
  innerAudioContext.src = url;
  this.setData({
    ...initPlayData,
    ...initRData,
    detail: {
      ...this.data.detail,
      url,
      duration: Math.ceil(duration / 1000),
    },
    duration: formatClock(duration, true),
  });
  // await this.getDetail('CUR');
});
  • 监听录音异常、中断,录音异常千奇百怪,且无文档具体说明。
    比如电话会打断录音,触发暂停事件。拒绝授权会出发错误事件。这里都设置异常变量为 true,在 onStop 事件中不进行上传逻辑,而是恢复到录音初始状态。
// 监听录音错误事件
recorderManager.onError((err) => {
  this.noEffectStopRecorder();

  showErrMsg(msgMap[err.errMsg] || err.errMsg || '小程序错误');
  console.log('recorderManager.onError', err);
});

// 监听录音暂停事件
recorderManager.onPause(() => {
  console.log('recorder pause');
  // 立马停止,重新开始,没有恢复机制
  this.noEffectStopRecorder();
});
  • 记录异常不进行业务处理并调用终止录音。这里注意录音不像播放调用 stop 是无副作用的。未开始或暂停录音调用 stop 会抛出异常。小心导致死循环。
noEffectStopRecorder() {
  if (this.data.isRecording) {
    isError = true;
    recorderManager.stop();
  }
}

上传

  • 需小程序后台配置相关业务域名
export function uploadFile({ fileName, filePath }: {
  fileName?: string;
  filePath: string;
}) {
  return new Promise<string>((resolve) => {
    wx.showLoading({
      title: '上传中...',
    });
    const name = fileName || filePath;
    // 获取 CDN token
    getNosToken({ fileName: name }).then((data) => {
      console.log('uploadToken: ', data);
      wx.uploadFile({
        url: 'https://nos.com/',
        name: 'file', // 服务器获取流的参数名
        filePath,
        formData: {
          Object: data.objectName,
          'x-nos-token': data.token,
        },
        success(res) {
          console.log('上传成功回调', res);
          wx.hideLoading();
          const url = `https://cdn.com/${data.objectName}`
          console.log(url);
          resolve(url);
        },
        fail(err) {
          wx.hideLoading();
          wx.showToast({
            title: err.errMsg,
            icon: 'none',
          });
          reject(err);
        },
      })
    });
  });
}

播放

  • 未播放状态
播放
  • 监听播放开始事件,设置播放状态,且展示播放进度条
// 监听音频播放事件
innerAudioContext.onPlay(() => {
  console.log('开始播放');
  this.setData({
    ...playingData,
  });
});
  • 监听音频播放进度更新事件,更新 process 百分比
// 监听音频播放进度更新事件
innerAudioContext.onTimeUpdate(() => {
  console.log('监听音频播放进度更新事件');

  let playPercent = 0;
  const duration = this.data.detail.duration || innerAudioContext.duration;
  try {
    playPercent = Math.ceil(((innerAudioContext.currentTime * 1000) / (duration * 1000)) * 100) || 0;
  } catch (e) {
    playPercent = 0;
  }
  playPercent = playPercent && playPercent > 100 ? 100 : playPercent;
  const currentTime = formatClock(innerAudioContext.currentTime * 1000, true);
  console.log('当前播放时间:', currentTime);
  console.log('微信暴露时间:', innerAudioContext.duration);
  console.log('后端返回时间:', duration);
  console.log('当前播放进度:', playPercent);
  this.setData({
    currentTime,
    playPercent,
  });
});
播放中
  • 需求不需要暂停或拖拽进度条。监听音频正常、异常停止或暂停时,都恢复到初始状态。需要恢复或拖拽进度能力,可自行相应事件中处理
// 监听音频自然播放至结束的事件
innerAudioContext.onEnded(() => {
  console.log('监听音频自然播放至结束的事件');
  this.setData({
    ...initPlayData
  });
});

// 监听音频播放错误事件
innerAudioContext.onError((res) => {
  /**
   * 10001	系统错误
   * 10002	网络错误
   * 10003	文件错误
   * 10004	格式错误
   * -1	    未知错误
   */
  console.log(res.errCode, res.errMsg);
  this.setData({
    ...initPlayData
  });
});

// 监听音频暂停事件
innerAudioContext.onPause(() => {
  console.log('监听音频暂停事件');
  this.setData({
    ...initPlayData
  });
});

// 监听音频停止事件
innerAudioContext.onStop(() => {
  console.log('监听音频停止事件');
  this.setData({
    ...initPlayData,
  });
});

Page 事件

  1. 页面初次渲染完成,初始化音频录音、播放事件
  2. 页面每次重新进入加载最新业务数据
  3. 页面离开当前页面或退出小程序,停止录音、播放
/**
   * 生命周期函数--监听页面初次渲染完成
   */
onReady() {
  this.initRecorder();
  this.initAudioPlayer();
},

/**
 * 生命周期函数--监听页面显示
 */
onShow() {
  this.getDetail('CUR');
},

/**
 * 生命周期函数--监听页面卸载
 */
onUnload() {
  console.log('切换页面停止录音或播放');
  innerAudioContext.stop();

  this.noEffectStopRecorder();
},

参考

  1. 原文地址
  2. Github 地址
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-07-17 16:34:46  更:2022-07-17 16:37:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 3:31:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码