From 05a8c038929df911fe517bf61f55d12feb4e7a9b Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Tue, 30 Dec 2025 16:18:17 +0900 Subject: [PATCH] =?UTF-8?q?fix(audio):=20=EC=9B=B9=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=20=EC=A0=95=EC=B1=85=20=EB=8C=80=EC=9D=91=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EB=B0=94=EB=A1=9C=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 웹에서 사용자 상호작용 전 BGM 대기 상태로 저장 - SFX 재생 또는 클릭 시 대기 중 BGM 자동 재생 - 모바일/데스크톱에서는 자동재생 제한 없이 바로 재생 - notifyUserInteraction() 메서드 추가 --- lib/src/core/audio/audio_service.dart | 54 +++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/lib/src/core/audio/audio_service.dart b/lib/src/core/audio/audio_service.dart index 2251002..7617b23 100644 --- a/lib/src/core/audio/audio_service.dart +++ b/lib/src/core/audio/audio_service.dart @@ -33,6 +33,12 @@ class AudioService { // 초기화 실패 여부 (WASM 등에서 오디오 지원 안됨) bool _initFailed = false; + // 웹에서 사용자 상호작용 대기 중인 BGM (자동재생 정책 대응) + String? _pendingBgm; + + // 사용자 상호작용 발생 여부 (웹 자동재생 정책 우회용) + bool _userInteracted = false; + /// 서비스 초기화 Future init() async { if (_initialized || _initFailed) return; @@ -55,7 +61,11 @@ class AudioService { } _initialized = true; - if (kIsWeb) { + + // 모바일/데스크톱에서는 자동재생 제한 없음 + if (!kIsWeb) { + _userInteracted = true; + } else { debugPrint('[AudioService] Initialized on Web platform'); } } catch (e) { @@ -68,6 +78,9 @@ class AudioService { /// /// [name]은 assets/audio/bgm/ 폴더 내 파일명 (확장자 제외) /// 예: playBgm('battle') → assets/audio/bgm/battle.mp3 + /// + /// 웹에서 사용자 상호작용 없이 호출되면 대기 상태로 저장되고, + /// 다음 SFX 재생 시 함께 시작됩니다. Future playBgm(String name) async { if (_initFailed) return; // 초기화 실패 시 무시 if (!_initialized) await init(); @@ -75,13 +88,20 @@ class AudioService { if (_currentBgm == name) return; // 이미 재생 중 try { - _currentBgm = name; await _bgmPlayer!.setAsset('assets/audio/bgm/$name.mp3'); await _bgmPlayer!.play(); + _currentBgm = name; + _pendingBgm = null; + _userInteracted = true; // 재생 성공 → 상호작용 확인됨 debugPrint('[AudioService] Playing BGM: $name'); } catch (e) { - // 파일이 없으면 무시 (개발 중 에셋 미추가 상태) - debugPrint('[AudioService] Failed to play BGM $name: $e'); + // 웹 자동재생 정책으로 실패 시 대기 상태로 저장 + if (kIsWeb && e.toString().contains('NotAllowedError')) { + _pendingBgm = name; + debugPrint('[AudioService] BGM $name pending (waiting for user interaction)'); + } else { + debugPrint('[AudioService] Failed to play BGM $name: $e'); + } _currentBgm = null; } } @@ -112,6 +132,8 @@ class AudioService { /// /// [name]은 assets/audio/sfx/ 폴더 내 파일명 (확장자 제외) /// 예: playSfx('attack') → assets/audio/sfx/attack.mp3 + /// + /// 웹에서 대기 중인 BGM이 있으면 함께 재생 시작합니다. Future playSfx(String name) async { if (_initFailed) return; // 초기화 실패 시 무시 if (!_initialized) await init(); @@ -119,6 +141,15 @@ class AudioService { if (_sfxVolume == 0) return; // 볼륨이 0이면 재생 안함 if (_sfxPlayers.isEmpty) return; + // 웹에서 대기 중인 BGM 재생 시도 (사용자 상호작용 발생) + if (!_userInteracted && _pendingBgm != null) { + _userInteracted = true; + final pending = _pendingBgm; + _pendingBgm = null; + // BGM 재생 (비동기로 진행) + playBgm(pending!); + } + // 사용 가능한 플레이어 찾기 AudioPlayer? availablePlayer; for (final player in _sfxPlayers) { @@ -170,6 +201,21 @@ class AudioService { /// 현재 재생 중인 BGM String? get currentBgm => _currentBgm; + /// 사용자 상호작용 발생 알림 (웹 자동재생 정책 우회) + /// + /// 버튼 클릭 등 사용자 상호작용 시 호출하면 + /// 대기 중인 BGM이 재생됩니다. + Future notifyUserInteraction() async { + if (_userInteracted) return; + _userInteracted = true; + + if (_pendingBgm != null) { + final pending = _pendingBgm; + _pendingBgm = null; + await playBgm(pending!); + } + } + /// 서비스 정리 Future dispose() async { await _bgmPlayer?.dispose();