fix(audio): BGM 동시 호출 보호 추가

- _isBgmBusy 락으로 동시 작업 방지
- 대기열(_queuedBgm)로 마지막 요청만 처리
- Loading interrupted 에러 시 플레이어 재생성 방지
This commit is contained in:
JiWoong Sul
2026-01-08 19:54:43 +09:00
parent d1eeb7ca37
commit 76090a46b6

View File

@@ -85,6 +85,12 @@ class AudioService {
// 오디오 일시정지 상태 (앱 백그라운드 시) // 오디오 일시정지 상태 (앱 백그라운드 시)
bool _isPaused = false; bool _isPaused = false;
// BGM 작업 진행 중 여부 (동시 호출 방지)
bool _isBgmBusy = false;
// 대기 중인 BGM (작업 중 새 요청이 들어온 경우)
String? _queuedBgm;
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
// 초기화 // 초기화
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
@@ -202,16 +208,35 @@ class AudioService {
// BGM 재생 // BGM 재생
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
/// BGM 재생 (단순화된 버전) /// BGM 재생 (동시 호출 보호)
/// ///
/// 여러 곳에서 동시에 호출되어도 마지막 요청만 처리합니다. /// 여러 곳에서 동시에 호출되어도 마지막 요청만 처리합니다.
/// 작업 중 새 요청이 들어오면 대기열에 저장하고 완료 후 재생합니다.
Future<void> playBgm(String name) async { Future<void> playBgm(String name) async {
if (_isPaused) return; if (_isPaused) return;
if (!_staticInitialized) await init(); if (!_staticInitialized) await init();
if (_currentBgm == name) return; if (_currentBgm == name) return;
if (_staticBgmPlayer == null) return; if (_staticBgmPlayer == null) return;
await _playBgmInternal(name); // 작업 중이면 대기열에 저장 (마지막 요청만 유지)
if (_isBgmBusy) {
_queuedBgm = name;
debugPrint('[AudioService] BGM $name queued');
return;
}
_isBgmBusy = true;
try {
await _playBgmInternal(name);
} finally {
_isBgmBusy = false;
// 대기 중인 BGM이 있으면 재생
if (_queuedBgm != null && _queuedBgm != _currentBgm) {
final queued = _queuedBgm;
_queuedBgm = null;
await playBgm(queued!);
}
}
} }
/// 내부 BGM 재생 (뮤텍스 내에서 호출) /// 내부 BGM 재생 (뮤텍스 내에서 호출)
@@ -236,15 +261,23 @@ class AudioService {
_userInteracted = true; _userInteracted = true;
debugPrint('[AudioService] Playing BGM: $name'); debugPrint('[AudioService] Playing BGM: $name');
} on PlayerInterruptedException catch (e) { } on PlayerInterruptedException catch (e) {
// 다른 BGM 요청으로 인한 중단 - 정상적인 상황
debugPrint('[AudioService] BGM $name interrupted: ${e.message}'); debugPrint('[AudioService] BGM $name interrupted: ${e.message}');
} catch (e) { } catch (e) {
final errorStr = e.toString(); final errorStr = e.toString();
// "Loading interrupted"는 새 BGM 요청으로 인한 정상 중단
if (errorStr.contains('Loading interrupted') ||
errorStr.contains('abort')) {
debugPrint('[AudioService] BGM $name loading interrupted (new request)');
return; // 플레이어 재생성 불필요
}
debugPrint('[AudioService] BGM error: $errorStr'); debugPrint('[AudioService] BGM error: $errorStr');
// macOS Operation Stopped 에러: 플레이어 재생성 후 재시도 // macOS Operation Stopped 에러: 플레이어 재생성 후 재시도
if (errorStr.contains('Operation Stopped') || if (errorStr.contains('Operation Stopped') ||
errorStr.contains('-11849') || errorStr.contains('-11849')) {
errorStr.contains('abort')) {
debugPrint('[AudioService] Recreating BGM player...'); debugPrint('[AudioService] Recreating BGM player...');
await _recreateBgmPlayer(); await _recreateBgmPlayer();