import 'dart:async'; import 'package:aves_model/aves_model.dart'; import 'package:aves_video/aves_video.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; abstract class AvesVideoControllerFactory { void init(); AvesVideoController buildController( AvesEntryBase entry, { required PlaybackStateHandler playbackStateHandler, required VideoSettings settings, }); } abstract class AvesVideoController { final AvesEntryBase _entry; final PlaybackStateHandler playbackStateHandler; final VideoSettings settings; bool _disposed = false; AvesEntryBase get entry => _entry; static const resumeTimeSaveMinDuration = Duration(minutes: 2); AvesVideoController( AvesEntryBase entry, { required this.playbackStateHandler, required this.settings, }) : _entry = entry { if (kFlutterMemoryAllocationsEnabled) { FlutterMemoryAllocations.instance.dispatchObjectCreated( library: 'aves', className: '$AvesVideoController', object: this, ); } entry.visualChangeNotifier.addListener(onVisualChanged); } @mustCallSuper Future dispose() async { assert(!_disposed); _disposed = true; if (kFlutterMemoryAllocationsEnabled) { FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this); } _entry.visualChangeNotifier.removeListener(onVisualChanged); await _savePlaybackState(); } Future _savePlaybackState() async { if (!isReady || duration < resumeTimeSaveMinDuration.inMilliseconds) return; await playbackStateHandler.saveResumeTime(entryId: _entry.id, position: currentPosition, progress: progress); } Future getResumeTime(BuildContext context) => playbackStateHandler.getResumeTime(entryId: _entry.id, context: context); void onVisualChanged(); Future play(); Future pause(); Future seekTo(int targetMillis); Future seekToProgress(double progress) => seekTo((duration * progress.clamp(0, 1)).toInt()); Listenable get playCompletedListenable; VideoStatus get status; Stream get statusStream; Stream get volumeStream; Stream get speedStream; bool get isReady; Future get untilReady { if (isReady) return Future.value(); final completer = Completer(); late StreamSubscription sub; sub = statusStream.where((_) => isReady).listen((_) { sub.cancel(); completer.complete(); }); return completer.future; } bool get isPlaying => status == VideoStatus.playing; int get duration; int get currentPosition; double get progress { final _duration = duration; return _duration != 0 ? currentPosition.toDouble() / _duration : 0; } Stream get positionStream; Stream get timedTextStream; ValueNotifier get canCaptureFrameNotifier; ValueNotifier get canMuteNotifier; ValueNotifier get canSetSpeedNotifier; ValueNotifier get canSelectStreamNotifier; ValueNotifier get sarNotifier; bool get isMuted; double get speed; double get minSpeed; double get maxSpeed; set speed(double speed); Future selectStream(MediaStreamType type, MediaStreamSummary? selected); Future getSelectedStream(MediaStreamType type); List get streams; Future captureFrame(); Future mute(bool muted); Widget buildPlayerWidget(BuildContext context); } enum VideoStatus { idle, initialized, paused, playing, completed, error, } abstract class PlaybackStateHandler { Future getResumeTime({required int entryId, required BuildContext context}); Future saveResumeTime({required int entryId, required int position, required double progress}); }