更新超广角相机的变焦比例,确保在相机能力允许的情况下使用0.6x的缩放比例,优化相关UI和测试用例。

This commit is contained in:
2026-06-12 19:04:00 +08:00
parent d39d85cd99
commit 88d8dfda04
4 changed files with 49 additions and 33 deletions

View File

@@ -32,6 +32,7 @@ class RecordingCameraController(
private var camera: Camera? = null private var camera: Camera? = null
private var mainCameraId: String? = null private var mainCameraId: String? = null
private var ultraWideCameraId: String? = null private var ultraWideCameraId: String? = null
private var ultraWideZoomRatio: Float = DEFAULT_ULTRA_WIDE_ZOOM_RATIO
private var currentLensMode: LensMode = LensMode.MAIN private var currentLensMode: LensMode = LensMode.MAIN
private var activeRecording: Recording? = null private var activeRecording: Recording? = null
private var boundLifecycleOwner: LifecycleOwner? = null private var boundLifecycleOwner: LifecycleOwner? = null
@@ -224,11 +225,16 @@ class RecordingCameraController(
fun zoomCapabilitiesMap(): Map<String, Any> { fun zoomCapabilitiesMap(): Map<String, Any> {
val zoomState = camera?.cameraInfo?.zoomState?.value val zoomState = camera?.cameraInfo?.zoomState?.value
val minZoom = if (hasUltraWideCamera()) 0.5f else (zoomState?.minZoomRatio ?: 1f) val minZoom =
if (hasUltraWideCamera()) {
ultraWideZoomRatio
} else {
zoomState?.minZoomRatio ?: 1f
}
val maxZoom = zoomState?.maxZoomRatio ?: 3f val maxZoom = zoomState?.maxZoomRatio ?: 3f
val zoom = val zoom =
if (currentLensMode == LensMode.ULTRA_WIDE) { if (currentLensMode == LensMode.ULTRA_WIDE) {
0.5f ultraWideZoomRatio
} else { } else {
(zoomState?.zoomRatio ?: currentZoomRatio).coerceIn(minZoom, maxZoom) (zoomState?.zoomRatio ?: currentZoomRatio).coerceIn(minZoom, maxZoom)
} }
@@ -248,7 +254,7 @@ class RecordingCameraController(
if (boundCamera == null) { if (boundCamera == null) {
val clamped = val clamped =
if (ratio < 1.0 && hasUltraWideCamera()) { if (ratio < 1.0 && hasUltraWideCamera()) {
0.5f ultraWideZoomRatio
} else { } else {
ratio.toFloat().coerceAtLeast(1f) ratio.toFloat().coerceAtLeast(1f)
} }
@@ -299,6 +305,7 @@ class RecordingCameraController(
boundLifecycleOwner = null boundLifecycleOwner = null
currentLensMode = LensMode.MAIN currentLensMode = LensMode.MAIN
currentZoomRatio = 1f currentZoomRatio = 1f
ultraWideZoomRatio = DEFAULT_ULTRA_WIDE_ZOOM_RATIO
updateStatus(RecordingStatus(RecordingState.IDLE)) updateStatus(RecordingStatus(RecordingState.IDLE))
} }
@@ -315,7 +322,7 @@ class RecordingCameraController(
private fun applyCurrentZoom() { private fun applyCurrentZoom() {
val boundCamera = camera ?: return val boundCamera = camera ?: return
if (currentLensMode == LensMode.ULTRA_WIDE) { if (currentLensMode == LensMode.ULTRA_WIDE) {
currentZoomRatio = 0.5f currentZoomRatio = ultraWideZoomRatio
boundCamera.cameraControl.setZoomRatio(1f) boundCamera.cameraControl.setZoomRatio(1f)
return return
} }
@@ -334,12 +341,18 @@ class RecordingCameraController(
if (mainCameraId == null) { if (mainCameraId == null) {
mainCameraId = cameraIdForSelector(provider, CameraSelector.DEFAULT_BACK_CAMERA) mainCameraId = cameraIdForSelector(provider, CameraSelector.DEFAULT_BACK_CAMERA)
} }
ultraWideCameraId = findUltraWideCameraId(provider, mainCameraId) val ultraWideCamera = findUltraWideCamera(provider, mainCameraId)
if (ultraWideCameraId == null && currentLensMode == LensMode.ULTRA_WIDE) { ultraWideCameraId = ultraWideCamera?.cameraId
ultraWideZoomRatio = ultraWideCamera?.zoomRatio ?: DEFAULT_ULTRA_WIDE_ZOOM_RATIO
if (ultraWideCamera == null && currentLensMode == LensMode.ULTRA_WIDE) {
currentLensMode = LensMode.MAIN currentLensMode = LensMode.MAIN
currentZoomRatio = 1f currentZoomRatio = 1f
} }
Log.d(TAG, "mainCameraId=$mainCameraId ultraWideCameraId=$ultraWideCameraId") Log.d(
TAG,
"mainCameraId=$mainCameraId ultraWideCameraId=$ultraWideCameraId " +
"ultraWideZoomRatio=$ultraWideZoomRatio",
)
} }
private fun cameraIdForSelector( private fun cameraIdForSelector(
@@ -355,10 +368,10 @@ class RecordingCameraController(
} }
} }
private fun findUltraWideCameraId( private fun findUltraWideCamera(
provider: ProcessCameraProvider, provider: ProcessCameraProvider,
excludedCameraId: String?, excludedCameraId: String?,
): String? { ): UltraWideCamera? {
val manager = appContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager val manager = appContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val candidates = val candidates =
manager.cameraIdList manager.cameraIdList
@@ -373,13 +386,17 @@ class RecordingCameraController(
val mainProfile = excludedCameraId?.let { backCameraProfile(manager, it) } val mainProfile = excludedCameraId?.let { backCameraProfile(manager, it) }
val widest = candidates.firstOrNull() ?: return null val widest = candidates.firstOrNull() ?: return null
if (mainProfile == null) { if (mainProfile == null) {
return widest.cameraId return UltraWideCamera(widest.cameraId, DEFAULT_ULTRA_WIDE_ZOOM_RATIO)
} }
val meaningfullyWider = val meaningfullyWider =
widest.horizontalFov > mainProfile.horizontalFov * ULTRA_WIDE_FOV_FACTOR || widest.horizontalFov > mainProfile.horizontalFov * ULTRA_WIDE_FOV_FACTOR ||
widest.minFocalLength < mainProfile.minFocalLength * ULTRA_WIDE_FOCAL_FACTOR widest.minFocalLength < mainProfile.minFocalLength * ULTRA_WIDE_FOCAL_FACTOR
return if (meaningfullyWider) widest.cameraId else null if (!meaningfullyWider) {
return null
}
return UltraWideCamera(widest.cameraId, DEFAULT_ULTRA_WIDE_ZOOM_RATIO)
} }
private fun backCameraProfile( private fun backCameraProfile(
@@ -457,7 +474,7 @@ class RecordingCameraController(
return return
} }
if (currentLensMode == LensMode.ULTRA_WIDE) { if (currentLensMode == LensMode.ULTRA_WIDE) {
currentZoomRatio = 0.5f currentZoomRatio = ultraWideZoomRatio
onComplete(true, zoomCapabilitiesMap(), null) onComplete(true, zoomCapabilitiesMap(), null)
return return
} }
@@ -473,7 +490,7 @@ class RecordingCameraController(
} }
try { try {
currentLensMode = LensMode.ULTRA_WIDE currentLensMode = LensMode.ULTRA_WIDE
currentZoomRatio = 0.5f currentZoomRatio = ultraWideZoomRatio
bindUseCases(provider, lifecycleOwner, selectorForCameraId(ultraWideId)) bindUseCases(provider, lifecycleOwner, selectorForCameraId(ultraWideId))
applyCurrentZoom() applyCurrentZoom()
onComplete(true, zoomCapabilitiesMap(), null) onComplete(true, zoomCapabilitiesMap(), null)
@@ -539,8 +556,14 @@ class RecordingCameraController(
val horizontalFov: Double, val horizontalFov: Double,
) )
private data class UltraWideCamera(
val cameraId: String,
val zoomRatio: Float,
)
companion object { companion object {
private const val TAG = "RecordingCamera" private const val TAG = "RecordingCamera"
private const val DEFAULT_ULTRA_WIDE_ZOOM_RATIO = 0.6f
private const val ULTRA_WIDE_FOV_FACTOR = 1.08 private const val ULTRA_WIDE_FOV_FACTOR = 1.08
private const val ULTRA_WIDE_FOCAL_FACTOR = 0.92 private const val ULTRA_WIDE_FOCAL_FACTOR = 0.92
} }

View File

@@ -234,7 +234,7 @@ class _ZoomPresetControl extends StatelessWidget {
for (final preset in availablePresets) for (final preset in availablePresets)
_ZoomPresetButton( _ZoomPresetButton(
displayRatio: preset, displayRatio: preset,
requestRatio: _requestRatioFor(preset), requestRatio: preset,
selected: _isPresetSelected(preset), selected: _isPresetSelected(preset),
enabled: !_wouldSwitchPhysicalCamera(preset), enabled: !_wouldSwitchPhysicalCamera(preset),
onSelected: onSelected, onSelected: onSelected,
@@ -259,13 +259,6 @@ class _ZoomPresetControl extends StatelessWidget {
return (zoomRatio - preset).abs() < 0.05; return (zoomRatio - preset).abs() < 0.05;
} }
double _requestRatioFor(double preset) {
if (preset < 1.0) {
return minZoomRatio <= 0.5 ? 0.5 : preset;
}
return preset;
}
bool _wouldSwitchPhysicalCamera(double preset) { bool _wouldSwitchPhysicalCamera(double preset) {
if (!isRecording) { if (!isRecording) {
return false; return false;

View File

@@ -77,7 +77,7 @@ void main() {
}); });
test( test(
'passes native ultra-wide ratio when camera capabilities allow it', 'clamps legacy 0.5x request to 0.6x ultra-wide ratio',
() async { () async {
final calls = <MethodCall>[]; final calls = <MethodCall>[];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
@@ -86,8 +86,8 @@ void main() {
(call) async { (call) async {
calls.add(call); calls.add(call);
return <String, dynamic>{ return <String, dynamic>{
'zoomRatio': 0.5, 'zoomRatio': 0.6,
'minZoomRatio': 0.5, 'minZoomRatio': 0.6,
'maxZoomRatio': 3.0, 'maxZoomRatio': 3.0,
}; };
}, },
@@ -101,17 +101,17 @@ void main() {
.copyWith( .copyWith(
session: const RecordingSessionState( session: const RecordingSessionState(
zoomRatio: 1.0, zoomRatio: 1.0,
minZoomRatio: 0.5, minZoomRatio: 0.6,
maxZoomRatio: 3.0, maxZoomRatio: 3.0,
), ),
); );
await notifier.setZoomRatio(0.5); await notifier.setZoomRatio(0.5);
expect(calls.single.arguments, <String, dynamic>{'zoomRatio': 0.5}); expect(calls.single.arguments, <String, dynamic>{'zoomRatio': 0.6});
final session = container.read(recordingViewModelProvider).session; final session = container.read(recordingViewModelProvider).session;
expect(session.zoomRatio, 0.5); expect(session.zoomRatio, 0.6);
expect(session.minZoomRatio, 0.5); expect(session.minZoomRatio, 0.6);
expect(session.maxZoomRatio, 3.0); expect(session.maxZoomRatio, 3.0);
}, },
); );

View File

@@ -54,7 +54,7 @@ void main() {
expect(find.text('3x'), findsNothing); expect(find.text('3x'), findsNothing);
}); });
testWidgets('shows 0.6x when 0.5x camera capability supports it', ( testWidgets('shows 0.6x when ultra-wide camera capability is below 0.6', (
tester, tester,
) async { ) async {
await pumpHud(tester, minZoomRatio: 0.5); await pumpHud(tester, minZoomRatio: 0.5);
@@ -75,7 +75,7 @@ void main() {
expect(find.text('1x'), findsOneWidget); expect(find.text('1x'), findsOneWidget);
}); });
testWidgets('marks current 0.5x zoom ratio as selected on 0.6x UI', ( testWidgets('marks current ultra-wide zoom ratio as selected on 0.6x UI', (
tester, tester,
) async { ) async {
await pumpHud(tester, zoomRatio: 0.5, minZoomRatio: 0.5); await pumpHud(tester, zoomRatio: 0.5, minZoomRatio: 0.5);
@@ -102,7 +102,7 @@ void main() {
expect(find.text('1x'), findsNothing); expect(find.text('1x'), findsNothing);
}); });
testWidgets('tapping 0.6x reports 0.5 when camera supports 0.5x', ( testWidgets('tapping 0.6x reports 0.6 when camera capability is below 0.6', (
tester, tester,
) async { ) async {
double? selected; double? selected;
@@ -115,7 +115,7 @@ void main() {
await tester.tap(find.text('0.6x')); await tester.tap(find.text('0.6x'));
await tester.pump(); await tester.pump();
expect(selected, 0.5); expect(selected, 0.6);
}); });
testWidgets('tapping 0.6x reports 0.6 when camera only supports 0.6x', ( testWidgets('tapping 0.6x reports 0.6 when camera only supports 0.6x', (
@@ -148,7 +148,7 @@ void main() {
expect(mainButton.enabled, isFalse); expect(mainButton.enabled, isFalse);
}); });
testWidgets('disables main zoom presets while recording on 0.5x', ( testWidgets('disables main zoom presets while recording on ultra-wide', (
tester, tester,
) async { ) async {
await pumpHud(tester, zoomRatio: 0.5, minZoomRatio: 0.5, isRecording: true); await pumpHud(tester, zoomRatio: 0.5, minZoomRatio: 0.5, isRecording: true);