A Work-Around with the Flutter Video Trimmer Package!

Jesutoni Aderibigbe
6 min readSep 1, 2023

So today, I decided to play around with this package and see what to come out with. The Video Trimmer Package is one of the packages to edit and crop your videos for your Flutter application. It is simple and very easy to use.

On your Tracks, Set, Let’s build…….

We will be using the following packages to build our application.

  • file_picker
  • video_trimmer

In part 2 of this tutorial, we will also be using

  • url_launcher
  • path_provider.

Lezzz Gooooo……….

  1. Create a new flutter project
flutter create <new_video_cropper_app>

2. Let’s create a folder called “pages” in our lib folder

3. In our pages folder, create three screens

  • home_page.dart: This screen will help navigate to the video files we will be taking from the internal storage of our device.
  • video_trimmer_page.dart: This page will help trim and crop the videos we select from the storage of our device
  • video_edited_page.dart: This page will finally display the edited/ cropped video.

4. In our home_page.dart

import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:video_edit_player/pages/Timmer.dart';
import 'package:video_edit_player/pages/video_player_widget.dart';


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

@override
State<VideoPage> createState() => _VideoPageState();
}

class _VideoPageState extends State<VideoPage> {
String? editedVideoPath;


@override
Widget build(BuildContext context) {
return Scaffold(
//video trimmer
appBar: AppBar(
title: Text("Video Trimmer"),
centerTitle: true,
),
body: Center(
child: Container(
child: ElevatedButton(
child: Text("LOAD VIDEO"),
onPressed: () async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.video,
allowCompression: false,
);


setState(() {
editedVideoPath = result;
});


}
},
),
),
),



}
}

BREAKDOWN

In this screen, I created a class named “VideoPage” that extends to a stateful widget. In the body of this page, we have a button called “Load Video” that has this line

FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.video,
allowCompression: false,
);

This means that the file picker packager will help you to select which of the videos you want to crop/edit.

5. In your video_trimmer_page.dart

Let’s do some code explanation

To use the package video trimmer, you will have to first install the package

flutter pub add video_trimmer

After this is done, create an instance of it

final Trimmer _trimmer = Trimmer();

The package takes different methods such as

  • saveTrimmedVideo: It returns a string when the operation is successful. It takes different parameters such as the startValue, endValue, onSave amongst others.
await _trimmer
.saveTrimmedVideo(startValue: _startValue, endValue: _endValue)
.then((value) {
setState(() {
_value = value;
});
});
  • video playback State: Returns the video playback state. If true then the video is playing, otherwise it is paused.
await _trimmer.videoPlaybackControl(
startValue: _startValue,
endValue: _endValue,
);
  • Load Video State
await _trimmer.loadVideo(videoFile:file);

Since we want to crop the video and save it, let’s create a function “saveVideo” that handles and crops the video as well as saves the video.

 Future<String?> _saveVideo() async {
setState(() {
_progressVisibility = true;
});

String? _value;

await _trimmer.
saveTrimmedVideo(
startValue: _startValue,
endValue: _endValue,
onSave: (String? value) {
setState(() {
_progressVisibility = false;
if (value != null) {
// Pass the edited video file path back to the previous screen
Navigator.of(context).pop(value);
}
});
},
);



}

In other words, this function takes the video, gets the start value and ed value, and then saves it.

Since this is done, let’s have the full UI

import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:video_trimmer/video_trimmer.dart';

class TrimmerView extends StatefulWidget {
final File file;

TrimmerView(this.file);

@override
_TrimmerViewState createState() => _TrimmerViewState();
}

