# splash_video A Flutter package for creating smooth video splash screens using media_kit. Provides seamless transitions from native splash screens to video playback with flexible overlay support and manual controls. ## Platform Support | Platform | Supported | |----------|-----------| | Android | ✅ | | iOS | ✅ | | macOS | ✅ | | Windows | ✅ | | Linux | ✅ | ## Features - ✨ **Smooth Transitions** - Defer first frame pattern prevents jank between native and video splash - 🎬 **Media Kit Integration** - Cross-platform video playback with hardware acceleration - 🎮 **Manual Controls** - Full controller access for play, pause, skip operations - 🔄 **Looping Support** - Infinite video loops with user-controlled exit - 📱 **Flexible Overlays** - Title, footer, and custom overlay widgets - 🎯 **Auto-Navigation** - Automatic screen transitions or manual control - 🎨 **7 Scale Modes** - Cover, contain, fill, fitWidth, fitHeight, scaleDown, none - 📐 **Aspect Ratio Control** - Full control over video sizing and scaling strategies - ⚡ **Hardware Acceleration** - Smart platform defaults with 9 configurable decode modes ## Installation Add to your `pubspec.yaml`: ```yaml dependencies: splash_video: ^0.1.3 ## Quick Start ### 1. Initialize in main() ```dart import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; import 'package:splash_video/splash_video.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); // Required: Initialize media_kit SplashVideo.initialize(); runApp(MyApp()); } ``` ### 2. Basic Usage (Auto-Navigate) ```dart class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: SplashVideo( source: AssetSource('assets/videos/splash.mp4'), nextScreen: HomeScreen(), backgroundColor: Colors.black, ), ); } } ``` ## Usage Examples ### Auto-Navigation Video plays and automatically navigates to the next screen: ```dart SplashVideo( source: AssetSource('assets/videos/splash.mp4'), nextScreen: HomeScreen(), backgroundColor: Colors.black, ) ``` ### Manual Control Use a callback for custom logic after video completes: ```dart SplashVideo( source: AssetSource('assets/videos/splash.mp4'), onVideoComplete: () { // Custom logic // ... // ... Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => HomeScreen()), ); }, ) ``` ### Looping Video with Controller Create an infinite loop that the user can manually exit: ```dart class MyScreen extends StatefulWidget { @override State createState() => _MyScreenState(); } class _MyScreenState extends State { late final SplashVideoController controller; @override void initState() { super.initState(); controller = SplashVideoController(loopVideo: true); } @override void dispose() { controller.dispose(); super.dispose(); } void _onSkip() { controller.skip(); Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => HomeScreen()), ); } @override Widget build(BuildContext context) { return SplashVideo( source: AssetSource('assets/videos/splash.mp4'), controller: controller, footerWidget: ElevatedButton( onPressed: _onSkip, child: Text('Skip'), ), ); } } ``` ### With Overlays Add title, footer, and custom overlays: ```dart SplashVideo( source: AssetSource('assets/videos/splash.mp4'), nextScreen: HomeScreen(), backgroundColor: Colors.black, // Title at top titleWidget: Padding( padding: EdgeInsets.all(20), child: Text( 'Welcome', style: TextStyle(fontSize: 32, color: Colors.white), ), ), // Footer at bottom footerWidget: CircularProgressIndicator(), // Custom overlay with full control overlayBuilder: (context) => Positioned( right: 20, top: 100, child: Text('v1.0.0', style: TextStyle(color: Colors.white)), ), ) ``` ### Network Video Load video from URL: ```dart SplashVideo( source: NetworkFileSource('https://example.com/splash.mp4'), nextScreen: HomeScreen(), ) ``` ### Video Scale Modes Control how your video fills the screen with 7 different scale modes: ```dart SplashVideo( source: AssetSource('assets/videos/splash.mp4'), nextScreen: HomeScreen(), videoConfig: VideoConfig( fitMode: VideoFitMode.cover, // Fill screen, may crop edges (best for splash) ), ) ``` **Available Scale Modes:** - `VideoFitMode.cover` - Fill screen, maintain aspect, may crop (recommended for splash screens) - `VideoFitMode.contain` - Fit inside, maintain aspect, may show letterboxing - `VideoFitMode.fill` - Stretch to fill (ignores aspect ratio, may distort) - `VideoFitMode.fitWidth` - Match width, maintain aspect - `VideoFitMode.fitHeight` - Match height, maintain aspect - `VideoFitMode.scaleDown` - Like contain but won't upscale beyond original size - `VideoFitMode.none` - Original size, centered **Quick Comparison:** | Fit Mode | Best For | Maintains Aspect | May Crop | May Letterbox | |----------|----------|------------------|----------|---------------| | `cover` | Splash screens, hero sections | ✅ | ✅ | ❌ | | `contain` | Viewing entire video | ✅ | ❌ | ✅ | | `fill` | Exact fill (rare) | ❌ | ❌ | ❌ | | `fitWidth` | Horizontal content | ✅ | Vertical | Horizontal | | `fitHeight` | Vertical content | ✅ | Horizontal | Vertical | | `scaleDown` | Small videos | ✅ | ❌ | ✅ | | `none` | Pixel-perfect display | ✅ | ❌ | ✅ | ### Custom Configuration ```dart SplashVideo( source: AssetSource('assets/videos/splash.mp4'), nextScreen: HomeScreen(), videoConfig: VideoConfig( playImmediately: true, fitMode: VideoFitMode.cover, useSafeArea: true, volume: 50.0, enableAudio: true, onPlayerInitialized: (player) { print('Player ready: ${player.state.duration}'); }, ), ) ``` ## API Reference ### SplashVideo Main widget for video splash screen. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `source` | `Source` | ✅ | Video source (Asset, Network, DeviceFile, Bytes) | | `controller` | `SplashVideoController?` | ⚠️ | Required when looping is enabled | | `videoConfig` | `VideoConfig?` | ❌ | Playback configuration | | `backgroundColor` | `Color?` | ❌ | Background color behind video | | `titleWidget` | `Widget?` | ❌ | Widget at top of screen | | `footerWidget` | `Widget?` | ❌ | Widget at bottom of screen | | `overlayBuilder` | `Widget Function(BuildContext)?` | ❌ | Custom overlay builder | | `nextScreen` | `Widget?` | ❌ | Screen to navigate to (auto-navigate) | | `onVideoComplete` | `VoidCallback?` | ❌ | Callback when video completes (manual control) | | `onSourceLoaded` | `VoidCallback?` | ❌ | Callback when video loads | **Validation Rules:** - Cannot use both `nextScreen` and `onVideoComplete` - Looping videos require `controller` and cannot use `nextScreen` or `onVideoComplete` ### SplashVideoController Controls video playback. ```dart final controller = SplashVideoController(loopVideo: true); // Access media_kit Player controller.player.play(); controller.player.pause(); controller.player.seek(Duration(seconds: 5)); // Controller methods controller.play(); controller.pause(); controller.skip(); // Complete immediately controller.dispose(); ``` ### VideoConfig Configuration for video playback. ```dart VideoConfig( playImmediately: true, // Auto-play on load fitMode: VideoFitMode.cover, // How video fills screen (7 modes) useSafeArea: false, // Wrap in SafeArea volume: 80.0, // Volume (0-100), default 80 enableAudio: true, // Enable/disable audio track hwdec: HwdecMode.autoSafe, // Hardware decode mode (9 modes) onPlayerInitialized: (player) { },// Player ready callback ) ``` **Parameters:** - `playImmediately` (bool) - Start playing immediately after load. Default: `true` - `fitMode` (VideoFitMode) - Video sizing strategy. Default: `VideoFitMode.cover` - `useSafeArea` (bool) - Avoid notches and system UI. Default: `false` - `volume` (double) - Initial volume 0-100. Default: `80.0` - `enableAudio` (bool) - Enable audio track (more efficient than volume=0). Default: `true` - `hwdec` (HwdecMode?) - Hardware decode mode. Default: `null` (smart platform defaults) - `onPlayerInitialized` (Function?) - Callback when Player is ready ### HwdecMode Hardware decode modes for video playback. When `null`, smart platform defaults are used: - **Windows**: `HwdecMode.autoSafe` (avoids CUDA errors on non-NVIDIA systems) - **Other platforms**: `HwdecMode.auto` ```dart // Let the package use smart defaults (recommended) VideoConfig() // hwdec: null // Explicit safe mode (recommended for Windows) VideoConfig(hwdec: HwdecMode.autoSafe) // Force software decoding VideoConfig(hwdec: HwdecMode.disabled) // Platform-specific VideoConfig(hwdec: HwdecMode.d3d11va) // Windows DirectX 11 VideoConfig(hwdec: HwdecMode.videoToolbox) // macOS/iOS VideoConfig(hwdec: HwdecMode.vaapi) // Linux VideoConfig(hwdec: HwdecMode.mediaCodec) // Android ``` **Available Modes:** | Mode | String Value | Description | |------|--------------|-------------| | `auto` | `"auto"` | Try all methods (may log errors on Windows) | | `autoSafe` | `"auto-safe"` | Safe methods only (Windows recommended) | | `autoCopy` | `"auto-copy"` | Auto with memory copy | | `disabled` | `"no"` | Software decoding only | | `d3d11va` | `"d3d11va"` | DirectX 11 (Windows) | | `dxva2` | `"dxva2"` | DirectX 9 (Windows legacy) | | `videoToolbox` | `"videotoolbox"` | VideoToolbox (macOS/iOS) | | `vaapi` | `"vaapi"` | VA-API (Linux) | | `mediaCodec` | `"mediacodec"` | MediaCodec (Android) | ### Source Types ```dart // Asset bundled with app AssetSource('assets/videos/splash.mp4') // Network URL NetworkFileSource('https://example.com/video.mp4') // Device file DeviceFileSource('/path/to/video.mp4') // Raw bytes BytesSource(videoBytes) ``` ## Migration Guide ### From v0.0.1 to v0.0.2+ The `VideoAspectRatio` enum has been replaced with `VideoFitMode` for better video sizing control: **Old API (Deprecated but still works):** ```dart VideoConfig( videoVisibilityEnum: VideoAspectRatio.useFullScreen, ) ``` **New API (Recommended):** ```dart VideoConfig( fitMode: VideoFitMode.cover, ) ``` **Migration Table:** | Old API | New API | Notes | |---------|---------|-------| | `VideoAspectRatio.useFullScreen` | `VideoFitMode.cover` | Now properly fills screen | | `VideoAspectRatio.useAspectRatio` | `VideoFitMode.contain` | Same behavior | | `VideoAspectRatio.none` | `VideoFitMode.none` | Same behavior | | `videoVisibilityEnum` parameter | `fitMode` parameter | Renamed for clarity | The old API will be removed in v1.0.0. Update your code to use the new `fitMode` parameter. ## Lifecycle Methods ```dart // Defer first frame (prevents jank) SplashVideo.initialize(); // Resumes frame painting SplashVideo( source: AssetSource(Assets.splashVideo), backgroundColor: Colors.black, onVideoComplete: () => _videoComplete(), videoConfig: const VideoConfig( scale: VideoScaleMode.fill, useSafeArea: true, ) ); ``` ## License MIT License - see LICENSE file for details.