top of page
Writer's pictureJoseph Muller III

Beautifying the Flutter Video Player

A Stacked Implementation In my last article I wrote about using the video_player package to show video thumbnails and play videos from a URL. The implementation was quite straightforward and involved wrapping the VideoPlayer widget with a GestureDetector. This also meant manually checking whether or not the video was playing when it was tapped so that we would know to play or pause it:


onTap: () => model.videoPlayerController.value.isPlaying
    ? model.pauseVideo()
    : model.playVideo(),

Since writing that article, I’ve discovered a much cleaner way to do the same thing…so that’s what I’m writing about today!

To summarize, the video_player package includes a few built in video overlay widgets that will handle playing, pausing, scrubbing, and displaying captions on top of your video. The example from the GitHub repo also includes code to change the playback speed of the video. The end result, once all improvements are made, will look like this:

I’ll be building off of the code from the last article so if you haven’t read it, here’s the link → Displaying Videos from a Firebase Cloud Storage URL With the Stacked architecturemedium.com

Setup Stacked Stacked is an MVVM state management solution built on top of the provider package. It allows you to separate your UI and business logic into Views and ViewModels, respectively and it’s currently my architecture of choice. stacked:


^1.7.7Video Player

The video_player package is an official flutter.dev package and it contains the essential widgets we’ll be using in this tutorial. video_player:

^1.0.1The View

Video Thumbnail In the last article, we made a StackedVideoView that would dynamically fit it’s container based on the value of a ‘showFull’ boolean. For the sake of modularity, I’ll be extracting the contents of that widget into a ViewModelWidget called VideoThumbnail. It will continue to use the same ViewModel as before since we want one shared class controlling the video and it’s overlays. Inside the videoViewStacked folder, I’ll add a new videoViewWidgets folder like this:

View — ViewModel — ViewModelWidgets


Inside this new video_thumbnail.dart file, we can add the ViewModelWidget like so:


With this extracted, we can modify the root StackedVideoView to determine when it shows just the video thumbnail and when it adds the video control overlays. What overlays are available?


 

Video Overlays

Full disclaimer: I initially found this code in the video_player example repo before refactoring it to work with Stacked

To make our video player actually presentable, we need to add a bunch of things. First, it would be nice to make the play/pause functionality obvious with an icon. Second, we should let the user scrub the video forward or backward to start their viewing experience wherever they like. And finally, we should have a theme-matching progress bar that shows how much time is left in the video. We’re not asking for much.

The video_player plugin comes built with these classes:

  • VideoProgressIndicator — “Displays the play/buffering status of the video controlled by [controller]”

  • VideoProgressColors — “Used to configure the [VideoProgressIndicator] widget’s colors for how it describes the video’s status”

  • ClosedCaption — “Widget for displaying closed captions on top of a video”

The example repo also provides a _ControlsOverlay class that’s used primarily to show and hide the video’s play button. My refactor of this is below: With the built in widgets and our new overlay, the last step is to stack it all up:

Stack(
  alignment: Alignment.bottomCenter,
  children: [
    VideoThumbnail(),
    VideoControlOverlay()
    VideoProgressIndicator(
       model.videoPlayerController,allowScrubbing: true,),
  ],
);

The stack order is important here since both the VideoControlOverlay and VideoProgressIndicator can accept gestures.


 

Customize It There’s a few things you can change about the video widget’s appearance so that it matches the theme of your app. The easiest one is the VideoProgressIndicator’s color scheme.


Pass an instance of the VideoProgrossColors class to the indicator like this:

 VideoProgressIndicator(
  model.videoPlayerController,
  allowScrubbing: true,
  colors: VideoProgressColors(
    backgroundColor: Colors.green,
    bufferedColor: Colors.yellow,
    playedColor: Colors.purple
  ),
),

Adding padding to the VideoProgressIndicator does exactly what you’d expect.


VideoProgressIndicator(
  model.videoPlayerController,
  allowScrubbing: true,
 padding: EdgeInsets.all(8),
  colors: VideoProgressColors(
    backgroundColor: Colors.green,
    bufferedColor: Colors.yellow,
    playedColor: Colors.purple
 ),
),

 

ViewModel (and Video Thumbnail Sizing)

The ViewModel

Just like in the last article, the ViewModel here is primarily responsible for initializing and managing the VideoPlayerController. Because we’re dynamically determining the size of the video thumbnail and then stacking things on it, we need the ViewModel to do a bit more: keep track of the video size. The full code is here for reference.

Video Thumbnail Sizing

If you add the VideoControlOverlay to the stack without giving it an explicit size, it fills the container and looks sloppy (the darker box is the overlay).

Our goal is to size the overlay to match that of the video. In order to calculate the video thumbnail’s size, we need to do a few things:

  1. Make the VideoThumbnail widget use the WidgetsBindingObserver mixin

  2. Mark the final video thumbnail widget with a GlobalObjectKey. There are three potential thumbnail widgets that will be shown so we need to tag them all

  3. Add a PostFrameCallback to the widget to get it’s size

  4. Once the callback is triggered, get the RenderBox from the GlobalObjectKey and extract it’s size

  5. notifyListeners()

Once we have the width and height from the video thumbnail, we can use that to size the VideoControlOverlay. The gist below is commented to show these steps.


Steps 4 and 5 take place in the ViewModel (see above). The width and height of the thumbnail can be plugged into the VideoControlOverlay and BAM!



Extras

Time Remaining

To display the video time remaining, add the following getters to your ViewModel:



Duration get totalVideoLength{
  return videoPlayerController.value.duration;
}
String get totalVideoLengthString{
  return _printDuration(totalVideoLength);
}
Duration get timeRemaining {
  Duration current = videoPlayerController.value.position;
  int millis = totalVideoLength.inMilliseconds - current.inMilliseconds;
  return Duration(milliseconds: millis);
}
String get timeRemainingString {
  return _printDuration( timeRemaining);
}

The _printDuration function comes from this Stack Overflow question.

In your ViewModel initialize() function, add a listener to your videoPlayerController that will notify all listeners when the video is playing. We need this piece so that the calculated time remaining gets refreshed regularly.


videoPlayerController.addListener(() {
  if(remaining && videoPlayerController.value.isPlaying) {
    notifyListeners();
  }
});

The VideoTimeRemaining widget looks like this:


Notice that we need to wrap the Text widget in a container with the same size as the video thumbnail. If you forget this, it’s possible the time remaining won’t be inside the video frame.

Time Elapsed

How valuable is knowing how much time is left if we don’t know how much time we’ve watched? No valuable.

Add these getters to your view model:

Duration get timeElapsed {
  return videoPlayerController.value.position;
}
String get timeElapsedString {
  return _printDuration(timeElapsed);
}

And create the VideoTimeElapsed widget. I’ll be updating this post with additional video features as I come across them so check back←


 

Widget Depot

You can find the full code for this widget and a handful of others in my Widget Depot repo. My goal is to have Stacked and Stateful versions of all widgets so you can use them regardless of your app’s structure. Feel free to open pull requests and report issues, too!

286 views0 comments

Recent Posts

See All

Commentaires


bottom of page