Cache Audio Player Plus
A lightweight, drop-in Flutter audio player that streams and caches network audio. The first time a URL is played it is downloaded to local storage; every time after that it plays instantly from disk — no re-downloads, no wasted bandwidth, and full offline support.
Built on top of audioplayers with
dio for downloads and
hive_flutter for the cache index.
Features
- Stream and play network audio with one line of code.
- Automatic on-disk caching — each URL is downloaded only once.
- Pre-cache audio files ahead of time for instant playback.
- Full cache management: check, inspect size, clear single entry or the whole cache.
- Download progress callback so you can show a spinner/progress bar.
- Play from network, local files, or Flutter assets.
- Fine-grained playback controls: play, pause, resume, stop, seek, volume, balance, playback rate, release mode, audio context.
- Reactive streams for position, duration, state, seek and completion.
- Works on Android, iOS, macOS, Windows and Linux.
Installation
Add the package to your pubspec.yaml:
dependencies:
cache_audio_player_plus: ^1.2.0
Then run:
flutter pub get
Getting started
Initialize the plugin once in your main():
import 'package:flutter/material.dart';
import 'package:cache_audio_player_plus/cache_audio_player_plus.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await CacheAudioPlayerPlus.init(); // optional but recommended
runApp(const MyApp());
}
init()is optional. If you skip it, the library will lazily set itself up the first time a cache-aware method is called. Calling it explicitly just removes that first-call latency and makes startup predictable.
Create a player instance and play something:
final player = CacheAudioPlayerPlus();
// Downloads and caches the file on the first call, then plays it.
// Every subsequent call plays instantly from local storage.
await player.playerNetworkAudio(
url: 'https://example.com/audio/track.mp3',
);
That's it. No manual cache plumbing required.
Usage
Play a network audio file (with caching)
await player.playerNetworkAudio(
url: 'https://example.com/track.mp3',
cache: true, // default
volume: 1.0,
onDownloadProgress: (received, total) {
if (total > 0) {
final percent = (received / total * 100).toStringAsFixed(0);
debugPrint('Downloading: $percent%');
}
},
);
Set cache: false if you want to stream straight from the network without
saving a copy.
Play a local file
await player.playLocalAudio(filePath: '/path/to/audio.mp3');
Play a Flutter asset
await player.playAssetAudio(assetPath: 'sounds/ding.mp3');
Remember to register the asset in your pubspec.yaml:
flutter:
assets:
- sounds/ding.mp3
Pre-cache audio (download now, play later)
// Warm up the cache for the next track so it starts instantly.
await player.preCacheAudio(
url: 'https://example.com/next-track.mp3',
onDownloadProgress: (received, total) {
// update a progress indicator
},
);
Inspect the cache
final cached = await player.isCached('https://example.com/track.mp3');
final path = await player.getCachedFilePath('https://example.com/track.mp3');
final keys = await player.getCachedKeys();
final bytes = await player.getCacheSize();
debugPrint('Cache size: ${(bytes / 1024 / 1024).toStringAsFixed(2)} MB');
Clear the cache
// Remove a single file
await player.clearCacheForUrl('https://example.com/track.mp3');
// Or wipe everything
await player.clearCache();
Playback controls
await player.pause();
await player.resume();
await player.stop();
await player.release();
await player.seek(const Duration(seconds: 30));
await player.setVolume(0.5); // 0.0 .. 1.0
await player.setBalance(0.0); // -1.0 .. 1.0
await player.setPlaybackRate(1.5); // 0.5 .. 2.0 (iOS/macOS)
await player.setReleaseMode(ReleaseMode.loop);
Listen to streams
player.onPlayerStateChanged.listen((PlayerState state) {
// stopped / playing / paused / completed / disposed
});
player.onPositionChanged.listen((Duration position) {
// update your progress bar
});
player.onDurationChanged.listen((Duration duration) {
// total length of the currently loaded file
});
player.onPlayerComplete.listen((_) {
// the track finished
});
player.onSeekComplete.listen((_) {
// a seek operation finished
});
Query the current state
final PlayerState state = player.state;
final Duration? position = await player.getCurrentPosition();
final Duration? duration = await player.getDuration();
Dispose when you are done
await player.dispose();
Full example
import 'package:flutter/material.dart';
import 'package:cache_audio_player_plus/cache_audio_player_plus.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await CacheAudioPlayerPlus.init();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) =>
const MaterialApp(home: PlayerScreen());
}
class PlayerScreen extends StatefulWidget {
const PlayerScreen({super.key});
@override
State<PlayerScreen> createState() => _PlayerScreenState();
}
class _PlayerScreenState extends State<PlayerScreen> {
final _player = CacheAudioPlayerPlus();
static const _url =
'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
double _progress = 0;
@override
void dispose() {
_player.dispose();
super.dispose();
}
Future<void> _play() async {
await _player.playerNetworkAudio(
url: _url,
onDownloadProgress: (received, total) {
if (total > 0) {
setState(() => _progress = received / total);
}
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Cache Audio Player Plus')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_progress > 0 && _progress < 1)
LinearProgressIndicator(value: _progress),
ElevatedButton(onPressed: _play, child: const Text('Play')),
ElevatedButton(
onPressed: _player.pause,
child: const Text('Pause'),
),
ElevatedButton(
onPressed: _player.resume,
child: const Text('Resume'),
),
ElevatedButton(
onPressed: _player.stop,
child: const Text('Stop'),
),
ElevatedButton(
onPressed: _player.clearCache,
child: const Text('Clear cache'),
),
],
),
),
);
}
}
API reference
Setup
| Method | Description |
|---|---|
static Future<void> CacheAudioPlayerPlus.init() |
One-shot initializer. Optional — call from main() to prewarm Hive. |
Playback
| Method | Description |
|---|---|
playerNetworkAudio({url, cache, volume, balance, ctx, position, mode, onDownloadProgress}) |
Play a network URL. Downloads & caches on first use (when cache: true, the default). |
playLocalAudio({filePath, ...}) |
Play a file that already exists on the device. |
playAssetAudio({assetPath, ...}) |
Play an asset bundled with the app. |
pause() |
Pause current playback. |
resume() |
Resume paused playback. |
stop() |
Stop playback and reset position. |
release() |
Release native resources. |
seek(Duration position) |
Move the playhead. |
dispose() |
Dispose of the player instance. |
Cache management
| Method | Description |
|---|---|
isCached(String url) |
true if the URL is cached and the file still exists. |
getCachedFilePath(String url) |
Local path for the cached URL, or null. |
preCacheAudio({url, onDownloadProgress}) |
Download & cache a URL without playing it. |
clearCacheForUrl(String url) |
Remove a single cached entry. |
clearCache() |
Wipe the entire cache. |
getCacheSize() |
Total cached bytes on disk. |
getCachedKeys() |
List of all cached URL keys. |
Audio settings
| Method | Description |
|---|---|
setVolume(double) |
Volume between 0.0 (mute) and 1.0 (max). |
setBalance(double) |
Stereo balance between -1.0 (left) and 1.0 (right). |
setPlaybackRate(double) |
Playback speed. |
setReleaseMode(ReleaseMode) |
stop, loop or release on completion. |
setPlayerMode(PlayerMode) |
mediaPlayer or lowLatency. |
setAudioContext(AudioContext) |
Platform-specific audio focus / category. |
Queries
| Getter / method | Description |
|---|---|
state |
Current PlayerState. |
getCurrentPosition() |
Current playhead position. |
getDuration() |
Total duration of the loaded file. |
Streams
| Stream | Fires when |
|---|---|
onPlayerStateChanged |
State changes (stopped / playing / paused / completed / disposed). |
onPositionChanged |
Playhead position advances (≈ every 200 ms). |
onDurationChanged |
Duration of the loaded file becomes known. |
onPlayerComplete |
Playback reaches the end. |
onSeekComplete |
A seek() operation finishes. |
onLog |
Internal log messages. |
Migration from 1.1.x
Version 1.2.0 is fully backwards compatible with 1.1.x — every existing
method keeps the same signature and behavior. The new init(), cache
management and asset/local playback APIs are purely additive.
The only optional change worth making is calling
await CacheAudioPlayerPlus.init() from your main() to prewarm Hive.
Contributing
Issues and pull requests are very welcome on GitHub.