Skip to content

Commit 2dfe645

Browse files
authored
[camera] MediaSettings parameter for createCameraWithSettings (flutter#3586)
This PR is for enabling fps and bitrate control of recorded video. Allow users to more control over recorded video size. `CameraPlatform.createCameraWithSettings` is added, leaving original `CameraPlatform.createCamera` commented as deprecated. So this is not breaking change. Tested on a set of mobile devices. Web support depends on browser (perfect with Firefox). Fixes flutter#54339
1 parent 9a94bfd commit 2dfe645

File tree

5 files changed

+106
-23
lines changed

5 files changed

+106
-23
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 0.10.6
22

3+
* Adds support to control video fps and bitrate. See `CameraController` constructor.
34
* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1.
45
* Updates support matrix in README to indicate that iOS 11 is no longer supported.
56
* Clients on versions of Flutter that still support iOS 11 can continue to use this

packages/camera/camera/lib/src/camera_controller.dart

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,30 @@ class CameraValue {
232232
/// To show the camera preview on the screen use a [CameraPreview] widget.
233233
class CameraController extends ValueNotifier<CameraValue> {
234234
/// Creates a new camera controller in an uninitialized state.
235+
///
236+
/// - [resolutionPreset] affect the quality of video recording and image capture.
237+
/// - [enableAudio] controls audio presence in recorded video.
238+
///
239+
/// Following parameters (if present) will overwrite [resolutionPreset] settings:
240+
/// - [fps] controls rate at which frames should be captured by the camera in frames per second.
241+
/// - [videoBitrate] controls the video encoding bit rate for recording.
242+
/// - [audioBitrate] controls the audio encoding bit rate for recording.
243+
235244
CameraController(
236245
CameraDescription description,
237-
this.resolutionPreset, {
238-
this.enableAudio = true,
246+
ResolutionPreset resolutionPreset, {
247+
bool enableAudio = true,
248+
int? fps,
249+
int? videoBitrate,
250+
int? audioBitrate,
239251
this.imageFormatGroup,
240-
}) : super(CameraValue.uninitialized(description));
252+
}) : mediaSettings = MediaSettings(
253+
resolutionPreset: resolutionPreset,
254+
enableAudio: enableAudio,
255+
fps: fps,
256+
videoBitrate: videoBitrate,
257+
audioBitrate: audioBitrate),
258+
super(CameraValue.uninitialized(description));
241259

242260
/// The properties of the camera device controlled by this controller.
243261
CameraDescription get description => value.description;
@@ -248,10 +266,19 @@ class CameraController extends ValueNotifier<CameraValue> {
248266
/// if unavailable a lower resolution will be used.
249267
///
250268
/// See also: [ResolutionPreset].
251-
final ResolutionPreset resolutionPreset;
269+
ResolutionPreset get resolutionPreset =>
270+
mediaSettings.resolutionPreset ?? ResolutionPreset.max;
252271

253272
/// Whether to include audio when recording a video.
254-
final bool enableAudio;
273+
bool get enableAudio => mediaSettings.enableAudio;
274+
275+
/// The media settings this controller is targeting.
276+
///
277+
/// This media settings are not guaranteed to be available on the device,
278+
/// if unavailable a [resolutionPreset] default values will be used.
279+
///
280+
/// See also: [MediaSettings].
281+
final MediaSettings mediaSettings;
255282

256283
/// The [ImageFormatGroup] describes the output of the raw image format.
257284
///
@@ -265,6 +292,7 @@ class CameraController extends ValueNotifier<CameraValue> {
265292

266293
bool _isDisposed = false;
267294
StreamSubscription<CameraImageData>? _imageStreamSubscription;
295+
268296
// A Future awaiting an attempt to initialize (e.g. after `initialize` was
269297
// just called). If the controller has not been initialized at least once,
270298
// this value is null.
@@ -313,10 +341,9 @@ class CameraController extends ValueNotifier<CameraValue> {
313341
);
314342
});
315343

316-
_cameraId = await CameraPlatform.instance.createCamera(
344+
_cameraId = await CameraPlatform.instance.createCameraWithSettings(
317345
description,
318-
resolutionPreset,
319-
enableAudio: enableAudio,
346+
mediaSettings,
320347
);
321348

322349
_unawaited(CameraPlatform.instance
@@ -372,7 +399,7 @@ class CameraController extends ValueNotifier<CameraValue> {
372399

373400
/// Pauses the current camera preview
374401
Future<void> pausePreview() async {
375-
if (value.isPreviewPaused) {
402+
if (value.isPreviewPaused || !value.isInitialized || _isDisposed) {
376403
return;
377404
}
378405
try {
@@ -923,7 +950,7 @@ class Optional<T> extends IterableBase<T> {
923950
if (_value == null) {
924951
throw StateError('value called on absent Optional.');
925952
}
926-
return _value!;
953+
return _value;
927954
}
928955

929956
/// Executes a function if the Optional value is present.
@@ -960,7 +987,7 @@ class Optional<T> extends IterableBase<T> {
960987
Optional<S> transform<S>(S Function(T value) transformer) {
961988
return _value == null
962989
? Optional<S>.absent()
963-
: Optional<S>.of(transformer(_value as T));
990+
: Optional<S>.of(transformer(_value));
964991
}
965992

966993
/// Transforms the Optional value.
@@ -971,7 +998,7 @@ class Optional<T> extends IterableBase<T> {
971998
Optional<S> transformNullable<S>(S? Function(T value) transformer) {
972999
return _value == null
9731000
? Optional<S>.absent()
974-
: Optional<S>.fromNullable(transformer(_value as T));
1001+
: Optional<S>.fromNullable(transformer(_value));
9751002
}
9761003

9771004
@override

packages/camera/camera/pubspec.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ description: A Flutter plugin for controlling the camera. Supports previewing
44
Dart.
55
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
7-
version: 0.10.5+9
7+
version: 0.10.6
88

99
environment:
10-
sdk: ^3.1.0
11-
flutter: ">=3.13.0"
10+
sdk: ^3.2.3
11+
flutter: ">=3.16.6"
1212

1313
flutter:
1414
plugin:
@@ -21,10 +21,10 @@ flutter:
2121
default_package: camera_web
2222

2323
dependencies:
24-
camera_android: ^0.10.7
25-
camera_avfoundation: ^0.9.13
26-
camera_platform_interface: ^2.5.0
27-
camera_web: ^0.3.1
24+
camera_android: ^0.10.9
25+
camera_avfoundation: ^0.9.15
26+
camera_platform_interface: ^2.6.0
27+
camera_web: ^0.3.3
2828
flutter:
2929
sdk: flutter
3030
flutter_plugin_android_lifecycle: ^2.0.2

packages/camera/camera/test/camera_preview_test.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'package:camera/camera.dart';
6+
import 'package:camera_platform_interface/camera_platform_interface.dart';
67
import 'package:flutter/foundation.dart';
78
import 'package:flutter/material.dart';
89
import 'package:flutter/services.dart';
@@ -68,6 +69,15 @@ class FakeController extends ValueNotifier<CameraValue>
6869
@override
6970
ResolutionPreset get resolutionPreset => ResolutionPreset.low;
7071

72+
@override
73+
MediaSettings get mediaSettings => const MediaSettings(
74+
resolutionPreset: ResolutionPreset.low,
75+
fps: 15,
76+
videoBitrate: 200000,
77+
audioBitrate: 32000,
78+
enableAudio: true,
79+
);
80+
7181
@override
7282
Future<void> resumeVideoRecording() async {}
7383

packages/camera/camera/test/camera_test.dart

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,46 @@ void main() {
111111
expect(cameraController.value.isInitialized, isTrue);
112112
});
113113

114+
test('can be initialized with media settings', () async {
115+
final CameraController cameraController = CameraController(
116+
const CameraDescription(
117+
name: 'cam',
118+
lensDirection: CameraLensDirection.back,
119+
sensorOrientation: 90),
120+
ResolutionPreset.low,
121+
fps: 15,
122+
videoBitrate: 200000,
123+
audioBitrate: 32000,
124+
enableAudio: false,
125+
);
126+
await cameraController.initialize();
127+
128+
expect(cameraController.value.aspectRatio, 1);
129+
expect(cameraController.value.previewSize, const Size(75, 75));
130+
expect(cameraController.value.isInitialized, isTrue);
131+
expect(cameraController.resolutionPreset, ResolutionPreset.low);
132+
expect(cameraController.enableAudio, false);
133+
expect(cameraController.mediaSettings.fps, 15);
134+
expect(cameraController.mediaSettings.videoBitrate, 200000);
135+
expect(cameraController.mediaSettings.audioBitrate, 32000);
136+
});
137+
138+
test('default constructor initializes media settings', () async {
139+
final CameraController cameraController = CameraController(
140+
const CameraDescription(
141+
name: 'cam',
142+
lensDirection: CameraLensDirection.back,
143+
sensorOrientation: 90),
144+
ResolutionPreset.max);
145+
await cameraController.initialize();
146+
147+
expect(cameraController.resolutionPreset, ResolutionPreset.max);
148+
expect(cameraController.enableAudio, true);
149+
expect(cameraController.mediaSettings.fps, isNull);
150+
expect(cameraController.mediaSettings.videoBitrate, isNull);
151+
expect(cameraController.mediaSettings.audioBitrate, isNull);
152+
});
153+
114154
test('can be disposed', () async {
115155
final CameraController cameraController = CameraController(
116156
const CameraDescription(
@@ -1429,15 +1469,20 @@ class MockCameraPlatform extends Mock
14291469
Future<List<CameraDescription>> availableCameras() =>
14301470
Future<List<CameraDescription>>.value(mockAvailableCameras);
14311471

1472+
@override
1473+
Future<int> createCameraWithSettings(
1474+
CameraDescription cameraDescription, MediaSettings? mediaSettings) =>
1475+
mockPlatformException
1476+
? throw PlatformException(code: 'foo', message: 'bar')
1477+
: Future<int>.value(mockInitializeCamera);
1478+
14321479
@override
14331480
Future<int> createCamera(
14341481
CameraDescription description,
14351482
ResolutionPreset? resolutionPreset, {
14361483
bool enableAudio = false,
14371484
}) =>
1438-
mockPlatformException
1439-
? throw PlatformException(code: 'foo', message: 'bar')
1440-
: Future<int>.value(mockInitializeCamera);
1485+
createCameraWithSettings(description, null);
14411486

14421487
@override
14431488
Stream<CameraInitializedEvent> onCameraInitialized(int cameraId) =>

0 commit comments

Comments
 (0)