Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New min duration argument #126

Merged
merged 6 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ Those Android API level and iOS deployment target are required to uses this pack
| Duration endTrim | The end value of the trimmed area |
| bool isTrimmed | Set to `true` when the trimmed values are not the default video duration |
| bool isTrimming | Set to `true` when startTrim or endTrim is changing |
| Duration maxDuration | The different between endTrim & startTrim |
| Duration maxDuration | The maxDuration possible for the trimmed area |
| Duration minDuration | The minDuration possible for the trimmed area |
| Offset minCrop | The top left position of the crop area (between `0.0` and `1.0`) |
| Offset maxCrop | The bottom right position of the crop area (between `0.0` and `1.0`) |
| Size croppedArea | The actual Size of the crop area |
Expand Down
22 changes: 14 additions & 8 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,22 @@ class _VideoEditorState extends State<VideoEditor> {
final _isExporting = ValueNotifier<bool>(false);
final double height = 60;

late VideoEditorController _controller;
late final VideoEditorController _controller = VideoEditorController.file(
widget.file,
minDuration: const Duration(seconds: 1),
maxDuration: const Duration(seconds: 10),
);

@override
void initState() {
super.initState();
_controller = VideoEditorController.file(widget.file,
maxDuration: const Duration(seconds: 10))
..initialize(aspectRatio: 9 / 16).then((_) => setState(() {}));
_controller
.initialize(aspectRatio: 9 / 16)
.then((_) => setState(() {}))
.catchError((error) {
// handle minumum duration bigger than video duration error
Navigator.pop(context);
}, test: (e) => e is VideoMinDurationError);
}

@override
Expand Down Expand Up @@ -360,8 +368,6 @@ class _VideoEditorState extends State<VideoEditor> {
builder: (_, __) {
final duration = _controller.videoDuration.inSeconds;
final pos = _controller.trimPosition * duration;
final start = _controller.minTrim * duration;
final end = _controller.maxTrim * duration;

return Padding(
padding: EdgeInsets.symmetric(horizontal: height / 4),
Expand All @@ -371,9 +377,9 @@ class _VideoEditorState extends State<VideoEditor> {
OpacityTransition(
visible: _controller.isTrimming,
child: Row(mainAxisSize: MainAxisSize.min, children: [
Text(formatter(Duration(seconds: start.toInt()))),
Text(formatter(_controller.startTrim)),
const SizedBox(width: 10),
Text(formatter(Duration(seconds: end.toInt()))),
Text(formatter(_controller.endTrim)),
]),
),
]),
Expand Down
61 changes: 45 additions & 16 deletions lib/domain/bloc/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ import 'package:video_editor/domain/entities/cover_style.dart';
import 'package:video_editor/domain/entities/cover_data.dart';
import 'package:video_thumbnail/video_thumbnail.dart';

class VideoMinDurationError extends Error {
final Duration minDuration;
final Duration videoDuration;

VideoMinDurationError(this.minDuration, this.videoDuration);

@override
String toString() =>
"Invalid argument (minDuration): The minimum duration ($minDuration) cannot be bigger than the duration of the video file ($videoDuration)";
}

enum RotateDirection { left, right }

/// A preset is a collection of options that will provide a certain encoding speed to compression ratio.
Expand Down Expand Up @@ -66,18 +77,18 @@ class VideoEditorController extends ChangeNotifier {
/// The [file] argument must not be null.
VideoEditorController.file(
this.file, {
Duration? maxDuration,
this.maxDuration = Duration.zero,
this.minDuration = Duration.zero,
this.coverStyle = const CoverSelectionStyle(),
this.cropStyle = const CropGridStyle(),
TrimSliderStyle? trimStyle,
CoverSelectionStyle? coverStyle,
CropGridStyle? cropStyle,
}) : _video = VideoPlayerController.file(File(
// https://github.com/flutter/flutter/issues/40429#issuecomment-549746165
Platform.isIOS ? Uri.encodeFull(file.path) : file.path,
)),
_maxDuration = maxDuration ?? Duration.zero,
cropStyle = cropStyle ?? const CropGridStyle(),
coverStyle = coverStyle ?? const CoverSelectionStyle(),
trimStyle = trimStyle ?? TrimSliderStyle();
trimStyle = trimStyle ?? TrimSliderStyle(),
assert(maxDuration > minDuration,
'The maximum duration must be bigger than the minimum duration');

int _rotation = 0;
bool _isTrimming = false;
Expand All @@ -99,9 +110,6 @@ class VideoEditorController extends ChangeNotifier {
Duration _trimStart = Duration.zero;
final VideoPlayerController _video;

/// The max duration to trim the [file] video
Duration _maxDuration;

// Selected cover value
final ValueNotifier<CoverData?> _selectedCover =
ValueNotifier<CoverData?>(null);
Expand Down Expand Up @@ -229,22 +237,37 @@ class VideoEditorController extends ChangeNotifier {
//----------------//

/// Attempts to open the given video [File] and load metadata about the video.
///
/// Update the trim position depending on the [maxDuration] param
/// Generate the default cover [_selectedCover]
/// Initialize [minCrop] & [maxCrop] values based on [aspectRatio]
/// Initialize [minCrop] & [maxCrop] values base on [aspectRatio]
///
/// Throw a [VideoMinDurationError] error if the [minDuration] is bigger than [videoDuration], the error should be handled as such:
/// ```dart
/// controller
/// .initialize()
/// .then((_) => setState(() {}))
/// .catchError((error) {
/// // NOTE : handle the error here
/// }, test: (e) => e is VideoMinDurationError);
/// ```
Future<void> initialize({double? aspectRatio}) async {
await _video.initialize();

if (minDuration > videoDuration) {
throw VideoMinDurationError(minDuration, videoDuration);
}

_video.addListener(_videoListener);
_video.setLooping(true);

// if no [maxDuration] param given, maxDuration is the videoDuration
_maxDuration = _maxDuration == Duration.zero ? videoDuration : _maxDuration;
maxDuration = maxDuration == Duration.zero ? videoDuration : maxDuration;

// Trim straight away when maxDuration is lower than video duration
if (_maxDuration < videoDuration) {
if (maxDuration < videoDuration) {
updateTrim(
0.0, _maxDuration.inMilliseconds / videoDuration.inMilliseconds);
0.0, maxDuration.inMilliseconds / videoDuration.inMilliseconds);
} else {
_updateTrimRange();
}
Expand Down Expand Up @@ -355,8 +378,14 @@ class VideoEditorController extends ChangeNotifier {

/// Get the [maxDuration] param
///
/// if no [maxDuration] param given in VideoEditorController constructor, maxDuration is equal to the videoDuration
Duration get maxDuration => _maxDuration;
/// if no [maxDuration] param given in VideoEditorController constructor, maxDuration is equals to the videoDuration
Duration maxDuration;

/// Get the [minDuration] param
///
/// if no [minDuration] param given in VideoEditorController constructor, minDuration is equals to [Duration.zero]
/// throw a [VideoMinDurationError] error at initialization if the [minDuration] is bigger then [videoDuration]
Duration minDuration;

/// Get the [trimPosition], which is the videoPosition in the trim slider
///
Expand Down
5 changes: 3 additions & 2 deletions lib/ui/trim/trim_slider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class _TrimSliderState extends State<TrimSlider>
// adding to the rect the difference between current scroll position and the last one fixes it
if (_scrollController.offset == 0.0) {
_changeTrimRect(
left: _rect.left - _lastScrollPixels,
left: _rect.left - _lastScrollPixels.abs(),
updateTrim: false,
);
} else if (_scrollController.offset ==
Expand Down Expand Up @@ -385,7 +385,8 @@ class _TrimSliderState extends State<TrimSlider>
}

final Duration diff = _getDurationDiff(left, width);
if (diff <= widget.controller.maxDuration) {
if (diff >= widget.controller.minDuration &&
diff <= widget.controller.maxDuration) {
if (updateTrim) {
_rect = Rect.fromLTWH(left, _rect.top, width, _rect.height);
_updateControllerTrim();
Expand Down
18 changes: 6 additions & 12 deletions lib/ui/trim/trim_timeline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ class TrimTimeline extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, contrainst) {
int count =
max(1, (contrainst.maxWidth ~/ MediaQuery.of(context).size.width)) *
min(quantity, controller.videoDuration.inMilliseconds ~/ 100);
final int count =
(max(1, (contrainst.maxWidth / MediaQuery.of(context).size.width)) *
min(quantity, controller.videoDuration.inMilliseconds ~/ 100))
.toInt();
final gap = controller.videoDuration.inMilliseconds ~/ (count - 1);

return Padding(
Expand All @@ -55,15 +56,8 @@ class TrimTimeline extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(count, (i) {
final t = Duration(milliseconds: i * gap);
final String text;

if (gap < 1000) {
text = (t.inMilliseconds / 1000)
.toStringAsFixed(1)
.padLeft(2, '0');
} else {
text = '${t.inSeconds}';
}
final text =
(t.inMilliseconds / 1000).toStringAsFixed(1).padLeft(2, '0');

return Text(
'$text$localSeconds',
Expand Down