Exploring Flutter Video Features

flutter_video_main

Did you know that Flutter extends beyond just creating visually stunning and responsive UIs? It also enables developers to integrate dynamic features like video previews, a key element in today’s app experiences. With video content being consumed by over 85% of internet users, incorporating video features can significantly enhance user engagement and satisfaction.

In this guide, we’ll focus on video previews within Flutter. Video previews are invaluable for giving users a glimpse of the content, making your app more interactive and appealing. Using Flutter’s capabilities alongside Cloudinary’s SDK for seamless video integration, we’ll walk you through the steps of fetching video content and displaying it effectively within your app.

In this article:

flutter_video_main

How do Flutter Videos Work?

Flutter’s video playback functionality is primarily achieved using the video_player package. This provides a high-level API for controlling video playback, including starting, pausing, seeking, and setting volume.

To play a video in Flutter, you first need to specify the video source (local file, network stream, etc.). Then, you create a VideoPlayerController instance and initialize it. Finally, you use the VideoPlayer widget to display the video content and control playback using the VideoPlayerController’s methods.

Creating a Flutter Video App

For this tutorial, we will be using Android Studio to build out the app. If you don’t have it installed, you can download it from the Android Studio website. Next, you will need to install Flutter, which you can download from the Flutter website. Follow the guide to install the latest version of Flutter.

Initializing our VideoPlayer App in Flutter

Once you have everything set up, open Android Studio and click on the New Flutter Project option.

flutter_video_main

Next, open the Flutter page from the left panel and click the button to configure your Flutter SDK path. Once you have it configured, click on Next to go to the next page. Here, we will define our project. Start by giving your project a name. For this tutorial, we will be naming our project as ‘videoapp’.

Now set the project path, and once you are finished setting up the app, click on the sb button to create a blank Flutter project. Here is what our project looks like:

flutter_video_main

Now head over to your pubspec.yaml file and add the following dependencies:

dependencies:
 http: ^0.13.5
 video_player: ^2.8.6

With this, our setup is complete, and we can begin building our Flutter Video Player app.

Playing Flutter Video With VideoPlayerController

Now open up your main.dart file and start by importing the necessary modules:

// Import necessary Flutter packages
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

Next, you’ll need to initialize the Flutter app using the MaterialApp widget. In the main() function, we ensure the Flutter engine is initialized, and then we call runApp() to load our main widget. Here, MyApp is a stateless widget that simply renders the VideoApp widget (which we will define later) and contains our main video player logic:

void main() {
 WidgetsFlutterBinding.ensureInitialized(); // Ensure the Flutter engine is initialized
 runApp(MyApp());
}

// Main App widget
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: VideoApp(),
   );
 }
}

Next, we use a stateful widget, VideoApp, to handle video playback and UI updates. Stateful widgets are used for elements that change over time, like a video player. While the VideoApp class doesn’t directly manage playback, it sets up an internal state (_VideoAppState) responsible for handling the video player and refreshing the UI accordingly.

Here’s how we define the VideoApp class:

// Stateful widget to manage video playback
class VideoApp extends StatefulWidget {
 @override
 _VideoAppState createState() => _VideoAppState();
}

Now, we will create our _VideoAppState class, which will contain the main logic of our project and will run our videos. We begin by defining three variables: VideoPlayerController for running our Videos, a Future array for video initialization, and a double variable storing our playback speed:

