You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native useCallback依赖初始状态致元数据统计异常求助

React Native音频元数据追踪问题:length_played始终为0,time_listened数值不准确

我在实现React Native应用的音频收听元数据追踪功能时遇到问题:length_played始终为0,time_listened数值不准确。推测原因是onSpeakDone回调执行时,无法获取spokenText和speakStartTime的更新后状态值。我是React Native新手,求可行的解决思路。

日志输出

!!!
{"length": 460, "length_played": 0, "name": "San Diego", "termination_kind": "done", "time_listened": 1718124515}
!!!

相关代码

export default function PlaceCard({ className, isCurrent, latitude, longitude, placeName }: PlaceCardProps) {
  const { place } = usePlace(latitude, longitude, placeName)

  const { name, summary } = place

  const speaking = useAppStore(state => state.speaking)
  const setSpeaking = useAppStore(state => state.setSpeaking)
  const setCanSpeak = useAppStore(state => state.setCanSpeak)

  const [spokenText, setSpokenText] = useState('')
  const [speakStartTime, setSpeakStartTime] = useState(0)

  const unspokenText = summary.slice(spokenText.length)

  const onSpeakBoundary = useCallback(({ charIndex, charLength }: NativeBoundaryEvent) => {
    const spoken = summary.slice(0, charIndex) + summary.slice(charIndex, charIndex + charLength)
    setSpokenText(spoken)
  }, [summary])

  const onSpeakDone = useCallback((termination: string) => {
    setSpeaking(false)

    const metadata = {
      length: summary.length,
      length_played: spokenText.length,
      name,
      termination_kind: termination,
      time_listened: Math.round((Date.now() - speakStartTime) / 1000),
    }

    console.log('!!!')
    console.log(metadata)
    console.log('!!!')

    logEvent('place_summary_play', metadata)

    setSpokenText('')
    setSpeakStartTime(0)
  }, [spokenText, speakStartTime, name, summary, setSpeaking, setSpokenText, setSpeakStartTime])

  useEffect(() => {
    if (place.summaryState === 'complete')
      setCanSpeak(true)

    const options: Speech.SpeechOptions = {
      language: 'en-US',
      onBoundary: onSpeakBoundary,
      onDone: () => onSpeakDone('done'),
      onError: () => onSpeakDone('error'),
      onStart: () => setSpeakStartTime(Date.now()),
      voice: 'com.apple.ttsbundle.siri_Nicky_en-US_compact',
    }

    speaking ? Speech.speak(place.summary, options) : Speech.stop()
  }, [place.summary, place.summaryState, speaking, setCanSpeak, onSpeakDone, onSpeakBoundary])

  if (!place)
    return null

  return (
    <View>{/* rest of the irrelevant code here */}</View>
  )
}

解决思路

1. 用useRef替代useState存储实时值

useState的更新是异步的,且回调函数会捕获创建时的闭包状态,导致onSpeakDone拿到的是初始值。改用useRef存储spokenText和speakStartTime,ref的current属性是可变的,能直接获取最新值:

// 替换原有的useState
const spokenTextRef = useRef('')
const speakStartTimeRef = useRef(0)

// 修改onSpeakBoundary
const onSpeakBoundary = useCallback(({ charIndex, charLength }: NativeBoundaryEvent) => {
  const spoken = summary.slice(0, charIndex) + summary.slice(charIndex, charIndex + charLength)
  spokenTextRef.current = spoken
  setSpokenText(spoken) // 保留setSpokenText用于UI更新
}, [summary])

// 修改onSpeakDone
const onSpeakDone = useCallback((termination: string) => {
  setSpeaking(false)

  const metadata = {
    length: summary.length,
    length_played: spokenTextRef.current.length,
    name,
    termination_kind: termination,
    time_listened: Math.round((Date.now() - speakStartTimeRef.current) / 1000),
  }

  console.log('!!!')
  console.log(metadata)
  console.log('!!!')

  logEvent('place_summary_play', metadata)

  spokenTextRef.current = ''
  speakStartTimeRef.current = 0
  setSpokenText('')
  setSpeakStartTime(0)
}, [name, summary, setSpeaking, setSpokenText, setSpeakStartTime])

// 修改useEffect中的onStart
const options: Speech.SpeechOptions = {
  // ...其他配置
  onStart: () => {
    speakStartTimeRef.current = Date.now()
    setSpeakStartTime(Date.now())
  },
}

2. 验证onBoundary回调是否触发

length_played为0可能是onSpeakBoundary根本没执行,在回调里加日志确认:

const onSpeakBoundary = useCallback(({ charIndex, charLength }: NativeBoundaryEvent) => {
  console.log('Boundary事件触发:', charIndex, charLength) // 新增日志
  const spoken = summary.slice(0, charIndex) + summary.slice(charIndex, charIndex + charLength)
  spokenTextRef.current = spoken
  setSpokenText(spoken)
}, [summary])

如果没触发,检查使用的语音库是否支持onBoundary事件,或平台(iOS/Android)是否有额外配置要求。

3. 检查useEffect依赖项是否准确

当前useEffect依赖onSpeakDone和onSpeakBoundary,确保这两个useCallback的依赖项完整,避免因依赖变化导致语音实例重复创建,影响事件触发逻辑。

内容的提问来源于stack exchange,提问作者Moon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 11:08:15