아이패드에서 특정 페이지/화면에서만 소리가 재생되지 않아요 🤔

미연·2025년 9월 2일

상황 설명

아이패드 유저에게 '특정 페이지/화면에서만 소리가 재생되지 않아요' voc 전달받음 🤔
센트리에 기록된 에러도 없음 🤔

가정 및 생각의 흐름

  1. 아이패드의 무음모드가 켜져 있나? → 아니다. 오히려 꺼져 있는데도 아이패드 여러 대에서 이 이슈가 종종 발생했다고 하신다.
  2. 소리가 재생되기 전에 유저의 제스쳐가 있었나? → [Start] 버튼을 눌러야지만 학습이 진행되면서 소리가 재생되는 프로세스이므로, 제스쳐는 소리 재생 전에서부터 있었음.
    • iOS Safari/크롬(webkit 기반)에서 오디오 컨텍스트(AudioContext)는 user gesture (터치/클릭)로 활성화된 뒤에만 소리가 나옴.
  3. 코드를 살펴보다가 실제 코드 중 initState에 오타가 있었다. 이것 때문인가? → 오타를 수정한 후 로컬에서 테스트를 해보았는데, 해당 이슈에 대한 재현이 되지 않았다. 단순 오타 때문만은 아니었던 것이다.
  4. audioContext.resume() 의 코드 부재 때문인가? → 소리를 재생시킬 때마다 playSound() 메서드를 호출한다. 그 안에서 resume() 를 항시 호출해 주고 있었다. 해당 코드가 없었던 건 아니지만 의심해 볼만 한 상황.
    • audioContext.resume()을 호출하면 audioContext의 실행 상태를 running 으로 전환시켜준다.
  const playSound = async () => {
    if (audioContext) {
       resume(); // ← 의심되는 코드
       await axiosInstance
        .get(url, { responseType: 'arraybuffer', adapter: axiosInstance.defaults.adapter })
        .then((response) => audioContext.decodeAudioData(response.data))
        .then((decodedData) => {
          const audioSource = audioContext.createBufferSource();
          audioSource.buffer = decodedData;

          audioSource.connect(audioContext.destination);
          audioSource.start();
        })
        .catch((error) => {
           ...
        });
    }
  };

이슈 재현 성공

내 애증의 아이패드로 이것저것 테스트 해보다가 재현이 되었는데,

  • 해당 프로세스 진행 중 화면에 아이패드를 잠시 sleep 시켜놓고
    (= 일반적으로 사이드 버튼 클릭하여 전원 Off라고 일컫는 상태)
  • 10분 가량 가만히 두다가, sleep 모드를 해제하면 (=전원을 켜서) 다시 해당 화면이 뜬다.
  • (아무런 제스쳐를 하지 않고) 해당 화면에서 소리를 재생시키면 진짜 소리가 나지 않았다.

해당 유저님의 패턴도 파악 완.. 아이패드의 sleep 모드를 자주 실행하시는군요 🙂

해결 방법

  1. await을 추가하여 resume()의 실행이 끝나야지만 재생되어야 할 소리가 재생되게끔 했다.
  2. console.log(audioContext.state) 를 찍어보니, suspended 가 아니라 interrupted 이었다.
    기존 코드는 suspended 상태만 대응하던 코드였는데, interrupted 의 상태도 추가하여 개선했다.
  • suspended : 오디오 처리가 일시정지 됨.
    → resume() 이 성공하면 state가 running으로 변함
    - 새로 만든 audioContext의 default 상태
    - 브라우저나 OS가 오디오를 잠시 멈춰야 할 때
  • interrupted : 오디오 출력이 시스템 이벤트 때문에 강제로 중단됨.
    → safari가 강제로 끊어버린 컨텍스트라서 resume()으로는 살아나지 않음.
    • 아이폰에서 전화가 오거나 siri가 켜짐
    • 알림 소리, 다른 앱에서 오디오 점유
    • 에어팟 연결/해제 등
  const resume = async () => {
    if (audioContext?.state === 'suspended') {
      // suspended 상태를 running 상태로 변경하려면 resume() 호출
      await audioContext.resume();
    }
    if (audioContext?.state === 'interrupted') {
      // 1. 기존에 연결되었던 audioContext를 종료시키고
      await audioContext?.close();
      // 2. 새로운 audioContext를 생성하여
      const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
      // 3. audioContext를 관리하는 state에 새 context를 저장
      setAudioContext(audioCtx);
    }
  };


const playSound = async () => {
  if (!audioContext) return;
  
    // audioContext.state에 따라 resume 하거나 새 audioContext를 생성
    await resume();

    // resume이 성공한 상태에서만 실행됨
    const response = await axiosInstance.get(url, {
      responseType: "arraybuffer",
      adapter: axiosInstance.defaults.adapter,
    });

    const decodedData = await audioContext.decodeAudioData(response.data);

    const audioSource = audioContext.createBufferSource();
    audioSource.buffer = decodedData;
    audioSource.connect(audioContext.destination);
    audioSource.start();
  } catch (error) {
    // 에러 처리
  }
};

정리

예상하지 못했던 safari의 interrupted 상태에 따른 처리 부재가 해당 이슈의 원인이었다.
코드 개선 이후, 당장의 이슈 재현은 되지 않지만
동일한 voc가 들어오는지 지켜봐야겠다. 😎

출처: https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/state#interrupted

profile
FE Developer

0개의 댓글