vlc_player

A Flutter plugin for video playback using VLC.

The plugin creates a native VLC-backed video view and exposes a Dart controller for loading media, playback controls, seeking, volume, speed, and state updates.

Supported platforms

  • Android
  • iOS
  • macOS
  • Windows
  • Linux

The plugin forwards media URIs to the native VLC library. It does not keep a Dart-side format allowlist or parse playlists in Dart.

Playback support depends on the platform VLC library and the codecs used by the media source. Common VLC-supported sources include MP4, MOV, MKV, WebM, AVI, FLV, MPEG-TS, HLS (.m3u8), DASH, RTSP, and RTP.

Tested format coverage

The repository includes local integration fixtures for MP4, HLS/M3U8 with a local MPEG-TS segment, external SRT subtitles, MOV, MKV, WebM, MPEG-TS, MP3, AAC/M4A, FLAC, Ogg Vorbis, and Opus.

Run the full format suite from the example app:

cd example
flutter drive --no-dds --timeout=1200 -d macos \
  --driver=test_driver/integration_test.dart \
  --target=integration_test/format_compatibility_test.dart

CI runs the smoke format suite on Android and the full suite on macOS and Windows. iOS CI builds the example app, but simulator integration tests are kept out of the GitHub runner because the runner can hang while launching Flutter integration tests for this plugin. Linux CI validates native tests and the existing plugin lifecycle integration path; media-loading format tests are kept out of Linux/Xvfb because that runner currently drops the Flutter Driver service connection when local media is loaded. To run a smaller local subset, pass --dart-define=VLC_PLAYER_FORMAT_SUITE=smoke, video, or audio.

Installation

Add the package to your app:

dependencies:
  vlc_player: ^1.0.0

If you are using this repository directly:

dependencies:
  vlc_player:
    path: ../vlc_player

Then run:

flutter pub get

Applications embedding this plugin must satisfy the binary distribution and license requirements for libVLC, MobileVLCKit, VLCKit, and the VLC Windows runtime.

See THIRD_PARTY_NOTICES.md for the third-party runtime and source components that app distributors need to account for.

Platform setup

Android

The Android implementation requires minSdk 26 or newer.

Network playback requires internet permission. The plugin manifest declares it:

<uses-permission android:name="android.permission.INTERNET" />

The repository CI checks Android APKs for 16 KB native page-size support across 64-bit packaged native libraries:

tool/ci/check_android_16kb_page_size.sh example/build/app/outputs/flutter-apk/app-debug.apk

iOS

The plugin depends on MobileVLCKit.

If your app plays non-HTTPS URLs, configure App Transport Security in the app's Info.plist. HTTPS URLs do not need ATS exceptions.

macOS

The plugin depends on VLCKit.

For sandboxed apps that play network media, enable the network client entitlement in the app:

<key>com.apple.security.network.client</key>
<true/>

The macOS podspec patches the VLCKit runtime path during build. Use CocoaPods 1.13.0 or newer so the script phase runs reliably.

After changing the macOS podspec or Pods state, run:

cd example/macos
pod install

Windows

The Windows implementation downloads the VLC Windows runtime during flutter build windows and bundles it into your app automatically.

build/windows/x64/runner/Release/
  your_app.exe
  libvlc.dll
  libvlccore.dll
  plugins/

The build downloads VLC 3.0.21 for Windows x64 from VideoLAN and verifies the archive SHA-256 before extraction. Windows builds require network access the first time the runtime is downloaded.

Windows video is rendered through a Flutter texture backed by libVLC video callbacks, so the Dart API is the same as the other platforms. The native Windows implementation uses the vendored official libvlcpp header-only bindings and links against the VLC SDK import library from the downloaded runtime archive.

Linux

The Linux implementation links against the system libVLC package through pkg-config. Install VLC development files before building a Linux app:

sudo apt install libvlc-dev vlc

Linux video is rendered through a Flutter texture backed by libVLC video callbacks, so the Dart API is the same as the other desktop platforms. The native Linux implementation uses the vendored official libvlcpp header-only bindings and links against the system libvlc package.

Usage

Play a video file

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

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

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

class _VideoPageState extends State<VideoPage> {
  late final VlcPlayerController controller = VlcPlayerController(
    mediaSource: VlcMediaSource(
      uri: Uri.parse('https://example.com/video.mp4'),
    ),
    autoPlay: true,
  );

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

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 16 / 9,
      child: VlcPlayer(controller: controller),
    );
  }
}

