ESite Flutter Player

pub package License: MIT Platform

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 initialization
  • initializing - Player is initializing
  • buffering - Loading content
  • ready - Ready to play
  • playing - Currently playing
  • paused - Playback paused
  • completed - Playback finished
  • error - Error occurred

DRM Event Types

  • licenseRequested - License acquisition started
  • licenseAcquired - License successfully obtained
  • licenseFailed - License acquisition failed

Error Codes

  • invalidSource - Invalid or inaccessible source URL
  • drmFailure - DRM license or provisioning failure
  • networkError - Network connection issues
  • playbackError - General playback errors
  • unknown - Unclassified errors

๐Ÿงช Testing

The plugin includes a comprehensive example app with 5 essential test scenarios:

cd example
flutter run

Test Scenarios:

  1. โœ… Valid Auth - No Security - Basic DRM playback without security (development mode)
  2. โœ… Valid Auth - Full Security - Production mode with complete security features
  3. โŒ Invalid Authentication - Network and authentication error handling
  4. ๐Ÿšซ Emulator Detection - Tests emulator blocking (blocks on emulator, works on device)
  5. ๐Ÿ”’ 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:

  1. Verify license URL and headers
  2. Check token expiration
  3. Test on L1/L3 compatible device
  4. 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 errorStream for 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:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Update documentation
  5. 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

๐Ÿ“ž Support

For issues, questions, or contributions:


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:

  1. โœ… Valid Auth - No Security
  2. โœ… Valid Auth - Full Security
  3. โŒ Invalid Authentication
  4. ๐Ÿšซ Emulator Detection
  5. ๐Ÿ”’ 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:

  1. Test on Android Studio emulator โ†’ Should block
  2. 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 emulator
  • screenCaptureAttempt - Screenshot/recording attempt
  • rootDetected - 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 configuration
  • Future<void> play() - Start playback
  • Future<void> pause() - Pause playback
  • Future<void> seekTo(Duration position) - Seek to specific position
  • Future<void> seekForward(Duration offset) - Seek forward by offset
  • Future<void> seekBackward(Duration offset) - Seek backward by offset
  • Future<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/unmute
  • Future<bool> isMuted() - Check if muted
  • Future<double> getVolume() - Get current volume
  • Future<List<Map<String, dynamic>>> getAvailableVideoTracks() - Get video tracks
  • Future<void> setVideoTrack(String trackId) - Select video track
  • Future<void> setSubtitleEnabled(bool enabled) - Enable/disable subtitles
  • Future<bool> isSubtitleEnabled() - Check subtitle status
  • Future<bool> hasNext() - Check if next item available
  • Future<bool> hasPrevious() - Check if previous item available
  • Future<void> next() - Go to next media item
  • Future<void> previous() - Go to previous media item
  • Future<void> dispose() - Dispose player and cleanup

Properties

  • ESitePlayerState currentState - Current playback state
  • Stream<ESitePlayerState> stateStream - Playback state stream
  • Stream<ESiteDrmEvent> drmStream - DRM events stream
  • Stream<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 state
  • initializing - Player initializing
  • buffering - Buffering content
  • ready - Ready to play
  • playing - Currently playing
  • paused - Paused
  • completed - Playback completed
  • error - Error occurred

ESiteDrmEvent

DRM event with type and optional message:

  • licenseRequested - License request initiated
  • licenseAcquired - License successfully acquired
  • licenseFailed - License acquisition failed

ESitePlayerError

Error with code and message:

  • invalidSource - Invalid source URL
  • drmFailure - DRM-related failure
  • networkError - Network error
  • playbackError - Playback error
  • unknown - 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

  1. Verify license URL is correct
  2. Check license headers (especially Axinom headers)
  3. Ensure device supports Widevine L1 or L3
  4. Test on physical device (not emulator)

Playback Doesn't Start

  1. Check network connectivity
  2. Verify source URL is accessible
  3. Check DRM configuration
  4. 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.