just_audio_engine 1.0.1
just_audio_engine: ^1.0.1 copied to clipboard
High-performance audio processing pipeline for just_game_engine. Supports all platforms with native backends (AVAudioEngine, XAudio2, OpenAL) and Web Audio API.
// ignore_for_file: avoid_print
/// just_audio_engine — runnable example
///
/// This file demonstrates every major feature of the package.
/// Run it with:
/// flutter run -t example/example.dart
library;
import 'package:flutter/material.dart';
import 'package:just_audio_engine/just_audio_engine.dart';
void main() => runApp(const AudioExampleApp());
// ─────────────────────────────────────────────────────────────────────────────
// App shell
// ─────────────────────────────────────────────────────────────────────────────
class AudioExampleApp extends StatelessWidget {
const AudioExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'just_audio_engine example',
theme: ThemeData.dark(),
home: const AudioExamplePage(),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Main page
// ─────────────────────────────────────────────────────────────────────────────
class AudioExamplePage extends StatefulWidget {
const AudioExamplePage({super.key});
@override
State<AudioExamplePage> createState() => _AudioExamplePageState();
}
class _AudioExamplePageState extends State<AudioExamplePage> {
// ── Engine instances ──────────────────────────────────────────────────────
final AudioEngine _engine = AudioEngine();
late final MusicManager _music;
late final SoundEffectManager _sfx;
// Streaming large audio files without loading the whole asset into memory.
late final AudioStream _stream;
// Track the last playing SFX so we can stop / update it.
String? _lastSfxId;
bool _engineReady = false;
// ── Lifecycle ─────────────────────────────────────────────────────────────
@override
void initState() {
super.initState();
_music = MusicManager(_engine);
_sfx = SoundEffectManager(_engine);
_init();
}
Future<void> _init() async {
// Initialize once; safe to call multiple times.
await _engine.initialize();
// AudioStream needs the backend reference.
_stream = AudioStream(
path: 'assets/audio/bgm_loop.mp3',
channel: AudioChannel.music,
);
await _stream.open(_engine.backend, voicePool: _engine.voicePool);
setState(() => _engineReady = true);
print('AudioEngine ready: ${_engine.backend.runtimeType}');
}
@override
void dispose() {
_stream.dispose();
_engine.dispose();
super.dispose();
}
// ─────────────────────────────────────────────────────────────────────────
// Feature demos
// ─────────────────────────────────────────────────────────────────────────
// 1. Background music via MusicManager ─────────────────────────────────────
Future<void> _playBgm() async {
await _music.play(
'assets/audio/bgm.mp3',
volume: 0.8,
loop: true,
fadeIn: true,
fadeDuration: const Duration(seconds: 2),
);
}
Future<void> _stopBgm() async {
await _music.stop(fadeOut: true, fadeDuration: const Duration(seconds: 1));
}
// 2. One-shot SFX via SoundEffectManager ───────────────────────────────────
Future<void> _playSfx() async {
_lastSfxId = await _sfx.play(
'assets/audio/hit.wav',
volume: 0.9,
pan: 0.0, // -1 (left) … 1 (right)
pitch: 1.0, // 1.0 = normal, 2.0 = octave up
);
print('SFX id: $_lastSfxId');
}
void _stopLastSfx() {
if (_lastSfxId != null) {
_sfx.stop(_lastSfxId!);
_lastSfxId = null;
}
}
// 3. DSP effects applied per voice ─────────────────────────────────────────
Future<void> _playSfxWithEffects() async {
_lastSfxId = await _engine.playSfx(
'assets/audio/hit.wav',
volume: 0.8,
effects: [
AudioEffect.reverb(roomSize: 0.7, damping: 0.5, wetLevel: 0.4),
AudioEffect.lowpass(frequency: 1200, q: 1.0),
AudioEffect.delay(delayMs: 200, feedback: 0.3, mix: 0.25),
],
);
}
// 4. Spatial 3D audio ──────────────────────────────────────────────────────
Future<void> _playSpatialSfx() async {
// Position a sound source 10 units to the right of the origin.
_lastSfxId = await _engine.playSfx(
'assets/audio/ambient.wav',
loop: true,
position3d: const Audio3DPosition(10, 0, 0),
);
// Queued — flushed on the next update() tick.
_engine.setListener3D(
const Audio3DListener(
position: Audio3DPosition(0, 0, 0),
forward: Audio3DPosition(0, 0, -1),
up: Audio3DPosition(0, 1, 0),
),
);
}
// 5. Move a 3D source while it plays ──────────────────────────────────────
// Now sync — queued and flushed in the next update() tick.
void _moveSource(double x) {
if (_lastSfxId != null) {
_engine.updateSfxPosition(_lastSfxId!, Audio3DPosition(x, 0, 0));
}
}
// 6. AudioStream – chunk-streamed playback ─────────────────────────────────
Future<void> _playStream() async {
await _stream.play(volume: 0.7, loop: true);
}
Future<void> _fadeStream() async {
await _stream.fade(0.0, const Duration(seconds: 3));
await _stream.stop();
}
// 7. Channel volumes ───────────────────────────────────────────────────────
void _setChannelVolumes() {
_engine.setMasterVolume(1.0);
_engine.setChannelVolume(AudioChannel.music, 0.7);
_engine.setChannelVolume(AudioChannel.sfx, 1.0);
_engine.setChannelVolume(AudioChannel.voice, 0.9);
_engine.setChannelVolume(AudioChannel.ambient, 0.5);
}
// 8. Mute / Unmute ─────────────────────────────────────────────────────────
void _toggleMute() {
_engine.toggleMute();
setState(() {});
print('Muted: ${_engine.isMuted}');
}
// ─────────────────────────────────────────────────────────────────────────
// UI
// ─────────────────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
if (!_engineReady) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
return Scaffold(
appBar: AppBar(
title: const Text('just_audio_engine example'),
actions: [
IconButton(
icon: Icon(_engine.isMuted ? Icons.volume_off : Icons.volume_up),
onPressed: _toggleMute,
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_Section(
title: '1 — Background Music',
children: [
_Btn('Play BGM (fade-in)', onPressed: _playBgm),
_Btn('Stop BGM (fade-out)', onPressed: _stopBgm),
_Btn('Pause', onPressed: _music.pause),
_Btn('Resume', onPressed: _music.resume),
],
),
_Section(
title: '2 — Sound Effects',
children: [
_Btn('Play SFX', onPressed: _playSfx),
_Btn('Stop last SFX', onPressed: _stopLastSfx),
_Btn('Stop all SFX', onPressed: _engine.stopAllSfx),
],
),
_Section(
title: '3 — DSP Effects (reverb + lowpass + delay)',
children: [
_Btn('Play SFX with effects', onPressed: _playSfxWithEffects),
],
),
_Section(
title: '4 — Spatial 3D Audio',
children: [
_Btn('Play spatial SFX (x=10)', onPressed: _playSpatialSfx),
_Btn(
'Move source → left (x=-10)',
onPressed: () => _moveSource(-10),
),
_Btn(
'Move source → right (x= 10)',
onPressed: () => _moveSource(10),
),
],
),
_Section(
title: '5 — Streaming (AudioStream)',
children: [
_Btn('Start stream', onPressed: _playStream),
_Btn('Fade out & stop', onPressed: _fadeStream),
],
),
_Section(
title: '6 — Channel Volumes',
children: [
_Btn(
'Apply demo volumes',
onPressed: () {
_setChannelVolumes();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Channel volumes applied')),
);
},
),
],
),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Small helpers
// ─────────────────────────────────────────────────────────────────────────────
class _Section extends StatelessWidget {
const _Section({required this.title, required this.children});
final String title;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
Text(
title,
style: Theme.of(
context,
).textTheme.titleSmall?.copyWith(color: Colors.tealAccent),
),
const SizedBox(height: 8),
Wrap(spacing: 8, runSpacing: 8, children: children),
const Divider(height: 24),
],
);
}
}
class _Btn extends StatelessWidget {
const _Btn(this.label, {required this.onPressed});
final String label;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return ElevatedButton(onPressed: onPressed, child: Text(label));
}
}