class _VideoAppState extends State<VideoApp> {
 late VideoPlayerController _controller; // Late initialization of video controller
 late Future<void> _initializeVideoPlayerFuture; // Future for video initialization
 double _playbackSpeed = 1.0; // Variable to store the playback speed

Next, we will define the initState method, which is called once when the widget is inserted into the widget tree. This method starts by calling super.initState() to handle the essential setup. We then initialize the video controller with the URL of our video.

For this example, we use Cloudinary, a cloud-based service that simplifies hosting and managing media assets. Our video, dance-2.mp4, is stored on Cloudinary’s demo cloud, which allows us to easily serve videos via a secure and reliable URL.

Cloudinary is especially helpful for video management. It provides adaptive streaming, automatic optimizations, and different transformations to fit various devices and network conditions. Using it ensures that the video loads quickly and efficiently, even on slower networks.

To further streamline the process, we store the initialization process in a Future object (_initializeVideoPlayerFuture). This ensures that the video is fully loaded and ready to play before it is displayed, which is handled asynchronously:

@override
void initState() {
 super.initState();
 // Initialize the video controller with a network video
 _controller = VideoPlayerController.networkUrl(
   Uri.parse('https://res.cloudinary.com/demo/video/upload/samples/dance-2.mp4'),
 );

 // Store the initialization Future
 _initializeVideoPlayerFuture = _controller.initialize().then((_) {
   setState(() {}); // Update UI once the video is initialized
 });
}

How to Display and Style Video Player in Flutter

Now that the video player is set up, we need a way to control its playback. Thankfully, the VideoPlayerController provides methods to play, pause, and stop the video, so we will need to create a UI that includes play, pause, and stop functionality through buttons. The buttons should be placed inside a row and trigger actions on the video player controller.

To do this, we will begin by defining our Widget class inside the _VideoAppState class:

@override
Widget build(BuildContext context) {
 return Scaffold(
   appBar: AppBar(
     title: const Text('Video Player'),
   ),
...

Here, we are defining a simple Widget that displays the title of our app.

Next, we will define the body of our app, which will contain all of the elements of our app. We create a Column widget containing a Center widget to center our video player. We then use the FutureBuilder widget to initialize our video player and add it to the app once the video is loaded:

body: Column(
 children: [
   // Center the video player
   Center(
     child: FutureBuilder(
       future: _initializeVideoPlayerFuture,
       builder: (context, snapshot) {
         if (snapshot.connectionState == ConnectionState.done) {
           return AspectRatio(
             aspectRatio: _controller.value.aspectRatio, // Set aspect ratio
             child: VideoPlayer(_controller), // Show the video player
           );
         } else {
           // Show loading indicator while the video is initializing
           return const Center(child: CircularProgressIndicator());
         }
       },
     ),
   ),

Next, we will add two buttons to our app: play/pause and stop. The play/pause button, as the name implies, will be used to play/pause our video, while the stop button pauses the video and resets the time passed to 0:

...
const SizedBox(height: 20), // Space between video and buttons
Row(
 mainAxisAlignment: MainAxisAlignment.center,
 children: [
   // Play/Pause button
   ElevatedButton(
     onPressed: () {
       setState(() {
         if (_controller.value.isPlaying) {
           _controller.pause(); // Pause the video if it's playing
         } else {
           _controller.play(); // Play the video if it's paused
         }
       });
     },
     // Update button text based on the playing state
     child: Text(_controller.value.isPlaying ? 'Pause' : 'Play'),
   ),
   const SizedBox(width: 10),
   ElevatedButton(
     onPressed: () {
       setState(() {
         _controller.seekTo(Duration.zero); // Stop the video (reset position)
         _controller.pause(); // Pause after resetting to the beginning
       });
     },
     child: const Text('Stop'),
   ),
 ],
),
...

Finally, we will define a dropdown that will contain multiple values and allow us to set the playback speed of our video:

const SizedBox(height: 20), // Space between buttons and dropdown

// Dropdown to select the playback speed
Row(
 mainAxisAlignment: MainAxisAlignment.center,
 children: [
   const Text('Playback Speed: '),
   DropdownButton<double>(
     value: _playbackSpeed,
     items: [
       const DropdownMenuItem(value: 0.5, child: Text('0.5x')),
       const DropdownMenuItem(value: 1.0, child: Text('1x')),
       const DropdownMenuItem(value: 1.5, child: Text('1.5x')),
       const DropdownMenuItem(value: 2.0, child: Text('2x')),
     ],
     onChanged: (value) {
       setState(() {
         _playbackSpeed = value!;
         _controller.setPlaybackSpeed(_playbackSpeed); // Set the playback speed
       });
     },
   ),
 ],
),

With this, our app is now complete, and all we need to do is create a dispose() function that frees up the resources once the app is closed:

@override
void dispose() {
 _controller.dispose(); // Dispose of the controller to free resources
 super.dispose();
}

Here is what our main.dart file looks like:

// Import necessary Flutter packages
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

void main() {
 WidgetsFlutterBinding.ensureInitialized(); // Ensure the Flutter engine is initialized
 runApp(MyApp());
}

// Main App widget
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: VideoApp(),
   );
 }
}

// Stateful widget to manage video playback
class VideoApp extends StatefulWidget {
 @override
 _VideoAppState createState() => _VideoAppState();
}

class _VideoAppState extends State<VideoApp> {
 late VideoPlayerController _controller; // Late initialization of video controller
 late Future<void> _initializeVideoPlayerFuture; // Future for video initialization
 double _playbackSpeed = 1.0; // Variable to store the playback speed

 @override
 void initState() {
   super.initState();
   // Initialize the video controller with a network video
   _controller = VideoPlayerController.networkUrl(
     Uri.parse('https://res.cloudinary.com/demo/video/upload/samples/dance-2.mp4'),
   );

   // Store the initialization Future
   _initializeVideoPlayerFuture = _controller.initialize().then((_) {
     setState(() {}); // Update UI once the video is initialized
   });
 }

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Video Player'),
     ),
     body: Column(
       children: [
         // Center the video player
         Center(
           child: FutureBuilder(
             future: _initializeVideoPlayerFuture,
             builder: (context, snapshot) {
               if (snapshot.connectionState == ConnectionState.done) {
                 return AspectRatio(
                   aspectRatio: _controller.value.aspectRatio, // Set aspect ratio
                   child: VideoPlayer(_controller), // Show the video player
                 );
               } else {
                 // Show loading indicator while the video is initializing
                 return const Center(child: CircularProgressIndicator());
               }
             },
           ),
         ),
         const SizedBox(height: 20), // Space between video and buttons
         Row(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             // Play/Pause button
             ElevatedButton(
               onPressed: () {
                 setState(() {
                   if (_controller.value.isPlaying) {
                     _controller.pause(); // Pause the video if it's playing
                   } else {
                     _controller.play(); // Play the video if it's paused
                   }
                 });
               },
               // Update button text based on the playing state
               child: Text(_controller.value.isPlaying ? 'Pause' : 'Play'),
             ),
             const SizedBox(width: 10),
             ElevatedButton(
               onPressed: () {
                 setState(() {
                   _controller.seekTo(Duration.zero); // Stop the video (reset position)
                   _controller.pause(); // Pause after resetting to the beginning
                 });
               },
               child: const Text('Stop'),
             ),
           ],
         ),
         const SizedBox(height: 20), // Space between buttons and dropdown

         // Dropdown to select the playback speed
         Row(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             const Text('Playback Speed: '),
             DropdownButton<double>(
               value: _playbackSpeed,
               items: [
                 const DropdownMenuItem(value: 0.5, child: Text('0.5x')),
                 const DropdownMenuItem(value: 1.0, child: Text('1x')),
                 const DropdownMenuItem(value: 1.5, child: Text('1.5x')),
                 const DropdownMenuItem(value: 2.0, child: Text('2x')),
               ],
               onChanged: (value) {
                 setState(() {
                   _playbackSpeed = value!;
                   _controller.setPlaybackSpeed(_playbackSpeed); // Set the playback speed
                 });
               },
             ),
           ],
         ),
       ],
     ),
   );
 }

 @override
 void dispose() {
   _controller.dispose(); // Dispose of the controller to free resources
   super.dispose();
 }
}

