import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fvp/fvp.dart' as fvp; import 'package:fvp/mdk.dart'; import 'package:video_player/video_player.dart'; import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/playback/playback_model.dart'; import 'package:fladder/wrappers/players/base_player.dart'; import 'package:fladder/wrappers/players/player_states.dart'; class LibMDK extends BasePlayer { VideoPlayerController? _controller; late final player = Player(); bool externalSubEnabled = false; final StreamController _stateController = StreamController.broadcast(); @override Stream get stateStream => _stateController.stream; @override Future init(Ref ref) async { dispose(); fvp.registerWith(options: { 'global': {'log': 'off'}, 'subtitleFontFile': libassFallbackFont, }); } @override Future dispose() async { _controller?.dispose(); _controller = null; } @override Future open(String url, bool play) async { final validUrl = isValidUrl(url); if (validUrl != null) { _controller = VideoPlayerController.networkUrl(validUrl); } else { _controller = VideoPlayerController.file(File(url)); } await _controller?.initialize(); _controller?.addListener(() => updateState()); if (play) { await _controller?.play(); } return setState(lastState.update( buffering: true, )); } void setState(PlayerState state) { lastState = state; _stateController.add(state); } void updateState() { setState(lastState.update( playing: _controller?.value.isPlaying ?? false, completed: _controller?.value.isCompleted ?? false, position: _controller?.value.position ?? Duration.zero, duration: _controller?.value.duration ?? Duration.zero, volume: (_controller?.value.volume ?? 1.0) * 100, rate: _controller?.value.playbackSpeed ?? 1.0, buffering: _controller?.value.isBuffering ?? true, buffer: _controller?.value.buffered.last.end ?? Duration.zero, )); } @override Future pause() async => _controller?.pause(); @override Future play() async => _controller?.play(); @override Future playOrPause() async => lastState.playing ? _controller?.pause() : _controller?.play(); @override Future seek(Duration position) async => _controller?.seekTo(position); @override Future setAudioTrack(AudioStreamModel? model, PlaybackModel playbackModel) async { final wantedAudioStream = model ?? playbackModel.defaultAudioStream; if (wantedAudioStream == AudioStreamModel.no() || wantedAudioStream == null) { _controller?.setAudioTracks([-1]); return -1; } else { final indexOf = playbackModel.audioStreams?.indexOf(wantedAudioStream); if (indexOf != null) { _controller?.setAudioTracks([indexOf - 1]); } return wantedAudioStream.index; } } @override Future setSpeed(double speed) async => _controller?.setPlaybackSpeed(speed); @override Future setSubtitleTrack(SubStreamModel? model, PlaybackModel playbackModel) async { final wantedSubtitle = model ?? playbackModel.defaultSubStream; if (wantedSubtitle == SubStreamModel.no()) { externalSubEnabled = false; _controller?.setSubtitleTracks([-1]); return -1; } if (wantedSubtitle != null) { if (wantedSubtitle.isExternal && wantedSubtitle.url != null) { externalSubEnabled = true; _controller?.setExternalSubtitle(wantedSubtitle.url!); return wantedSubtitle.index; } else { if (externalSubEnabled) { externalSubEnabled = false; _controller?.setExternalSubtitle(""); } final indexOf = playbackModel.subStreams?.indexOf(wantedSubtitle); if (indexOf != null) { _controller?.setSubtitleTracks([indexOf - 1]); } return wantedSubtitle.index; } } return -1; } @override Future stop() async => _controller?.dispose(); @override Widget? videoWidget( Key key, BoxFit fit, ) => _controller == null ? null : Container( key: key, color: Colors.transparent, child: LayoutBuilder( builder: (context, constraints) => Stack( fit: StackFit.expand, children: [ FittedBox( fit: fit, alignment: Alignment.center, child: ValueListenableBuilder( valueListenable: _controller!, builder: (context, value, child) { final aspectRatio = value.isInitialized ? value.aspectRatio : 1.77; return SizedBox( width: constraints.maxWidth, child: AspectRatio( aspectRatio: aspectRatio, child: VideoPlayer(_controller!), ), ); }, ), ), ], ), ), ); @override Widget? subtitles(bool showOverlay) => null; @override Future setVolume(double volume) async => _controller?.setVolume(volume / 100); @override Future loop(bool loop) async => _controller?.setLooping(loop); }