플러터에서 배경음악 연주를 할때 중요한 것은 지연시간입니다.
음악 연주를 하다가 화면이 바뀔때 다른 배경음악을 연주해야 한다면 아래와 같은 현상이 있게 됩니다.
- 재생이 끝나고 다음 재생을 시작할때까지의 지연시간이 생각보다 긴 경우가 있게 됨
이 지연시간을 줄인 패키지가 just_audio입니다. just_audio는 지연시간이 1초보다 작아서 활용에 유리합니다. 지원되는 플랫폼은 Android, iOS, macOS, Web으로 윈도우는 지원을 안하는 것 같애요. 그래도 안드로이드와 iOS가 지원되서 좋습니다.
사용법은 매우 쉬운 편인데요. 함수가 간결하고 직관적입니다. 미디어 플레이어처럼 UI를 세심하게 만들지 않는다면 아래와 같은 함수로도 재생이 됩니다.
- AudioPlayer()로 재생기 객체 생성
- AudioSource.asset()으로 재생할 파일 지정
- ConcatenatingAudioSource()로 여러 파일 연속 재생 소스 설정
- setAudioSource()로 재생 소스를 객체에 넘김
- setLoopMode()로 반복재생 설정
- play() 함수로 재생 시작
- pause() 함수로 재생 잠시 중단
- play() 함수로 이어서 재생 (재생 시작 함수와 같음)
이 기반에서 아래와 같은 소스코드를 작성해볼 수 있습니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
import 'package:just_audio/just_audio.dart'; class BGMAudioPlayer { // 오디오 플레이어 인스턴스 생성 static final _player = AudioPlayer(); // MP3 파일 목록 (assets/audio/ 폴더 기준) static final List<String> _mp3Files = [ 'bgm1.mp3', 'bgm2.mp3', 'bgm3.mp3', 'bgm4.mp3', 'bgm5.mp3', 'bgm6.mp3', 'bgm7.mp3', 'bgm8.mp3', 'bgm9.mp3', 'bgm10.mp3', 'bgm11.mp3', 'enka1.mp3', 'enka2.mp3', 'enka3.mp3', 'enka4.mp3', 'versus.mp3', ]; static late final List<String> _path = List<String>.filled(_mp3Files.length, ""); static List<int> index = List<int>.filled(_mp3Files.length, -1); static shuffleData() { for (int i = 0; i < _mp3Files.length; i++) { index[i] = i; } index.shuffle(); for (int i = 0; i < _mp3Files.length; i++) { _path[i] = _mp3Files[index[i]]; } } static Future<void> playBackgroundMusic() async { try { // 1. 재생 목록 셔플 shuffleData(); // 2. 셔플된 목록으로 연속 재생 소스(Playlist) 생성 final playlist = ConcatenatingAudioSource( children: _path .map((fileName) => AudioSource.asset('assets/audios/$fileName')) .toList(), ); // 3. 플레이어에 재생 목록 설정 await _player.setAudioSource(playlist); // 4. 전체 목록 반복 재생 설정 _player.setLoopMode(LoopMode.all); // 5. 재생 시작 _player.play(); } catch (e) { // 오류 처리 print("Error occurred while playing audios: $e"); } } static Future<void> pauseBackgroundMusic() async { try { await _player.pause(); } catch (e) { print("Error occurred while pausing audios: $e"); } } static Future<void> resumeBackgroundMusic() async { try { await _player.play(); } catch (e) { print("Error occurred while pausing audios: $e"); } } static Future<void> nextBackgroundMusic() async { try { await _player.seekToNext(); await _player.play(); } catch (e) { print("Error occurred while seek to next audios: $e"); } } static Future<void> prevBackgroundMusic() async { try { await _player.seekToPrevious(); await _player.play(); } catch (e) { print("Error occurred while seek to next audios: $e"); } } static Future<void> dispose() async { try { await _player.dispose(); } catch (e) { print("Error occurred while diposing AudioPlayer: $e"); } } } |
이렇게 해두고
|
1 2 3 4 5 6 7 8 |
@override void initState() { … BGMAudioPlayer.play(); … } |
처럼 기재해서 실행하면 배경음악이 연주되구요.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@override void dispose() { // 생명주기 감지기 해제 및 플레이어 dispose (메모리 누수 방지) WidgetsBinding.instance.removeObserver(this); super.dispose(); } // 앱 생명주기 변경 시 호출되는 메서드 @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.resumed: // 앱이 다시 활성화되면 음악 재생 BGMAudioPlayer.resumeBackgroundMusic(); break; case AppLifecycleState.inactive: case AppLifecycleState.paused: case AppLifecycleState.hidden: // 앱이 비활성화되거나 백그라운드로 가면 음악 일시정지 BGMAudioPlayer.pauseBackgroundMusic(); break; case AppLifecycleState.detached: // 앱이 종료되면 플레이어 리소스 해제 BGMAudioPlayer.dispose(); break; } } |
처럼 각 화면 담당 State<>() 안에 두면 대기상태가 되면 잠시 음악을 멈추고, 앱이 종료되면 dispose()가 되게 할 수 있습니다. WidgetsBindingObserver를 쓰는 방법인데 State<> 선언 시그니처에
|
1 2 3 4 5 |
class _SongTitleGameScreenState extends State<SongTitleGameScreen> with WidgetsBindingObserver { … } |
처럼 해두어야 쓸 수 있구요. initState()에
|
1 2 3 4 5 6 7 8 |
@override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); BGMAudioPlayer.resumeBackgroundMusic(); } |
처럼 addObserver()를 시작해두어야 합니다.
이 WidgetsBindingObserver를 안쓰면 앱을 대기상태로 두어도 음악이 재생되기에 방해가 되니 꼭 추가해두어야 합니다.
- 구글 Gemini로 기능 질문을 하니 금방 해결되었습니다.