Skip to main content

Better Player Enterprise

better_player_enterprise extends Better Player with client-side VAST and VMAP advertising, real-time Quality of Experience (QoE) video telemetry, and white-labeling.

Built entirely in Dart: no platform views, no native Google IMA wrappers, and no compilation headaches across Android, iOS, Web, macOS, Windows, and Linux.

Why Enterprise?​

Standard video ad integrations wrap native platform SDKs inside Flutter platform views. That architecture introduces three practical problems:

  • Buffer eviction: When a native ad overlay mounts over the Flutter canvas, the underlying media player often gets paused or destroyed to free graphics memory. Once the ad finishes, your primary stream has to re-buffer from scratch, leaving users staring at a loading spinner.
  • Platform view jank: Synchronizing native views with Flutter widgets during orientation changes or animations causes dropped frames and visual tearing.
  • Platform fragmentation: Native wrappers require specific Kotlin, Swift, and CocoaPods toolchains that break when upgrading Flutter versions, and offer zero support for Flutter Web, macOS, Windows, or Linux.

better_player_enterprise runs ads directly through the player pipeline as structured media streams.

The Dual-Player Stack​

Switching a single media player back and forth between main content and ad streams causes decoding delays and forces buffer re-allocation. Better Player Enterprise avoids this with a dual-player stack:

  • Content player (base layer): Renders the primary video. When reaching a scheduled mid-roll cue point, the player pauses and preserves its network buffer in RAM.
  • Ad player (overlay layer): Instantiated directly above the content surface. It pre-buffers upcoming ad media a few seconds before the scheduled cue point so ads start immediately.
  • Handoff: When the ad finishes or the viewer skips, the overlay hides and the base player unpauses from its warm buffer.

Because the main video stays warm in memory, viewers return to content without a re-buffering pause.

Licensing & Setup​

better_player_enterprise is a commercial package. Core playback (better_player) remains free and open-source under Apache 2.0. Enterprise capabilities require an Ed25519-signed license token.

If you attach an enterprise extension without an active license, it throws a BetterPlayerCapabilitySecurityException. Core player playback continues to run normally.

How Licensing Works​

  • Cryptographic signatures: Licenses are signed with Ed25519 keypairs. The client validates signatures against embedded public keys without sending proprietary code or user data to external servers.
  • Bundle ID binding: Each license binds to authorized application bundle IDs (com.example.app) resolved through package_info_plus.
  • Offline resilience: The signed token is cached in local device storage (SharedPreferences). It refreshes every 72 hours, but active offline playback continues uninterrupted if the device is offline.
  • Capability gating: Feature access is locked behind individual capability grants encoded into the signed token:
    • vast: Authorizes BetterPlayerVastExtension for VAST/VMAP ad breaks and companion banners.
    • metrics: Authorizes BetterPlayerMetricsExtension for real-time telemetry and cloud exporters.
    • hide_about: Authorizes BetterPlayerEnterpriseExtension(hideAbout: true) to hide the About dialog.

Initializing the License​

Call BetterPlayerEnterprise.initialize in your application 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 still works; enterprise extensions will throw
print('Enterprise license inactive: ${result.errorMessage}');
}

runApp(const MyApp());
}

To request a free evaluation key for testing in staging or development, email betterplayer@hasoft.pl.

Client-Side VAST & VMAP Ad Engine​

Attach BetterPlayerVastExtension to your PlayerConfiguration:

import 'package:better_player/better_player.dart';
import 'package:better_player_enterprise/better_player_enterprise.dart';

final adExtension = BetterPlayerVastExtension(
configuration: BetterPlayerAdConfiguration(
adTagUrl: 'https://example.com/vmap.xml',
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://example.com/master.m3u8',
),
);

In your widget tree, mount BetterPlayer(controller: controller) as normal.

Ad Engine Capabilities​

  • Format support: VAST 2.0 through 4.3 linear ads, nested wrapper chains up to a configurable hop limit (default 5), and VMAP 1.0 ad playlists (pre-rolls, mid-rolls, post-rolls).
  • Mid-roll pre-warming: Pre-fetches ad manifests before scheduled cue points so mid-rolls start without stutter.
  • Fail-open policy: If an ad break fails to resolve within adTimeout (network drop, timeout, or empty IAB Error 303 response), the engine aborts the ad and resumes main content immediately. Content is never blocked behind a broken tag.
  • Tracking beacons: Dispatches impression, quartile milestones (firstQuartile, midpoint, thirdQuartile), completion, skip, error, and click-through URLs asynchronously with retry logic.
  • IAB privacy macros: Automatically substitutes macros including [IFA], [GDPRCONSENT], [USPRIVACY], [CACHEBUSTING], [TIMESTAMP], [ERRORCODE], and [CONTENTPLAYHEAD].
  • Companion ads & AdChoices: Renders static image, HTML, and IFrame companion banners, plus AdChoices icons with direct click-through routing.

