ESite Flutter Player
A professional, production-ready Flutter plugin for secure DRM-protected video playback on Android.
Built on Media3 ExoPlayer, this plugin provides enterprise-grade video streaming with comprehensive security features, making it ideal for premium content delivery, OTT platforms, and educational applications.
โจ Key Features
๐ Enterprise Security
- DRM Protection: Industry-standard Widevine and PlayReady DRM support
- Screen Protection: Automatic screenshot and screen recording blocking (FLAG_SECURE)
- Emulator Detection: Block playback on emulators to prevent piracy
- Security Callbacks: Real-time security violation notifications
- Configurable Levels: Maximum, custom, or disabled security modes
๐ฌ Professional Playback
- Adaptive Streaming: Automatic quality adaptation for optimal viewing
- Format Support: DASH (.mpd) and HLS (.m3u8) streaming protocols
- Speed Control: Variable playback speed (0.5x to 4.0x)
- Quality Selection: Manual video track/quality switching
- Subtitle Support: Enable/disable subtitle tracks
- Playlist Management: Navigate between media items
๐ฏ Production Ready
- Memory Optimized: Efficient resource management
- Thread Safe: Proper lifecycle and synchronization
- Error Handling: User-friendly error messages with recovery guidance
- Event Streams: Real-time state, DRM, and error notifications
- Null Safety: Full Dart null-safety support
๐ฑ Platform Support
| Platform | Supported | Version |
|---|---|---|
| Android | โ | API 24+ (Android 7.0+) |
| iOS | โณ | Planned for future release |
๐ฅ Supported Formats
- DASH (.mpd) - Dynamic Adaptive Streaming over HTTP
- HLS (.m3u8) - HTTP Live Streaming
- DRM Schemes: Widevine, PlayReady
๐ฆ Installation
Add this to your package's pubspec.yaml file:
dependencies:
esite_flutter_player: ^0.1.0
Then run:
flutter pub get
๐ Quick Start
1. Import the Package
import 'package:esite_flutter_player/esite_flutter_player.dart';
2. Create Player Configuration
final config = ESitePlayerConfig(
sourceUrl: 'https://example.com/video.mpd',
drm: ESiteDrmConfig(
licenseUrl: 'https://drm-license-server.com/license',
scheme: ESiteDrmScheme.widevine,
licenseHeaders: {
'Authorization': 'Bearer your_token_here',
},
),
autoPlay: false,
// Security configuration (recommended)
securityConfig: ESiteSecurityConfig.maximum(
onSecurityViolation: (violation) {
print('Security alert: ${violation.message}');
},
),
);
3. Initialize Controller
final controller = ESitePlayerController(config);
await controller.initialize();
4. Add Player to Your Widget Tree
@override
Widget build(BuildContext context) {
return ESitePlayerView(controller: controller);
}
5. Control Playback
// Play/Pause
await controller.play();
await controller.pause();
// Seek
await controller.seekTo(Duration(seconds: 30));
await controller.seekForward(Duration(seconds: 10));
await controller.seekBackward(Duration(seconds: 10));
// Speed control
await controller.setPlaybackSpeed(1.5); // 0.5x to 4.0x
// Volume
await controller.setVolume(0.8); // 0.0 to 1.0
await controller.setMuted(true);
// Quality selection
final tracks = await controller.getAvailableVideoTracks();
await controller.setVideoTrack(tracks[0]['id']);
// Subtitles
await controller.setSubtitleEnabled(true);
// Playlist navigation
if (await controller.hasNext()) {
await controller.next();
}
6. Listen to Events
// Player state changes
controller.stateStream.listen((state) {
print('State: $state');
// States: idle, initializing, buffering, ready,
// playing, paused, completed, error
});
// DRM events
controller.drmStream.listen((event) {
print('DRM: ${event.type} - ${event.message}');
// Types: licenseRequested, licenseAcquired, licenseFailed
});
// Error handling
controller.errorStream.listen((error) {
print('Error: ${error.code} - ${error.message}');
// Detailed, user-friendly error messages
});
7. Cleanup
@override
void dispose() {
controller.dispose();
super.dispose();
}
๐ Complete Example
import 'package:flutter/material.dart';
import 'package:esite_flutter_player/esite_flutter_player.dart';
class VideoPlayerPage extends StatefulWidget {
@override
_VideoPlayerPageState createState() => _VideoPlayerPageState();
}
class _VideoPlayerPageState extends State<VideoPlayerPage> {
late ESitePlayerController controller;
ESitePlayerState currentState = ESitePlayerState.idle;
@override
void initState() {
super.initState();
final config = ESitePlayerConfig(
sourceUrl: 'https://media.axprod.net/TestVectors/Cmaf/protected_1080p_h264_cbcs/manifest.mpd',
drm: ESiteDrmConfig(
licenseUrl: 'https://drm-widevine-licensing.axprod.net/AcquireLicense?AxDrmMessage=...',
scheme: ESiteDrmScheme.widevine,
licenseHeaders: {},
),
autoPlay: false,
securityConfig: ESiteSecurityConfig.maximum(),
);
controller = ESitePlayerController(config);
controller.initialize();
controller.stateStream.listen((state) {
setState(() => currentState = state);
});
controller.errorStream.listen((error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${error.message}')),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Video Player')),
body: Column(
children: [
Expanded(
child: ESitePlayerView(controller: controller),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.replay_10),
onPressed: () => controller.seekBackward(Duration(seconds: 10)),
),
IconButton(
icon: Icon(
currentState == ESitePlayerState.playing
? Icons.pause
: Icons.play_arrow,
),
iconSize: 48,
onPressed: () {
if (currentState == ESitePlayerState.playing) {
controller.pause();
} else {
controller.play();
}
},
),
IconButton(
icon: Icon(Icons.forward_10),
onPressed: () => controller.seekForward(Duration(seconds: 10)),
),
],
),
],
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
See the example app for a comprehensive implementation with:
- 10 test scenarios including:
- Valid authentication with DRM
- Error cases (expired auth, invalid URLs, network errors)
- Security scenarios (screen protection, emulator blocking)
- Full UI controls (play, pause, seek, speed, volume)
- Real-time event logging
- Error handling with retry functionality
- Quality selection interface
- Security violation handling
๐ Security Features
Screen Protection
The plugin provides comprehensive screen protection features:
- โ Screenshots blocked - Prevents screenshot capture
- โ Screen recording blocked - Prevents screen recording
- โ Recent apps protection - Blurs content in recent apps switcher
Using Security Configuration (Recommended):
final config = ESitePlayerConfig(
sourceUrl: 'https://example.com/video.mpd',
drm: ESiteDrmConfig(
licenseUrl: 'https://license-server.com/license',
scheme: ESiteDrmScheme.widevine,
),
// Maximum security (default)
securityConfig: ESiteSecurityConfig.maximum(
onSecurityViolation: (violation) {
print('Security violation: ${violation.message}');
},
),
);
Security Configurations:
// Maximum security (recommended for production)
securityConfig: ESiteSecurityConfig.maximum()
// Custom configuration
securityConfig: ESiteSecurityConfig(
blockEmulators: true, // Block playback on emulators
enableScreenProtection: true, // Enable screen capture blocking
onSecurityViolation: (violation) { // Optional callback
// Handle security violations
switch (violation.type) {
case SecurityViolationType.emulatorDetected:
showError('Playback not supported on emulators');
break;
case SecurityViolationType.screenCaptureAttempt:
showError('Screen recording is blocked');
break;
}
},
)
// Development mode (all security disabled)
securityConfig: ESiteSecurityConfig.disabled()
Backward Compatibility:
The deprecated flags still work but use the new securityConfig instead:
// โ Deprecated (still works)
preventScreenshots: true,
preventScreenRecording: true,
// โ
Recommended
securityConfig: ESiteSecurityConfig(
enableScreenProtection: true,
)
Emulator Detection
Protect premium content by blocking playback on emulators:
securityConfig: ESiteSecurityConfig(
blockEmulators: true, // Will prevent playback on emulators
onSecurityViolation: (violation) {
if (violation.type == SecurityViolationType.emulatorDetected) {
// Show user-friendly error message
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Device Not Supported'),
content: Text('Playback is not available on emulators'),
),
);
}
},
)
Detection Methods:
- Build properties analysis (product, model, brand, device)
- Hardware identifiers (goldfish, ranchu)
- Emulator-specific files (/dev/qemu_pipe, etc.)
- System properties (ro.kernel.qemu, ro.hardware)
- Known emulators (Genymotion, BlueStacks, NOX, Andy)
Security Behavior:
- ๐ข Enabled when playback starts
- ๐ด Disabled when playback pauses or ends
- ๐ด Disabled when player is disposed
- ๐ Emulator check runs during initialization
This ensures security is active only during actual content playback.
DRM Protection
- Widevine DRM support for encrypted content
- PlayReady support
- Secure license acquisition with custom headers
- No DRM tokens or license details logged
- Generic error messages to prevent information leakage
โ๏ธ Configuration Options
ESitePlayerConfig
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sourceUrl |
String |
Yes | - | Video source URL (.mpd or .m3u8) |
drm |
ESiteDrmConfig |
Yes | - | DRM configuration |
securityConfig |
ESiteSecurityConfig |
No | ESiteSecurityConfig() |
Security settings |
headers |
Map<String, String>? |
No | null |
Custom HTTP headers for media requests |
autoPlay |
bool |
No | false |
Start playback automatically |
preventScreenshots |
bool |
No | false |
Deprecated Use securityConfig instead |
preventScreenRecording |
bool |
No | false |
Deprecated Use securityConfig instead |
ESiteSecurityConfig
| Parameter | Type | Default | Description |
|---|---|---|---|
blockEmulators |
bool |
true |
Block playback on emulators |
enableScreenProtection |
bool |
true |
Enable screen capture/recording protection |
onSecurityViolation |
Function? |
null |
Callback for security violations |
ESiteDrmConfig
| Parameter | Type | Required | Description |
|---|---|---|---|
licenseUrl |
String |
Yes | DRM license server URL |
scheme |
ESiteDrmScheme |
Yes | DRM scheme (widevine/playready) |
licenseHeaders |
Map<String, String>? |
No | Custom headers for license requests |
๐ API Reference
Controller Methods
| Method | Return Type | Description |
|---|---|---|
initialize() |
Future<void> |
Initialize player with configuration |
play() |
Future<void> |
Start or resume playback |
pause() |
Future<void> |
Pause playback |
seekTo(Duration) |
Future<void> |
Seek to specific position |
seekForward(Duration) |
Future<void> |
Seek forward by offset |
seekBackward(Duration) |
Future<void> |
Seek backward by offset |
setPlaybackSpeed(double) |
Future<void> |
Set speed (0.5-4.0) |
setVolume(double) |
Future<void> |
Set volume (0.0-1.0) |
setMuted(bool) |
Future<void> |
Mute/unmute audio |
isMuted() |
Future<bool> |
Check if muted |
getVolume() |
Future<double> |
Get current volume |
getAvailableVideoTracks() |
Future<List<Map>> |
Get available quality tracks |
setVideoTrack(String) |
Future<void> |
Select quality track |
setSubtitleEnabled(bool) |
Future<void> |
Enable/disable subtitles |
isSubtitleEnabled() |
Future<bool> |
Check subtitle status |
hasNext() |
Future<bool> |
Check if next item exists |
hasPrevious() |
Future<bool> |
Check if previous item exists |
next() |
Future<void> |
Navigate to next item |
previous() |
Future<void> |
Navigate to previous item |
dispose() |
Future<void> |
Cleanup and release resources |
Event Streams
| Stream | Type | Description |
|---|---|---|
stateStream |
Stream<ESitePlayerState> |
Player state changes |
drmStream |
Stream<ESiteDrmEvent> |
DRM-related events |
errorStream |
Stream<ESitePlayerError> |
Error events |
Player States
idle- Initial state before initializationinitializing- Player is initializingbuffering- Loading contentready- Ready to playplaying- Currently playingpaused- Playback pausedcompleted- Playback finishederror- Error occurred
DRM Event Types
licenseRequested- License acquisition startedlicenseAcquired- License successfully obtainedlicenseFailed- License acquisition failed
Error Codes
invalidSource- Invalid or inaccessible source URLdrmFailure- DRM license or provisioning failurenetworkError- Network connection issuesplaybackError- General playback errorsunknown- Unclassified errors
๐งช Testing
The plugin includes a comprehensive example app with 5 essential test scenarios:
cd example
flutter run
Test Scenarios:
- โ Valid Auth - No Security - Basic DRM playback without security (development mode)
- โ Valid Auth - Full Security - Production mode with complete security features
- โ Invalid Authentication - Network and authentication error handling
- ๐ซ Emulator Detection - Tests emulator blocking (blocks on emulator, works on device)
- ๐ Screenshot Protection - Tests screen capture prevention (try screenshot during playback)
Each scenario demonstrates proper error handling, security features, and user feedback.
Testing on Different Devices
On Android Emulator:
- Scenarios 1, 2, 3, 5: Should work (with security active)
- Scenario 4: Should block with error message
On Physical Device:
- All scenarios should work
- Scenario 4 proves emulator detection doesn't block real devices
- Scenario 5 proves screenshot blocking works (try capturing screen during playback)
โ ๏ธ Known Limitations
Android Emulator
DRM-protected content may not work properly on Android emulators due to:
- Limited Widevine L3 support (L1 typically unavailable)
- Missing hardware security features
- Incomplete DRM stack implementation
Recommendation: Always test on physical Android devices for DRM functionality.
iOS Platform
iOS support is planned but not yet implemented. The plugin currently throws UnsupportedError on iOS platforms.
๐ง Troubleshooting
"No suitable media source factory found"
Cause: Missing DASH/HLS dependencies Solution: Plugin automatically includes required dependencies. If error persists:
cd example
flutter clean && rm -rf build/ .dart_tool/
flutter pub get
DRM License Acquisition Fails
Possible Causes:
- Invalid or expired license token
- Incorrect license URL
- Missing required headers
- Device DRM level incompatibility
Solutions:
- Verify license URL and headers
- Check token expiration
- Test on L1/L3 compatible device
- Review error messages in
errorStream
Video Doesn't Play
Checklist:
- โ Internet connection available
- โ Source URL accessible
- โ DRM configuration correct
- โ Testing on physical device (not emulator)
- โ
Listening to
errorStreamfor details
Performance Issues
Optimization Tips:
- Use appropriate quality tracks for device
- Ensure good network connectivity
- Avoid multiple player instances
- Properly dispose controllers when done
๐ค Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Update documentation
- Submit a pull request
Please follow:
- Dart/Flutter style guidelines
- Include tests for new features
- Update README for API changes
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
- Built with Media3 ExoPlayer
- DRM integration with Axinom
- Test content provided by Axinom test vectors
๐ Support
For issues, questions, or contributions:
- ๐ Report bugs
- ๐ก Request features
- ๐ View documentation
Made with โค๏ธ for secure video streaming in Flutter
- โ DRM Support: Widevine DRM via Axinom integration
- โ Security: Automatic screenshot and screen recording blocking (FLAG_SECURE)
- โ Playback Controls: Play, pause, seek, speed control (0.5x-4x)
- โ Track Selection: Video quality selection
- โ Subtitle Support: Enable/disable subtitles
- โ Volume Control: Volume adjustment and mute
- โ Playlist Support: Next/previous media item navigation
- โ Event Streams: Real-time playback state, DRM events, and error handling
- โ Production Ready: Secure, no DRM data logging, proper lifecycle management
Platform Support
- โ Android: Full support (Media3 ExoPlayer)
- โ iOS: Not yet implemented (planned for future release)
Installation
Add esite_flutter_player to your pubspec.yaml:
dependencies:
esite_flutter_player: ^0.1.0
Then run:
flutter pub get
Quick Start
1. Basic Setup
import 'package:esite_flutter_player/esite_flutter_player.dart';
// Create player configuration
final config = ESitePlayerConfig(
sourceUrl: 'https://your-cdn.com/video.mpd',
drm: ESiteDrmConfig(
licenseUrl: 'https://license.axinom.com/widevine',
scheme: ESiteDrmScheme.widevine,
licenseHeaders: {
'X-AxDRM-Message': 'your-license-header',
},
),
autoPlay: false,
);
// Create controller
final controller = ESitePlayerController(config);
// Initialize player
await controller.initialize();
2. Display Video Player
ESitePlayerView(controller: controller)
3. Control Playback
// Play
await controller.play();
// Pause
await controller.pause();
// Seek to position
await controller.seekTo(Duration(seconds: 30));
// Seek forward/backward
await controller.seekForward(Duration(seconds: 10));
await controller.seekBackward(Duration(seconds: 10));
// Set playback speed (0.5x to 4x)
await controller.setPlaybackSpeed(1.5);
// Volume control
await controller.setVolume(0.8);
await controller.setMuted(true);
// Track selection
final tracks = await controller.getAvailableVideoTracks();
await controller.setVideoTrack(tracks[0]['id']);
// Subtitle control
await controller.setSubtitleEnabled(true);
// Playlist navigation
if (await controller.hasNext()) {
await controller.next();
}
4. Listen to Events
// Playback state
controller.stateStream.listen((state) {
print('Player state: $state');
// States: idle, initializing, buffering, ready, playing, paused, completed, error
});
// DRM events
controller.drmStream.listen((event) {
print('DRM event: ${event.type}');
// Types: licenseRequested, licenseAcquired, licenseFailed
});
// Errors
controller.errorStream.listen((error) {
print('Error: ${error.code} - ${error.message}');
});
5. Cleanup
@override
void dispose() {
controller.dispose();
super.dispose();
}
๐ Complete Example
See the example app for a full implementation with 5 test scenarios:
- โ Valid Auth - No Security
- โ Valid Auth - Full Security
- โ Invalid Authentication
- ๐ซ Emulator Detection
- ๐ Screenshot Protection
๐ Security Configuration
ESiteSecurityConfig
Configure security features to protect your premium content.
Security Options
| Parameter | Type | Default | Description |
|---|---|---|---|
blockEmulators |
bool |
true |
Block playback on emulators |
enableScreenProtection |
bool |
true |
Enable screenshot/recording blocking |
onSecurityViolation |
Function? |
null |
Callback for security events |
Security Presets
1. Maximum Security (Production)
ESitePlayerConfig(
sourceUrl: videoUrl,
drm: drmConfig,
securityConfig: ESiteSecurityConfig.maximum(
onSecurityViolation: (violation) {
print('Security alert: ${violation.message}');
analytics.logSecurityEvent(violation.type.name);
},
),
)
โ
Emulator detection enabled
โ
Screenshot blocking enabled
โ
Security monitoring active
2. Custom Configuration
securityConfig: ESiteSecurityConfig(
blockEmulators: true, // Block emulators
enableScreenProtection: true, // Block screenshots
onSecurityViolation: (violation) {
// Handle security events
},
)
3. Development Mode
securityConfig: ESiteSecurityConfig.disabled() // All security off
4. Selective Security
// Emulator detection only
ESiteSecurityConfig(blockEmulators: true, enableScreenProtection: false)
// Screenshot protection only
ESiteSecurityConfig(blockEmulators: false, enableScreenProtection: true)
Screenshot & Screen Recording Protection
Automatically blocks screenshots and screen recording using Android's FLAG_SECURE.
What's Protected:
- โ Screenshots (Power + Volume Down)
- โ Screen recording apps
- โ Recent apps preview
- โ Display mirroring (some cases)
Security Lifecycle:
- ๐ข Enabled when playback starts
- ๐ด Disabled when paused or ended
- ๐ด Disabled when player disposed
Example:
ESitePlayerConfig(
sourceUrl: videoUrl,
drm: drmConfig,
securityConfig: ESiteSecurityConfig(
enableScreenProtection: true,
),
)
Emulator Detection
Multi-method emulator detection to prevent automated piracy.
Detection Methods:
- Build properties analysis
- Hardware identifier checks
- Emulator-specific file detection
- System property inspection
- Known emulator signatures
Behavior:
- On Emulator: Playback blocked with error
- On Real Device: Plays normally
Configuration:
ESitePlayerConfig(
sourceUrl: videoUrl,
drm: drmConfig,
securityConfig: ESiteSecurityConfig(
blockEmulators: true,
onSecurityViolation: (violation) {
if (violation.type == SecurityViolationType.emulatorDetected) {
showDialog('Playback not available on emulators');
}
},
),
)
Testing:
- Test on Android Studio emulator โ Should block
- Test on physical device โ Should play
Security Violation Callbacks
Monitor security events in real-time:
securityConfig: ESiteSecurityConfig.maximum(
onSecurityViolation: (SecurityViolation violation) {
// Log security incident
logger.warn('Security: ${violation.type} - ${violation.message}');
// Track in analytics
analytics.logEvent('security_violation', {
'type': violation.type.name,
'timestamp': violation.timestamp,
});
// Handle based on type
switch (violation.type) {
case SecurityViolationType.emulatorDetected:
showError('Device not supported');
break;
case SecurityViolationType.screenCaptureAttempt:
showWarning('Screen recording blocked');
break;
}
},
)
Violation Types:
emulatorDetected- Playback blocked on emulatorscreenCaptureAttempt- Screenshot/recording attemptrootDetected- Rooted device (future)tampering- App tampering (future)
Environment-Based Configuration
ESiteSecurityConfig getSecurityConfig() {
if (kDebugMode) {
return ESiteSecurityConfig.disabled(); // Development
}
if (environment == 'staging') {
return ESiteSecurityConfig(
blockEmulators: false,
enableScreenProtection: true,
);
}
return ESiteSecurityConfig.maximum(); // Production
}
Backward Compatibility
Old flags still work but use securityConfig instead:
// โ Deprecated (still works)
ESitePlayerConfig(
preventScreenshots: true,
preventScreenRecording: true,
)
// โ
Recommended
ESitePlayerConfig(
securityConfig: ESiteSecurityConfig(
enableScreenProtection: true,
),
)
DRM Configuration
Axinom Widevine Setup
ESiteDrmConfig(
licenseUrl: 'https://license.axinom.com/widevine',
scheme: ESiteDrmScheme.widevine,
licenseHeaders: {
'X-AxDRM-Message': 'your-license-header',
'Authorization': 'Bearer your-token',
},
)
Supported DRM Schemes
ESiteDrmScheme.widevine- Widevine DRM (Axinom)
API Reference
ESitePlayerController
Main controller for video playback.
Methods
Future<void> initialize()- Initialize the player with configurationFuture<void> play()- Start playbackFuture<void> pause()- Pause playbackFuture<void> seekTo(Duration position)- Seek to specific positionFuture<void> seekForward(Duration offset)- Seek forward by offsetFuture<void> seekBackward(Duration offset)- Seek backward by offsetFuture<void> setPlaybackSpeed(double speed)- Set speed (0.5-4.0)Future<void> setVolume(double volume)- Set volume (0.0-1.0)Future<void> setMuted(bool muted)- Mute/unmuteFuture<bool> isMuted()- Check if mutedFuture<double> getVolume()- Get current volumeFuture<List<Map<String, dynamic>>> getAvailableVideoTracks()- Get video tracksFuture<void> setVideoTrack(String trackId)- Select video trackFuture<void> setSubtitleEnabled(bool enabled)- Enable/disable subtitlesFuture<bool> isSubtitleEnabled()- Check subtitle statusFuture<bool> hasNext()- Check if next item availableFuture<bool> hasPrevious()- Check if previous item availableFuture<void> next()- Go to next media itemFuture<void> previous()- Go to previous media itemFuture<void> dispose()- Dispose player and cleanup
Properties
ESitePlayerState currentState- Current playback stateStream<ESitePlayerState> stateStream- Playback state streamStream<ESiteDrmEvent> drmStream- DRM events streamStream<ESitePlayerError> errorStream- Error stream
ESitePlayerConfig
Player configuration.
ESitePlayerConfig({
required String sourceUrl,
required ESiteDrmConfig drm,
Map<String, String>? headers,
bool autoPlay = false,
bool preventScreenshots = false,
bool preventScreenRecording = false,
})
ESiteDrmConfig
DRM configuration.
ESiteDrmConfig({
required String licenseUrl,
required ESiteDrmScheme scheme,
Map<String, String>? licenseHeaders,
})
ESitePlayerState
Playback state enum:
idle- Initial stateinitializing- Player initializingbuffering- Buffering contentready- Ready to playplaying- Currently playingpaused- Pausedcompleted- Playback completederror- Error occurred
ESiteDrmEvent
DRM event with type and optional message:
licenseRequested- License request initiatedlicenseAcquired- License successfully acquiredlicenseFailed- License acquisition failed
ESitePlayerError
Error with code and message:
invalidSource- Invalid source URLdrmFailure- DRM-related failurenetworkError- Network errorplaybackError- Playback errorunknown- Unknown error
Limitations
Emulator Support
DRM-protected content may not work on Android emulators due to:
- Lack of Widevine L1 support
- Missing hardware security features
Recommendation: Test on physical Android devices.
iOS Support
iOS support is planned for a future release. The plugin currently throws UnsupportedError on iOS.
Testing
This plugin includes comprehensive test coverage across multiple levels:
Running Tests
# Run all unit tests
flutter test
# Run with coverage report
flutter test --coverage
# Run integration tests (requires device/emulator)
cd example
flutter test integration_test/
# Run Android native tests
cd example/android
./gradlew test
# Generate HTML coverage report
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html
Test Coverage
The plugin maintains high test coverage across:
- โ Unit Tests (70%): Controller, configuration, events, platform layer
- โ Widget Tests (20%): Player view and UI components
- โ Integration Tests (10%): End-to-end playback, DRM, security
Coverage Goals:
- Overall: >85%
- Controller: >95%
- Configuration: 100%
- Events/Errors: 100%
Troubleshooting
DRM License Fails
- Verify license URL is correct
- Check license headers (especially Axinom headers)
- Ensure device supports Widevine L1 or L3
- Test on physical device (not emulator)
Playback Doesn't Start
- Check network connectivity
- Verify source URL is accessible
- Check DRM configuration
- Monitor error stream for details
Security Not Working
- Security (FLAG_SECURE) is only enabled during active playback
- Ensure player is in playing state
- Check that activity is properly attached
Contributing
Contributions are welcome! Please ensure:
- Code follows Dart/Flutter style guidelines
- Tests are included for new features
- Documentation is updated
License
See LICENSE file for details.
Support
For issues and questions:
- Open an issue on GitHub
- Check the example app for usage patterns---Note: This plugin is designed for production use with secure DRM content. Always test thoroughly on physical devices before deploying.