421 lines
11 KiB
Markdown
421 lines
11 KiB
Markdown
# 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<MyScreen> createState() => _MyScreenState();
|
|
}
|
|
|
|
class _MyScreenState extends State<MyScreen> {
|
|
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.
|
|
|