사운드 재생 관련 문의 (백그라운드/포그라운드)

질문 / 문제 해결

  • 개발환경: Mac, Unity (WebView)
  • 사용중인 SDK:
    • @apps-in-toss/web-framework”: “^1.8.0”
    • “react”: “19.2.3”
    • “react-dom”: “19.2.3”
  • 테스트 환경:
    • iPhone 17 (iOS 26.2)

    • 앱인토스(ios simulator)

    • 토스 v5.244.0 (intoss-private: …)

  • 사운드 모듈: FMOD
  • 테스트 방법:
    • 앱인토스

      1. yarn dev 를 이용하여 granite dev 실행
      2. ios simulator 에서 앱 실행 (사운드 재생됨)
      3. 약 10초 뒤 백그라운드로 전환
      4. 약 30초 뒤 포그라운드로 전환 (사운드 재생됨)
      • 1 ~ 4 를 10회이상 반복해도 문제 없이 사운드 재생됨
    • 토스 빌드 후 테스트

      1. 앱 실행 (사운드 정상작동함)
      2. 약 5초 후 백그라운드 전환
      3. 약 5~10초 뒤 포그라운드로 전환 (사운드 재생안됨)
      4. 약 4~10초 뒤 백그라운드 전환
      5. 약 5~20초뒤 포그라운드로 전환 (사운드 재생됨)
      • 5번 이후로는 몇 번을 백그라운드로 이동하든 상관없이 정상적으로 재생됨

우선 개발자 커뮤니티 및 SDK 문서를 다 확인해보았지만 원인을 도저히 찾을 수 없어 글 남깁니다.

백그라운드/포그라운드 사운드가 멈추는 문제가 발생하는데, 앱인토스의 경우 해당 문제를 발견하지 못했습니다.
토스 내 테스트 결과는 2회이상 백그라운드로 이동하거나 토스내 내비게이션 으로 다른 페이지를 이동시켜야 사운드가 정상작동되는것을 확인했습니다.

추가적으로 광고를 본 경우에 백그라운드 > 포그라운드 를 시도해도 정상적으로 사운드가 재생되는것을 확인했습니다.

이것을 제 역량으로 해결이 가능한 부분인지 알고 싶습니다.

granite.config.ts

  webViewProps: {
    type: 'game',
    mediaPlaybackRequiresUserAction: false,
    allowsInlineMediaPlayback: true,
	overScrollMode: 'never',
  },

로그 정보

사운드 시간 타입 audioAction / event 결과/상태 요약 restored / saved
재생안됨 2026-01-21 21:25:09 webgl_suspend_audio event=suspend_audio PLAYING → fmodPausedAfter=true savedMusicTime=4.608s
재생안됨 2026-01-21 21:25:09 webgl_foreground_restore audioAction=force_resumed unpause+seek 성공, fmodPaused=false restoredPosition=4608ms
재생안됨 2026-01-21 21:25:09 webgl_fmod_resume source=RestoreAudioAsync fmodResumeSuccess=true, PLAYING -
재생됨 2026-01-21 21:24:59 webgl_suspend_audio event=suspend_audio PLAYING → fmodPausedAfter=true savedMusicTime=4.501s
재생됨 2026-01-21 21:24:59 webgl_foreground_restore audioAction=force_resumed unpause+seek 성공, fmodPaused=false restoredPosition=4501ms
재생됨 2026-01-21 21:24:59 webgl_fmod_resume source=RestoreAudioAsync fmodResumeSuccess=true, PLAYING -

확인해보겠습니다 :man_bowing:

1개의 좋아요

해결했습니다.

UnityLoader.tsx 쪽에
별도의 div(overlay) 를 최상위 레이어로 두고 투명으로 만든다음 해당 div(overlay) 클릭시 UnityLoader 쪽에 click 을 전달하며 div(overlay) 사라지게 하니 사운드가 백그라운드 > 포그라운드 전환시에도 정상적으로 표기되는것을 확인했습니다.

