flutter_vap_player

A federated Flutter plugin for playing Tencent VAP (Video Animation Player) alpha-video animations on Android and iOS, including VAPX (mix/fusion) dynamic text and image injection.

Packages

Package Role
flutter_vap_player App-facing API (VapPlayerController, VapPlayer widget)
vap_player_platform_interface Shared platform interface
vap_player_android Android implementation (io.github.tencent:vap:2.0.28)
vap_player_ios iOS implementation (CocoaPods/SPM QGVAPlayer)

Platform communication uses pigeon (@HostApi for commands, @FlutterApi for VAPX resource callbacks, and @EventChannelApi for per-player event streams).

Rendering modes

VapViewType.textureView VapViewType.platformView
Android ✅ Flutter Texture (headless IAnimView feeding a SurfaceTexture) ✅ native AnimView
iOS ⚠️ falls back to platform view ✅ native QGVAPWrapView

The iOS VAP renderer draws directly into an on-screen CAMetalLayer, so a true Flutter texture is not possible there without forking the renderer; requesting textureView on iOS silently uses a platform view instead.

Usage

import 'package:flutter_vap_player/flutter_vap_player.dart';

final controller = VapPlayerController.asset(
  'assets/vap/demo.mp4',
  options: const VapPlayerOptions(
    viewType: VapViewType.textureView, // Android texture; iOS platform view
    repeatCount: 0,                    // 0 = once, n = n+1 plays, -1 = loop
    scaleType: VapScaleType.fitCenter,
  ),
);
await controller.initialize();
// ...
VapPlayer(controller) // in your widget tree
// ...
await controller.play();

Sources: VapPlayerController.asset, .file, and .networkUrl (assets and network URLs are materialized to a local cache file first — the native VAP libraries only play local files).

Playback events

VapPlayerController exposes a broadcast events stream for one-shot playback and interaction notifications. The stream does not replay earlier events and closes when the controller is disposed. Use controller.value for the current player state.

final subscription = controller.events.listen(
  (event) {
    switch (event) {
      case VapCompletedEvent():
        print('playback completed');
      case VapErrorEvent(:final code, :final message):
        print('playback failed: $code $message');
      default:
        break;
    }
  },
  onError: (Object error, StackTrace stackTrace) {
    print('event channel failed: $error');
  },
);

Frame events are emitted only when VapPlayerOptions.enableFrameEvents is enabled. Cancel application subscriptions when they are no longer needed.

VAPX (mix resources)

VapPlayerOptions(
  resourceDelegate: VapResourceDelegate(
    resolveText: (resource) async => 'text for ${resource.tag}',
    resolveImage: (resource) async => await loadPngBytes(resource.tag),
  ),
  onResourceClick: (resource) => print('tapped ${resource.tag}'),
)

resolveImage returns encoded PNG/JPEG bytes; decoding happens natively. Native code applies a 5-second timeout per resource — a slow or missing delegate leaves the slot empty instead of blocking playback forever.

Platform notes

  • No pause/seek on Android: the VAP Android library only supports start/stop/loop. pause()/resume() work on iOS (value.canPause), and throw UnsupportedError on Android.
  • play() always restarts from the first frame (VAP has no prepare/seek step). initialize() reads the animation's size from the mp4, so value.size and value.aspectRatio are usable for layout before playback starts; the platform refines the rest (frameCount, fps) once it has parsed the file.
  • VAP v1 files — plain alpha mp4s with no vapc box, played with enableOldVersion: true — are assumed to be split horizontally with the alpha channel on the left, which is what VAP's default video mode expects on both platforms. Their frameCount and fps stay 0 on iOS.
  • Per-frame progress events are opt-in (VapPlayerOptions.enableFrameEvents) to limit channel traffic.
  • Resource-click events require platform-view mode (touches are handled by the native view).
  • iOS mute changes apply at the next play/loop start.
  • The iOS simulator does not render VAP animations (Metal is stubbed out); test on a real device.

Example

example demonstrates all three modes: Texture, PlatformView, and VAPX with dynamic text/images. The Texture and PlatformView pages switch between scale types and between a v1 and a v2 source file, and report the animation size read from the mp4.

Development

# Regenerate pigeon bindings after editing pigeons/messages.dart:
cd vap_player_android && dart run pigeon --input pigeons/messages.dart
cd vap_player_ios && dart run pigeon --input pigeons/messages.dart

# Run tests:
cd vap_player && flutter test
cd vap_player_android && flutter test
cd vap_player_ios && flutter test

# Build the example:
cd vap_player/example
flutter build apk --debug
flutter build ios --no-codesign --debug

Libraries

flutter_vap_player