class _TrimmerViewState extends State<TrimmerView> {
final Trimmer _trimmer = Trimmer();

double _startValue = 0.0;
double _endValue = 0.0;

bool _isPlaying = false;
bool _progressVisibility = false;


Future<String?> _saveVideo() async {
setState(() {
_progressVisibility = true;
});

String? _value;



await _trimmer.
saveTrimmedVideo(
startValue: _startValue,
endValue: _endValue,
onSave: (String? value) {
setState(() {
_progressVisibility = false;
if (value != null) {
// Pass the edited video file path back to the previous screen
Navigator.of(context).pop(value);
}
});
},
);
}

void _loadVideo() {
_trimmer.loadVideo(videoFile: widget.file);
}

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

_loadVideo();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Video Trimmer"),
),
body: Builder(
builder: (context) => Center(
child: Container(
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.black,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Visibility(
visible: _progressVisibility,
child: LinearProgressIndicator(
backgroundColor: Colors.red,
),
),
ElevatedButton(
onPressed: _progressVisibility
? null
: () async {
_saveVideo()
},

Expanded(
child: VideoViewer(trimmer: _trimmer),
),
Center(
child: TrimViewer(
trimmer: _trimmer,
viewerHeight: 50.0,
viewerWidth: MediaQuery.of(context).size.width,
maxVideoLength: const Duration(seconds: 10),
onChangeStart: (value) => _startValue = value,
onChangeEnd: (value) => _endValue = value,
onChangePlaybackState: (value) =>
setState(() => _isPlaying = value),
),
),
TextButton(
child: _isPlaying
? Icon(
Icons.pause,
size: 80.0,
color: Colors.white,
)
: Icon(
Icons.play_arrow,
size: 80.0,
color: Colors.white,
),
onPressed: () async {
bool playbackState = await _trimmer.videoPlaybackControl(
startValue: _startValue,
endValue: _endValue,
);
setState(() {
_isPlaying = playbackState;
});
},
)
],
),
),
),
),
);
}
}

Note: TheTrimViever handles the UI of your video. It takes a required trimmer alongside the

viewerHeight: For defining the total trimmer area height

viewer width: For defining the total trimmer area width

maxVideoLength(you can set it to the video length you wish to crop): For defining the maximum length of the output video.

method onChangeStart: Callback to the video start position. Returns the selected video start position in milliseconds

method onChangeEnd: Callback to the video end position.

onChangePlayBackState: Callback to the video playback state to know whether it is currently playing or paused.

show duration: It takes a boolean to show the duration of the video. The default mode is true

duration TextStyle: The styling of your text

Let’s build the UI to display the cropped video

Firstly, we have to find a way where we pass the value of the image(recall that it takes a string), to this new screen. To do that let’s update the UI of the “home_page”.

import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:video_edit_player/pages/Timmer.dart';
import 'package:video_edit_player/pages/video_player_widget.dart';


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

@override
State<VideoPage> createState() => _VideoPageState();
}

class _VideoPageState extends State<VideoPage> {
String? editedVideoPath;

@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
//video trimmer
appBar: AppBar(
title: Text("Video Trimmer"),
centerTitle: true,
),
body: Center(
child: Container(
child: ElevatedButton(
child: Text("LOAD VIDEO"),
onPressed: () async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.video,
allowCompression: false,
);
if (result != null) {
File file = File(result.files.single.path!);
final editedPath = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return TrimmerView(file);
}),
);

setState(() {
editedVideoPath = editedPath;
});


}
},
),
),
),


floatingActionButton: editedVideoPath != null
? FloatingActionButton(
onPressed: () {
// Display the edited video using a video player or any other widget
// Replace 'YourVideoPlayerWidget' with your video player widget
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return VideoPlayerWidget(editedVideoPath!);
}),
);
},
child: Icon(Icons.play_arrow),
)
: null,


);
}
}

In our onPressed parameter, I simply stated that the edited video should be passed into the new screen. I also stated that if we have an edited video from the video_trimmer_page, there should be a floating action button that navigates the user to this new page. Thus in our new screen, we will have this UI

import 'dart:io';

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

class VideoEditedPage extends StatefulWidget {
final String videoPath;

VideoEditedPage(this.videoPath);

@override
_VideoEditedPageState createState() => _VideoEditedPageState();
}

class _VideoEditedPageState extends State<VideoEditedPage> {
late VideoPlayerController _controller;

@override
void initState() {
super.initState();
_controller = VideoPlayerController.file(File(widget.videoPath))
..initialize().then((_) {
// Ensure the first frame is shown
setState(() {});
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Video Player'),
),
body: Center(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: CircularProgressIndicator(), // Show a loading indicator until video is initialized
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}

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

After this is done, we are good to go! I hope this helps. Watch out for Part 2 of this tutorial.

You can check out my GitHub repo for more updates

--

--