webview 정책으로 터치를 해야 사운드가 재생되는것을 알고 있었지만 게임쪽에서 터치 이벤트를 가로채는것인지(확인 못함) 브라우저 터치로 인식을 못하는거 같았습니다.
정확하지 않은 정보라 불안하기 때문에 해당 부분을 확인해주시면 감사하겠습니다. :smiling_face_with_tear:

UnityLoader.tsx 일부

import { useEffect, useRef, useState, type PointerEvent, type TouchEvent } from 'react';
      8  import unityBridge from '../services/unityBridge';
        ⋮
     66    const [error, setError] = useState<string | null>(null);
     67   const [showInteractionOverlay, setShowInteractionOverlay] = useState(false);
     68
        ⋮
     71    const entryMessageUnsubscribeRef = useRef<(() => void) | null>(null);
     72   const overlayConsumedRef = useRef(false);
     73
        ⋮
    179
    180   const dispatchSyntheticTapToCanvas = (clientX: number, clientY: number, pointerType: string) => {
    181     const canvas = canvasRef.current;
    182     if (!canvas) {
    183       return;
    184     }
    185 
    186     const baseOptions = {
    187       bubbles: true,
    188       cancelable: true,
    189       clientX,
    190       clientY,
    191       view: window,
    192     };
    193 
    194     if (typeof PointerEvent !== 'undefined') {
    195       const pointerInit = {
    196         ...baseOptions,
    197         pointerId: 1,
    198         pointerType: pointerType || 'touch',
    199         isPrimary: true,
    200       };
    201       canvas.dispatchEvent(new PointerEvent('pointerdown', pointerInit));
    202       canvas.dispatchEvent(new PointerEvent('pointerup', pointerInit));
    203     } else {
    204       canvas.dispatchEvent(new MouseEvent('mousedown', baseOptions));
    205       canvas.dispatchEvent(new MouseEvent('mouseup', baseOptions));
    206     }
    207 
    208     canvas.dispatchEvent(new MouseEvent('click', baseOptions));
    209   };
    210 
    211   const handleInteractionOverlay = (clientX: number, clientY: number, pointerType: string) => {
    212     if (overlayConsumedRef.current) {
    213       return;
    214     }
    215     overlayConsumedRef.current = true;
    216     setShowInteractionOverlay(false);
    217 
    218     void resumeAudioAfterForeground('unlock_overlay');
    219     unityBridge.sendMessage(UNITY_BROWSER_BRIDGE, 'OnUserGestureForAudio', '');
    220     dispatchSyntheticTapToCanvas(clientX, clientY, pointerType);
    221   };
    222 
    223   const onOverlayPointerDown = (event: PointerEvent<HTMLDivElement>) => {
    224     event.preventDefault();
    225     event.stopPropagation();
    226     handleInteractionOverlay(event.clientX, event.clientY, event.pointerType || 'touch');
    227   };
    228 
    229   const onOverlayTouchStart = (event: TouchEvent<HTMLDivElement>) => {
    230     event.preventDefault();
    231     event.stopPropagation();
    232     const touch = event.touches[0];
    233     if (!touch) {
    234       return;
    235     }
    236     handleInteractionOverlay(touch.clientX, touch.clientY, 'touch');
    237   };
    238 
        ⋮
    434                setIsLoading(false);
    435               setShowInteractionOverlay(true);
    436
        ⋮
    580
    581       {showInteractionOverlay && !isLoading && !error && (
    582         <div
    583           onPointerDown={onOverlayPointerDown}
    584           onTouchStart={onOverlayTouchStart}
    585           role="button"
    586           aria-label="Tap to start"
    587           style={{
    588             position: 'absolute',
    589             inset: 0,
    590             background: 'rgba(0, 0, 0, 0.65)',
    591             zIndex: 20,
    592             display: 'flex',
    593             alignItems: 'center',
    594             justifyContent: 'center',
    595             color: '#fff',
    596             fontSize: '18px',
    597             letterSpacing: '0.04em',
    598             touchAction: 'none',
    599             userSelect: 'none',
    600           }}
    601         >
    602           Tap to start
    603         </div>
    604       )}
    605 

1개의 좋아요