Running the Flutter Video Player App on an Android Emulator

Now, all we need to do is to test our app. To do this, open up the device manager by clicking on the icon in the image below. Next, run your Android emulator by clicking the Play button. Finally, in the Flutter Device Selection drop-down, select your Android device and launch your app by clicking the Play button in the top navigation bar.

This will launch your mobile emulator and run your Flutter video app:

flutter_video_main

Here is what our app looks like:

flutter_video_main

Now click on the Play button to start playing the video. As you can see, once the video starts running, the Play button will turn into the Pause button. You can even click on Stop to stop the video:

flutter_video_main

You can also select the playback speed by selecting an option from the drop-down menu:

flutter_video_main

Insights into Various Flutter Video Formats

Understanding the supported video formats is crucial when working with video content in Flutter, as it ensures compatibility and seamless playback within your applications.

Here’s an overview of the key video formats supported by Flutter:

  • MP4: The most widely used video format, MP4 (MPEG-4 Part 14), is highly compatible with various devices and platforms. It provides good quality at relatively low file sizes, making it a popular choice for web and mobile video content. Flutter’s video_player package supports MP4 files, allowing for smooth playback and integration.
  • WebM: This format, developed by Google, is designed for web use and offers efficient video compression. WebM provides high-quality video playback while maintaining smaller file sizes. Flutter supports WebM videos, though support may depend on the underlying platform and player implementation.
  • AVI: Although less common in modern applications, AVI (Audio Video Interleave) is still supported by many video players. It is known for its flexibility and quality but can result in larger file sizes. Compatibility with AVI in Flutter may require additional handling or conversion to more optimized formats.
  • MOV: The MOV format, developed by Apple, is often used for high-quality video recordings. While it is supported by many video players and platforms, MOV files can be larger and may require conversion to MP4 for better compatibility across all devices in Flutter apps.
  • MKV: Matroska (MKV) is an open-source container format that supports a wide range of video and audio codecs. It is known for its high-quality video and flexibility. However, native support for MKV in Flutter might require additional configuration or third-party libraries.

