Skip to main content

max2000_dual_video 0.0.2 copy "max2000_dual_video: ^0.0.2" to clipboard
max2000_dual_video: ^0.0.2 copied to clipboard

Native synchronized dual-video playback for Flutter using Android Media3 and ExoPlayer.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:max2000_dual_video/max2000_dual_video.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _engine = Max2000DualVideoEngine();

  String _mediaDir = '';
  String _statusLine = 'Not loaded yet.';
  DualVideoEngineState? _state;
  StreamSubscription<DualVideoEngineState>? _stateSubscription;

  @override
  void initState() {
    super.initState();
    _engine.getMediaDir().then((dir) {
      if (!mounted) return;
      setState(() => _mediaDir = dir);
    });
    _stateSubscription = _engine.stateStream.listen((state) {
      if (!mounted) return;
      setState(() => _state = state);
    });
  }

  @override
  void dispose() {
    _stateSubscription?.cancel();
    _engine.dispose();
    super.dispose();
  }

  String get _pathA => '$_mediaDir/videoA.mp4';
  String get _pathB => '$_mediaDir/videoB.mp4';

  Future<void> _load() async {
    try {
      await _engine.load(pathA: _pathA, pathB: _pathB);
      setState(() => _statusLine = 'Loaded A=$_pathA, B=$_pathB');
    } catch (e) {
      setState(() => _statusLine = 'Load failed: $e');
    }
  }

  Future<void> _play() => _engine.play();

  Future<void> _pause() => _engine.pause();

  Future<void> _stop() => _engine.stop();

  Future<void> _nudgeOffset(int deltaMs) => _engine.nudgeOffsetMs(deltaMs);

  Future<void> _setSpeed(double speed) => _engine.setSpeed(speed);

  Future<void> _stepFrame(FrameStepDirection direction) =>
      _engine.stepFrame(direction);

  Widget _playerColumn(String label, String player, PlayerState? playerState) {
    return Expanded(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Text(label, textAlign: TextAlign.center),
          Expanded(
            child: Container(
              color: Colors.black,
              child: Max2000DualVideoPlayerView(player: player),
            ),
          ),
          Text(
            _playerStatusText(playerState),
            style: const TextStyle(fontSize: 12),
          ),
        ],
      ),
    );
  }

  String _playerStatusText(PlayerState? s) {
    if (s == null) return 'no status yet';
    return 'pos=${s.positionMs}ms dur=${s.durationMs}ms '
        'playing=${s.isPlaying} state=${s.playbackState.name} '
        'buffering=${s.isBuffering} outOfRange=${s.outOfRange} '
        'fps=${s.frameRate?.toStringAsFixed(2) ?? '?'}'
        '${s.frameRateKnown ? '' : ' (assumed)'} '
        'error=${s.error}';
  }

  String _engineStatusText() {
    final s = _state;
    if (s == null) return 'no engine status yet';
    return 'master=${s.masterPositionMs}ms playing=${s.isPlaying} '
        'speed=${s.speed}x offset=${s.offsetMs}ms drift=${s.driftMs}ms';
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Dual Video Engine')),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Text(
                'adb push videoA.mp4 "$_pathA"\nadb push videoB.mp4 "$_pathB"',
                style: const TextStyle(fontSize: 12),
              ),
              const SizedBox(height: 4),
              Text(_statusLine, style: const TextStyle(fontSize: 12)),
              Text(_engineStatusText(), style: const TextStyle(fontSize: 12)),
              const SizedBox(height: 8),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(onPressed: _load, child: const Text('Load')),
                  ElevatedButton(onPressed: _play, child: const Text('Play')),
                  ElevatedButton(
                    onPressed: _pause,
                    child: const Text('Pause'),
                  ),
                  ElevatedButton(onPressed: _stop, child: const Text('Stop')),
                ],
              ),
              const SizedBox(height: 4),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  const Text('Offset:'),
                  ElevatedButton(
                    onPressed: () => _nudgeOffset(-33),
                    child: const Text('-33ms'),
                  ),
                  ElevatedButton(
                    onPressed: () => _nudgeOffset(33),
                    child: const Text('+33ms'),
                  ),
                  const Text('Frame:'),
                  ElevatedButton(
                    onPressed: () => _stepFrame(FrameStepDirection.backward),
                    child: const Text('<'),
                  ),
                  ElevatedButton(
                    onPressed: () => _stepFrame(FrameStepDirection.forward),
                    child: const Text('>'),
                  ),
                ],
              ),
              const SizedBox(height: 4),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  const Text('Speed:'),
                  for (final speed in const [0.25, 0.5, 1.0, 2.0])
                    ElevatedButton(
                      onPressed: () => _setSpeed(speed),
                      child: Text('${speed}x'),
                    ),
                ],
              ),
              const SizedBox(height: 8),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    _playerColumn('Player A', 'A', _state?.playerA),
                    _playerColumn('Player B', 'B', _state?.playerB),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
likes
0
points
60
downloads

Publisher

unverified uploader

Weekly Downloads

Native synchronized dual-video playback for Flutter using Android Media3 and ExoPlayer.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on max2000_dual_video

Packages that implement max2000_dual_video