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

Expo AV Audio.Sound卸载后导航报错:Seeking interrupted 求助

Fixing "Seeking interrupted" Unhandled Promise Rejection in Expo-AV Countdown Component

Got it, let's break down why you're hitting this error and how to fix it. The core issue is that after your component unmounts (when you navigate to the next page), the countdown timer is still firing the onChange event and trying to replay the sound—but by then the audio object might already be unloaded, or the component is no longer mounted, leading to an interrupted seek operation and an unhandled promise rejection.

Here are the key fixes you need to implement:

1. Track Component Mount State

Use a useRef to track whether the component is still mounted. This prevents us from trying to interact with the audio object after the component has been unmounted.

2. Wrap Audio Operations in Mount Checks

Before calling any Expo-AV methods (like replayAsync), check if the component is still mounted to avoid unnecessary operations on a stale audio instance.

3. Handle Promise Rejections

Always add a .catch() to replayAsync (and other async audio methods) to catch any errors that might slip through, even if we think we've covered all cases.

4. Properly Initialize and Clean Up the Audio Instance

Keep the audio instance in a useRef instead of a regular variable—this ensures it persists across re-renders and doesn't get recreated every time the component updates.

Updated Code

import React, { useRef, useEffect } from 'react'
import CountDown from 'react-native-countdown-component'
import { Audio } from 'expo-av'

const BEEP_START = 3

const CountDownBeep = (props) => {
  // Track if component is mounted
  const isMounted = useRef(true)
  // Persist audio instance across re-renders
  const beepSound = useRef(new Audio.Sound())

  useEffect(() => {
    const loadSound = async () => {
      try {
        console.log("Sound Initialized")
        await beepSound.current.loadAsync(require('../assets/sounds/beep.wav'), {
          shouldPlay: false,
          isLooping: false,
        })
      } catch (error) {
        console.error('Failed to load sound:', error)
      }
    }

    loadSound()

    // Cleanup function: mark component as unmounted and unload audio
    return async () => {
      isMounted.current = false
      console.log('Sound destroyed')
      try {
        await beepSound.current.unloadAsync()
      } catch (error) {
        console.error('Failed to unload sound:', error)
      }
    }
  }, [])

  const countDownTimerChangedHandler = async (timeLeft) => {
    // Only proceed if component is still mounted
    if (!isMounted.current) return

    if (timeLeft <= BEEP_START + 1 && timeLeft > 0) {
      console.log('Sound Played:', timeLeft)
      try {
        await beepSound.current.replayAsync()
      } catch (error) {
        // Catch the "Seeking interrupted" error (and others) to avoid unhandled rejection
        console.warn('Sound playback error:', error)
      }
    }
  }

  return (
    <CountDown {...props} onChange={countDownTimerChangedHandler} />
  )
}

export default CountDownBeep

Why This Works

  • isMounted Ref: This acts as a guard clause—if the component has unmounted, we skip any audio operations entirely, preventing attempts to use a stale or unloaded audio instance.
  • useRef for Audio Instance: Storing the sound in a ref ensures we're always working with the same instance across re-renders, instead of creating a new one every time the component updates.
  • Error Handling: Adding try/catch blocks around all async audio methods ensures that any unexpected errors (like the seek interruption) are caught and logged, rather than becoming unhandled promise rejections.

This should eliminate the "Seeking interrupted" error you're seeing after navigating away from the component.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 20:27:35