esite_flutter_player 0.1.1
esite_flutter_player: ^0.1.1 copied to clipboard
Professional Flutter plugin for secure DRM-protected video playback on Android. Features Widevine/PlayReady DRM, emulator detection, screenshot/screen recording blocking (FLAG_SECURE), adaptive strea [...]
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.