收藏
回答

微信公众号stopRecord停止录音在iOS中无返回结果,卡住

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html

环境:iOS公众号

微信版本:8+

现象: 无法停止录音,但是在Android手机中正常

主要代码如下

/**
 * 语音输入组件 - 微信JS-SDK版本
 * 
 * 提供语音识别功能,包括:
 * - 录音状态管理
 * - 录音动画效果
 * - 错误处理
 * - 结果回调
 * - 微信JS-SDK语音接口集成
 */


'use client'


import { useState, useEffect, useCallback, useRef } from 'react'
import { MicrophoneIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { isWechatBrowser, initWechatJsSdk, checkJsApiSupported } from '@/utils/wechat'


/**
 * 防抖函数,限制函数在指定时间内只执行一次
 */
function debounce unknown>(
  func: T,
  wait: number
): (...args: Parameters) => void {
  let timeout: NodeJS.Timeout | null = null;
  
  return function(...args: Parameters) {
    if (timeout) {
      clearTimeout(timeout);
    }
    
    timeout = setTimeout(() => {
      func(...args);
      timeout = null;
    }, wait);
  };
}


/**
 * 语音输入组件的属性接口
 */
interface VoiceInputProps {
  show: boolean
  onClose: () => void
  onResult: (text: string) => void
  sendDirectly?: boolean
}


/**
 * 录音状态类型
 */
type RecordingStatus = 'inactive' | 'recording' | 'processing'


// 录音相关的JS API列表
const VOICE_JS_API_LIST = [
  'startRecord',
  'stopRecord',
  'onVoiceRecordEnd',
  'translateVoice',
  'uploadVoice',
];


export function VoiceInput({ show, onClose, onResult, sendDirectly = true }: VoiceInputProps) {
  // 状态管理
  const [status, setStatus] = useState('inactive')
  const [error, setError] = useState(null)
  const [isWechat, setIsWechat] = useState(false)
  const [sdkInitialized, setSdkInitialized] = useState(false)
  const [apiSupported, setApiSupported] = useState<{[key: string]: boolean}>({})
  
  // 录音相关引用
  const processingRef = useRef(false) // 用于跟踪处理状态,防止重复调用
  const localIdRef = useRef('') // 微信语音唯一识别ID
  const voiceRecordEndHandlerRef = useRef<(() => void) | null>(null); // 保存录音自动停止事件处理函数的引用


  // 是否支持录音
  const isRecordingSupported = apiSupported['startRecord'] && apiSupported['stopRecord'] && apiSupported['translateVoice'];
  
  /**
   * 取消录音
   */
  const cancelRecording = useCallback(() => {
    if (isWechat && status === 'recording') {
      try {
        window.wx.stopRecord();
      } catch (error) {
        console.error('停止录音失败:', error)
      }
    }
    setStatus('inactive')
    processingRef.current = false
    onClose()
  }, [onClose, status, isWechat])


  /**
   * 初始化微信JS-SDK
   */
  useEffect(() => {
    let mounted = true;
    
    const initSdk = async () => {
      const inWechat = isWechatBrowser()
      if (mounted) setIsWechat(inWechat)
      
      if (inWechat) {
        try {
          // 初始化JS-SDK,配置需要使用的录音接口
          const initialized = await initWechatJsSdk(VOICE_JS_API_LIST)
          if (mounted) setSdkInitialized(initialized)
          
          if (initialized && mounted) {
            console.log('微信JS-SDK初始化成功,准备检查接口支持情况')
            
            // 检查当前微信版本是否支持所需接口
            const supported = await checkJsApiSupported(VOICE_JS_API_LIST);
            if (mounted) {
              setApiSupported(supported);
              
              // 输出支持情况日志
              console.log('录音接口支持情况:', 
                `startRecord: ${supported['startRecord'] ? '支持' : '不支持'}, `,
                `stopRecord: ${supported['stopRecord'] ? '支持' : '不支持'}, `,
                `translateVoice: ${supported['translateVoice'] ? '支持' : '不支持'}`
              );
              
              // 如果不支持关键接口,显示错误提示
              if (!supported['startRecord'] || !supported['stopRecord']) {
                setError('当前微信版本不支持录音功能,请升级微信版本');
              }
            }
          } else if (mounted) {
            setError('微信JS-SDK初始化失败,请刷新页面重试')
          }
        } catch (error) {
          console.error('初始化微信JS-SDK失败:', error)
          if (mounted) setError('初始化微信JS-SDK失败,请刷新页面重试')
        }
      }
    }
    
    initSdk()
    
    return () => {
      mounted = false;
    }
  }, [])
  
  /**
   * 设置录音结束事件监听
   */
  const setupVoiceRecordEndHandler = useCallback(() => {
    if (!isWechat || !sdkInitialized || !window.wx || !isRecordingSupported) return;
    
    // 先移除之前的事件监听(如果存在)
    if (voiceRecordEndHandlerRef.current) {
      // 微信JS-SDK没有提供直接的移除监听方法,但重新监听会替换之前的处理函数
      voiceRecordEndHandlerRef.current();
      voiceRecordEndHandlerRef.current = null;
    }
    
    // 添加新的事件监听
    const handler = () => {
      window.wx.onVoiceRecordEnd({
        complete: function(res: { localId: string }) {
          console.log('录音自动停止事件触发', res.localId);
          if (status === 'recording') {
            localIdRef.current = res.localId
            handleRecordingComplete(res.localId)
          }
        }
      });
    };
    
    handler(); // 立即执行一次
    voiceRecordEndHandlerRef.current = handler; // 保存引用以便之后可以移除
    
    console.log('已设置录音结束事件监听');
  }, [isWechat, sdkInitialized, status, isRecordingSupported]);


  /**
   * 开始录音
   */
  const startRecording = useCallback(async () => {
    try {
      setError(null)
      
      if (isWechat && sdkInitialized) {
        // 检查是否支持录音
        if (!isRecordingSupported) {
          setError('当前微信版本不支持录音功能,请升级微信');
          return;
        }
        
        // 确保事件监听已设置
        setupVoiceRecordEndHandler();
        
        console.log('开始录音...');
        // 使用微信JS-SDK开始录音
        window.wx.startRecord({
          success: function() {
            console.log('微信录音开始成功');
            setStatus('recording')
          },
          cancel: function () {
            console.log('用户拒绝授权录音')
          },
          fail: function(res: { errMsg: string }) {
            console.error('微信录音失败:', res.errMsg)
            setError('启动录音失败: ' + res.errMsg)
          }
        })
      } else if (!isWechat) {
        setError('请在微信浏览器中使用语音功能')
      } else {
        setError('JS-SDK未初始化,无法使用录音功能')
      }
    } catch (error) {
      console.error('启动录音失败:', error)
      setError('启动录音失败,请确保授予麦克风权限')
    }
  }, [isWechat, sdkInitialized, setupVoiceRecordEndHandler, isRecordingSupported])


  /**
   * 停止录音 - 添加防抖处理
   */
  const handleStopRecording = useCallback(() => {
    // 如果不在录音状态或已经在处理中,则忽略点击
    if (status !== 'recording' || processingRef.current) {
      return;
    }
    
    // 设置处理标志为true,防止重复调用
    processingRef.current = true;
    
    try {
      setStatus('processing')
      
      if (isWechat && sdkInitialized && isRecordingSupported) {
        console.log('停止录音...');
        // 使用微信JS-SDK停止录音
        window.wx.stopRecord({
          success: function(res: { localId: string }) {
            console.log('微信录音停止成功,localId:', res.localId);
            localIdRef.current = res.localId
            handleRecordingComplete(res.localId)
          },
          fail: function(res: { errMsg: string }) {
            console.error('停止录音失败:', res.errMsg)
            setError('停止录音失败: ' + res.errMsg)
            setStatus('inactive')
            processingRef.current = false
          }
        })
      }
    } catch (error) {
      console.error('停止录音失败:', error)
      setError('停止录音失败,请刷新页面后重试')
      setStatus('inactive')
      processingRef.current = false
    }
  }, [status, isWechat, sdkInitialized, isRecordingSupported])
  
  // 防抖处理,300ms内只响应一次点击
  const stopRecording = useCallback(debounce(handleStopRecording, 300), [handleStopRecording]);


  /**
   * 处理录音完成后的操作
   */
  const handleRecordingComplete = useCallback((localId: string) => {
    if (!localId) {
      setError('录音失败,未获取到录音ID')
      setStatus('inactive')
      processingRef.current = false
      return
    }
    
    if (!apiSupported['translateVoice']) {
      setError('当前微信版本不支持语音识别功能,请升级微信');
      setStatus('inactive')
      processingRef.current = false
      return
    }
    
    console.log('处理录音完成,开始识别,localId:', localId);
    // 使用微信内置的语音识别接口
    window.wx.translateVoice({
      localId: localId,
      isShowProgressTips: 1, // 显示进度提示
      success: function(res: { translateResult: string }) {
        console.log('微信语音识别成功:', res.translateResult);
        if (res.translateResult && res.translateResult.trim()) {
          onResult(res.translateResult.trim())
          // 如果是直接发送模式,处理完成后关闭弹窗
          if (sendDirectly) {
            onClose()
          }
        } else {
          console.log('微信语音识别结果为空,尝试通过服务器识别');
          if (apiSupported['uploadVoice']) {
            // 如果微信识别失败,尝试通过服务器端识别
            uploadVoiceToServer(localId)
          } else {
            setError('语音识别失败,当前微信版本不支持语音上传');
            setStatus('inactive')
            processingRef.current = false
          }
        }
      },
      fail: function(res: { errMsg: string }) {
        console.error('微信语音识别失败:', res.errMsg)
        if (apiSupported['uploadVoice']) {
          // 如果微信自带语音识别失败,尝试通过上传到服务器识别
          uploadVoiceToServer(localId)
        } else {
          setError('语音识别失败,当前微信版本不支持语音上传');
          setStatus('inactive')
          processingRef.current = false
        }
      }
    })
  }, [onResult, onClose, sendDirectly, apiSupported])


  /**
   * 上传语音到服务器进行识别
   */
  const uploadVoiceToServer = useCallback(async (localId: string) => {
    try {
      console.log('开始上传语音到服务器,localId:', localId);
      // 先将语音上传到微信服务器获取serverId
      window.wx.uploadVoice({
        localId: localId,
        isShowProgressTips: 1,
        success: async function(res: { serverId: string }) {
          console.log('语音上传到微信服务器成功,serverId:', res.serverId);
          try {
            // 通过API将微信服务器的语音转换为文本
            // 这里需要后端提供一个接口,接收serverId,然后通过微信公众号接口获取语音文件并转换为文本
            const response = await fetch('/api/wechat/voice-to-text', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ 
                mediaId: res.serverId 
              })
            })
            
            if (!response.ok) {
              throw new Error(`服务器识别失败: ${response.status}`)
            }
            
            const data = await response.json()
            console.log('服务器语音识别结果:', data);
            
            if (data.text && data.text.trim()) {
              onResult(data.text.trim())
              // 如果是直接发送模式,处理完成后关闭弹窗
              if (sendDirectly) {
                onClose()
              }
            } else {
              setError('未能识别语音内容,请重新尝试')
            }
          } catch (error) {
            console.error('服务器语音识别失败:', error)
            setError('语音识别失败,请重试')
          } finally {
            setStatus('inactive')
            processingRef.current = false
          }
        },
        fail: function(res: { errMsg: string }) {
          console.error('上传语音失败:', res.errMsg)
          setError('上传语音失败: ' + res.errMsg)
          setStatus('inactive')
          processingRef.current = false
        }
      })
    } catch (error) {
      console.error('处理录音失败:', error)
      setError('处理录音失败,请重试')
      setStatus('inactive')
      processingRef.current = false
    }
  }, [onResult, onClose, sendDirectly])


  // 当组件显示或隐藏状态变化时重置组件状态
  useEffect(() => {
    if (show) {
      // 如果组件显示,且状态是非活动的,则开始录音
      if (status === 'inactive' && !processingRef.current) {
        console.log('VoiceInput组件显示,准备开始录音');
        // 清空之前的状态
        setError(null);
        localIdRef.current = '';
        
        // 延迟一点开始录音,确保UI已经完全渲染
        setTimeout(() => {
          startRecording();
        }, 300);
      }
    } else {
      // 如果组件隐藏,但仍在录音中,则停止录音
      if (status === 'recording' && isWechat && sdkInitialized && isRecordingSupported) {
        try {
          console.log('VoiceInput组件隐藏,停止录音');
          window.wx.stopRecord();
        } catch (error) {
          console.error('停止录音失败:', error);
        }
      }
      
      // 组件隐藏时重置状态
      setStatus('inactive');
      processingRef.current = false;
    }
  }, [show, status, isWechat, sdkInitialized, startRecording, isRecordingSupported]);


  // 组件卸载时清理资源
  useEffect(() => {
    return () => {
      // 如果正在录音,停止录音
      if (status === 'recording' && isWechat && sdkInitialized && isRecordingSupported) {
        try {
          window.wx.stopRecord();
        } catch (error) {
          console.error('清理时停止录音失败:', error);
        }
      }
      
      // 确保标志被重置
      processingRef.current = false;
    };
  }, [status, isWechat, sdkInitialized, isRecordingSupported]);


  if (!show) return null


  return (
    
      
        {/* 关闭按钮 - 在处理中时禁用 */}
        
          
        


        
          {/* 标题 */}
          
            {isWechat ? '微信语音输入' : '语音输入'}
          




          {/* 录音按钮 - 已修改为停止录音按钮 */}
        
            {status === 'recording' ? (
              
                
                
                
                
                  
                
              

            ) : status === 'processing' ? (
              
            ) : (
              
            )}
        


          {/* 状态提示 */}
          
            
              {status === 'inactive' && isWechat && isRecordingSupported ? '准备录音...' : 
               status === 'inactive' && isWechat && !isRecordingSupported ? '当前微信版本不支持录音功能' :
               status === 'inactive' && !isWechat ? '请在微信中使用此功能' : ''}
              {status === 'recording' && '正在录音...点击停止'}
              {status === 'processing' && '正在识别语音...'}
            

          



          {/* 错误提示 */}
          {error && (
            
              {error}
            


          )}
          
          {/* 使用提示 */}
          
            {isWechat && isRecordingSupported ? 
              '使用微信语音接口识别,准确度更高' : 
              isWechat && !isRecordingSupported ? 
              '当前微信版本不支持录音,请升级微信版本' :
              '请在微信浏览器中打开此页面使用语音功能'}
          

        

      

    

  )
} 


最后一次编辑于  1天前
回答关注问题邀请回答
收藏

1 个回答

登录 后发表内容