bmdrm_mobile 0.0.26 copy "bmdrm_mobile: ^0.0.26" to clipboard
bmdrm_mobile: ^0.0.26 copied to clipboard

A Flutter plugin for DRM-protected video playback with watermark support.

BMDRM Mobile #

A Flutter plugin for DRM-protected video playback with watermark support.

Features #

  • DRM Support: Supports Widevine, FairPlay, and PlayReady DRM systems
  • Resume Playback: Remembers the last watched position per video on the device and resumes automatically
  • Watermark Overlay: Dynamic and static watermark support with customizable positioning and styling
  • Platform Optimized: Native Android implementation using ExoPlayer with Media3
  • Cryptographic Security: Real ECDSA signature generation and ECDH key exchange
  • Session Management: Automatic DRM session generation and management
  • Error Handling: Comprehensive error handling and logging
  • Code Obfuscation: Production-ready obfuscation support for both Android and iOS

Installation #

Add this to your pubspec.yaml:

dependencies:
  bmdrm_mobile: ^0.0.6

Usage #

Basic Usage #

import 'package:bmdrm_mobile/bmdrm_mobile.dart';

// Automatic DRM session generation
BmdrmVideoPlayer.drmMinimal(
  apiKey: 'your-api-key',
  userId: 'user-123',
  videoId: 'video-456',
  watermarkConfig: WatermarkConfig(
    id: 'watermark-1',
    type: 'static',
    name: 'My Watermark',
    text: 'Protected Content',
    alpha: 0.8,
    color: '#FFFFFF',
    size: 16,
    interval: 0,
    skip: 0,
    x: 10.0,
    y: 10.0,
  ),
  onPlay: () {},
  onPause: () {},
  onError: (error) {},
)

Pre-generated Session Data #

// Using pre-generated DRM session data
BmdrmVideoPlayer.drmWithSession(
  drmSessionData: DrmSessionData(
    edgeName: 'edge-server',
    token: 'session-token',
    ecdsaKey: 'signing-key',
    drmServerUrl: 'https://drm-server.com/license',
  ),
  watermarkConfig: WatermarkConfig(...),
)

Resume from Last Position #

Playback automatically resumes from where the viewer last stopped. The position is saved on the device (Android SharedPreferences / iOS UserDefaults) as the video plays, on pause, and when the player is closed, then restored the next time the same video is opened. A fully watched video starts over from the beginning.

This is enabled by default. Each video is keyed by a stable resume id, resolved in this order: the explicit resumeId, then videoId, then the videoId inside a backend session. If none is available, resume is silently disabled for that playback.

BmdrmVideoPlayer.drmMinimal(
  apiKey: 'your-api-key',
  userId: 'user-123',
  videoId: 'video-456',          // used as the resume key when resumeId is omitted
  resumeId: 'course-1/lesson-3', // optional explicit, stable key
  resumeFromLastPosition: true,  // default; set false to always start at 0 and skip saving
)

To forget a saved position (e.g. a "Start over" action) or read it back, use the static helpers (they don't touch any player that's currently on screen):

await VideoPlayerController.clearSavedPosition('video-456');           // forget it
final Duration last = await VideoPlayerController.getSavedPosition('video-456'); // read it

Resume is per device. Positions are not synced across devices or accounts; use the onPositionChanged callback if you want to persist progress to your backend.

Customizable Fullscreen Drawer #

Pass a fullscreenDrawerBuilder to show a customizable, app-owned panel beside the video while in fullscreen — like a "Resources / Notes / Q&A" side panel. The plugin provides the sliding panel, a toggle button and an exit-fullscreen button; you provide the content (any Flutter widget).

Passing the builder switches fullscreen to a Flutter-rendered mode (so your widget can be drawn), and the video shrinks to sit beside the open drawer rather than behind it. When you don't pass a builder, fullscreen keeps its native behaviour, unchanged.

BmdrmVideoPlayer.drmFromBackendSession(
  backendSession: session,
  // Any widget you want. `controller` lets the panel close itself or leave
  // fullscreen from a button inside it.
  fullscreenDrawerBuilder: (context, controller) => MyLessonPanel(
    onClose: controller.closeDrawer,
  ),
  fullscreenDrawerIcon: const Icon(Icons.menu_book_rounded), // toggle button icon
  fullscreenDrawerWidth: 360,                 // optional; defaults to ~38% of width
  fullscreenDrawerAlignment: Alignment.centerRight, // or centerLeft
  fullscreenDrawerBackgroundColor: Colors.white,     // optional
)

Drive it programmatically (e.g. from your own button) with a BmdrmFullscreenController:

final fsController = BmdrmFullscreenController();

BmdrmVideoPlayer.drmFromBackendSession(
  backendSession: session,
  fullscreenController: fsController,
  fullscreenDrawerBuilder: (context, controller) => MyLessonPanel(),
);

// later:
fsController.enterFullscreen();
fsController.openDrawer();
// fsController.isFullscreen / fsController.isDrawerOpen — and it's a
// ChangeNotifier, so you can listen for changes.

The drawer is rendered by Flutter, so the player must sit under a MaterialApp (or otherwise have an Overlay / Navigator ancestor), which is the normal case.

Video Quality #

The player reads the resolutions the stream carries (1080p, 720p, …) and lets the viewer pin one instead of leaving every switch to adaptive bitrate. On Android this is built in: the control bar's speed chip is a gear button opening a settings menu with Speed and Quality rows. On iOS AVKit has nowhere to put a quality control, so the plugin exposes quality programmatically and your app draws the picker — the API below is the same on both platforms.

