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

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

example/lib/main.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:bmdrm_mobile/bmdrm_video_player.dart';
import 'package:bmdrm_mobile/bmdrm_resume_store.dart';

const String _sessionJson =
    '{"edge":"https://YOUR_EDGE_SERVER.video-crypt.com","videoId":"YOUR_VIDEO_ID","signature":"YOUR_SIGNATURE","userId":"YOUR_USER_ID","subscriptionId":"YOUR_SUBSCRIPTION_ID","sessionId":"YOUR_SESSION_ID","nonce":"YOUR_NONCE","date":0}';

class Lesson {
  const Lesson(this.id, this.title);
  final String id;
  final String title;
}

// Each lesson tracks its own resume position under its id.
const _lessons = [
  Lesson('lesson-1', 'Lesson 1 · DRM playback'),
  Lesson('lesson-2', 'Lesson 2 · Adaptive streaming'),
];

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final scheme = ColorScheme.fromSeed(seedColor: Colors.indigo);
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'BMDRM Player',
      theme: ThemeData(useMaterial3: true, colorScheme: scheme),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<ResumeEntry> _inProgress = const [];

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    final entries = await VideoPlayerController.continueWatching();
    if (mounted) setState(() => _inProgress = entries);
  }

  Future<void> _open(Lesson lesson) async {
    await Navigator.of(
      context,
    ).push(MaterialPageRoute(builder: (_) => PlayerPage(lesson: lesson)));
    _load();
  }

  String _titleFor(String id) =>
      _lessons.firstWhere((l) => l.id == id, orElse: () => Lesson(id, id)).title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('BMDRM Player')),
      body: ListView(
        children: [
          if (_inProgress.isNotEmpty) ...[
            const _SectionTitle('Continue watching'),
            for (final e in _inProgress)
              ListTile(
                leading: const Icon(Icons.play_circle_fill),
                title: Text(_titleFor(e.resumeId)),
                subtitle: LinearProgressIndicator(value: e.progress),
                trailing: Text('${(e.progress * 100).round()}%'),
                onTap: () => _open(Lesson(e.resumeId, _titleFor(e.resumeId))),
              ),
            const Divider(height: 24),
          ],
          const _SectionTitle('Lessons'),
          for (final lesson in _lessons)
            ListTile(
              leading: const Icon(Icons.ondemand_video),
              title: Text(lesson.title),
              onTap: () => _open(lesson),
            ),
        ],
      ),
    );
  }
}

class _SectionTitle extends StatelessWidget {
  const _SectionTitle(this.text);
  final String text;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
      child: Text(text, style: Theme.of(context).textTheme.titleMedium),
    );
  }
}

class PlayerPage extends StatefulWidget {
  const PlayerPage({super.key, required this.lesson});

  final Lesson lesson;

  @override
  State<PlayerPage> createState() => _PlayerPageState();
}

class _PlayerPageState extends State<PlayerPage> {
  // Also the handle for the quality picker below — see _QualityPicker.
  final BmdrmFullscreenController _fsController = BmdrmFullscreenController();

  @override
  void dispose() {
    _fsController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final videoHeight = (size.width * 9 / 16)
        .clamp(0.0, size.height * 0.7)
        .toDouble();
    return Scaffold(
      appBar: AppBar(title: Text(widget.lesson.title)),
      body: SingleChildScrollView(
        child: Column(
          children: [
            BmdrmVideoPlayer.drmFromBackendSession(
              backendSession: jsonDecode(_sessionJson),
              width: double.infinity,
              height: videoHeight,
              autoPlay: true,
              resumeId: widget.lesson.id,
              resumeFromLastPosition: true,
              fullscreenController: _fsController,
              onVideoQualityChanged: (quality) =>
                  debugPrint('Video quality is now ${quality.label}'),
              fullscreenDrawerBuilder: (context, controller) =>
                  _AboutDrawer(onClose: controller.closeDrawer),
              fullscreenDrawerToggle: const BmdrmDrawerToggle(
                label: 'À propos de cette leçon',
              ),
            ),
            _QualityPicker(controller: _fsController),
          ],
        ),
      ),
    );
  }
}

/// App-drawn video quality picker.
///
/// Android already has one inside the player (the gear button), but AVKit has
/// nowhere to hang a quality control, so on iOS a picker only exists if the app
/// draws it — from `videoQualities` / `selectVideoQuality`, as done here. The
/// controller is a `ChangeNotifier`, so the list and the current selection both
/// arrive as notifications once native knows the stream's renditions.
class _QualityPicker extends StatelessWidget {
  const _QualityPicker({required this.controller});

  final BmdrmFullscreenController controller;

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, _) {
        final qualities = controller.videoQualities;
        return ListTile(
          leading: const Icon(Icons.high_quality_outlined),
          title: const Text('Video quality'),
          subtitle: Text(
            qualities.isEmpty
                ? 'Only one rendition, or not known yet'
                : controller.currentVideoQuality.label,
          ),
          trailing: PopupMenuButton<int>(
            enabled: qualities.isNotEmpty,
            initialValue: controller.currentVideoQuality.id,
            onSelected: controller.selectVideoQuality,
            itemBuilder: (context) => <PopupMenuEntry<int>>[
              // -1 is Auto; it is never part of the reported list.
              const PopupMenuItem<int>(value: -1, child: Text('Auto')),
              for (final quality in qualities)
                PopupMenuItem<int>(
                  value: quality.id,
                  child: Text(quality.label),
                ),
            ],
          ),
        );
      },
    );
  }
}

/// Example content for the player's fullscreen drawer. Any Flutter widget works
/// here — the plugin only provides the sliding panel and the toggle button.
class _AboutDrawer extends StatelessWidget {
  const _AboutDrawer({required this.onClose});

  final VoidCallback onClose;

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      left: false,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 12, 8, 4),
            child: Row(
              children: [
                const Expanded(
                  child: Text(
                    'About this lesson',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
                  ),
                ),
                IconButton(icon: const Icon(Icons.close), onPressed: onClose),
              ],
            ),
          ),
          const Expanded(
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Text(
                'Your custom fullscreen panel goes here — put any Flutter '
                'widgets you like beside the video.',
              ),
            ),
          ),
        ],
      ),
    );
  }
}
2
likes
0
points
874
downloads

Publisher

unverified uploader

Weekly Downloads

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

Homepage

License

unknown (license)

Dependencies

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

More

Packages that depend on bmdrm_mobile

Packages that implement bmdrm_mobile