Files
ux/lib/src/video/x_video_player_view.dart
agra de4925adf9 video_player + insets: native playback backend + animated viewPadding
- video_player: ExoPlayer (Android) / AVPlayer (iOS/macOS) backend with
  PixelBufferSink, method-channel adapter, Dart-side XVideoPlayer +
  testing fake.
- insets: XInsets singleton + XAnimatedInsets widget lerp the system
  viewPadding over 220ms so OS bar visibility toggles
  (immersiveSticky <-> edgeToEdge) slide bottom-/top-anchored UI into
  place instead of snapping by the nav-bar / status-bar height.
2026-05-23 15:57:15 +03:00

58 lines
2.4 KiB
Dart

import 'package:flutter/widgets.dart';
import 'x_video_player.dart' show XVideoPlayerController, XVideoPlayerValue;
/// Renders the active frame of [controller] into the parent's box.
/// Sizes itself to the parent — wrap in `AspectRatio` / `FittedBox` /
/// `Hero` to control framing.
///
/// Single render path: a Flutter [Texture] widget over the texture
/// the native side hands back at `create()` time. Android feeds the
/// texture from an [`ExoPlayer`] via a `SurfaceTexture`; Apple feeds
/// it from `AVPlayerItemVideoOutput`. Because the rendered content
/// lives inside Flutter's compositor:
///
/// - gallery hero / dismiss animations stay buttery
/// - `RenderRepaintBoundary.toImage` (the gallery's `snapshot`
/// mechanism) sees the actual frame and can freeze it across
/// transitions
/// - no per-frame `ImageReader` round-trip — scrub latency stays at
/// codec-seek speed
///
/// Codec crop is handled native-side: Media3's
/// `DefaultVideoFrameProcessor` (Android) re-applies the codec's crop
/// rect downstream of the broken Huawei `SurfaceTexture` transform
/// matrix, so the green right-edge artifact never reaches Flutter's
/// sampler. Apple's `AVPlayerItemVideoOutput` is crop-correct by
/// construction.
class XVideoPlayerView extends StatelessWidget {
const XVideoPlayerView({super.key, required this.controller});
final XVideoPlayerController controller;
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<XVideoPlayerValue>(
valueListenable: controller,
builder: (context, value, _) {
if (!value.isInitialized) return SizedBox.expand();
final textureId = controller.textureId;
if (textureId == null) return SizedBox.expand();
Widget child = Texture(textureId: textureId);
// Apple's `AVPlayerItemVideoOutput` hands us `CVPixelBuffer`s in
// the file's natural orientation — the video track's
// `preferredTransform` rotation is NOT applied. The native side
// reports the rotation as quarter-turns; we apply it here so
// the rendered frame reads upright. Android always reports 0
// because the codec + GLES blit handle rotation upstream of
// Flutter.
final turns = value.rotationQuarterTurns;
if (turns != 0) {
child = RotatedBox(quarterTurns: turns, child: child);
}
return child;
},
);
}
}