video_pool 0.3.1 copy "video_pool: ^0.3.1" to clipboard
video_pool: ^0.3.1 copied to clipboard

Video orchestration for Flutter - controller pooling, instance reuse, visibility lifecycle, thermal throttling, disk caching, and ready-to-use widgets.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_pool/video_pool.dart';

import 'discover_tab.dart';
import 'event_debug_overlay.dart';
import 'feed_tab.dart';
import 'insights_tab.dart';
import 'video_sources.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  MediaKit.ensureInitialized();
  runApp(const ShowcaseApp());
}

/// Production-grade showcase app for the video_pool package.
///
/// Three tabs:
/// - Feed: TikTok/Reels full-screen vertical video feed
/// - Discover: Instagram-style mixed content list
/// - Insights: Live analytics dashboard
class ShowcaseApp extends StatelessWidget {
  const ShowcaseApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'video_pool Showcase',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        brightness: Brightness.dark,
        useMaterial3: true,
        scaffoldBackgroundColor: const Color(0xFF0A0A1A),
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.transparent,
          elevation: 0,
        ),
      ),
      home: const _AppShell(),
    );
  }
}

/// The app shell managing bottom navigation and shared pool lifecycle.
class _AppShell extends StatefulWidget {
  const _AppShell();

  @override
  State<_AppShell> createState() => _AppShellState();
}

class _AppShellState extends State<_AppShell> {
  int _currentTab = 0;

  // Shared decoder budget for cooperative multi-pool.
  final GlobalDecoderBudget _decoderBudget = GlobalDecoderBudget(totalTokens: 4);

  // Shared pool for Feed + Insights tabs.
  VideoPool? _pool;
  FilePreloadManager? _cacheManager;
  DeviceMonitor? _deviceMonitor;
  AudioFocusManager? _audioFocusManager;
  StreamSubscription<DeviceStatus>? _statusSubscription;
  bool _ready = false;

  @override
  void initState() {
    super.initState();
    _initPool();
  }

  Future<void> _initPool() async {
    // Initialize disk cache.
    final cacheDir = await getTemporaryDirectory();
    final cacheManager = FilePreloadManager(
      cacheDirectory: '${cacheDir.path}/video_pool_cache',
    );
    await cacheManager.loadManifest();

    if (!mounted) {
      cacheManager.dispose();
      return;
    }

    // Create the shared pool.
    final pool = VideoPool(
      config: const VideoPoolConfig(
        maxConcurrent: 3,
        preloadCount: 1,
        logLevel: LogLevel.debug,
        bandwidthThresholds: BandwidthThresholds(),
      ),
      adapterFactory: (_) => MediaKitAdapter(),
      sourceResolver: (index) =>
          index >= 0 && index < feedVideos.length ? feedVideos[index] : null,
      filePreloadManager: cacheManager,
      decoderBudget: _decoderBudget,
    );

    // Device monitoring.
    final deviceMonitor = DeviceMonitor();
    try {
      await deviceMonitor.startMonitoring();
    } catch (_) {
      // May not be available on all platforms.
    }

    final statusSub = deviceMonitor.statusStream.listen((status) {
      pool.onDeviceStatusChanged(
        thermalLevel: status.thermalLevel,
        memoryPressure: status.memoryPressureLevel,
      );
    });

    // Audio focus.
    final audioFocus = AudioFocusManager(platform: deviceMonitor);
    audioFocus.setCallbacks(
      onPause: () {
        pool.onVisibilityChanged(
          primaryIndex: -1,
          visibilityRatios: const {},
        );
      },
      onResume: () {
        pool.resumeLastState();
      },
    );
    audioFocus.startObserving();

    if (!mounted) {
      pool.dispose();
      cacheManager.dispose();
      statusSub.cancel();
      audioFocus.dispose();
      return;
    }

    setState(() {
      _pool = pool;
      _cacheManager = cacheManager;
      _deviceMonitor = deviceMonitor;
      _audioFocusManager = audioFocus;
      _statusSubscription = statusSub;
      _ready = true;
    });
  }

  Widget _buildCurrentTab() {
    switch (_currentTab) {
      case 0:
        return VideoPoolProvider(
          pool: _pool!,
          child: const EventDebugOverlay(
            child: FeedTab(),
          ),
        );
      case 1:
        return DiscoverTab(decoderBudget: _decoderBudget);
      case 2:
        return InsightsTab(pool: _pool!);
      default:
        return const SizedBox.shrink();
    }
  }

  @override
  void dispose() {
    _statusSubscription?.cancel();
    _audioFocusManager?.dispose().catchError((_) {});
    _pool?.dispose().catchError((_) {});
    _cacheManager?.dispose().catchError((_) {});
    try {
      _deviceMonitor?.stopMonitoring();
    } catch (_) {}
    _decoderBudget.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (!_ready || _pool == null) {
      return const Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              CircularProgressIndicator(color: Color(0xFF7C4DFF)),
              SizedBox(height: 16),
              Text(
                'Initializing pool...',
                style: TextStyle(color: Colors.white54, fontSize: 14),
              ),
            ],
          ),
        ),
      );
    }

    return Scaffold(
      body: _buildCurrentTab(),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _currentTab,
        onDestinationSelected: (index) {
          final previousTab = _currentTab;
          setState(() => _currentTab = index);

          // Pause pool when leaving Feed tab, resume when returning
          if (_pool != null) {
            if (previousTab == 0 && index != 0) {
              // Leaving Feed — pause all players
              _pool!.onVisibilityChanged(
                primaryIndex: -1,
                visibilityRatios: const {},
              );
            } else if (previousTab != 0 && index == 0) {
              // Returning to Feed — resume last state
              _pool!.resumeLastState();
            }
          }
        },
        backgroundColor: const Color(0xFF0D0D1A),
        indicatorColor: const Color(0xFF7C4DFF).withValues(alpha: 0.2),
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.play_circle_outline),
            selectedIcon: Icon(Icons.play_circle_filled,
                color: Color(0xFF7C4DFF)),
            label: 'Feed',
          ),
          NavigationDestination(
            icon: Icon(Icons.explore_outlined),
            selectedIcon:
                Icon(Icons.explore, color: Color(0xFF7C4DFF)),
            label: 'Discover',
          ),
          NavigationDestination(
            icon: Icon(Icons.insights_outlined),
            selectedIcon:
                Icon(Icons.insights, color: Color(0xFF7C4DFF)),
            label: 'Insights',
          ),
        ],
      ),
    );
  }
}
8
likes
150
points
177
downloads

Documentation

API reference

Publisher

verified publisherabdullahtas.dev

Weekly Downloads

Video orchestration for Flutter - controller pooling, instance reuse, visibility lifecycle, thermal throttling, disk caching, and ready-to-use widgets.

Repository (GitHub)
View/report issues

Topics

#video #video-player #media #cache #performance

License

MIT (license)

Dependencies

crypto, flutter, media_kit, media_kit_libs_video, media_kit_video

More

Packages that depend on video_pool

Packages that implement video_pool