← Back to posts Cover image for Flutter Video Playback: When to Choose `video_player` vs. `media_kit` (and APK Size Concerns)

Flutter Video Playback: When to Choose `video_player` vs. `media_kit` (and APK Size Concerns)

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

So your Flutter app needs to play videos. You reach for the most common package, video_player, but then you hear about media_kit with its promises of broader format support. Which one should you choose? The decision isn’t just about API preference—it can drastically affect your app’s size, performance, and ability to handle complex video workloads. Let’s break down the key differences to help you pick the right tool.

The Core Contenders

video_player is the official, first-party plugin maintained by the Flutter team. It provides a basic, reliable wrapper around the platform’s native video playback capabilities (ExoPlayer on Android, AVPlayer on iOS/macOS). It’s the “batteries-included” choice for standard use cases.

media_kit is a powerful third-party package that uses libmpv under the hood—a highly portable, feature-rich media player library. It brings desktop support (Windows, Linux, macOS) and advanced features to all platforms, often at the cost of increased app size.

Key Comparison: Features & Limitations

Here’s the practical trade-off:

  • Format & Codec Support: video_player depends on what the underlying OS supports. This is usually fine for common formats (MP4, H.264) but can fail with specialty codecs or containers (like MKV with ASS subtitles). media_kit, via libmpv and bundled FFmpeg, can play almost anything you throw at it.
  • Hardware Decoder Limits: This is a critical, often overlooked constraint. Mobile OSes impose a strict limit on the number of concurrent hardware decoders. If your app needs to play multiple videos simultaneously (e.g., in a grid, or a video feed with previews), video_player can quickly hit this ceiling, causing crashes or silent failures. media_kit can often work around this by using software decoding or more efficient resource management.
  • Advanced Features: Need frame-perfect seeking, custom shaders for video filters, or complex subtitle rendering? media_kit is built for this. video_player offers a simpler, more constrained API.

The App Size Elephant in the Room

This is where the choice becomes very tangible. Let’s look at the impact on your build.

video_player is incredibly lightweight for your APK/IPA. It adds almost nothing, as it relies on system libraries. Your final binary size remains largely unaffected.

media_kit is heavy. It bundles libmpv and often FFmpeg shared libraries. As noted in community discussions, this can lead to an APK size increase of 50-150 MB. After installation on a device, the extracted native libraries can consume 600-800 MB of storage. For mobile, this is a massive commitment.

Code Example: Basic Implementation

Let’s see how a simple playback setup differs.

Using video_player:

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

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

  @override
  State<SimpleVideoPlayer> createState() => _SimpleVideoPlayerState();
}

class _SimpleVideoPlayerState extends State<SimpleVideoPlayer> {
  late VideoPlayerController _controller;

  @override
  void initState() {
    super.initState();
    // Initialize from a network source
    _controller = VideoPlayerController.networkUrl(
      Uri.parse('https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'),
    )..initialize().then((_) {
        setState(() {});
        _controller.play();
      });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _controller.value.isInitialized
            ? AspectRatio(
                aspectRatio: _controller.value.aspectRatio,
                child: VideoPlayer(_controller),
              )
            : const CircularProgressIndicator(),
      ),
    );
  }

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

Using media_kit:

import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';

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

  @override
  State<AdvancedVideoPlayer> createState() => _AdvancedVideoPlayerState();
}

class _AdvancedVideoPlayerState extends State<AdvancedVideoPlayer> {
  late final Player _player;
  late final VideoController _videoController;

  @override
  void initState() {
    super.initState();
    // Initialize the player
    _player = Player();
    _videoController = VideoController(_player);
    // Open a media source
    _player.open(Media('https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Video(
          controller: _videoController,
        ),
      ),
    );
  }

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

The media_kit API is more object-oriented, separating the Player (logic) from the Video (widget).

Decision Guide: Which One When?

Choose video_player if:

  • You’re building a typical mobile app (iOS/Android).
  • You play common video formats (MP4, WebM) one at a time.
  • App size is a major concern (e.g., for user acquisition).
  • You don’t need advanced playback features.

Choose media_kit if:

  • You require desktop support (Windows, Linux, macOS).
  • Your app must play multiple videos concurrently and you fear hardware decoder limits.
  • You need support for exotic codecs, containers, or advanced subtitle formats.
  • You’re building a video-centric app (like a downloader, player, or editor) where rich features justify the large binary size.
  • Crucially, you are already bundling FFmpeg (e.g., via ffmpeg_kit_flutter). In this case, media_kit can sometimes share these existing native dependencies, mitigating the total size impact.

Common Mistake: Not Planning for Concurrent Playback

The biggest “gotcha” with video_player is not testing multi-video scenarios. If your design has a scrolling list of auto-playing previews, you will hit the hardware decoder limit (often as low as 16-32 instances). The app will crash without a clear error. If your design requires this, media_kit is likely the safer choice from the start.

Final Verdict

There’s no universal winner. For most standard applications—social media feeds, e-learning content, simple video feeds—the official video_player is perfectly adequate and keeps your app lean. The moment your project steps into the realm of professional media handling, multi-platform deployment, or intensive multi-video layouts, the investment in media_kit’s size and complexity becomes justified. Assess your format requirements, concurrency needs, and target platforms first; the correct package choice will follow.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Localizing Dynamic Content in Flutter: A Guide to Backend-Driven Translations

Localizing Dynamic Content in Flutter: A Guide to Backend-Driven Translations

Many Flutter apps need to display content that changes based on user locale, but also comes from a backend (like Firebase). This post will explore best practices for fetching and integrating dynamic, localized content from a backend, ensuring a seamless user experience across different languages and regions without hardcoding translations.

Cover image for Unraveling Type Mismatch Errors in Flutter: A Guide to 'X can't be assigned to Y' and '_InternalLinkedHashMap' Issues

Unraveling Type Mismatch Errors in Flutter: A Guide to 'X can't be assigned to Y' and '_InternalLinkedHashMap' Issues

Developers frequently encounter cryptic type mismatch errors like 'The argument type X can't be assigned to the parameter type Y' or '_InternalLinkedHashMap has no instance method 'cast''. This post will demystify these common Flutter/Dart type errors, explain their root causes (e.g., conflicting imports, dynamic typing pitfalls, JSON deserialization issues), and provide practical solutions to diagnose and fix them, improving code robustness and reducing debugging time.