更新超广角相机的变焦比例,确保在相机能力允许的情况下使用0.6x的缩放比例,优化相关UI和测试用例。
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user