音视频处理:微信小程序实现语音转文字功能

文章目录

    • 需求
    • 分析
      • [1. 添加插件](#1. 添加插件)
      • [2. 实现](#2. 实现)
        • [1. `app.json`](#1. app.json)
        • [2. `pages/voiceToText/voiceToText.json`](#2. pages/voiceToText/voiceToText.json)
        • [3. `pages/voiceToText/voiceToText.ts`](#3. pages/voiceToText/voiceToText.ts)
        • [4. `pages/voiceToText/voiceToText.wxml`](#4. pages/voiceToText/voiceToText.wxml)
        • [5. `pages/voiceToText/voiceToText.wxss`](#5. pages/voiceToText/voiceToText.wxss)
      • [3. 隐私说明](#3. 隐私说明)

需求

如下所示,需要实现在微信小程序中语音转文字功能:

分析

1. 添加插件

登录微信公众平台(https://mp.weixin.qq.com),进入「设置 → 第三方服务 → 插件管理」,搜索「微信同声传译」并添加,获取其 AppID(如wx069ba97219f66d99)和最新版本号

配置小程序:在app.json中配置插件信息,示例代码如下需确保版本号与插件详情页一致,否则可能导致功能异常

javascript 复制代码
    {
      "plugins": {
        "WechatSI": {
          "version": "0.3.4", // 填写实际版本号
          "provider": "wx069ba97219f66d99"
        }
      }
    }

2. 实现

1. app.json
javascript 复制代码
  {
    "pages": [
      "pages/voiceToText/voiceToText"
    ],
    "window": {
      "backgroundTextStyle": "light",
      "navigationBarBackgroundColor": "#fff",
      "navigationBarTitleText": "微信同声转文字功能",
      "navigationBarTextStyle": "black"
    },
    "plugins": {
      "WechatSI": {
        "version": "0.3.4",
        "provider": "wx069ba97219f66d99"
      }
    },
    "sitemapLocation": "sitemap.json"
  }
2. pages/voiceToText/voiceToText.json
javascript 复制代码
  {
    "navigationBarTitleText": "语音转文字",
    "usingComponents": {}
  }
3. pages/voiceToText/voiceToText.ts
js 复制代码
  // 引入微信同声传译插件
  const plugin = requirePlugin('WechatSI')
  // 获取全局唯一的语音识别管理器
  const manager = plugin.getRecordRecognitionManager()

  Page({
    /**
    * 页面的初始数据
    */
    data: {
      // 录音状态:false-未录音,true-正在录音
      isRecording: false,
      // 识别结果文本
      resultText: '',
      // 历史记录列表
      historyList: [] as string[],
      // 录音时长(秒)
      recordDuration: 0,
      // 定时器ID
      timer: 0 as number,
      // 支持的语言列表
      languages: [
        { code: 'zh_CN', name: '中文' },
        { code: 'en_US', name: '英语' },
        { code: 'zh_HK', name: '粤语' }
      ],
      // 当前选中的语言
      currentLang: 'zh_CN'
    },

    /**
    * 生命周期函数--监听页面加载
    */
    onLoad() {
      this.initRecordManager()
      // 从本地存储加载历史记录
      const history = wx.getStorageSync('voiceToTextHistory')
      if (history && Array.isArray(history)) {
        this.setData({ historyList: history })
      }
    },

    /**
    * 初始化录音管理器
    */
    initRecordManager() {
      // 识别中事件
      manager.onRecognize = (res: { result: string }) => {
        console.log('实时识别结果:', res.result)
        this.setData({
          resultText: res.result
        })
      }

      // 识别结束事件
      manager.onStop = (res: { result: string }) => {
        console.log('识别结束:', res)
        let text = res.result
        
        // 处理可能的空结果
        if (!text) {
          wx.showToast({
            title: '未识别到内容',
            icon: 'none',
            duration: 2000
          })
          text = ''
        } else {
          // 保存到历史记录
          this.addToHistory(text)
        }
        
        this.setData({
          resultText: text,
          isRecording: false,
          recordDuration: 0
        })
        
        // 清除定时器
        if (this.data.timer) {
          clearInterval(this.data.timer)
        }
      }

      // 识别错误事件
      manager.onError = (err: { errMsg: string }) => {
        console.error('识别错误:', err)
        wx.showToast({
          title: '识别失败: ' + err.errMsg,
          icon: 'none',
          duration: 3000
        })
        this.setData({
          isRecording: false,
          recordDuration: 0
        })
        
        if (this.data.timer) {
          clearInterval(this.data.timer)
        }
      }
    },

    /**
    * 开始录音
    */
    startRecord() {
      // 检查录音权限
      wx.getSetting({
        success: (res) => {
          if (!res.authSetting['scope.record']) {
            // 申请录音权限
            wx.authorize({
              scope: 'scope.record',
              success: () => {
                this.startRecording()
              },
              fail: () => {
                wx.showModal({
                  title: '权限不足',
                  content: '需要录音权限才能使用语音识别功能,请在设置中开启',
                  confirmText: '去设置',
                  success: (modalRes) => {
                    if (modalRes.confirm) {
                      wx.openSetting()
                    }
                  }
                })
              }
            })
          } else {
            this.startRecording()
          }
        }
      })
    },

    /**
    * 实际开始录音的逻辑
    */
    startRecording() {
      // 重置结果
      this.setData({
        resultText: '',
        isRecording: true,
        recordDuration: 0
      })

      // 开始录音
      manager.start({
        lang: this.data.currentLang,
        duration: 60000 // 最长录音时间,单位ms
      })

      // 开始计时
      const timer = setInterval(() => {
        this.setData({
          recordDuration: this.data.recordDuration + 1
        })
      }, 1000)

      this.setData({ timer })
    },

    /**
    * 停止录音
    */
    stopRecord() {
      if (this.data.isRecording) {
        manager.stop()
      }
    },

    /**
    * 添加到历史记录
    */
    addToHistory(text: string) {
      const newHistory = [text, ...this.data.historyList]
      // 限制历史记录数量为20条
      if (newHistory.length > 20) {
        newHistory.pop()
      }
      
      this.setData({ historyList: newHistory })
      // 保存到本地存储
      wx.setStorageSync('voiceToTextHistory', newHistory)
    },

    /**
    * 清空当前结果
    */
    clearResult() {
      this.setData({ resultText: '' })
    },

    /**
    * 复制结果到剪贴板
    */
    copyResult() {
      if (!this.data.resultText) {
        wx.showToast({
          title: '没有可复制的内容',
          icon: 'none'
        })
        return
      }

      wx.setClipboardData({
        data: this.data.resultText,
        success: () => {
          wx.showToast({
            title: '复制成功',
            icon: 'success'
          })
        }
      })
    },

    /**
    * 清空历史记录
    */
    clearHistory() {
      if (this.data.historyList.length === 0) return

      wx.showModal({
        title: '确认',
        content: '确定要清空所有历史记录吗?',
        success: (res) => {
          if (res.confirm) {
            this.setData({ historyList: [] })
            wx.removeStorageSync('voiceToTextHistory')
          }
        }
      })
    },

    /**
    * 选择语言
    */
    selectLanguage(e: { currentTarget: { dataset: { code: string } } }) {
      const langCode = e.currentTarget.dataset.code
      this.setData({ currentLang: langCode })
    },

    /**
    * 生命周期函数--监听页面卸载
    */
    onUnload() {
      // 页面卸载时停止录音并清除定时器
      if (this.data.isRecording) {
        manager.stop()
      }
      if (this.data.timer) {
        clearInterval(this.data.timer)
      }
    }
  })
4. pages/voiceToText/voiceToText.wxml
javascript 复制代码
    <view class="container">
      <view class="title">语音转文字</view>
      
      <!-- 语言选择 -->
      <view class="language-selector">
        <view class="lang-item" 
              wx:for="{{languages}}" 
              wx:key="code"
              data-code="{{item.code}}"
              bindtap="selectLanguage"
              class="{{currentLang === item.code ? 'lang-selected' : ''}}">
          {{item.name}}
        </view>
      </view>
      
      <!-- 结果显示区域 -->
      <view class="result-container">
        <textarea 
          class="result-text" 
          value="{{resultText}}" 
          placeholder="点击下方按钮开始说话..."
          disabled
        ></textarea>
        
        <!-- 操作按钮 -->
        <view class="action-buttons">
          <button 
            class="btn clear-btn" 
            bindtap="clearResult"
            disabled="{{!resultText}}"
          >
            清空
          </button>
          <button 
            class="btn copy-btn" 
            bindtap="copyResult"
            disabled="{{!resultText}}"
          >
            复制
          </button>
        </view>
      </view>
      
      <!-- 录音控制 -->
      <view class="record-control">
        <view 
          class="record-button" 
          bindtouchstart="startRecord" 
          bindtouchend="stopRecord"
          class="{{isRecording ? 'recording' : ''}}"
        >
          <text class="record-icon">{{isRecording ? '⏹️' : '🎤'}}</text>
          <text class="record-text">{{isRecording ? '松开停止' : '按住说话'}}</text>
          <text class="record-time" wx:if="{{isRecording}}">{{recordDuration}}s</text>
        </view>
      </view>
      
      <!-- 历史记录 -->
      <view class="history-section" wx:if="{{historyList.length > 0}}">
        <view class="history-header">
          <text>历史记录</text>
          <button class="clear-history-btn" bindtap="clearHistory">清空</button>
        </view>
        
        <view class="history-list">
          <view class="history-item" wx:for="{{historyList}}" wx:key="index">
            <text>{{item}}</text>
          </view>
        </view>
      </view>
    </view>
5. pages/voiceToText/voiceToText.wxss
javascript 复制代码
  .container {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background-color: #f5f5f5;
    padding: 20rpx;
  }

  .title {
    font-size: 36rpx;
    font-weight: bold;
    text-align: center;
    margin: 30rpx 0;
    color: #333;
  }

  .language-selector {
    display: flex;
    justify-content: center;
    margin-bottom: 20rpx;
    flex-wrap: wrap;
  }

  .lang-item {
    padding: 10rpx 20rpx;
    margin: 0 10rpx 10rpx;
    background-color: #fff;
    border-radius: 20rpx;
    font-size: 28rpx;
    border: 1px solid #eee;
  }

  .lang-selected {
    background-color: #07c160;
    color: white;
    border-color: #07c160;
  }

  .result-container {
    background-color: #fff;
    border-radius: 16rpx;
    padding: 20rpx;
    margin-bottom: 30rpx;
    box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
  }

  .result-text {
    width: 100%;
    min-height: 200rpx;
    font-size: 30rpx;
    line-height: 1.6;
    padding: 10rpx;
    border: none;
    resize: none;
    color: #333;
  }

  .result-text::placeholder {
    color: #ccc;
  }

  .action-buttons {
    display: flex;
    justify-content: flex-end;
    margin-top: 10rpx;
  }

  .btn {
    padding: 10rpx 20rpx;
    margin-left: 15rpx;
    font-size: 26rpx;
    border-radius: 8rpx;
  }

  .clear-btn {
    background-color: #f5f5f5;
    color: #666;
  }

  .copy-btn {
    background-color: #07c160;
    color: white;
  }

  .record-control {
    display: flex;
    justify-content: center;
    margin: 40rpx 0 60rpx;
  }

  .record-button {
    width: 160rpx;
    height: 160rpx;
    border-radius: 50%;
    background-color: #ff4d4f;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    color: white;
    box-shadow: 0 5rpx 15rpx rgba(255,77,79,0.4);
    touch-action: manipulation;
  }

  .record-button.recording {
    background-color: #faad14;
    box-shadow: 0 5rpx 15rpx rgba(250,173,20,0.4);
  }

  .record-icon {
    font-size: 60rpx;
    margin-bottom: 5rpx;
  }

  .record-text {
    font-size: 24rpx;
  }

  .record-time {
    font-size: 22rpx;
    margin-top: 5rpx;
  }

  .history-section {
    margin-top: auto;
    background-color: #fff;
    border-radius: 16rpx;
    padding: 20rpx;
    box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
  }

  .history-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15rpx;
    padding-bottom: 10rpx;
    border-bottom: 1px solid #f5f5f5;
  }

  .history-header text {
    font-size: 30rpx;
    font-weight: 500;
    color: #666;
  }

  .clear-history-btn {
    font-size: 24rpx;
    color: #07c160;
    background: none;
    padding: 0;
  }

  .history-list {
    max-height: 300rpx;
    overflow-y: auto;
  }

  .history-item {
    padding: 15rpx 0;
    border-bottom: 1px solid #f5f5f5;
    font-size: 26rpx;
    color: #333;
    line-height: 1.5;
  }

  .history-item:last-child {
    border-bottom: none;
  }

3. 隐私说明

权限申请:需在小程序后台的「用户隐私保护指引」中声明使用录音功能,否则无法获取麦克风权限

相关推荐
00后程序员张2 小时前
iOS上架工具,AppUploader(开心上架)用于证书生成、描述文件管理Xcode用于应用构建
android·macos·ios·小程序·uni-app·iphone·xcode
2501_915921432 小时前
只有 IPA 没有源码时,如何给 iOS 应用做安全处理
android·安全·ios·小程序·uni-app·iphone·webview
汤姆yu3 小时前
2026版基于微信小程序的儿童疫苗预约接种系统
微信小程序·小程序
CHU7290353 小时前
趣味抽赏,解锁惊喜——扭蛋机盲盒抽赏小程序前端功能解析
前端·小程序
毕设源码-朱学姐3 小时前
【开题答辩全过程】以 汇华商场停车位预约小程序为例,包含答辩的问题和答案
小程序
偷偷摸鱼的少年4 小时前
小程序提现功能升级改造
微信小程序
2501_915921434 小时前
iOS APP上架工具,在没有 Mac 的环境中发布苹果应用
android·macos·ios·小程序·uni-app·iphone·webview
吴声子夜歌4 小时前
小程序——视频
小程序·音视频
00后程序员张5 小时前
iOS 应用的 HTTPS 连接端口在网络抓包调试中有什么作用
android·网络·ios·小程序·https·uni-app·iphone