A professional, production-ready video player for Flutter. Built on video_player and flutter_riverpod with a YouTube-style UI out of the box.
- Multiple Sources — Network, Assets, Files, YouTube, Vimeo, Vimeo Private
- YouTube-Style Controls — Gradient overlays, double-tap seek, auto-hide controls
- Playback Speed — Configurable speed selector (0.5x to 3x)
- Position & Status Streams — Real-time position, buffered position, and player status
- Error Handling — Error stream, auto-retry, built-in error UI with retry button
- Buffering Timeout — Detects stuck buffering and surfaces timeout errors
- Quality Control — Multiple video qualities (Network & Vimeo)
- Fullscreen — Landscape + immersive mode, auto-rotation
- Customizable Theme — Colors, progress bar, labels, loading widget
- Thumbnail — Display a thumbnail before the video starts
- Wakelock — Keep screen on during playback
- iOS + Android — Mobile-only, no web/desktop
dependencies:
max_player: ^3.0.0Important: Wrap your app with
ProviderScope(fromflutter_riverpod):
void main() {
runApp(const ProviderScope(child: MyApp()));
}import 'package:max_player/max_player.dart';
class VideoScreen extends StatefulWidget {
const VideoScreen({super.key});
@override
State<VideoScreen> createState() => _VideoScreenState();
}
class _VideoScreenState extends State<VideoScreen> {
late final MaxPlayerController _controller;
@override
void initState() {
super.initState();
_controller = MaxPlayerController(
playVideoFrom: PlayVideoFrom.network(
'https://example.com/video.mp4',
),
);
_controller.initialise();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AspectRatio(
aspectRatio: 16 / 9,
child: MaxVideoPlayer(controller: _controller),
),
),
);
}
}MaxPlayerController(
playVideoFrom: PlayVideoFrom.network(
'https://example.com/video.mp4',
),
);MaxPlayerController(
playVideoFrom: PlayVideoFrom.youtube(
'https://www.youtube.com/watch?v=VIDEO_ID',
live: false, // Set true for live streams
),
);MaxPlayerController(
playVideoFrom: PlayVideoFrom.vimeo('VIMEO_VIDEO_ID'),
);MaxPlayerController(
playVideoFrom: PlayVideoFrom.vimeoPrivateVideos(
'VIMEO_VIDEO_ID',
httpHeaders: {'Authorization': 'Bearer YOUR_TOKEN'},
),
);MaxPlayerController(
playVideoFrom: PlayVideoFrom.asset('assets/videos/video.mp4'),
);import 'dart:io';
MaxPlayerController(
playVideoFrom: PlayVideoFrom.file(File('/path/to/video.mp4')),
);MaxPlayerController(
playVideoFrom: PlayVideoFrom.networkQualityUrls(
videoUrls: [
VideoQalityUrls(quality: 360, url: 'https://example.com/360.mp4'),
VideoQalityUrls(quality: 720, url: 'https://example.com/720.mp4'),
VideoQalityUrls(quality: 1080, url: 'https://example.com/1080.mp4'),
],
),
);MaxPlayerController(
playVideoFrom: PlayVideoFrom.network('https://example.com/video.mp4'),
maxPlayerConfig: const MaxPlayerConfig(
autoPlay: true, // Auto-play on init (default: true)
isLooping: false, // Loop video (default: false)
wakelockEnabled: true, // Keep screen on (default: true)
videoQualityPriority: [1080, 720, 360], // Quality preference order
availableSpeeds: [0.5, 0.75, 1.0, 1.25, 1.5, 2.0], // Speed options
positionStreamInterval: Duration(milliseconds: 500), // Position update rate
bufferingTimeoutDuration: Duration(seconds: 15), // Buffering timeout
theme: MaxPlayerTheme(
primaryColor: Colors.blue,
iconColor: Colors.white,
backgroundColor: Colors.black,
playingBarColor: Colors.blue,
bufferedBarColor: Colors.grey,
circleHandlerColor: Colors.blueAccent,
),
),
);MaxVideoPlayer(
controller: _controller,
frameAspectRatio: 16 / 9,
videoAspectRatio: 16 / 9,
alwaysShowProgressBar: true,
matchVideoAspectRatioToFrame: true,
videoTitle: const Text('Video Title', style: TextStyle(color: Colors.white)),
videoThumbnail: const DecorationImage(
image: NetworkImage('https://example.com/thumb.jpg'),
fit: BoxFit.cover,
),
maxProgressBarConfig: const MaxProgressBarConfig(
playingBarColor: Colors.blue,
bufferedBarColor: Colors.white24,
circleHandlerColor: Colors.blueAccent,
height: 4,
circleHandlerRadius: 7,
),
maxPlayerLabels: const MaxPlayerLabels(
play: 'Play',
pause: 'Pause',
settings: 'Settings',
quality: 'Quality',
playbackSpeed: 'Speed',
loopVideo: 'Loop',
),
onLoading: (context) => const CircularProgressIndicator(color: Colors.white),
);_controller.play();
_controller.pause();
_controller.togglePlayPause();
_controller.videoSeekTo(const Duration(seconds: 30));
_controller.videoSeekForward(const Duration(seconds: 10));
_controller.videoSeekBackward(const Duration(seconds: 10));_controller.mute();
_controller.unMute();
_controller.toggleVolume();await _controller.setPlaybackSpeed(1.5);
print(_controller.currentSpeed); // 1.5_controller.enableFullScreen(); // Landscape + immersive
_controller.disableFullScreen(context); // Back to portrait_controller.changeVideo(
playVideoFrom: PlayVideoFrom.network('https://example.com/other.mp4'),
);await _controller.retry(); // Re-initializes from the same source_controller.positionStream.listen((position) {
print('Position: $position');
});_controller.bufferedPositionStream.listen((buffered) {
print('Buffered to: $buffered');
});_controller.statusStream.listen((status) {
// MaxPlayerStatus: idle, initializing, playing, paused, buffering, completed, error
print('Status: ${status.name}');
});_controller.onError.listen((error) {
// MaxPlayerError with type (network, format, source, timeout, unknown) and message
print('Error: ${error.type} - ${error.message}');
});_controller.isPlaying; // bool
_controller.isPaused; // bool
_controller.isBuffering; // bool
_controller.progress; // 0.0 to 1.0
_controller.totalDuration; // Duration?
_controller.currentSpeed; // double
_controller.isFullScreen; // bool
_controller.isMute; // bool
_controller.currentVideoPosition; // Duration
_controller.totalVideoLength; // DurationFor HTTP video URLs, add to android/app/src/main/AndroidManifest.xml:
<manifest>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:networkSecurityConfig="@xml/network_security_config">Create android/app/src/main/res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>For HTTP video URLs, add to ios/Runner/Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>See the example directory for a complete app with multiple demos:
- Basic player with video list
- Playback speed & streams dashboard
- Error handling & retry
- Video list with thumbnails
- Custom themed player
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Abdirizak Abdalla
- Email: [email protected]
- GitHub: Abdirizak Abdalla
- Website: abdorizak.dev
