Video in Flutter

I started a quick app to try to organize a group of photos, which I intend to turn into a video. The problem is that I have a bunch of videos in the batch as well that I need to review. I thought I could get the video to work inside my app, but long story short, I wasn’t able to figure it out. I don’t think video is supported on the Linux platform. Maybe if I was working on Android, it would be a different story.

Anyway, maybe some of my notes will be helpful if you are trying things similar.

I started by installing the “video_player” package:

flutter pub add video_player

Then, I found a player that would take less work building a UI:

flutter pub add chewie

I declared a couple of variables for my controllers up at the top of my class:

late VideoPlayerController _controller;
late Future<void> _initializeVideoPlayerFuture;
late Future<void> _controllersFuture;
late ChewieController _chewieController;

Then, in the initialization method, I setup the controllers. I had planned to use the file method and pull the path for the video on my drive, but I never got the sample to work.

@override
void initState() {
  // Create an store the VideoPlayerController. The VideoPlayerController
  // offers several different constructors to play videos from assets, files,
  // or the internet.
  // _controller = VideoPlayerController.file(File(widget.videoPath));
  _controller = VideoPlayerController.network(
      'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4');

  _controllersFuture = initControllers();

  super.initState();
}

My initControllers() method sets up both controllers at the same time:

Future<void> initControllers() async {
  _initializeVideoPlayerFuture = _controller.initialize();
  await _initializeVideoPlayerFuture;
  _chewieController = ChewieController(videoPlayerController: _controller, autoPlay:  true, looping: true);
}

Here’s the build method that creates the UI. It uses a FutureBuilder to show when it is ready:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: _controllersFuture,
    builder: (context, snapshot) {
      print("Built ... connectionState -- ${snapshot.connectionState}, hasData ${snapshot.hasError}");
      if (snapshot.connectionState == ConnectionState.done) {
        // If the VideoPlayerController has finished initialization, use
        // the data it provides to limit the aspect ratio of the video.
        return AspectRatio(
          aspectRatio: _controller.value.aspectRatio,
          // Use the VideoPlayer widget to display the video.
          child: //VideoPlayer(_controller),
          Chewie(controller: _chewieController,),
        );
      } else {
        // If the VideoPlayerController is still initializing, show a
        // loading spinner.
        return Center(child: CircularProgressIndicator());
      }
    },
  );
}

Don’t forget to dispose the controllers:

@override
void dispose() {
  // Ensure disposing of the VideoPlayerController to free up resources.
  _controller.dispose();
  _chewieController.dispose();

  super.dispose();
}

Linux Desktop not supported

Originally, I thought my problem was that Apple .mov files were not supported. So, I thought I could simply change to a supported format such as .mp4 or .webm. Maybe that would have been the case had I been running on Android. Anyway, here is what I went through.

I was getting this error:

PlatformException(channel-error, Unable to establish connection on channel., null, null)

A quick search shows this command can convert the video:

ffmpeg -i input.mov -vcodec h264 -acodec mp2 output.mp4

or for webm:

ffmpeg -i IMG_3548.mov -c:v libvpx -crf 10 -b:v 1M -c:a libvorbis IMG_3548.webm

For the command, I need the new file name with an mp4 on it. I wrote this little procedure to swap out the last 3 characters on the file name:

String getMP4Path() {
  if(fileName.length > 0) {
    if(fileName.substring(fileName.length - 3, fileName.length).toLowerCase() == "mov") {
      return "${_backend.path}/${fileName.substring(0, fileName.length - 3)}mp4";
    } else {
      return "";
    }
  } else {
    return "";
    }
}

So, I found I could use the Process class to call ffmpeg directly from Flutter:

var result = await Process.run("ffmpeg", [
  "-i",
  picture.path,
  "-vcodec",
  "h264",
  "-acodec",
  "mp2",
  picture.getMP4Path()
]);
print("converted: ${result.exitCode} -- ${result.stdout}");

Cheating

So, since I can’t get it to work, I just added a button to open the video with the xdg-open command. Here’s the code:

ElevatedButton(
    onPressed: () {
      var result = Process.run("xdg-open", [picture.path]);
    },
    child: Text("Open"),
),

Resources

Leave a Comment

Your email address will not be published. Required fields are marked *