Monitor Better Player (Flutter)

The FastPix Better Player Wrapper is a Flutter plugin that combines the power of BetterPlayer Plus with FastPix Video Data. It allows you to track granular playback metrics, measure viewer engagement, and monitor performance - all while preserving the native flexibility of BetterPlayer.

If you're building a mobile video experience in Flutter and want accurate playback data without writing manual instrumentation, this wrapper takes care of that in a clean, extensible way.



Installation & Setup


Step 1: Add the SDK Dependency

You can install the FastPix Better Player Wrapper in one of two ways:


Option A: Using the terminal

flutter  pub  add  fastpix_better_player_wrapper

Option B: Add the dependency directly

Alternatively, you can add the dependency directly in your pubspec.yaml:

dependencies:  
fastpix_better_player_wrapper: 0.0.1 
 better_player_plus: ^1.0.8 

Then run:

flutter  pub  get 

PLEASE NOTE

If you’re behind a proxy, or using a CI/CD runner, confirm that the SDK is being correctly fetched and cached. You can always verify installation by running flutter pub deps.


Step 2: Platform configuration

FastPix Better Player Wrapper doesn’t require any additional configuration for iOS or Android.

  • Android: Auto-configured by the plugin
  • iOS: Auto-configured by the plugin
  • No need to edit native files or add permissions manually - you're good to go right after installation.


Basic usage

Step 1: Import the SDK

Now that the SDK is installed, you need to import it into any Dart file where you want to use FastPix player components. Typically, this will be in a page or file where you’ll implement video playback features - like your main.dart or any feature module.

At the very top of the file, add the import statement:

// Import FastPix Better Player Wrapper SDK into Dart files
import  'package:fastpix_better_player_wrapper/fastpix_better_player_wrapper.dart';

This import statement ensures that all the classes, methods, and functionalities provided by the FastPix Better Player Wrapper SDK are available for use in your project.


Step 2: Initialize BetterPlayer

Once the SDK is imported, you can begin working with its APIs. This includes initializing the player, configuring playback options, and handling video streams.

