video_pool 0.6.1
video_pool: ^0.6.1 copied to clipboard
Video orchestration for Flutter - controller pooling, instance reuse, visibility lifecycle, thermal throttling, disk caching, and ready-to-use widgets.
Changelog #
All notable changes to this project will be documented in this file.
0.6.1 #
Removed #
- Deleted
example/lib/log.md— a 253 KB rawadb logcatdump that was committed into the example'slib/directory back in 0.3.0. It had no purpose, was the single largest file in the published archive, and showed up as an example source file on the package's pub.flutter-io.cn page. - Excluded
assets/from the published archive via.pubignore. It holds onlydemo.gif(4.2 MB), which the README references by absolute GitHub URL and which no code orpubspec.yamlasset declaration uses — so every consumer was downloading it for nothing. The file stays in the repository.
0.6.0 #
Breaking #
- Minimum SDK raised to Flutter 3.44 / Dart 3.12 (was Flutter 3.16 / Dart 3.2).
This matches what the package's own dependencies already require —
video_player2.14 declaresflutter: '>=3.44.0'andsdk: '^3.12.0'— so the old floor was a claim that could never actually be built or tested. - Android
minSdk21 → 24 andcompileSdk34 → 36, matching Flutter's current Android floor and compile target. - iOS deployment target 13.0 → 15.0, in both
ios/video_pool.podspecand the Swift Package Manager manifest, matching Flutter's current iOS floor. - The Android plugin now compiles against Java 17 (was Java 8).
- Migrated the Android plugin to Built-in Kotlin. The plugin no longer applies
org.jetbrains.kotlin.androiditself. Flutter 3.47 warns that plugins which apply the Kotlin Gradle Plugin "will fail to build" in future Flutter versions; Kotlin is now supplied by AGP 9's built-in Kotlin, or auto-applied by the Flutter Gradle Plugin when built-in Kotlin is off. Verified by building the example app three ways: AGP 9.1.0 withandroid.builtInKotlin=true, AGP 9.1.0 with itfalse, and AGP 8.11.1 / Gradle 8.14 / Kotlin 2.2.20 — all succeed, so apps still on AGP 8 are not broken.
Fixed #
- Cleanup was skipped when a non-resumed download failed. In
file_preload_manager_io.dart,_downloadFresh()was returned withoutawaitfrom inside atryblock, so anything it threw bypassed the enclosingcatch— the partial-file deletion and sink close never ran. Both call sites nowawait.
Changed #
- Adopted Dart 3.12 private named initializing formals (
this._field) inVideoPool,GlobalDecoderBudget, andAudioFocusManager. Callers are unaffected: the public argument names (sourceResolver:,filePreloadManager:,decoderBudget:,totalTokens:,platform:) are unchanged. - Removed the deprecated
packageattribute from the Android manifest — AGP 8 takes the package name fromnamespace. - Replaced the deprecated
kotlinOptions { }block withkotlin { compilerOptions { } }(Kotlin 2.2 deprecation). - Synced
ios/video_pool.podspec'ss.versionwith the package version; it had been stuck at0.1.0since the first release. - The example app now exercises
media_kit_video2.x. It was pinned to^1.2.5while the package itself already resolved 2.x, so the 2.x path was untested. - Example Android toolchain moved to Flutter 3.47's template versions: Gradle
8.14 → 9.3.1, AGP 8.11.1 → 9.1.0, Kotlin 2.2.20 → 2.4.0. The example app also
drops
kotlin-androidand moveskotlinOptionsintokotlin { compilerOptions }. - Example iOS deployment target raised to 15.0 to match the podspec.
- Reformatted the codebase with Dart's tall-style formatter, which activates now that the package's language version is ≥ 3.7. Formatting only — no behavior change.
- Committed the analyzer
excludeblock thatflutter pub getgenerates forbuild/,android/, andios/.
CI #
- Bumped
actions/checkout4→7,actions/upload-pages-artifact3→5,actions/first-interaction1→3,actions/stale9→10,actions/labeler5→6 (closes the five open Dependabot PRs). - CI now also analyzes the example app and runs
dart pub publish --dry-run.
0.5.3 #
Documentation & Tooling #
- 100% public API documentation — added dartdoc to the five previously
undocumented constructors (
PlayerAdapter,LifecyclePolicy,DecoderBudget,VideoPoolPlatform) and made the desktop registrant non-instantiable. - README polish — pub/CI/license badges, a live web-demo link, an honest
comparison vs.
video_player/chewie/better_player/preload_page_view, and a roadmap. - Live web demo — a GitHub Actions workflow builds the example for the web and publishes it to GitHub Pages.
- CI — added a workflow that runs
dart format,flutter analyze, and the full test suite on every push and pull request. - Added
CONTRIBUTING.mdand a minimal single-file example (example/lib/minimal_example.dart). - Normalized formatting across the codebase (
dart format). No behavior change.
0.5.2 #
Example #
- Fixed the example's sample video URLs. The previous Google
gtv-videos-bucketURLs now return HTTP 403, so the demo could not load any video. Replaced them with short, CORS-enabled CC0 clips (Flutter's asset CDN and MDN) that play on every platform. Verified live on web: the feed now plays real video with no 403/CORS errors.
0.5.1 #
Platform Declaration #
- Declared web, macOS, Windows, and Linux as supported plugin platforms so
pub.flutter-io.cn shows all six platform badges (previously only Android/iOS). Android
and iOS keep their native plugin classes; web uses a no-op web plugin
registrant and desktop uses a no-op
dartPluginClass— no native code is needed on those platforms because the device monitor and disk cache already fall back to no-ops there. Verified withpana(6 / 6 platforms) and by building the example for web and macOS. - Added
flutter_web_plugins(Flutter SDK) as a dependency for the web registrant.
0.5.0 #
New Features #
VideoPlayerAdapter— aPlayerAdapterbacked by the officialvideo_playerplugin. Because it drives the standardvideo_playerplatform interface, you can now swap the playback backend (e.g.fvpfor libmpv, ExoPlayer, or AVPlayer) instead of media_kit — just passadapterFactory: (_) => VideoPlayerAdapter().MediaKitAdapterremains the default.- Maps
video_player'sVideoPlayerValueto the pool'sPlayerState(preparing / playing / buffering / paused / error), exposes a stable video surface that rebinds across swaps, and derives the memory estimate from the video size. - Swap semantics:
video_playerhas no in-place source swap, soswapSource()disposes the controller and creates a fresh one (decoder recreated, not reused) — the documented trade-off of the standard interface. - Hardened against failed initialization: a controller that throws during
initialize()is released without awaiting its (never-completing) dispose, so it can't hang the pool.
- Maps
- Added
video_playeras a dependency. The package still compiles on web (dart:iouse is guarded bykIsWeb).
Testing #
- Added
VideoPlayerAdapterunit tests (state mapping, recreate-on-swap, controls, error handling, widget rendering) and an end-to-end integration test driving a realVideoPlayerAdapterthroughVideoPoolvia a fake platform. 269 tests total.
0.4.0 #
Web Support #
- The package now compiles and runs on web via conditional compilation.
Two web-incompatible code paths were isolated behind
dart.library.ioconditional imports with no-op web stubs:MediaKitAdapter's HLS tuning (NativePlayer.setProperty, which does not exist on media_kit's web player) →network_tuning.dartresolves to a native impl or a web no-op.FilePreloadManager(dart:io+dart:isolate) andThumbnailExtractor(dart:io+ platform channel) → each resolves to the native impl or an inert web stub. The sharedCachedFiletype moved to a pure-Dartfile_preload_types.dart. On web the disk cache reports "not cached" andprefetchreturnsnull, so the pool streams network URLs directly.
- Verified end-to-end in Chrome:
flutter build websucceeds and the app runs — the pool initializes, reconciles, and assigns entries with no errors orMissingPluginException. Desktop (macOS) re-verified for no regression. - Example made platform-agnostic: skips the disk cache on web (
kIsWeb) and usesdefaultVideoPoolPlatform()so it runs on Android, iOS, web, and desktop.
Testing #
- Added web-stub unit tests (
FilePreloadManager,ThumbnailExtractor, and theapplyNetworkTuningno-op). 255 tests total.
0.3.4 #
Documentation / Platform Scope Correction #
- Corrected platform support after end-to-end verification. 0.3.3 described
web as running; that was premature. Verified results:
- macOS / Windows / Linux: supported. Built and ran the example on macOS —
the pool reconciles and plays through media_kit with no
MissingPluginException(the no-op device monitor is selected automatically). - Web: not yet compilable. Two blockers surfaced at build time: media_kit's
web player has no
NativePlayer.setProperty(used by the HLS tuning added in 0.3.2), and the disk cache / thumbnail extractor importdart:io. Web support now requires conditional compilation and is tracked on the roadmap.
- macOS / Windows / Linux: supported. Built and ran the example on macOS —
the pool reconciles and plays through media_kit with no
- No library code changed in this release.
0.3.3 #
Web & Desktop #
- Graceful web and desktop support — the pool no longer relies on the
Android/iOS-only native device-monitoring bridge being present. A new
NoOpVideoPoolPlatformis selected automatically (defaultVideoPoolPlatform()) on web, macOS, Windows, and Linux, soVideoPoolScoperuns everywhere the underlying player (media_kit) plays. Native thermal/memory throttling and system audio-focus management remain Android/iOS only; on other platforms the pool simply operates at its nominal state. - Hardened
AudioFocusManager—requestFocus()/releaseFocus()now swallowMissingPluginException(treating focus as granted) so a direct call on a platform without an audio-focus implementation never throws. - Exported
NoOpVideoPoolPlatformanddefaultVideoPoolPlatform().
Note: pub.flutter-io.cn platform badges still list Android/iOS (the package declares native plugin platforms for those). Declaring web/desktop as first-class plugin platforms is tracked for a future release.
0.3.2 #
Bug Fixes #
- Restored audio when scrolling back to an already-loaded video — When an
entry was demoted from primary to a preload/paused slot it was muted
(
setVolume(0)), but volume was only ever restored insideswapSource, which doesn't run on a cache hit. Revisiting a previously loaded video therefore replayed it silently. The pool now restores full volume whenever an entry transitions to playing (both in reconciliation and intogglePlayPause). Added a regression test.
Performance #
- Faster HLS startup —
MediaKitAdapternow accepts aPlayerConfigurationand applies libmpv network tuning (hls-bitrate=min, smaller initial read-ahead, network timeout) behind afastStartHlsflag (defaulttrue). HLS streams begin at the lowest variant so the first segment arrives quickly; ABR still adapts upward during playback.
iOS / Tooling #
- Swift Package Manager support — Added
ios/video_pool/Package.swiftand moved native sources toios/video_pool/Sources/video_pool/for Flutter's SwiftPM migration (3.44+). The CocoaPods podspec is retained and now points at the same shared sources, so both build systems keep working.
0.3.1 #
Bug Fixes #
- Activated predictive scroll engine in widgets —
VideoFeedViewandVideoListViewnow forward scroll velocity topool.onScrollUpdate()using drag position delta, correctly capturing fling velocity at drag end - Activated bandwidth-aware preload in example — Feed pool now configured with
BandwidthThresholds(), enabling EMA-based network adaptation - Activated cooperative multi-pool in example —
GlobalDecoderBudget(totalTokens: 4)shared between Feed and Discover pools - Added
decoderBudgetparameter toVideoPoolScope— enables cooperative multi-pool through the widget API without manual pool management
0.3.0 #
New Features #
- Event-Sourced Observability — All pool operations emit immutable
PoolEventobjects viaVideoPool.eventStream. Sealed class hierarchy with exhaustive switch support:SwapEvent,ReconcileEvent,ThrottleEvent,CacheEvent,LifecycleEvent,EmergencyFlushEvent,ErrorEvent,BandwidthSampleEvent,PredictionEvent,TokenEvent - MetricsSnapshot — Lazy-computed metrics from ring buffer: cache hit rate, avg swap latency, throttle count, bandwidth estimate, prediction accuracy. Access via
pool.metrics - Bandwidth Intelligence — EMA-based bandwidth estimation from prefetch download durations. Network-aware preload: automatically adjusts
preloadCountandprefetchBytesbased on measured bandwidth. Configurable thresholds viaBandwidthThresholdsinVideoPoolConfig - Hysteresis (Schmitt Trigger) — Prevents flip-flopping at bandwidth threshold boundaries with configurable buffer zone
- Progressive Download Resume — Interrupted prefetch downloads resume from where they left off using HTTP Range + If-Range headers. ETag validation prevents serving stale content. Max 3 retries per key
- Cache Janitor — Automatically cleans up incomplete cache entries older than 24 hours on app start
- Predictive Scroll Engine — Uses Flutter's deterministic scroll physics to predict where the user will stop scrolling. Confidence-based preload: high confidence triggers disk prefetch for target video. Target stabilization prevents redundant predictions
- Cooperative Multi-Pool —
DecoderBudgetinterface for sharing hardware decoder tokens across multipleVideoPoolinstances.GlobalDecoderBudgetimplementation with token request/release/preemption. Dynamic budget calibration on decoder init failures - Auto-Thumbnail Extraction — Extracts first-frame thumbnails from cached video files using native APIs (iOS
AVAssetImageGenerator, AndroidMediaMetadataRetriever). FastStart (moov atom) detection. Concurrency-limited extraction queue
Example App #
- Rebuilt as production-grade 3-tab showcase: Feed (TikTok), Discover (Instagram), Insights (live dashboard)
- Feed tab: full-screen video with lifecycle badges, cache status, social buttons, progress bar
- Insights tab: real-time metric cards, pool entry visualization, device status, color-coded event stream
- Tab switching pauses/resumes pool to prevent background audio
- Event debug overlay (toggle with bug icon FAB)
Testing #
- 227 unit tests (up from 132)
0.2.1 #
Bug Fixes #
- Fixed excessive reconciliation during scroll (BUG-1) —
VideoPool.onVisibilityChanged()now uses a threshold state machine that compares playable index sets (indices abovevisibilityPlayThreshold) instead of raw ratio values. Identical threshold states are skipped at near-zero cost, eliminating 17+ redundant reconciliations per scroll frame observed on Redmi Note 8 Pro - Fixed
VideoListViewtriggering reconciliation every frame — Added coarse widget-level guard that skips notifications whenprimaryIndexand visible count haven't changed, reducing calls before they reach the pool - Fixed
VideoFeedViewblocking mid-swipe preload — Removed overly restrictiveprimaryIndex != _currentPageguard fromNotificationListener; pool-level threshold filter now handles deduplication, allowing threshold crossings during page transitions to trigger timely preloads - Fixed
Map.of()copy inonVisibilityChanged— Visibility ratios are now stored by reference instead of copied, eliminating per-frame Map allocation and GC pressure - Fixed
resumeLastState()being silently skipped — Threshold state is now reset before re-emitting last visibility, ensuring reconciliation runs after app returns from background - Fixed
_tryRecoverEntries()not re-reconciling — Same threshold reset applied to post-emergency-flush recovery path
Testing #
- 132 unit and widget tests (up from 128)
- New tests: threshold deduplication (skip when unchanged), threshold crossing (trigger on boundary change)
0.2.0 #
Breaking Changes #
VideoPoolConfignow assertsmaxConcurrent <= 10andpreloadCount < maxConcurrent(debug mode only; production builds unaffected)
Bug Fixes #
- Fixed race condition between device events and reconciliation — Emergency flush now serializes through the
_activeReconciliationFuture chain, preventing disposed-adapter exceptions during concurrent reconciliation - Fixed emergency flush with no recovery — Pool now recreates adapters when memory pressure drops from terminal/critical to normal/warning, re-reconciling with the last known visibility state
- Fixed FilePreloadManager cache key collision — Replaced truncated base64 encoding with SHA-256 hash for deterministic, collision-resistant filenames
- Fixed FilePreloadManager missing HTTP timeout — Added configurable
connectionTimeoutSeconds(default: 15s) to prevent hanging downloads - Fixed FilePreloadManager missing HTTP status code check — Only 200/206 responses are accepted; other status codes return an error and clean up partial files
- Fixed FilePreloadManager evicting files in active use — Added
lockKey()/unlockKey()API; locked keys are skipped during LRU eviction - Fixed FilePreloadManager orphaning partial files on error — Disk write errors and HTTP failures now delete incomplete files
- Fixed Android thermal monitoring gap on API 21-28 — Added battery temperature proxy fallback when
PowerManager.currentThermalStatusis unavailable - Fixed audio focus not responding to system interruptions — Android
OnAudioFocusChangeListenerand iOSAVAudioSession.interruptionNotificationnow send events to Dart;AudioFocusManagerpauses/resumes playback accordingly - Fixed iOS audio resumption after interruption — Only resumes when system sets
shouldResumeflag, preventing unwanted playback after phone calls - Fixed
AudioFocusManagersubscription leak — Audio focus stream subscription is now cancelled on dispose - Fixed
VideoPoolScope.dispose()async issue — Async cleanup is now fire-and-forget with error catching, compatible with Flutter's synchronousState.dispose() - Fixed
swapSource()documentation — Updated to accurately describe player wrapper and texture surface reuse (decoder may be re-initialized)
New Features #
- Disk cache integration —
VideoPoolnow accepts an optionalFilePreloadManager; cache hits serve local files, misses trigger fire-and-forget prefetch - Cold-start manifest —
FilePreloadManager.loadManifest()recovers cached files from a previous session via_manifest.jsonsidecar file ResolutionHintenum —VideoSource.resolutionHintenables resolution-aware memory estimation (720p ~12MB, 1080p ~24MB, 4K ~96MB)audioFocusStream— New stream onVideoPoolPlatformfor system audio focus change events (default: empty stream for backward compatibility)- Runtime config safety —
maxConcurrentis clamped to[1, 10]at runtime as a safety net beyond assert-level validation
Example App #
- TikTok example now demonstrates
FilePreloadManagerwith disk caching andResolutionHint - Added
path_providerdependency for cache directory resolution
Testing #
- 128 unit and widget tests (up from 96)
- New test suites: race condition/recovery, FilePreloadManager enhancements, audio focus handling, VideoSource resolution hints
0.1.2 #
Improvements #
- Fix example app Android v1 embedding build failure — recreated with v2 embedding
- Add INTERNET permission and cleartext traffic support for Android
- Add NSAppTransportSecurity for iOS HTTP video playback
- Fix .gitignore rules that excluded example platform files
- Remove tracked generated files (Pods, .gradle, .symlinks)
- Add
repository,issue_tracker, andtopicsmetadata to pubspec - Shorten package description to meet pub.flutter-io.cn 180 char limit
- Widen
media_kit_videoconstraint to support latest version
0.1.1 #
Bug Fixes #
- Fixed audio overlap on TikTok-style feed scroll — When scrolling between videos, the previous video's audio could continue playing simultaneously with the new video. This occurred because the
DefaultLifecyclePolicyexcluded preloaded entries from the pause set, allowing a formerly-playing entry to keep its audio running when it transitioned from primary to preloaded state.DefaultLifecyclePolicy.reconcile()now correctly adds previously active entries that moved into the preload set totoPauseVideoPool._reconcile()includes a safety net that pauses any entry still inplayingstate during preload cache hits (sets volume to 0 and pauses)
0.1.0 #
Initial release of video_pool — enterprise video orchestration for Flutter.
Core Engine #
- Controller pooling with fixed-size player pool and instance reuse via
swapSource() - LifecycleOrchestrator with pluggable
LifecyclePolicystrategy pattern - MemoryManager with LRU eviction, pressure-based budget scaling, and emergency flush
- Serialized reconciliation with "latest wins" debouncing for fling scroll protection
- VideoPoolLogger with configurable log levels (none/error/warning/info/debug)
Player Adapter #
- MediaKitAdapter wrapping media_kit with ghost-frame prevention on source swap
- PlayerAdapter abstract interface for swappable player backends
- PlayerState with
ValueNotifierfor naturalValueListenableBuilderintegration
Disk Cache #
- FilePreloadManager pre-fetching first 2MB of upcoming videos to disk
- Isolate-based downloads (no UI thread blocking)
- 500MB LRU disk cache with automatic eviction
- Stable cache key hashing for cross-restart consistency
Native Monitoring #
- iOS: Thermal state via
ProcessInfo, memory viaos_proc_available_memory(), audio viaAVAudioSession - Android:
onTrimMemorymapping (RUNNING_CRITICAL → terminal flush),PowerManager.currentThermalStatus,AudioManagerfocus - DeviceCapabilities: Hardware decoder enumeration, codec support detection
Audio Focus #
- System audio focus management (AVAudioSession / AudioManager)
- Auto-pause on app background, auto-resume on foreground
- Respects phone calls and other media apps
Widgets #
- VideoPoolScope — StatefulWidget owning pool lifecycle with device monitoring
- VideoPoolProvider — InheritedWidget exposing pool to widget tree (zero dependencies)
- VideoFeedView — TikTok/Reels full-screen PageView with snapping
- VideoListView — Instagram-style ListView for mixed content feeds
- VideoCard — Full lifecycle rendering (thumbnail → loading → playing → error)
- VisibilityTracker — Pixel-level intersection ratio computation
- VideoThumbnail, VideoOverlay, VideoErrorWidget — Composable UI building blocks
Testing #
- 96 unit and widget tests
- Mock infrastructure for PlayerAdapter and DeviceMonitor