타국어로 썼던 글을 직접 번역하기가 귀찮아서 이번글은 그록한테 번역을 맡겼습니다. 번역 품질이 좋네요. 오타가 몇군데 있을 수도 있으니 주의하시길요.
just_audio (https://pub.dev/packages/just_audio)는 Android, iOS, macOS, 웹, Linux, Windows용 기능이 풍부한 오디오 플레이어입니다.
가장 뛰어난 기능은 재생 모드를 재설정하는 데 매우 낮은 지연 시간을 가진다는 점으로, 길고 복잡한 연결 해제, 해체, 재설정 과정 없이 다음 오디오를 준비하는 데 1초 미만만 필요합니다.
짧은 시간 간격으로 짧은 음향 효과를 재생하고자 하는 프로그래머에게 가장 적합한 선택입니다.
이를 사용하기 위해 따라야 할 몇 가지 사항이 있으며, 이 글에서는 그 방법을 소개합니다.
dependencies:
just_audio: ^0.9.36
assets:
assets/audios/
위는 pubspec.yaml에 의존성을 추가하는 방법입니다. just_audio를 의존성 목록에 추가하고 오디오 파일이 저장될 에셋 디렉토리를 설정한 뒤, pub get 명령어를 실행하면 사용할 준비가 됩니다.
import 'package:just_audio/just_audio.dart';
class AudioManager {
static final AudioPlayer _player = AudioPlayer();
static Future<void> preloadAudio(String assetPath) async {
await _player.setAsset(assetPath); // 에셋 미리 로드
}
static Future<void> playAudio() async {
await _player.play(); // 즉시 재생
}
}
위는 just_audio가 작동하는 최소 예제입니다. 먼저 AudioPlayer() 생성자를 사용하여 just_audio 인스턴스를 만들어야 합니다. 다음으로, 로컬 오디오 에셋에 대해 setAsset을 설정하여 실제 음향 효과 파일을 매개변수로 지정하고 미리 로드합니다. just_audio 인스턴스의 play() 메서드는 setAsset() 메서드에서 설정된 파일을 재생할 수 있습니다.
이와 함께 몇 가지 고려 사항을 추가하여 더 잘 작동하도록 코드를 확장할 수 있습니다.
import 'package:just_audio/just_audio.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart';
class AudioManager {
static final List<AudioPlayer> _players = [
AudioPlayer(),
AudioPlayer(),
AudioPlayer(),
AudioPlayer(),
];
static const Map<int, String> _audioPaths = {
0: 'audios/splash.mp3',
1: 'audios/welcome.wav',
2: 'audios/applaude.wav',
3: 'audios/dingding.wav',
};
static Future<void> audioInit() async {
try {
print('플레이어 0 초기화: ${_audioPaths[0]}');
await _players[0].setAsset(_audioPaths[0]);
print('플레이어 1 초기화: ${_audioPaths[1]}');
await _players[1].setAsset(_audioPaths[1]);
print('플레이어 2 초기화: ${_audioPaths[2]}');
await _players[2].setAsset(_audioPaths[2]);
print('플레이어 3 초기화: ${_audioPaths[3]}');
await _players[3].setAsset(_audioPaths[3]!);
print('오디오 초기화 완료');
} catch (e) {
print('오디오 초기화 실패: $e');
}
}
static Future<void> splashAudio() async {
print('스플래시 재생 시작: ${DateTime.now()}');
await _players[0].setVolume(1.0);
await _players[0].play().then((_) => print('스플래시 재생 성공'));
_players[0].playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
_players[0].stop();
print('스플래시 재생 완료: ${DateTime.now()}');
}
});
}
static Future<void> welcomeAudio() async {
print('환영 음향 재생 시작: ${DateTime.now()}');
await _players[1].setVolume(1.0);
await _players[1].play().then((_) => print('환영 음향 재생 성공'));
_players[1].playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
_players[1].stop();
print('환영 음향 재생 완료: ${DateTime.now()}');
}
});
}
static Future<void> applaudeAudio() async {
print('박수 재생 시작: ${DateTime.now()}');
await _players[2].setVolume(1.0);
await _players[2].play().then((_) => print('박수 재생 성공'));
_players[2].playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
_players[2].stop();
print('박수 재생 완료: ${DateTime.now()}');
}
});
}
static Future<void> dingdingAudio() async {
print('딩딩 재생 시작: ${DateTime.now()}');
await _players[3].setVolume(1.0);
await _players[3].play().then((_) => print('딩딩 재생 성공'));
_players[3].playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
_players[3].stop();
print('딩딩 재생 완료: ${DateTime.now()}');
}
});
}
}
위는 잘 작동하는 응용 가능한 최소 예제입니다.
static final List<AudioPlayer> _players = [
AudioPlayer(),
AudioPlayer(),
AudioPlayer(),
AudioPlayer(),
];
먼저 AudioPlayer 인스턴스를 보유할 List를 선언합니다.
static const List <String> _audioPaths = [
'assets/audios/splash.mp3',
'assets/audios/welcome.wav',
'assets/audios/applaude.wav',
'assets/audios/dingding.wav',
];
파일 경로를 지정하기 위해 String List를 할당합니다. 이는 에셋 루트 디렉토리를 포함한 전체 에셋 경로로 설정해야 합니다.
static Future<void> audioInit() async {
try {
print('플레이어 0 초기화: ${_audioPaths[0]}');
await _players[0].setAsset(_audioPaths[0]);
print('플레이어 1 초기화: ${_audioPaths[1]}');
await _players[1].setAsset(_audioPaths[1]);
print('플레이어 2 초기화: ${_audioPaths[2]}');
await _players[2].setAsset(_audioPaths[2]);
print('플레이어 3 초기화: ${_audioPaths[3]}');
await _players[3].setAsset(_audioPaths[3]);
print('오디오 초기화 완료');
} catch (e) {
print('오디오 초기화 실패: $e');
}
}
List와 메서드를 사용하여 오디오 파일을 미리 로드합니다. _players 리스트의 각 요소는 setAsset() 메서드를 호출할 수 있으며, 이는 오디오 경로 리스트의 각 요소를 매개변수로 받아들입니다. 이 예제에서는 _players[i].setAsset(_audioPaths[i])로 충분합니다.
static Future<void> splashAudio() async {
print('스플래시 재생 시작: ${DateTime.now()}');
await _players[0].setVolume(1.0);
await _players[0].play().then((_) => print('스플래시 재생 성공'));
_players[0].playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
_players[0].stop();
print('스플래시 재생 완료: ${DateTime.now()}');
}
});
}
다음은 지금까지 작업한 파일을 재생하는 것으로, setVolume(), play(), 그리고 playerStateStream을 사용하여 각 음향 효과가 완전히 완료되었는지 확인합니다. 가장 주목할 점은 재생을 중지할 때 playerStateStream의 state.processingState가 ProcessingState.completed와 같을 때만 stop()을 실행해야 한다는 점입니다. 이를 통해 다음 음향 효과 재생이 이전에 재생된 오디오의 잔여 길이를 재생하지 않도록 방지할 수 있습니다. 이 조건문 안에 넣지 않으면, 예를 들어 splashAudio() 다음에 welcomeAudio()를 재생할 때 잔여 효과가 발생하여 welcomeAudio()가 splashAudio()의 잔여 길이와 함께 출력되므로 바람직하지 않습니다.
이 예제 코드를 사용하지 않으면 제대로 작동하지 않습니다. 예를 들어, 각 재생 메서드의 맨 처음 줄에 _players[0].stop()을 넣으면 제대로 재생되지 않습니다.
static Future<void> splashAudio() async {
await _players[0].stop(); // 이건 작동하지 않음
await _players[0].setVolume(1.0);
await _players[0].play();
}
이 대신 앞서 예제에서 사용한 state.processingState를 활용하는 방법이 권장됩니다.
이 설정으로 AudioManager.audioInit()와 AudioManager.splashAudio() 등을 호출하면, 이들은 정적 메서드이므로 인스턴스화 과정 없이 프로젝트 소스 코드 어디에서나 직접 호출할 수 있습니다.
이것은 짧은 음향 효과를 재생하고자 할 때의 최소 예제일 수 있으며, 사용자의 목적에 따라 확장하시기 바랍니다.