audio_video_player 0.1.0-beta.6
audio_video_player: ^0.1.0-beta.6 copied to clipboard
A comprehensive Flutter plugin for audio and video playback with playlists, albums, background audio, PiP, downloads and more.
audio_video_player #
A comprehensive Flutter plugin for audio and video playback.
Replaces media_kit with a tightly integrated stack built on first-class packages:
| Concern | Package |
|---|---|
| Audio playback | just_audio |
| Background audio / lock-screen controls | just_audio_background + audio_service |
| Audio session management | audio_session |
| Video playback | video_player |
| Video UI & controls | chewie |
| Offline downloads | background_downloader |
| PiP (Picture-in-Picture) | Native (Android / iOS) |
| AirPlay / Chromecast | Native stub + Dart bridge |
Features #
- Single-file and playlist/album audio with background playback and lock-screen / notification controls
- Reactive streams for state, position, duration, queue, and current item
- Shuffle, repeat (off / one / all), playback speed, and volume
- Sleep timer with per-second countdown stream
- Equalizer (Android) — per-band gain control with preset reset
- Video player with full Chewie UI (full-screen, speed, subtitles)
- Video playlist with auto-advance and repeat modes
- Picture-in-Picture on Android and iOS
- Offline downloads via
background_downloader— progress streams, resume, delete - AirPlay (iOS) and Chromecast stub (Android) via
CastController
Table of Contents #
- Installation
- Platform setup
- Initialisation
- Models
- Audio playback
- Sleep Timer
- Equalizer (Android)
- Video playback
- Picture-in-Picture
- Downloads
- Cast / AirPlay
- Running the example app
Installation #
Add the plugin to your project's pubspec.yaml:
From pub.dev (recommended):
dependencies:
audio_video_player: ^0.1.0-beta.5
From a local path (during development):
dependencies:
audio_video_player:
path: ../audio_video_player
From a git repository:
dependencies:
audio_video_player:
git:
url: https://github.com/Rameshwar-Amancha/audio_video_player
ref: main
Then run:
flutter pub get
Platform setup #
Android #
Your app's android/app/src/main/AndroidManifest.xml must include the following
permissions and activity attributes:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application ...>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:supportsPictureInPicture="true" <!-- Required for PiP -->
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
...>
<!-- existing content -->
</activity>
</application>
</manifest>
Minimum SDK: The plugin requires
minSdk 21(Android 5.0). Ensure yourandroid/app/build.gradlehasminSdk = 21or higher.
iOS #
In your app's ios/Runner/Info.plist add the background audio mode and allow
HTTP streams during development:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<!-- Allow cleartext HTTP for non-HTTPS streams (development only) -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
For production, remove
NSAllowsArbitraryLoadsand use HTTPS-only sources or add specific domain exceptions.
iOS deployment target: 13.0 or higher (set in Xcode under Runner → General → Minimum Deployments).
Initialisation #
Call AudioVideoPlayerPlugin.init() once, before runApp():
import 'package:audio_video_player/audio_video_player.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AudioVideoPlayerPlugin.init(
// Required – identifies the Android foreground-service notification channel.
androidNotificationChannelId: 'com.yourapp.audio',
androidNotificationChannelName: 'Music',
// Optional
androidNotificationIcon: 'mipmap/ic_launcher', // drawable resource
notificationColor: Colors.deepPurple, // Android notification colour
pauseWhenDucked: false, // true = pause on nav-app focus loss
);
runApp(const MyApp());
}
AudioServiceWidget is not required — just_audio_background handles
foreground-service lifecycle internally.
Models #
AVMediaItem #
The unified media descriptor used by both stacks:
const item = AVMediaItem(
id: 'track_001', // unique ID (UUID recommended)
uri: 'https://example.com/track.mp3', // https://, file://, or asset://
type: AVMediaType.audio, // or AVMediaType.video
title: 'My Song',
artist: 'Artist Name',
album: 'Album Title',
albumArtUri: 'https://example.com/art.jpg',
duration: Duration(minutes: 3, seconds: 45),
);
URI schemes:
| Scheme | Example | Use case |
|---|---|---|
https:// |
https://cdn.example.com/track.mp3 |
Remote stream |
file:// |
file:///data/user/0/.../track.mp3 |
Local file |
asset:// |
asset://assets/audio/demo.mp3 |
Flutter asset |
Playlist #
final playlist = Playlist(
id: 'pl_001',
name: 'My Playlist',
description: 'Optional description',
artUri: 'https://example.com/art.jpg',
items: [item1, item2, item3],
);
// Mutations return a new Playlist (immutable pattern):
final updated = playlist.add(item4);
final reordered = playlist.reorder(0, 2); // move item at index 0 to index 2
final removed = playlist.remove('track_001');
Album #
final album = Album(
id: 'alb_001',
name: 'Greatest Hits',
artist: 'Artist Name',
year: 2026,
artUri: 'https://example.com/cover.jpg',
items: tracks,
);
Audio playback #
Single track #
final audio = AudioController();
// Remote URL
await audio.open(
URISource(AVMediaItem(id: '1', uri: 'https://example.com/track.mp3', type: AVMediaType.audio, title: 'Track')),
autoPlay: true,
);
// Flutter asset
await audio.open(
AssetSource(
AVMediaItem(id: '2', uri: 'asset://assets/audio/demo.mp3', type: AVMediaType.audio, title: 'Demo'),
'assets/audio/demo.mp3',
),
autoPlay: true,
);
// Local file
await audio.open(
FileSource(
AVMediaItem(id: '3', uri: 'file:///path/to/file.mp3', type: AVMediaType.audio, title: 'Local'),
File('/path/to/file.mp3'),
),
autoPlay: true,
);
Playlist & Album #
final audio = AudioController();
// Playlist (starts from item at index 2)
await audio.open(PlaylistSource(myPlaylist, initialIndex: 2), autoPlay: true);
// Album
await audio.open(AlbumSource(myAlbum), autoPlay: true);
// Queue management
await audio.skipToNext();
await audio.skipToPrevious();
await audio.skipToIndex(3);
await audio.add(newItem); // append one item
await audio.addAll([item4, item5]); // append multiple items
await audio.removeAt(1); // remove by index
await audio.move(0, 4); // reorder: move index 0 to index 4
await audio.clear(); // empty the queue
Shuffle & Repeat #
await audio.setShuffleMode(true);
await audio.setRepeatMode(RepeatMode.all); // off | one | all
Audio streams reference #
final audio = AudioController();
// Subscribe in initState / inside a StreamBuilder
audio.playerStateStream // Stream<AVPlayerState>
audio.positionStream // Stream<Duration>
audio.durationStream // Stream<Duration?>
audio.bufferedPositionStream // Stream<Duration>
audio.volumeStream // Stream<double> (0.0–1.0)
audio.speedStream // Stream<double>
audio.repeatModeStream // Stream<RepeatMode>
audio.shuffleEnabledStream // Stream<bool>
audio.queueStream // Stream<List<AVMediaItem>>
audio.currentItemStream // Stream<AVMediaItem?>
audio.currentIndexStream // Stream<int?>
AVPlayerState values: idle → loading → ready → playing → paused → completed → error
Audio controls reference #
await audio.play();
await audio.pause();
await audio.stop();
await audio.seek(Duration(seconds: 90)); // AudioController uses seek()
await audio.setVolume(0.8); // 0.0–1.0
await audio.setSpeed(1.5); // e.g. 0.5, 1.0, 1.5, 2.0
await audio.skipToNext();
await audio.skipToPrevious();
await audio.skipToIndex(2); // 0-based queue index
await audio.add(item); // append to queue
await audio.addAll([item2, item3]); // append multiple
await audio.removeAt(1); // remove by index
await audio.move(0, 3); // reorder
await audio.clear(); // empty the queue
await audio.setShuffleMode(true);
await audio.setRepeatMode(RepeatMode.one);
// Sync getters
audio.playerState // AVPlayerState
audio.position // Duration
audio.duration // Duration?
audio.volume // double
audio.speed // double
audio.repeatMode // RepeatMode
audio.shuffleEnabled // bool
audio.queue // List<AVMediaItem>
audio.currentItem // AVMediaItem?
audio.currentIndex // int?
audio.isPlaying // bool
// Always dispose when done
await audio.dispose();
Example widget #
class AudioPlayerWidget extends StatefulWidget { ... }
class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
final _audio = AudioController();
@override
void initState() {
super.initState();
_audio.open(URISource(myItem), autoPlay: true);
}
@override
void dispose() {
_audio.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder<AVPlayerState>(
stream: _audio.playerStateStream,
builder: (context, snapshot) {
final state = snapshot.data ?? AVPlayerState.idle;
return IconButton(
icon: Icon(state == AVPlayerState.playing ? Icons.pause : Icons.play_arrow),
onPressed: state == AVPlayerState.playing ? _audio.pause : _audio.play,
);
},
);
}
}
Sleep Timer #
The SleepTimer is already wired to the AudioController — access it via audio.sleepTimer:
final audio = AudioController();
// Start a 30-minute sleep timer
audio.sleepTimer.start(const Duration(minutes: 30));
// Cancel early
audio.sleepTimer.cancel();
// Show countdown in the UI
StreamBuilder<Duration?>(
stream: audio.sleepTimer.remainingStream,
builder: (context, snap) {
final remaining = snap.data;
if (remaining == null) return const Text('No sleep timer');
final m = remaining.inMinutes.remainder(60).toString().padLeft(2, '0');
final s = remaining.inSeconds.remainder(60).toString().padLeft(2, '0');
return Text('Sleep in $m:$s');
},
);
Equalizer (Android) #
Available on Android only. Access via audio.equalizer:
final audio = AudioController();
final eq = audio.equalizer;
if (eq.isAvailable) {
// Enable
await eq.setEnabled(true);
// Get bands and adjust gain
final bands = await eq.getBands();
for (final band in bands) {
print('${band.centerFrequency} Hz: ${band.gain} dB');
}
// Set 60 Hz band to +3 dB
await eq.setBandGain(0, 3.0);
// Reset all bands to 0 dB
await eq.reset();
// Disable
await eq.setEnabled(false);
}
Video playback #
Single video #
final vc = VideoController(
autoPlay: true,
looping: false,
allowFullScreen: true,
allowPlaybackSpeedChanging: true,
);
await vc.open(AVMediaItem(
id: 'v1',
uri: 'https://example.com/video.mp4',
type: AVMediaType.video,
title: 'My Video',
));
In your widget, pass vc.chewieController to a Chewie widget:
import 'package:chewie/chewie.dart';
@override
Widget build(BuildContext context) {
final chewieController = vc.chewieController;
if (chewieController == null) return const CircularProgressIndicator();
return AspectRatio(
aspectRatio: vc.videoPlayerController!.value.aspectRatio,
child: Chewie(controller: chewieController),
);
}
Video controls:
await vc.play();
await vc.pause();
await vc.seekTo(Duration(seconds: 30));
await vc.setVolume(0.5);
await vc.setSpeed(1.25);
await vc.dispose();
Video playlist #
final vpc = VideoPlaylistController(
items: myAlbum.items, // List<AVMediaItem>
initialIndex: 0,
autoPlay: true,
repeatMode: RepeatMode.all,
);
await vpc.start();
// Navigation
await vpc.skipToNext();
await vpc.skipToPrevious();
await vpc.skipToIndex(2);
// Queue management
vpc.add(newItem); // append item
await vpc.remove('track_id_here'); // remove by AVMediaItem.id
// Expose in widget
StreamBuilder<int>(
stream: vpc.currentIndexStream,
builder: (context, snap) {
final ctrl = vpc.currentController;
if (ctrl?.chewieController == null) return const SizedBox();
return Chewie(controller: ctrl!.chewieController!);
},
);
await vpc.dispose();
Picture-in-Picture #
PipController is accessible via VideoController.pip:
final vc = VideoController();
await vc.open(myVideoItem);
// Check availability
final supported = await vc.pip.isPipAvailable();
// Enter PiP (Android – typically called from onPause or a UI button)
if (supported) {
await vc.pip.enterPip();
}
// Exit PiP (iOS only; Android handles this via the system back gesture)
await vc.pip.exitPip();
Android: PiP triggers when the user presses the Home button while the app
activity has android:supportsPictureInPicture="true". Call enterPip() from
your FlutterActivity's onUserLeaveHint() override, or from a UI button.
iOS: PiP is managed automatically by AVPictureInPictureController. Call
enterPip() to activate it programmatically.
Downloads #
final dl = DownloadManager.instance;
// Download a track (returns a stream of progress events synchronously)
final stream = dl.download(myItem);
stream.listen((progress) {
if (progress.isComplete) {
print('Downloaded! local path in item.extras["localPath"]');
} else {
print('${(progress.progress * 100).toStringAsFixed(0)}%');
}
});
// Check / use downloaded file
final isDownloaded = await dl.isDownloaded(myItem);
final downloaded = await dl.getDownloadedItems(); // List<AVMediaItem> with localPath set
// Play a downloaded item offline (localPath is injected into extras)
final localItem = downloaded.first;
final localUri = localItem.extras['localPath'] as String;
final offlineItem = localItem.copyWith(uri: 'file://$localUri', isLocal: true);
await audio.open(URISource(offlineItem), autoPlay: true);
// Cancel / delete
await dl.cancel(myItem.id); // cancel an in-progress download
await dl.deleteDownload(myItem.id); // delete a completed local file
Downloaded files are stored under:
<appDocuments>/audio_video_player/downloads/<itemId>.<ext>
Cast / AirPlay #
final cast = CastController();
// iOS – show the system AirPlay route picker sheet
await cast.showAirPlayRoutePicker();
// Android – Chromecast (requires Google Cast SDK integration in host app)
final available = await cast.isAvailable();
if (available) {
final devices = await cast.discoverDevices();
await cast.connect(devices.first);
await cast.cast(myItem.uri);
await cast.disconnect();
}
Chromecast on Android requires manually adding the Google Cast SDK to your host app. See
android/src/main/kotlin/.../CastHandler.ktfor setup instructions.
Running the example app #
Prerequisites #
| Requirement | Version |
|---|---|
| Flutter SDK | ≥ 3.27.0 |
| Dart SDK | ≥ 3.9.0 |
| Android device / emulator | API 21+ (Android 5) |
| iOS device / simulator | iOS 13+ |
1. Install dependencies #
From the plugin root:
cd audio_video_player
flutter pub get
cd example
flutter pub get
2. Verify your device is connected #
flutter devices
You should see your physical device or emulator listed, e.g.:
sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 14 (API 34)
Pixel 7 (mobile) • ZX12345678 • android-arm64 • Android 14 (API 34)
3. Run on your attached Android device #
From the example/ directory:
cd example
flutter run
To target a specific device by ID:
flutter run -d ZX12345678
To run a release build (better performance, tests background audio properly):
flutter run --release
4. Run on iOS #
flutter run -d iPhone # or use the simulator device ID from flutter devices
5. What the example app demonstrates #
| Screen | How to trigger |
|---|---|
| Single Audio | Tap "Single Audio Track" — plays a remote MP3 with play/pause/seek controls |
| Audio Playlist | Tap "Audio Playlist" — queue of 3 tracks with shuffle, repeat, sleep timer |
| Video Player | Tap "Video Player" — Chewie UI with full-screen, speed selector, PiP button |
Testing background audio (Android):
- Open the Audio Playlist screen and start playback
- Press the Home button — audio continues in the background
- Pull down the notification shade — media controls appear on the lock screen
- Swipe the notification to stop playback
Testing Picture-in-Picture (Android):
- Open the Video Player screen and start a video
- Tap the PiP button (bottom-right of the video) or press the Home button
- Video shrinks into the system PiP window
API quick reference #
Classes #
| Class | Purpose |
|---|---|
AudioVideoPlayerPlugin |
One-time plugin initialiser — call .init() in main() |
AVMediaItem |
Unified media descriptor (audio + video) |
Playlist |
Ordered collection of AVMediaItems |
Album |
Playlist subtype with artist and year |
MediaSource |
Sealed: URISource, FileSource, AssetSource, PlaylistSource, AlbumSource |
AudioController |
Full audio playback controller (streams, queue, equalizer, sleep timer) |
SleepTimer |
Countdown timer that pauses AudioController |
EqualizerController |
Android-only per-band EQ (access via AudioController.equalizer) |
VideoController |
Single video + Chewie integration + PiP |
VideoPlaylistController |
Video queue with auto-advance |
PipController |
Enter/exit native Picture-in-Picture (access via VideoController.pip) |
DownloadManager |
Offline download queue with progress streams |
CastController |
Chromecast (Android stub) + AirPlay (iOS) bridge |
Enums #
| Enum | Values |
|---|---|
AVMediaType |
audio, video |
AVPlayerState |
idle, loading, ready, playing, paused, completed, error |
RepeatMode |
off, one, all |
CastDeviceType |
chromecast, airPlay, unknown |