simple_audio_kit 1.1.0
simple_audio_kit: ^1.1.0 copied to clipboard
A professional, zero-config audio playback library for Flutter. Supports playlists, background playback, lock screen controls, and robust error handling out-of-the-box.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:simple_audio_kit/simple_audio_kit.dart';
// Sample demonstration tracks
const _playlist = [
AudioItem(
url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3',
title: 'SoundHelix Song 1',
artist: 'SoundHelix',
image:
'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?auto=format&fit=crop&w=500&q=60',
),
AudioItem(
url: 'https://cdn.pixabay.com/audio/2022/03/15/audio_7ce11e3b5e.mp3',
title: 'Cinematic Atmosphere',
artist: 'Pixabay',
image:
'https://images.unsplash.com/photo-1536440136628-849c177e76a1?auto=format&fit=crop&w=500&q=60',
),
AudioItem(
url: 'https://this.is.a.broken.domain.example.com/missing.mp3',
title: 'Broken Error Test Track',
artist: 'Error Checking System',
image:
'https://images.unsplash.com/photo-1520697830682-8f15eee029ae?auto=format&fit=crop&w=500&q=60',
),
AudioItem(
url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3',
title: 'SoundHelix Song 3',
artist: 'SoundHelix',
image:
'https://images.unsplash.com/photo-1514525253161-7a46d19cd819?auto=format&fit=crop&w=500&q=60',
),
];
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Initialize audio wrapper
await Audio.init();
runApp(const SimpleAudioExampleApp());
}
class SimpleAudioExampleApp extends StatelessWidget {
const SimpleAudioExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Simple Audio Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
useMaterial3: true,
),
home: const ExampleHome(),
debugShowCheckedModeBanner: false,
);
}
}
class ExampleHome extends StatefulWidget {
const ExampleHome({super.key});
@override
State<ExampleHome> createState() => _ExampleHomeState();
}
class _ExampleHomeState extends State<ExampleHome> {
StreamSubscription? _errorSub;
@override
void initState() {
super.initState();
_errorSub = Audio.errorStream.listen((e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.message),
backgroundColor: Colors.redAccent,
behavior: SnackBarBehavior.floating,
),
);
}
});
// Setting up the audio session natively
Audio.playList(_playlist).then((_) {
// Begin paused for demonstration purposes
Audio.pause();
});
}
@override
void dispose() {
_errorSub?.cancel();
super.dispose();
}
String _formatDuration(Duration d) {
final m = d.inMinutes.remainder(60).toString().padLeft(2, '0');
final s = d.inSeconds.remainder(60).toString().padLeft(2, '0');
return '$m:$s';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Simple Audio Demo')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1. Current Player UI Card
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Icon(
Icons.music_note,
size: 48,
color: Colors.deepPurpleAccent,
),
const SizedBox(height: 16),
StreamBuilder<AudioPlaybackState>(
stream: Audio.playbackStateStream,
initialData: Audio.playbackStateNow,
builder: (context, snapshot) {
final state =
snapshot.data ?? AudioPlaybackState.stopped;
final isPlaying = state == AudioPlaybackState.playing;
return Text(
isPlaying ? 'Now Playing' : 'Paused / Stopped',
style: Theme.of(context).textTheme.titleMedium
?.copyWith(
fontWeight: FontWeight.bold,
color: isPlaying ? Colors.greenAccent : null,
),
);
},
),
const SizedBox(height: 16),
// Duration Slider
StreamBuilder<Duration?>(
stream: Audio.durationStream,
builder: (context, durationSnap) {
return StreamBuilder<Duration>(
stream: Audio.positionStream,
builder: (context, positionSnap) {
final duration =
durationSnap.data ?? Duration.zero;
final position =
positionSnap.data ?? Duration.zero;
double progress = 0.0;
if (duration.inMilliseconds > 0) {
progress =
position.inMilliseconds /
duration.inMilliseconds;
}
return Column(
children: [
SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 4,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 6,
),
),
child: Slider(
value: progress.clamp(0.0, 1.0),
activeColor: Colors.deepPurpleAccent,
inactiveColor: Colors.white24,
onChanged: (value) {
if (duration.inMilliseconds > 0) {
Audio.seek(
Duration(
milliseconds:
(value *
duration
.inMilliseconds)
.toInt(),
),
);
}
},
),
),
const SizedBox(height: 2),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
_formatDuration(position),
style: Theme.of(
context,
).textTheme.labelSmall,
),
Text(
_formatDuration(duration),
style: Theme.of(
context,
).textTheme.labelSmall,
),
],
),
],
);
},
);
},
),
const SizedBox(height: 24),
// Playback Controls
StreamBuilder<AudioPlaybackState>(
stream: Audio.playbackStateStream,
initialData: Audio.playbackStateNow,
builder: (context, snapshot) {
final isPlaying =
snapshot.data == AudioPlaybackState.playing;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.skip_previous, size: 36),
onPressed: Audio.previous,
),
FloatingActionButton(
elevation: 0,
onPressed: isPlaying
? Audio.pause
: Audio.resume,
child: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
size: 32,
),
),
IconButton(
icon: const Icon(Icons.skip_next, size: 36),
onPressed: Audio.next,
),
],
);
},
),
],
),
),
),
const SizedBox(height: 16),
// 2. Extra Controls (Repeat / Shuffle)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
StreamBuilder<AudioRepeatMode>(
stream: Audio.repeatModeStream,
initialData: AudioRepeatMode.off,
builder: (context, snapshot) {
final mode = snapshot.data ?? AudioRepeatMode.off;
IconData icon;
Color color = Colors.grey;
if (mode == AudioRepeatMode.off) {
icon = Icons.repeat;
} else if (mode == AudioRepeatMode.one) {
icon = Icons.repeat_one;
color = Colors.deepPurpleAccent;
} else {
icon = Icons.repeat;
color = Colors.deepPurpleAccent;
}
return OutlinedButton.icon(
icon: Icon(icon, color: color),
label: const Text('Repeat'),
onPressed: () {
final nextMode = mode == AudioRepeatMode.off
? AudioRepeatMode.all
: mode == AudioRepeatMode.all
? AudioRepeatMode.one
: AudioRepeatMode.off;
Audio.setRepeatMode(nextMode);
},
);
},
),
StreamBuilder<bool>(
stream: Audio.shuffleStream,
initialData: false,
builder: (context, snapshot) {
final isShuffled = snapshot.data ?? false;
return OutlinedButton.icon(
icon: Icon(
Icons.shuffle,
color: isShuffled
? Colors.deepPurpleAccent
: Colors.grey,
),
label: const Text('Shuffle'),
onPressed: () => Audio.setShuffle(!isShuffled),
);
},
),
StreamBuilder<double>(
stream: Audio.speedStream,
initialData: 1.0,
builder: (context, snapshot) {
final speed = snapshot.data ?? 1.0;
return OutlinedButton.icon(
icon: Icon(
Icons.speed,
color: speed == 1.0
? Colors.grey
: Colors.deepPurpleAccent,
),
label: Text('${speed}x'),
onPressed: () {
final nextSpeed = speed == 1.0
? 1.5
: speed == 1.5
? 2.0
: speed == 2.0
? 0.5
: 1.0;
Audio.setSpeed(nextSpeed);
},
);
},
),
],
),
const SizedBox(height: 24),
Text(
'Playlist Queue',
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
// 3. Playlist UI
Expanded(
child: ListView.builder(
itemCount: _playlist.length,
itemBuilder: (context, i) {
final item = _playlist[i];
return ListTile(
contentPadding: EdgeInsets.zero,
leading: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: NetworkImage(item.image ?? ''),
fit: BoxFit.cover,
),
),
),
title: Text(item.title ?? 'Unknown'),
subtitle: Text(item.artist ?? 'Unknown Artist'),
trailing: const Icon(Icons.play_circle_outline),
onTap: () {
// Play specifically this track by loading a subset
// Or simple playback
Audio.play(
item.url,
title: item.title,
artist: item.artist,
image: item.image,
);
},
);
},
),
),
],
),
),
),
);
}
}