Client-Side Video Ads in Flutter: VAST and VMAP with Better Player Enterprise
Most video ad integrations in Flutter wrap native platform SDKs (like Google IMA) inside platform views (AndroidView and UiKitView). That approach causes two persistent headaches:
- The buffer drop: When a mid-roll ad starts, the platform view mounts over Flutter's canvas. The underlying media player often dumps its decoded buffers to reclaim GPU memory. Once the ad finishes, your user sits staring at a black screen or spinner while the main video re-buffers from scratch.
- Platform fragmentation: Native wrappers pull in complex Gradle and CocoaPods dependencies that break during Flutter upgrades, and they do not compile at all on Flutter Web or Desktop.
In better_player_enterprise, we replaced native SDK wrappers with a pure-Dart VAST and VMAP ad engine. It runs across Android, iOS, Web, macOS, Windows, and Linux without native ad dependencies.
The Dual-Player Stack
Switching a single media player back and forth between content and ad streams causes stutter and forces re-buffering. Better Player Enterprise avoids this with a dual-player stack:
- Content player (base layer): Renders the main video. When reaching a scheduled mid-roll cue point, the player pauses and preserves its downloaded network buffer in RAM.
- Ad player (overlay layer): An overlay layer instantiated directly above the content surface. It pre-buffers the ad media a few seconds before the scheduled cue point, so the ad starts instantly.
- Handoff: When the ad finishes or the viewer skips, the overlay hides and the base player unpauses immediately from its existing buffer.
Because the main video stays warm in memory, viewers return to their content with zero re-buffering latency.
Setting Up VAST & VMAP
better_player_enterprise is a commercial extension for Better Player. Core playback remains open-source, while enterprise capabilities (VAST/VMAP ads, QoE video telemetry, white-labeling) require an Ed25519-signed license token.
1. Initialize the License
Call BetterPlayerEnterprise.initialize in your app entrypoint before mounting any player widgets:
import 'package:flutter/widgets.dart';
import 'package:better_player_enterprise/better_player_enterprise.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final result = await BetterPlayerEnterprise.initialize(
licenseId: 'your-license-id',
);
if (!result.isValid) {
// Core player continues working; enterprise extensions will throw
print('Enterprise license inactive: ${result.errorMessage}');
}
runApp(const MyApp());
}
2. Attach the VAST Extension
Attach BetterPlayerVastExtension to your PlayerConfiguration. The extension handles XML parsing, cue point scheduling, skip countdowns, and beacon dispatching:
import 'package:better_player/better_player.dart';
import 'package:better_player_enterprise/better_player_enterprise.dart';
final adExtension = BetterPlayerVastExtension(
configuration: BetterPlayerAdConfiguration(
adTagUrl: 'https://pubads.g.doubleclick.net/.../ads?output=vast',
skipOffset: const Duration(seconds: 5),
adTimeout: const Duration(seconds: 4),
failOpen: true,
preWarmMidRolls: true,
onAdStart: (ad) => print('Ad started: ${ad.id}'),
onAdComplete: (ad) => print('Ad finished: ${ad.id}'),
onAdSkipped: (ad) => print('Ad skipped: ${ad.id}'),
onAdError: (error) => print('Ad failed: $error'),
),
);
final controller = BetterPlayerController(
PlayerConfiguration(
aspectRatio: 16 / 9,
autoPlay: true,
extensions: [adExtension],
),
betterPlayerDataSource: PlayerDataSource(
DataSourceType.network,
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
),
);
Then mount BetterPlayer(controller: controller) in your widget tree as usual.
Rules for Production Video Ads
Ad servers are notoriously flaky. CDNs drop connections, third-party wrappers return 204 No Content, and auction timeouts happen under bad network conditions. Building a reliable player requires three defensive rules:
- Always fail open: If an ad tag times out or returns an error (such as IAB Error 303 for an empty wrapper chain), abort the ad break and resume main playback immediately. Setting
failOpen: truewith a strictadTimeoutprevents users from getting trapped behind a broken tag. - Resolve wrapper chains: Ad networks often chain VAST redirects through multiple intermediaries. The engine follows redirects recursively up to a configurable hop limit while collecting tracking URLs at each stage.
- Dispatch beacons asynchronously: Pings for impressions, quartiles (
firstQuartile,midpoint,thirdQuartile), completions, and skips must fire in the background with retry handling so temporary connection glitches do not forfeit ad revenue.
Get Started
If you want to test client-side VAST/VMAP in your staging environment or learn more about supported IAB macros and companion ads, check out the Better Player Enterprise docs or email betterplayer@hasoft.pl for an evaluation key.
