camera: mirror preview only, not capture (telegram fidelity)
Drop isVideoMirrored on the AVCaptureVideoDataOutput connection — the
data output feeds both the preview texture AND the recorder, so any
mirror set there ended up baked into the recorded MP4. Recorded video
+ captured JPEG now carry the raw sensor feed ("as others see you"),
matching telegram-iOS and the stock iOS Camera app default.
The selfie preview is mirrored inside UxCameraPreview itself
(Transform.flip(flipX: true) around the Texture when
description.lens == front) — the analog of telegram's
CameraPreviewView.mirroring CALayer transform. Consumers
(CameraThumb, etc.) don't need to know which lens is active.
This commit is contained in:
@@ -411,7 +411,15 @@ final class CameraInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply preview-output settings on the (new) connection.
|
// Apply preview-output orientation. Mirroring is deliberately
|
||||||
|
// NOT set here — the data output feeds both the preview
|
||||||
|
// texture and the recorder, so mirroring at the connection
|
||||||
|
// would land in the recorded MP4 too. Telegram avoids this
|
||||||
|
// by mirroring at the preview-LAYER level (CALayer transform
|
||||||
|
// in `CameraPreviewView.mirroring`). Our FlutterTexture
|
||||||
|
// equivalent is a `Transform.flip` in [CameraThumb] for the
|
||||||
|
// front camera — raw sensor feed at capture, mirror as a
|
||||||
|
// playback decision.
|
||||||
if let videoConn = videoDataOutput?.connection(with: .video) {
|
if let videoConn = videoDataOutput?.connection(with: .video) {
|
||||||
if videoConn.isVideoOrientationSupported {
|
if videoConn.isVideoOrientationSupported {
|
||||||
videoConn.videoOrientation = lockedOrientation?.avVideoOrientation
|
videoConn.videoOrientation = lockedOrientation?.avVideoOrientation
|
||||||
@@ -419,7 +427,7 @@ final class CameraInstance {
|
|||||||
}
|
}
|
||||||
if videoConn.isVideoMirroringSupported {
|
if videoConn.isVideoMirroringSupported {
|
||||||
videoConn.automaticallyAdjustsVideoMirroring = false
|
videoConn.automaticallyAdjustsVideoMirroring = false
|
||||||
videoConn.isVideoMirrored = (device.position == .front)
|
videoConn.isVideoMirrored = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'camera.dart' show UxCameraController, UxCameraValue;
|
import 'camera.dart' show UxCameraController, UxCameraLens, UxCameraValue;
|
||||||
|
|
||||||
/// Renders the live preview for [controller] into a [Texture]. Sizes
|
/// Renders the live preview for [controller] into a [Texture]. Sizes
|
||||||
/// itself to the parent — wrap in `AspectRatio` / `FittedBox` / `Hero`
|
/// itself to the parent — wrap in `AspectRatio` / `FittedBox` / `Hero`
|
||||||
@@ -10,6 +10,11 @@ import 'camera.dart' show UxCameraController, UxCameraValue;
|
|||||||
/// transparent placeholder. The widget rebuilds on every
|
/// transparent placeholder. The widget rebuilds on every
|
||||||
/// `UxCameraValue` change, so once the native session starts
|
/// `UxCameraValue` change, so once the native session starts
|
||||||
/// producing frames the texture appears automatically.
|
/// producing frames the texture appears automatically.
|
||||||
|
///
|
||||||
|
/// Front-camera preview is auto-mirrored here (the analog of
|
||||||
|
/// telegram-iOS's `CameraPreviewView.mirroring` property), so the
|
||||||
|
/// recorded MP4 + captured JPEG carry the raw sensor feed while the
|
||||||
|
/// on-screen preview still reads as a natural mirror to the user.
|
||||||
class UxCameraPreview extends StatelessWidget {
|
class UxCameraPreview extends StatelessWidget {
|
||||||
const UxCameraPreview({super.key, required this.controller});
|
const UxCameraPreview({super.key, required this.controller});
|
||||||
|
|
||||||
@@ -19,10 +24,14 @@ class UxCameraPreview extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ValueListenableBuilder<UxCameraValue>(
|
return ValueListenableBuilder<UxCameraValue>(
|
||||||
valueListenable: controller,
|
valueListenable: controller,
|
||||||
builder: (context, _, __) {
|
builder: (context, value, _) {
|
||||||
final id = controller.textureId;
|
final id = controller.textureId;
|
||||||
if (id == null) return const SizedBox.expand();
|
if (id == null) return SizedBox.expand();
|
||||||
return Texture(textureId: id);
|
final mirror = value.description.lens == UxCameraLens.front;
|
||||||
|
final texture = Texture(textureId: id);
|
||||||
|
return mirror
|
||||||
|
? Transform.flip(flipX: true, child: texture)
|
||||||
|
: texture;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user