Wrapping Up

From understanding how Flutter handles video playback to knowing the supported formats, you can use these capabilities to enhance user experiences with dynamic video content. Incorporating video features effectively can significantly boost user engagement and app functionality. Tools like Cloudinary play a crucial role in this by providing seamless video management, including easy integration, transformation, and optimization. Cloudinary’s powerful SDK simplifies video handling, ensuring smooth playback and efficient delivery across various platforms.

Ready to take your Flutter video features to the next level? Sign up to Cloudinary today to streamline your video content management for a more engaging and polished app experience.

More from Cloudinary:

Mobile Video Revolution: How-to Guide for Using Cloudinary’s iOS Native Video Player

Cloudinary with Flutter
</p

QUICK TIPS
Matthew Noyes
Cloudinary Logo Matthew Noyes

In my experience, here are tips that can help you better manage and optimize Flutter video features:

  1. Leverage the chewie package for advanced controls
    While the video_player package is the foundation for video playback in Flutter, chewie extends it by providing more user-friendly controls and customization options, such as full-screen toggling, custom controls, and aspect ratio adjustments. This saves you time building custom controllers from scratch.
  2. Optimize for different screen sizes using responsive design
    Flutter’s flexibility with layout widgets (like MediaQuery and AspectRatio) makes it easy to adapt video dimensions based on device screen size. Utilize these widgets to ensure your videos look great on both mobile and tablet screens without distortion or cropping.
  3. Implement caching to enhance playback speed
    Use packages like flutter_cache_manager to cache video files locally. This is particularly useful for network-based videos, as it can significantly reduce loading times, especially for repeated views, improving the overall user experience.
  4. Use video thumbnails for a faster UI
    Instead of loading entire videos for previews, generate and display video thumbnails using packages like flutter_video_info or use Cloudinary’s video_preview transformation URL. This will decrease the time it takes to load preview images, making your UI feel faster and more responsive.
  5. Choose the right format based on device and network conditions
    MP4 is widely supported, but consider using modern formats like WebM or HLS for more efficient streaming. With Cloudinary, use the f_auto parameter to serve the best format dynamically based on the device’s capabilities and the network speed. This ensures optimal quality and performance on all platforms.
  6. Utilize Cloudinary for adaptive bitrate streaming
    Adaptive bitrate streaming dynamically adjusts the video quality based on the user’s internet speed. Implement this using Cloudinary’s adaptive_bitrate transformation to serve optimized content. This prevents buffering on slower connections and delivers higher quality on fast networks, enhancing the viewing experience.
  7. Enable background video playback for seamless UX
    If your app design involves continuous video playback even when users navigate to other screens, consider using the flutter_vlc_player package, which supports background video playback. This is beneficial for apps with live streaming or apps where video is an integral part of the user experience.
  8. Implement gesture-based controls for intuitive video interactions
    Improve user engagement by implementing gesture-based controls like double-tap to seek, swipe to adjust volume, or pinch to zoom using Flutter’s gesture detection capabilities. Gesture controls make interactions more natural and can enhance video content usability.
  9. Monitor and handle various video states with state management
    Integrate a state management solution like Provider or Riverpod to track and handle different video states (loading, playing, buffering, or error states) systematically. This ensures smoother playback and reduces unexpected behaviors in more complex applications.
  10. Test video performance on multiple devices and platforms
    Always test video playback on both iOS and Android devices and across different emulators to identify any performance bottlenecks or compatibility issues. Variations in video rendering or codec support can impact playback quality, so thorough testing ensures a consistent experience across platforms.

These tips can help you optimize Flutter video features for better performance, quality, and user experience. Using Cloudinary’s advanced video management tools will further streamline your workflows and ensure that your videos are always displayed in the best format and quality for every user scenario.

Last updated: Oct 3, 2024