video_maestro 0.0.1
video_maestro: ^0.0.1 copied to clipboard
Video Maestro
Video Maestro 🎬 #
A powerful, customizable, and feature-rich Flutter video player package built on top of video_player. Video Maestro removes the boilerplate of building a video player UI from scratch — giving you smart auto-detected sources, gesture controls, captions, full-screen, theming, and localization out of the box.
Table of Contents #
- Features
- Installation
- Quick Start
- MaestroController
- VideoMaestroConfig
- OptionsVisibilityControl
- Captions
- Theming
- Localization
- Bottom Sheets
- Full Screen
- API Reference
Features #
| Feature | Description |
|---|---|
| 🔗 Auto Source Detection | Pass any URL/path string — network, asset, or local file is detected automatically |
| ▶️ Playback Controls | Play, pause, seek with a polished progress bar and floating time tooltip |
| 👆 Double-Tap to Seek | Tap the left/right edges to seek backward/forward by a configurable number of seconds |
| ⚡ Hold for 2× Speed | Long-press anywhere on the video to jump to 2× speed; release to restore |
| 📺 Full Screen | Native push-based full-screen transition with automatic orientation handling |
| 📝 Captions | .vtt and .srt from assets, local files, or network — with caching and deduplication |
| 🎨 Full Theming | Every color, icon size, gradient, text style, and slider is customizable via ThemeExtension |
| 🌍 Localization | Ships with English and Arabic built in; reads the Locale automatically |
| 🧩 Modular Visibility | Show or hide any UI element individually with OptionsVisibilityControl |
| ➕ Custom Widgets | Inject extra bottom-bar buttons or overlay widgets onto the video |
Screenshots #
Light Mode Dark Mode Full Screen
Installation #
Add the package to your pubspec.yaml:
dependencies:
video_maestro: ^0.0.1
Then run:
flutter pub get
Quick Start #
import 'package:flutter/material.dart';
import 'package:video_maestro/video_maestro.dart';
class MyVideoScreen extends StatefulWidget {
const MyVideoScreen({super.key});
@override
State<MyVideoScreen> createState() => _MyVideoScreenState();
}
class _MyVideoScreenState extends State<MyVideoScreen> {
late final MaestroController _controller;
@override
void initState() {
super.initState();
_controller = MaestroController(
url: 'https://www.example.com/sample_video.mp4',
);
_controller.initialize();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: VideoMaestro(
controller: _controller,
),
),
);
}
}
Important:
VideoMaestrorequires aVideoMaestroThemeextension to be present in the currentThemeData. See Theming for setup.
MaestroController #
MaestroController is the single entry point for all video operations. Create one instance per player and pass it to VideoMaestro.
Creating a controller #
// Network URL
final controller = MaestroController(url: 'https://example.com/video.mp4');
// Asset
final controller = MaestroController(url: 'assets/videos/intro.mp4');
// Local file path
final controller = MaestroController(url: '/storage/emulated/0/video.mp4');
// Provide your own VideoPlayerController
final controller = MaestroController(
url: 'https://example.com/video.mp4',
videoPlayerController: myExistingController,
);
Source type (network / asset / file) is auto-detected from the string. You never need to specify it manually.
Lifecycle #
// Initialize and start loading
await controller.initialize();
// Playback
controller.play();
controller.pause();
// Seek to a position
await controller.seekTo(const Duration(seconds: 30));
// Always dispose when done
controller.dispose();
State stream #
MaestroController exposes a BehaviorSubject you can listen to:
StreamBuilder<VideoMaestroState>(
stream: controller.playerStateNotifier,
builder: (context, snapshot) {
return switch (snapshot.data) {
VideoMaestroInitialState() => const SizedBox(),
VideoMaestroLoadingState() => const CircularProgressIndicator(),
VideoMaestroSuccessState() => const Text('Playing'),
VideoMaestroFailureState(:final message) => Text('Error: $message'),
null => const SizedBox(),
};
},
);
| State | Meaning |
|---|---|
VideoMaestroInitialState |
Controller created, not yet initialized |
VideoMaestroLoadingState |
initialize() called, loading in progress |
VideoMaestroSuccessState |
Video ready to play |
VideoMaestroFailureState |
Initialization failed (optional error message) |
Useful getters #
| Getter | Type | Description |
|---|---|---|
isPlaying |
bool |
Whether the video is currently playing |
playerController |
VideoPlayerController |
Direct access to the underlying controller |
captionLoaderHelper |
CaptionLoaderHelper |
Manages caption loading and switching |
playerStateNotifier |
BehaviorSubject<VideoMaestroState> |
Observable state stream |
isInFullScreenNotifier |
BehaviorSubject<bool> |
Tracks full-screen state |
VideoMaestroConfig #
VideoMaestroConfig is passed to VideoMaestro to configure behavior and visible elements.
VideoMaestro(
controller: _controller,
config: VideoMaestroConfig(
doubleTapSeekTime: 10, // Seek ±10 s on double-tap (default: 5)
optionsVisibility: OptionsVisibilityControl(
showSpeedButton: true,
showFullScreenButton: true,
doubleTapToSeek: true,
holdToDoubleSpeed: true,
showCaptions: true,
),
captions: [
CaptionData(name: 'English', path: 'assets/en.vtt'),
],
bufferLoader: const CircularProgressIndicator(strokeWidth: 3),
extraBottomButtons: [
IconButton(icon: const Icon(Icons.share), onPressed: _shareVideo),
],
onVideoWidgets: [
const Positioned(top: 12, right: 12, child: MyBadgeWidget()),
],
),
)
Config properties #
| Property | Type | Default | Description |
|---|---|---|---|
doubleTapSeekTime |
int? |
5 |
Seconds to seek on double-tap |
optionsVisibility |
OptionsVisibilityControl |
all true |
UI toggle flags |
captions |
List<CaptionData>? |
null |
Subtitle tracks to offer |
bufferLoader |
Widget |
CircularProgressIndicator |
Widget shown while buffering |
extraBottomButtons |
List<Widget>? |
null |
Extra icons appended to the bottom bar |
onVideoWidgets |
List<Widget>? |
null |
Widgets stacked on top of the video (use Positioned) |
Separate full-screen config #
You can provide a different config when the player enters full screen:
VideoMaestro(
controller: _controller,
config: VideoMaestroConfig(doubleTapSeekTime: 5),
fullScreenConfig: VideoMaestroConfig(
doubleTapSeekTime: 15,
optionsVisibility: OptionsVisibilityControl(showCaptions: false),
),
)
OptionsVisibilityControl #
Fine-tune which UI elements are visible.
// Show everything (default)
const OptionsVisibilityControl()
// Custom combination
const OptionsVisibilityControl(
showSpeedButton: false,
showFullScreenButton: true,
doubleTapToSeek: true,
holdToDoubleSpeed: false,
showCaptions: true,
)
| Flag | Default | Description |
|---|---|---|
showSpeedButton |
true |
Speed icon in the bottom bar |
showFullScreenButton |
true |
Full-screen toggle icon |
doubleTapToSeek |
true |
Double-tap left/right to seek |
holdToDoubleSpeed |
true |
Long-press to play at 2× |
showCaptions |
true |
Caption text overlay + captions icon |
Captions #
Video Maestro has a built-in CaptionLoaderHelper that handles loading, caching, and switching between multiple subtitle tracks.
Supported formats #
- WebVTT (
.vtt) - SubRip (
.srt)
Supported sources #
- App assets
- Local file paths
- Network URLs (downloaded, cached to disk, deduplicated)
Setting up captions #
VideoMaestroConfig(
captions: [
// From asset (type auto-detected from extension)
CaptionData(name: 'English', path: 'assets/captions/en.vtt'),
// From network
CaptionData(name: 'Arabic', path: 'https://example.com/ar.srt'),
// Explicit type
CaptionData(
name: 'French',
path: 'assets/fr_captions',
type: CaptionTypeEnum.vtt,
),
],
)
- If one caption track is provided, it is loaded and applied automatically.
- If multiple tracks are provided, a bottom sheet is shown when the user taps the caption icon, allowing them to pick a track or remove captions.
CaptionData fields #
| Field | Type | Description |
|---|---|---|
name |
String |
Display name shown in the selection sheet |
path |
String |
Asset path, file path, or network URL |
type |
CaptionTypeEnum? |
vtt or srt — auto-detected from extension if omitted |
Using CaptionLoaderHelper directly #
// Load and apply a track programmatically
await controller.captionLoaderHelper.loadAndApply(
const CaptionData(name: 'English', path: 'assets/en.vtt'),
);
// Remove the active caption
await controller.captionLoaderHelper.clearCaption();
// Check if a path is the currently active caption
final bool active = controller.captionLoaderHelper.isActive('assets/en.vtt');
// Observe the active caption
ValueListenableBuilder<CaptionData?>(
valueListenable: controller.captionLoaderHelper.activeCaptionNotifier,
builder: (context, caption, _) => Text(caption?.name ?? 'No caption'),
);
Theming #
Video Maestro integrates with Flutter's ThemeExtension system. Add VideoMaestroTheme to your app's ThemeData (this is required.)
Basic setup #
MaterialApp(
theme: ThemeData(
extensions: const [
VideoMaestroTheme(), // All defaults — dark player on black background
],
),
home: MyVideoScreen(),
)
Full customization #
MaterialApp(
theme: ThemeData(
extensions: [
VideoMaestroTheme(
// Background
backgroundColor: Colors.black,
// Icons
iconColor: Colors.white,
iconSize: 26,
// Seek overlay (double-tap animation)
seekOverlayGradient: [Colors.black12, Colors.black38],
seekOverlayIconColor: Colors.white70,
seekOverlayIconSize: 34,
// Bottom controls gradient
controlsBarGradient: [Colors.transparent, Colors.black54, Colors.black87],
// Progress / seek bar
seekBarTrackColor: Colors.black45,
seekBarBufferColor: Color(0x64757575),
sliderThemeData: SliderThemeData(
trackHeight: 4,
thumbShape: FlatThumbShape(thumbRadius: 8),
overlayColor: Colors.transparent,
inactiveTrackColor: Colors.white24,
),
// Time labels
timePositionStyle: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500),
timeDurationStyle: TextStyle(color: Color(0xDCFFFFFF), fontSize: 14),
// Floating time tooltip
floatingTimeBackgroundColor: Colors.black26,
floatingTimeTextStyle: TextStyle(fontSize: 12, color: Colors.white),
// Captions
captionTextStyle: TextStyle(fontSize: 15, color: Colors.white),
// 2× speed badge shown during long-press
doubleSpeedBadgeView: MaestroDoubleSpeedView(),
// Device orientations to restore after exiting full screen
appPreferredOrientations: [DeviceOrientation.portraitUp],
),
],
),
)
Per-widget theme override #
Use VideoMaestroThemeOverrider to override the theme for a specific VideoMaestro widget without touching the global theme:
VideoMaestroThemeOverrider(
data: (isDark, current) => current.copyWith(
backgroundColor: isDark ? Colors.grey[900]! : Colors.white,
iconColor: isDark ? Colors.white : Colors.black,
),
child: VideoMaestro(controller: _controller),
)
The data callback receives:
isDark— whether the currentThemeDatais darkcurrent— the currently resolvedVideoMaestroTheme, so you can use.copyWith()to change only what you need
VideoMaestroTheme properties reference #
| Property | Type | Default |
|---|---|---|
backgroundColor |
Color |
Colors.black |
iconColor |
Color |
Colors.white |
iconSize |
double |
26 |
seekOverlayGradient |
List<Color> |
[black12, black26] |
seekOverlayIconColor |
Color |
Colors.white38 |
seekOverlayIconSize |
double |
34 |
controlsBarGradient |
List<Color> |
[black12, black54, black87] |
seekBarTrackColor |
Color |
Colors.black45 |
seekBarBufferColor |
Color |
Color(0x64757575) |
sliderThemeData |
SliderThemeData |
See above |
timePositionStyle |
TextStyle |
white, 14sp, w500 |
timeDurationStyle |
TextStyle |
white 87%, 14sp, w500 |
floatingTimeBackgroundColor |
Color |
Colors.black26 |
floatingTimeTextStyle |
TextStyle |
white, 12sp, w500 |
captionTextStyle |
TextStyle? |
white, 14sp |
doubleSpeedBadgeView |
Widget |
MaestroDoubleSpeedView() |
appPreferredOrientations |
List<DeviceOrientation> |
All orientations |
FlatThumbShape
A custom SliderComponentShape included in the package that renders a flat circular thumb for the seek bar. Configurable via thumbRadius.
sliderThemeData: SliderThemeData(
thumbShape: FlatThumbShape(thumbRadius: 6),
)
Localization #
Video Maestro reads the Locale from Flutter's Localizations widget. English and Arabic are built in.
Setup #
MaterialApp(
supportedLocales: const [
Locale('en'),
Locale('ar'),
],
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
// ...
)
No additional delegate registration is required for Video Maestro itself — it hooks into the existing Localizations.localeOf(context).
Bottom Sheets #
Video Maestro provides two ready-made modal bottom sheets accessible via MaestroBottomSheets. They are already wired into the default bottom controls bar, but you can invoke them manually too.
Speed sheet #
await MaestroBottomSheets.openSpeedBottomSheet(
context: context,
controller: _controller,
// Optional route settings for navigator-based dismissal
routeSettings: const RouteSettings(name: 'maestro-speed-bottom-sheet'),
);
- Displays an interactive slider from 0.25× to 2.0× (step: 0.05×).
- Speed is applied live as the thumb is dragged.
- Video is automatically paused before opening and resumed after closing.
Captions sheet #
await MaestroBottomSheets.openCaptionsBottomSheet(
context: context,
controller: _controller,
captions: myCaptionList,
routeSettings: const RouteSettings(name: 'maestro-captions-bottom-sheet'),
);
- Lists all available caption tracks.
- The active track is highlighted with a check mark.
- Includes a Remove Caption option when a caption is active.
- Video is paused/resumed automatically.
Full Screen #
Full-screen mode is handled internally via VideoMaestroFullScreen. Tapping the full-screen icon in the bottom bar pushes a new route that forces landscape orientation.
// Entering full screen is done automatically via the button.
// To control orientations restored after exiting, configure:
VideoMaestroTheme(
appPreferredOrientations: [DeviceOrientation.portraitUp],
)
You can also pass a distinct fullScreenConfig to VideoMaestro so the full-screen player has different settings (e.g., larger seek increments, hidden captions):
VideoMaestro(
controller: _controller,
config: VideoMaestroConfig(doubleTapSeekTime: 5),
fullScreenConfig: VideoMaestroConfig(doubleTapSeekTime: 30),
)
API Reference #
VideoMaestro widget #
| Parameter | Type | Description |
|---|---|---|
controller |
MaestroController |
The playback controller |
config |
VideoMaestroConfig |
Behavior and UI config (defaults apply) |
fullScreenConfig |
VideoMaestroConfig? |
Overrides config when in full-screen |
Named constructors:
VideoMaestro(...)— standard embedded playerVideoMaestro.fullScreen(...)— used internally by the full-screen route
MaestroController #
| Method / Property | Description |
|---|---|
initialize() |
Load the video. Emits Loading → Success or Failure |
play() |
Start playback |
pause() |
Pause playback |
seekTo(Duration) |
Seek; tracks whether target is buffered |
dispose() |
Release all resources |
isPlaying |
bool — current playback state |
playerController |
Underlying VideoPlayerController |
playerStateNotifier |
BehaviorSubject<VideoMaestroState> |
isInFullScreenNotifier |
BehaviorSubject<bool> |
captionLoaderHelper |
CaptionLoaderHelper instance |
CaptionLoaderHelper #
| Method / Property | Description |
|---|---|
loadAndApply(CaptionData) |
Load (with cache) and activate a caption track |
clearCaption() |
Remove the active caption |
isActive(String path) |
Returns true if the given path is the active track |
activeCaptionNotifier |
BehaviorSubject<CaptionData?> — observe active caption |
License #
MIT License