This is not the quality you pass to DartDrmService.generateDrmStreamUrls ('h264', '720p', '4k'), which picks the codec profile of the manifest that gets fetched. Everything on the resolution axis is named videoQuality.

BmdrmVideoPlayer.drmFromBackendSession(
  backendSession: session,
  // Fires on every change — Android's in-player menu and selectVideoQuality()
  // below both land here.
  onVideoQualityChanged: (BmdrmVideoQuality quality) {
    debugPrint('now playing ${quality.label}'); // '1080p', or 'Auto'
  },
)

Drive it yourself (and on iOS, that is the only way) with a BmdrmFullscreenController:

final fsController = BmdrmFullscreenController();

BmdrmVideoPlayer.drmFromBackendSession(
  backendSession: session,
  fullscreenController: fsController,
);

// later — it's a ChangeNotifier, so rebuild your picker when it notifies:
final List<BmdrmVideoQuality> qualities = fsController.videoQualities;
// highest resolution first; each has id, width, height, bitrate and a label
fsController.selectVideoQuality(qualities.first.id); // pin the best rendition
fsController.selectVideoQuality(-1);                 // back to Auto
// fsController.currentVideoQuality — BmdrmVideoQuality.auto until something
// is pinned.

A pin lasts as long as the media does. Loading a video — a new BmdrmVideoPlayer, or a setDrmConfig on an existing one — resets the selection to Auto and empties videoQualities on both platforms, then repopulates the list once the new stream's renditions are known. Nothing carries a pin from one video to the next: ids index one stream's rendition list and even a matching height can mean a different bitrate in another stream. If you want a viewer's choice to stick across videos, remember it in your app and re-apply it from onVideoQualityChanged once the new list arrives.

The reverse also holds while one video plays: a pin survives everything the player does internally, including the player rebuild that DRM configuration triggers on Android and the item replacement iOS does, so you will not see a selection silently fall back mid-playback. It can be dropped if the rendition you pinned disappears from the stream — that arrives as an onVideoQualityChanged carrying Auto, so treat the callback (not your own last request) as the truth.

Listing renditions needs iOS 15+ (it reads the multivariant playlist). On older iOS the list stays empty and playback runs on Auto; Android has no such floor. The list is also empty until the stream's renditions are known, so hide or disable your picker while it is.

See example/lib/main.dart for a working picker built on this API.

DRM Service Usage #

// Generate DRM session
final sessionJson = await DartDrmService.generateSession(
  apiKey: 'your-api-key',
  userId: 'user-123',
  videoId: 'video-456',
  platform: 'Android',
);

// Parse session data
final sessionData = DartDrmService.parseSessionResponse(sessionJson);

// Generate stream URLs
final drmUrls = DartDrmService.generateDrmStreamUrls(
  sessionData: sessionData,
  streamType: 'widevine',
  quality: 'h264',
);

Configuration #

Watermark Configuration #

The WatermarkConfig class supports the following properties:

  • id: Unique identifier for the watermark
  • type: Either 'static' or 'dynamic'
  • name: Display name for the watermark
  • text: The text to display
  • alpha: Opacity (0.0 to 1.0)
  • color: Hex color code (e.g., '#FFFFFF')
  • size: Font size in pixels
  • interval: Animation interval in milliseconds (for dynamic watermarks)
  • skip: Number of intervals to skip
  • x, y: Position as percentage of video dimensions

DRM Session Data #

The DrmSessionData class contains:

  • edgeName: DRM edge server name
  • token: Session authentication token
  • ecdsaKey: ECDSA signing key for URL signing
  • drmServerUrl: License server URL
  • kid: Key ID (optional)
  • certificateUrl: Certificate URL (optional)
  • watermark: Embedded watermark configuration (optional)

Platform Support #

  • Android: Uses ExoPlayer with Media3 for optimal performance
  • iOS: Uses AVPlayer with FairPlay DRM support
  • Web: Uses video.js with EME support (planned)

Dependencies #

The plugin requires the following dependencies in your pubspec.yaml:

dependencies:
  bmdrm_mobile: ^0.0.1

Internal dependencies (managed by the plugin):

  • crypto: ^3.0.3
  • http: ^1.1.0
  • pointycastle: ^3.7.3
  • plugin_platform_interface: ^2.0.2

Android Setup #

The plugin automatically includes the necessary Android dependencies:

  • androidx.media3:media3-exoplayer
  • androidx.media3:media3-ui
  • androidx.media3:media3-datasource
  • androidx.media3:media3-common
  • androidx.media3:media3-session
  • androidx.media3:media3-exoplayer-drm

iOS Setup #

For iOS, ensure you have the following in your ios/Podfile:

platform :ios, '12.0'

Example #

See the example/ directory for a complete working example.

Building for Production #

To build an obfuscated release:

cd example
flutter build apk --release --obfuscate --split-debug-info=build/debug-info

License #

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing #

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Support #

For support, please open an issue on GitHub or contact the development team.

2
likes
130
points
896
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for DRM-protected video playback with watermark support.

Homepage

License

Apache-2.0 (license)

Dependencies

crypto, flutter, http, path, plugin_platform_interface, pointycastle, sqflite

More

Packages that depend on bmdrm_mobile

Packages that implement bmdrm_mobile