Theming & iOS Cupertino Chrome​

Switch between Material 3 and iOS Cupertino overlay chrome with a single parameter:

// Native iOS controls and typography
BetterPlayerAdConfiguration(
adTagUrl: 'https://example.com/ad.xml',
displayConfiguration: BetterPlayerAdDisplayConfiguration.cupertino(),
);

Or customize colors, badges, and localized copy:

BetterPlayerAdDisplayConfiguration(
badgeBackgroundColor: const Color(0xFFE50914),
customBadgeText: 'SPONSORED',
translations: [
BetterPlayerAdTranslations.spanish(),
BetterPlayerAdTranslations.polish(),
],
);

Real-Time Video Telemetry & QoE​

Monitor playback health and user experience metrics in real time with BetterPlayerMetricsExtension:

import 'package:better_player/better_player.dart';
import 'package:better_player_enterprise/better_player_enterprise.dart';

final metricsExtension = BetterPlayerMetricsExtension(
configuration: BetterPlayerMetricsConfiguration(
heartbeatInterval: const Duration(seconds: 15),
batchSize: 10,
maxPersistedEvents: 500,
eventTtl: const Duration(hours: 72),
exporter: BetterPlayerCompositeMetricsExporter(
exporters: [
BetterPlayerBitmovinMetricsExporter(apiKey: 'YOUR_BITMOVIN_KEY'),
BetterPlayerMuxMetricsExporter(propertyKey: 'YOUR_MUX_ENV_KEY'),
BetterPlayerHttpMetricsExporter(
endpointUrl: 'https://telemetry.yourdomain.com/v1/events',
headers: {'Authorization': 'Bearer YOUR_TOKEN'},
),
],
),
),
);

final controller = BetterPlayerController(
PlayerConfiguration(
aspectRatio: 16 / 9,
extensions: [metricsExtension],
),
);

Metrics Tracked​

  • Startup latency (TTFF): Time to First Frame and data source initialization duration. Ad break isolation ensures pre-roll ads do not artificially inflate main content startup numbers.
  • Rebuffer ratio & stall count: Tracks total rebuffer occurrences, cumulative stall duration, and rebuffer stall percentage relative to total watch time.
  • Adaptive bitrate shifts: Logs rendition changes, resolution shifts, and bitrate transitions for HLS and DASH streams.
  • Playback lifecycle: Tracks session start, play, pause, seek, completion, and error events.

Offline Persistence​

  • Bounded storage: Buffers events locally in PlayerSharedPreferencesMetricsStorage or PlayerMemoryMetricsStorage.
  • Priority queue: If storage reaches capacity, routine heartbeat events are dropped first to protect critical session start, error, and session end events.
  • Exponential backoff: Retries failed dispatches using exponential backoff with jitter to protect backend services during network recovery.
  • TTL pruning: Stale offline events older than 72 hours expire automatically.

Live Stats Inspection​

Inspect live metrics programmatically during active playback:

final stats = metricsExtension.playbackStats;
if (stats != null) {
print('TTFF: ${stats.timeToFirstFrame?.inMilliseconds} ms');
print('Rebuffer Ratio: ${(stats.rebufferRatio * 100).toStringAsFixed(2)}%');
print('Stall Count: ${stats.rebufferCount}');
}

Custom Branding & White-Labeling​

Better Player open-source displays an "About" dialog in the overflow menu with project links and logo. Enterprise licenses with the hide_about capability can hide this item:

import 'package:better_player/better_player.dart';
import 'package:better_player_enterprise/better_player_enterprise.dart';

final controller = BetterPlayerController(
const PlayerConfiguration(
aspectRatio: 16 / 9,
extensions: [
BetterPlayerEnterpriseExtension(hideAbout: true),
],
),
);

Feature Comparison​

CapabilityOpen Source Core (better_player)Enterprise (better_player_enterprise)
Native Playback EnginesMedia3, AVPlayer, Shaka, libmpvMedia3, AVPlayer, Shaka, libmpv
Adaptive Streaming (HLS / DASH)YesYes
Hardware DRM (Widevine, FairPlay)YesYes
Subtitles (SRT, WebVTT, TTML, CEA)YesYes
Pooled Video Feeds (BetterPlayerFeed)YesYes
Offline Disk CachingYesYes
Client-Side VAST 2.0–4.3 AdsNoYes (Linear, Wrappers)
VMAP 1.0 Break SchedulingNoYes (Pre/Mid/Post-rolls)
Dual-Player Instant ResumeNoYes (Keeps buffer in RAM)
Real-Time Video QoE (TTFF, Stalls)NoYes
Bitmovin, Mux & HTTP ExportersNoYes (With offline queue)
White-Labeling (Hide About Menu)NoYes (hideAbout: true)
Support & SLACommunityDirect from author (Jakub)

Evaluation Keys & Licensing​

To test better_player_enterprise in your staging environment or discuss production licensing: