- 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.
58 lines
2.4 KiB
Dart
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;
|
|
},
|
|
);
|
|
}
|
|
}
|