class  VideoPlayerScreen  extends  StatefulWidget { 
 @override 
 _VideoPlayerScreenState  createState() =>  _VideoPlayerScreenState(); 
 } 
 class  _VideoPlayerScreenState  extends  State<VideoPlayerScreen> { 
     late  BetterPlayerController _betterPlayerController; 
     late  FastPixBaseBetterPlayer _fastPixPlayer; 
  
@override 
 void  initState() { 
     super.initState(); 
     _initializePlayer(); 
 } 
  
void  _initializePlayer() { 
     _betterPlayerController =  BetterPlayerController( 
     BetterPlayerConfiguration( 
     autoPlay: true, 
     looping: false, 
     aspectRatio:  16  /  9, 
 ), 
  
betterPlayerDataSource:  BetterPlayerDataSource( 
     BetterPlayerDataSourceType.network, 
         "https://example.com/video.mp4", 
     ), 
 ); 
  
// Create FastPix wrapper 
 _fastPixPlayer =  FastPixBaseVideoPlayerBuilder( 
     playerController: _betterPlayerController, 
     workspaceId:  "your_workspace_id", // Required 
     beaconUrl:  "https://your-beacon-url.com", 
     viewerId:  "unique_viewer_id", // Required 
 ) 
  
.setVideoData( 
     VideoData( 
         videoUrl:  "https://example.com/video.mp4", 
         videoThumbnailUrl:  "https://example.com/thumbnail.jpg", 
     ), 
 ) 
 .setEnabledLogging(true) // Optional: for debugging 
 .build(); 
 _fastPixPlayer.start(); 
 }

Step 3: Build the UI

Use the standard BetterPlayer widget to render the video.

@override 
 Widget  build(BuildContext context) { 
 return  Scaffold( 
     appBar:  AppBar(title:  Text('Video Player')), 
         body:  Column( 
             children: [ 
                 AspectRatio( 
                     aspectRatio:  16  /  9, 
                     child:  BetterPlayer(controller: _betterPlayerController), 
                 ), 
             ], 
         ), 
     ); 
 } 

Step 4: Clean up resources

To avoid memory leaks and dangling analytics sessions, dispose of the FastPix wrapper when the player is no longer in use.

@override 
 void  dispose() { 
     // Dispose analytics and player controller 
     _fastPixPlayer.disposeMetrix(); 
     super.dispose(); 
 } 


Advanced configuration

The FastPix Better Player Wrapper supports custom metadata so you can enrich your video data with detailed context about videos, players, and user sessions.


Adding custom metadata

You can send custom tags, player details, and video attributes directly into FastPix analytics.


// Define custom analytics data 
 List<CustomData> customData = [ 
     CustomData(value:  "entertainment"), 
     CustomData(value:  "action"), 
     CustomData(value:  "PG-13"), 
     CustomData(value:  "English"), 
 ]; 
  
// Player metadata 
 PlayerData playerData =  PlayerData( 
     playerName:  "CustomVideoPlayer", 
     playerVersion:  "2.1.0", 
     playerType:  "mobile", 
 ); 
  
// Video metadata 
 VideoData videoData =  VideoData( 
     videoUrl:  "https://example.com/video.mp4", 
     videoThumbnailUrl:  "https://example.com/thumbnail.jpg", 
     videoTitle:  "Sample Video", 
     videoDuration:  120000, // in milliseconds 
 ); 
  
 
 
// Build with advanced configuration 
 _fastPixPlayer =  FastPixBaseVideoPlayerBuilder( 
     playerController: _betterPlayerController, 
     workspaceId:  "your_workspace_id", 
     beaconUrl:  "https://your-beacon-url.com", 
     viewerId:  "unique_viewer_id", 
 ) 
 .setVideoData(videoData) 
 .setPlayerData(playerData) 
 .setCustomData(customData) 
 .setEnabledLogging(true) 
 .build(); 

Player Dimension Tracking 

For player layout insights, you can report player size using a `GlobalKey`. 

 

class  _VideoPlayerScreenState  extends  State<VideoPlayerScreen> { 
 final  GlobalKey _playerKey =  GlobalKey(); 
  
@override 
 Widget  build(BuildContext context) { 
 return  Scaffold( 
         body:  Column( 
             children: [ 
                 AspectRatio( 
                     aspectRatio:  16  /  9, 
                     child:  Container( 
                         key: _playerKey, // Add key for dimension tracking 
                         child:  BetterPlayer(controller: _betterPlayerController), 
                 ), 
                 ), 
             ], 
         ), 
 ); 
 } 
  
@override 
 void  initState() { 
     super.initState(); 
     _initializePlayer(); 
     // Report player dimensions for analytics 
     WidgetsBinding.instance.addPostFrameCallback((_) { 
         _fastPixPlayer.reportPlayerSize(_playerKey); 
     }); 
     } 
 } 

Event tracking & analytics

The SDK automatically tracks standard playback events without additional code.

EventDescriptionWhen it occurs
playVideo starts playingUser presses play or autoplay triggers
playingActive playbackVideo is actively playing
pauseVideo pausedUser pauses or video is interrupted
seekingSeeking operation startedUser seeks to different position
seekedSeeking completedSeek operation finishes
bufferingVideo bufferingInsufficient data for playback
bufferedBuffering completedEnough data available
variantChangedQuality/resolution changeAdaptive bitrate changes
endedVideo completedVideo reaches end
errorPlayback errorAny playback failure

Builder methods

FastPixBaseVideoPlayerBuilder : Used to configure and create a FastPix-wrapped player instance.

FastPixBaseVideoPlayerBuilder({ 
     required  BetterPlayerController playerController, 
     required  String workspaceId, 
     String? beaconUrl, 
     required  String viewerId, 
 }) 

Configuration methods for customizing player behavior, metadata, and output:

FunctionParametersReturn TypeDescription
setEnabledLogging(bool value)bool valueFastPixBaseVideoPlayerBuilderEnable/disable detailed logging
setCustomData(List<CustomData> value)List<CustomData> valueFastPixBaseVideoPlayerBuilderSet custom analytics data
setPlayerData(PlayerData? value)PlayerData? valueFastPixBaseVideoPlayerBuilderSet player metadata
setVideoData(VideoData? value)VideoData? valueFastPixBaseVideoPlayerBuilderSet video information
build()NoneFastPixBaseBetterPlayerCreate the configured player instance

Player instance methods

FastPixBaseBetterPlayer once built, the player provides lifecycle utilities:

FunctionParametersReturn TypeDescription
start()NonevoidInitialize and start analytics tracking
disposeMetrix()NoneFuture<void>Clean up analytics resources and player
reportPlayerSize(GlobalKey key)GlobalKey keyvoidReport player dimensions for analytics

Properties:

PropertyTypeDescription
audioLanguageStringCurrent audio track language
playerWidthSizedoubleCurrent player width
playerHeightSizedoubleCurrent player height

PlayerObserver implementation

The class implements PlayerObserver interface with these methods:

FunctionReturn TypeDescription
isPlayerAutoPlayOn()boolCheck if autoplay is enabled
isPlayerFullScreen()boolCheck if player is in fullscreen
isPlayerPaused()boolCheck if player is paused
isVideoSourceLive()boolCheck if video is live stream
playerHeight()doubleGet current player height
playerWidth()doubleGet current player width
playerLanguageCode()StringGet current audio language
playerPlayHeadTime()Future<int>Get current playback position
videoSourceDuration()intGet total video duration
videoSourceUrl()StringGet video source URL
videoSourceMimeType()StringGet video MIME type

Error reporting

The SDK automatically captures and reports error details through ErrorModel:

// Check for player errors 
 ErrorModel? error = _fastPixPlayer.getPlayerError(); 
 if (error != null) { 
     print('Player error: ${error.message}'); 
     print('Error source: ${error.source}'); 
 } 


Best practices

1. Resource management

Handles resource cleanup by disposing the FastPix wrapper before the controller.

@override 
 void  dispose() { 
     // Always dispose FastPix wrapper before disposing controller 
     _fastPixPlayer.disposeMetrix(); 
     super.dispose(); 
 } 

2. Error handling

Wraps player initialization in a try–catch block to handle setup errors gracefully.

void  _initializePlayer() { 
     try { 
         _fastPixPlayer =  FastPixBaseVideoPlayerBuilder( 
         // ... configuration 
         ).build(); 
         _fastPixPlayer.start(); 
     } catch (e) { 
         print('Failed to initialize FastPix player: $e'); 
         // Handle initialization error 
     } 
 } 

3. Configuration validation

Ensures required IDs and video URLs are valid before building the player.

// Validate required parameters 
 if (workspaceId.isEmpty || viewerId.isEmpty) { 
     throw  ArgumentError('workspaceId and viewerId are required'); 
 } 
 // Validate video data 
 if (videoData?.videoUrl.isEmpty ?? true) { 
     throw  ArgumentError('Valid video URL is required'); 
 } 

4. Performance optimization

Applies settings to optimize performance and analytics, such as conditional logging and meaningful identifiers.

// Enable logging only in debug mode 
 .setEnabledLogging(kDebugMode) 
 // Use meaningful viewer IDs for analytics 
 .viewerId:  "user_${userId}_session_${sessionId}" 


Complete integration example

Below is a complete example showing how to integrate FastPix Better Player Wrapper in a Flutter application:

import 'package:flutter/material.dart'; 
import 'package:better_player_plus/better_player_plus.dart'; 
import 'package:fastpix_better_player_wrapper/fastpix_better_player_wrapper.dart'; 
 
class VideoPlayerPage extends StatefulWidget { 
  final String videoUrl; 
  final String videoTitle; 
  final String workspaceId; 
  final String viewerId; 
 
  const VideoPlayerPage({ 
    Key? key, 
    required this.videoUrl, 
    required this.videoTitle, 
    required this.workspaceId, 
    required this.viewerId, 
  }) : super(key: key); 
 
  @override 
  _VideoPlayerPageState createState() => _VideoPlayerPageState(); 
} 
 
class _VideoPlayerPageState extends State<VideoPlayerPage> { 
  late BetterPlayerController _betterPlayerController; 
  late FastPixBaseBetterPlayer _fastPixPlayer; 
  final GlobalKey _playerKey = GlobalKey(); 
   
  bool _isLoading = true; 
  String? _errorMessage; 
 
  @override 
  void initState() { 
    super.initState(); 
    _initializePlayer(); 
  } 
 
  void _initializePlayer() async { 
    try { 
      setState(() { 
        _isLoading = true; 
        _errorMessage = null; 
      }); 
 
      // Initialize BetterPlayer controller 
      _betterPlayerController = BetterPlayerController( 
        BetterPlayerConfiguration( 
          autoPlay: true, 
          looping: false, 
          aspectRatio: 16 / 9, 
          fit: BoxFit.contain, 
          controlsEnabled: true, 
          showPlaceholderUntilPlay: true, 
          placeholder: Container( 
            color: Colors.black, 
            child: const Center( 
              child: CircularProgressIndicator(color: Colors.white), 
            ), 
          ), 
        ), 
        betterPlayerDataSource: BetterPlayerDataSource( 
          BetterPlayerDataSourceType.network, 
          widget.videoUrl, 
        ), 
      ); 
 
      // Configure video metadata 
      VideoData videoData = VideoData( 
        videoUrl: widget.videoUrl, 
        videoTitle: widget.videoTitle, 
        videoThumbnailUrl: "https://example.com/thumbnail.jpg", 
        videoDuration: 0, // Will be auto-detected 
      ); 
 
      // Configure player metadata 
      PlayerData playerData = PlayerData( 
        playerName: "FastPixVideoPlayer", 
        playerVersion: "1.0.0", 
        playerType: "mobile", 
      ); 
 
      // Configure custom analytics data 
      List<CustomData> customData = [ 
        CustomData(value: "entertainment"), 
        CustomData(value: "movie"), 
        CustomData(value: "HD"), 
      ]; 
 
      // Build FastPix player with analytics 
      _fastPixPlayer = FastPixBaseVideoPlayerBuilder( 
        playerController: _betterPlayerController, 
        workspaceId: widget.workspaceId, 
        beaconUrl: "https://your-beacon-url.com", // Replace with your beacon URL 
        viewerId: widget.viewerId, 
      ) 
          .setVideoData(videoData) 
          .setPlayerData(playerData) 
          .setCustomData(customData) 
          .setEnabledLogging(kDebugMode) // Enable logging in debug mode 
          .build(); 
 
      // Start analytics tracking 
      _fastPixPlayer.start(); 
 
      // Report player dimensions after widget is built 
      WidgetsBinding.instance.addPostFrameCallback((_) { 
        _fastPixPlayer.reportPlayerSize(_playerKey); 
      }); 
 
      setState(() { 
        _isLoading = false; 
      }); 
 
    } catch (e) { 
      setState(() { 
        _isLoading = false; 
        _errorMessage = 'Failed to initialize player: $e'; 
      }); 
      print('Player initialization error: $e'); 
    } 
  } 
 
  @override 
  Widget build(BuildContext context) { 
    return Scaffold( 
      appBar: AppBar( 
        title: Text(widget.videoTitle), 
        backgroundColor: Colors.black, 
        foregroundColor: Colors.white, 
      ), 
      body: _buildBody(), 
    ); 
  } 
 
  Widget _buildBody() { 
    if (_isLoading) { 
      return const Center( 
        child: CircularProgressIndicator(), 
      ); 
    } 
 
    if (_errorMessage != null) { 
      return Center( 
        child: Column( 
          mainAxisAlignment: MainAxisAlignment.center, 
          children: [ 
            const Icon( 
              Icons.error_outline, 
              size: 64, 
              color: Colors.red, 
            ), 
            const SizedBox(height: 16), 
            Text( 
              'Error', 
              style: Theme.of(context).textTheme.headlineSmall, 
            ), 
            const SizedBox(height: 8), 
            Text( 
              _errorMessage!, 
              textAlign: TextAlign.center, 
              style: Theme.of(context).textTheme.bodyMedium, 
            ), 
            const SizedBox(height: 16), 
            ElevatedButton( 
              onPressed: _initializePlayer, 
              child: const Text('Retry'), 
            ), 
          ], 
        ), 
      ); 
    } 
 
    return Column( 
      children: [ 
        // Video Player 
        AspectRatio( 
          aspectRatio: 16 / 9, 
          child: Container( 
            key: _playerKey, 
            color: Colors.black, 
            child: BetterPlayer(controller: _betterPlayerController), 
          ), 
        ), 
         
        // Player Info Panel 
        Container( 
          padding: const EdgeInsets.all(16), 
          child: Column( 
            crossAxisAlignment: CrossAxisAlignment.start, 
            children: [ 
              Text( 
                widget.videoTitle, 
                style: Theme.of(context).textTheme.headlineSmall, 
              ), 
              const SizedBox(height: 8), 
              Text( 
                'Video URL: ${widget.videoUrl}', 
                style: Theme.of(context).textTheme.bodySmall, 
              ), 
              const SizedBox(height: 8), 
              Text( 
                'Viewer ID: ${widget.viewerId}', 
                style: Theme.of(context).textTheme.bodySmall, 
              ), 
            ], 
          ), 
        ), 
      ], 
    ); 
  } 
 
  @override 
  void dispose() { 
    // Clean up resources 
    _fastPixPlayer.disposeMetrix(); 
    _betterPlayerController.dispose(); 
    super.dispose(); 
  } 
} 
 
// Usage in main app 
class MyApp extends StatelessWidget { 
  @override 
  Widget build(BuildContext context) { 
    return MaterialApp( 
      title: 'FastPix Video Player Demo', 
      theme: ThemeData( 
        primarySwatch: Colors.blue, 
        brightness: Brightness.dark, 
      ), 
      home: const VideoPlayerPage( 
        videoUrl: "https://example.com/sample-video.mp4", 
        videoTitle: "Sample Video", 
        workspaceId: "your_workspace_id", 
        viewerId: "user_123_session_456", 
      ), 
    ); 
  } 
}


This integration example demonstrates:

  • Complete player initialization with error handling
  • Proper resource cleanup
  • UI state management
  • Integration with state management solutions
  • Best practices for production use