Play an HLS stream

HLS playback is handled by the native VLC library. Pass the .m3u8 playlist URL as the media source:

late final VlcPlayerController controller = VlcPlayerController(
  mediaSource: VlcMediaSource(
    uri: Uri.parse('https://example.com/live/playlist.m3u8'),
  ),
  autoPlay: true,
);

Control playback

await controller.play();
await controller.pause();
await controller.seekTo(const Duration(seconds: 30));
await controller.setVolume(100);
await controller.setPlaybackSpeed(1.25);
await controller.setAudioDelay(const Duration(milliseconds: -120));
await controller.setSubtitleDelay(const Duration(milliseconds: 250));

final pngBytes = await controller.takeSnapshot(width: 320);
debugPrint('snapshot bytes=${pngBytes.length}');

Fit the video

Use VlcPlayer.fit to choose how the native video is fitted inside the widget:

VlcPlayer(
  controller: controller,
  fit: VlcVideoFit.cover,
)

Play with headers and VLC media options

Use VlcMediaSource when a URL needs request headers, per-item VLC options, or an initial seek position:

await controller.setMedia(
  VlcMediaSource(
    uri: Uri.parse('https://example.com/protected/video.mp4'),
    httpHeaders: const <String, String>{
      'Authorization': 'Bearer token',
    },
    mediaOptions: const <String>[
      ':network-caching=1200',
    ],
    startPosition: const Duration(seconds: 30),
  ),
  autoPlay: true,
);

Play a playlist

await controller.setPlaylist(
  <VlcMediaSource>[
    VlcMediaSource(uri: Uri.parse('https://example.com/episode-1.mp4')),
    VlcMediaSource(uri: Uri.parse('https://example.com/episode-2.mp4')),
  ],
  autoPlay: true,
  autoAdvance: true,
  loopMode: VlcPlaylistLoopMode.loopAll,
);

await controller.next();
await controller.previous();
await controller.jumpTo(0);
await controller.addToPlaylist(
  VlcMediaSource(uri: Uri.parse('https://example.com/bonus.mp4')),
);
await controller.shufflePlaylist(seed: 42);

Select tracks and subtitles

final audioTracks = await controller.getAudioTracks();
if (audioTracks.isNotEmpty) {
  await controller.setAudioTrack(audioTracks.first.id);
}

final subtitleTracks = await controller.getSubtitleTracks();
if (subtitleTracks.isNotEmpty) {
  await controller.setSubtitleTrack(subtitleTracks.first.id);
}

await controller.addSubtitle(Uri.file('/path/to/subtitles.srt'));
await controller.disableSubtitle();

final info = await controller.getMediaInfo();
debugPrint('duration=${info.duration}, videoTracks=${info.videoTracks.length}');

Playback commands require the controller to be attached to a VlcPlayer. Calling commands such as play() before attachment throws a StateError. Native platform failures throw VlcPlayerException, which exposes a structured VlcPlayerError with code, message, and details.

setMedia() can be called before attachment. The media source is replayed when the platform view is created.

API stability

The 1.x line follows Semantic Versioning. Backward-compatible additions use minor versions, fixes use patch versions, and breaking API, platform, or behavior changes use major versions.

Use the exported package:vlc_player/vlc_player.dart library as the supported application-facing API. Native view attachment, texture creation, method channels, and platform view type details are implementation internals and are not part of the supported API contract.

Migrating from 0.8.x

Version 1.0.0 raises the Android minimum SDK from 24 to 26. Apps embedding the plugin must set minSdk 26 or newer in their Android app module. This change is required because Android snapshots use PixelCopy directly.

Migrating from 0.7.21 or earlier

Version 0.7.22 removed the controller setSource() shortcut and the controller constructor's source and httpHeaders compatibility parameters. Use VlcMediaSource consistently instead.

Before:

final controller = VlcPlayerController(
  source: Uri.parse('https://example.com/video.mp4'),
  httpHeaders: const <String, String>{
    'Authorization': 'Bearer token',
  },
  autoPlay: true,
);

await controller.setSource(
  Uri.parse('https://example.com/next.mp4'),
  autoPlay: true,
);

After:

final controller = VlcPlayerController(
  mediaSource: VlcMediaSource(
    uri: Uri.parse('https://example.com/video.mp4'),
    httpHeaders: const <String, String>{
      'Authorization': 'Bearer token',
    },
  ),
  autoPlay: true,
);

await controller.setMedia(
  VlcMediaSource(uri: Uri.parse('https://example.com/next.mp4')),
  autoPlay: true,
);

API

This section is a quick reference for the public API. The complete generated API reference is published with the package at pub.dev/documentation/vlc_player.

VlcPlayer

VlcPlayer is the Flutter widget that creates the native platform video view.

VlcPlayer({
  Key? key,
  required VlcPlayerController controller,
  Color backgroundColor = Colors.black,
  VlcVideoFit fit = VlcVideoFit.contain,
})

Parameters:

  • controller: Controls the native VLC player attached to this widget.
  • backgroundColor: Background color shown behind the native video view.
  • fit: How video is fitted inside the widget bounds.

VlcPlayerController

VlcPlayerController owns playback state and sends commands to the native VLC player.

VlcPlayerController({
  VlcMediaSource? mediaSource,
  bool autoPlay = false,
  List<String> options = const <String>[],
  Duration? eventThrottleInterval,
})

Constructor parameters:

  • mediaSource: Optional initial VlcMediaSource for headers, media options, and a start position.
  • autoPlay: Starts playback automatically after mediaSource is set.
  • options: VLC options passed to the native player when the platform view is created.
  • eventThrottleInterval: Optional positive interval for coalescing native events that only change position, duration, or bufferingProgress. Playback state, readiness, volume, speed, video size, and errors still notify immediately. Leave this as null to receive every distinct native event.

Methods:

  • setMedia(VlcMediaSource source, {bool autoPlay = false}): Loads a media URI with HTTP headers, VLC media options, and an optional start position.
  • setPlaylist(List<VlcMediaSource> sources, {int initialIndex = 0, bool autoPlay = false, bool autoAdvance = true, VlcPlaylistLoopMode loopMode = VlcPlaylistLoopMode.none}): Loads a playlist and selects the initial item.
  • next({bool autoPlay = true}): Moves to the next playlist item. Returns false at the end of the playlist.
  • previous({bool autoPlay = true}): Moves to the previous playlist item. Returns false at the beginning of the playlist.
  • jumpTo(int index, {bool autoPlay = true}): Loads a playlist item by index.
  • addToPlaylist(VlcMediaSource source): Appends a media item to the active playlist.
  • insertIntoPlaylist(int index, VlcMediaSource source): Inserts a media item into the active playlist.
  • removeFromPlaylistAt(int index): Removes a media item and updates the current item selection.
  • clearPlaylist(): Stops active playlist playback and clears playlist state.
  • shufflePlaylist({int? seed}): Shuffles the active playlist while preserving the current item.
  • play(): Starts or resumes playback.
  • pause(): Pauses playback.
  • stop(): Stops playback.
  • seekTo(Duration position): Seeks to a non-negative playback position.
  • setVolume(int volume): Sets volume. Values are clamped to 0..200.
  • setPlaybackSpeed(double speed): Sets playback speed. The value must be finite and greater than zero.
  • setAudioDelay(Duration delay): Sets VLC audio delay. Negative values play audio earlier.
  • setSubtitleDelay(Duration delay): Sets VLC subtitle delay. Negative values show subtitles earlier.
  • takeSnapshot({int? width, int? height}): Captures the current video as PNG bytes. If only one dimension is provided, VLC preserves the source aspect ratio where the platform API supports it.
  • getAudioTracks(): Returns available audio tracks.
  • setAudioTrack(int id): Selects an audio track by VLC track id.
  • getSubtitleTracks(): Returns available embedded subtitle tracks.
  • setSubtitleTrack(int id): Selects an embedded subtitle track by VLC track id.
  • disableSubtitle(): Disables subtitle rendering.
  • addSubtitle(Uri uri): Adds an external subtitle file or URL and selects it.
  • getMediaInfo(): Returns basic metadata and discovered media tracks.
  • dispose(): Releases the native player attached to this controller.

Track methods return VlcTrackDescription. getMediaInfo() returns VlcMediaInfo, including title, artist, album, duration, and basic video, audio, and subtitle track details when VLC exposes them.

Playlist state is exposed through playlist, playlistIndex, playlistLoopMode, currentMediaSource, hasNext, and hasPrevious. When autoAdvance is true, the controller loads the next item after VLC reports the current media ended. VlcPlaylistLoopMode.loopOne repeats the current item, and VlcPlaylistLoopMode.loopAll wraps at the beginning or end of the playlist.

Command failures throw VlcPlayerException. Commands that require a native player instance throw StateError when called before VlcPlayer attaches the controller or after the controller is disposed.

VlcMediaSource

VlcMediaSource describes one media item before it is passed to VLC.

final source = VlcMediaSource(
  uri: Uri.parse('https://example.com/video.mp4'),
  httpHeaders: const <String, String>{
    'Authorization': 'Bearer token',
  },
  mediaOptions: const <String>[
    ':network-caching=1200',
  ],
  startPosition: const Duration(seconds: 30),
);

await controller.setMedia(source, autoPlay: true);

uri must be non-empty and startPosition must be non-negative.

Track and media information

getAudioTracks() and getSubtitleTracks() return VlcTrackDescription objects:

  • id: Native VLC track id used by setAudioTrack() or setSubtitleTrack().
  • name: Track name reported by VLC.
  • language: Optional language code or label reported by VLC.

getMediaInfo() returns VlcMediaInfo:

  • title, artist, album: Metadata discovered by VLC when available.
  • duration: Media duration, or Duration.zero when unknown.
  • videoTracks, audioTracks, subtitleTracks: Lists of VlcMediaTrackInfo.

VlcMediaTrackInfo includes type, codec, language, bitrate, width, height, channels, and sampleRate. These fields are best-effort because containers and streams do not always expose all values.

VlcPlayerValue

VlcPlayerValue is emitted through VlcPlayerController.value.

Fields:

  • state: Current VlcPlaybackState.
  • position: Current playback position.
  • duration: Media duration.
  • volume: Current volume.
  • playbackSpeed: Current playback speed.
  • audioDelay: Current audio delay.
  • subtitleDelay: Current subtitle delay.
  • isReady: Whether the native player has reached a playable terminal or active playback state.
  • isSeekable: Whether VLC reports the current media as seekable.
  • isLive: Whether the current media looks like a non-seekable stream without a fixed duration.
  • videoSize: Current decoded video size when VLC exposes it.
  • bufferingProgress: Normalized buffering progress from 0.0 to 1.0 when the platform exposes it; otherwise null.
  • error: Structured native playback error, when available.
  • errorDescription: Native playback error text, when available.

Convenience getters:

  • isPlaying
  • isBuffering
  • hasError

VlcPlaybackState

Possible playback states:

  • idle
  • opening
  • buffering
  • playing
  • paused
  • stopped
  • ended
  • error

Errors

Native failures are exposed as VlcPlayerException, which wraps a VlcPlayerError:

try {
  await controller.play();
} on VlcPlayerException catch (error) {
  debugPrint('VLC command failed: ${error.code} ${error.message}');
}

Common error codes are available in VlcPlayerErrorCode, including playerNotFound, setSourceFailed, trackNotFound, addSubtitleFailed, playbackError, disposed, and eventChannelError.

Listening for state changes

VlcPlayerController extends ValueNotifier<VlcPlayerValue>, so it can be used with ValueListenableBuilder.

ValueListenableBuilder<VlcPlayerValue>(
  valueListenable: controller,
  builder: (context, value, child) {
    return Text('${value.state} ${value.position}');
  },
)

Example app

The example app contains:

  • A video file playback page.
  • An HLS stream playback page.
  • A full player page with play/pause, seek, time display, and orientation controls, fit selection, and snapshot capture.

Run it on macOS:

cd example
flutter run -d macos

Troubleshooting

player_not_found

This usually means a command was sent after the native platform view was disposed or before the controller attached to the current view. Keep one VlcPlayerController per player widget and call dispose() from the owning widget's dispose() method.

Network playback fails

Check:

  • The media URL is reachable from the device.
  • Android has internet permission.
  • macOS sandboxed apps have the network client entitlement.
  • iOS ATS allows the URL if it is not HTTPS.
  • Required HTTP headers are passed through VlcMediaSource.httpHeaders.
  • Linux has libvlc-dev and vlc installed.

macOS Library not loaded: VLCKit

Run pod install in the macOS app directory and rebuild:

cd example/macos
pod install
cd ..
flutter build macos

Also make sure CocoaPods is 1.13.0 or newer.

Libraries

vlc_player
VLC-backed video playback for Flutter.