diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index 2818f35b8c8..ffb540b6587 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.6.18
+
+* Adds support for the `MediaSettings.enableAudio` setting, which determines whether or not audio is
+ recorded during video recording.
+
## 0.6.17+1
* Replaces deprecated `onSurfaceDestroyed` with `onSurfaceCleanup`.
diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle
index 7c387331a74..4ed1e856598 100644
--- a/packages/camera/camera_android_camerax/android/build.gradle
+++ b/packages/camera/camera_android_camerax/android/build.gradle
@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.5.0'
+ classpath 'com.android.tools.build:gradle:8.6.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -57,16 +57,17 @@ android {
}
}
- lintOptions {
+ lint {
checkAllWarnings true
warningsAsErrors true
disable 'AndroidGradlePluginVersion', 'GradleDependency', 'InvalidPackage', 'NewerVersionAvailable'
+ baseline = file("lint-baseline.xml")
}
}
dependencies {
// CameraX core library using the camera2 implementation must use same version number.
- def camerax_version = "1.4.1"
+ def camerax_version = "1.5.0-beta01"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
diff --git a/packages/camera/camera_android_camerax/android/lint-baseline.xml b/packages/camera/camera_android_camerax/android/lint-baseline.xml
new file mode 100644
index 00000000000..7a4999067f3
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/lint-baseline.xml
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt
index 3841b8beb2a..58f599dee23 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt
@@ -3,7 +3,7 @@
// found in the LICENSE file.
// Autogenerated from Pigeon (v25.3.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
-@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass", "UnsafeOptInUsageError")
+@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package io.flutter.plugins.camerax
@@ -3643,6 +3643,12 @@ abstract class PigeonApiVideoRecordEventListener(
abstract class PigeonApiPendingRecording(
open val pigeonRegistrar: CameraXLibraryPigeonProxyApiRegistrar
) {
+ /** Enables audio to be recorded for this recording. */
+ abstract fun withAudioEnabled(
+ pigeon_instance: androidx.camera.video.PendingRecording,
+ initialMuted: Boolean
+ ): androidx.camera.video.PendingRecording
+
/** Starts the recording, making it an active recording. */
abstract fun start(
pigeon_instance: androidx.camera.video.PendingRecording,
@@ -3653,6 +3659,29 @@ abstract class PigeonApiPendingRecording(
@Suppress("LocalVariableName")
fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiPendingRecording?) {
val codec = api?.pigeonRegistrar?.codec ?: CameraXLibraryPigeonCodec()
+ run {
+ val channel =
+ BasicMessageChannel(
+ binaryMessenger,
+ "dev.flutter.pigeon.camera_android_camerax.PendingRecording.withAudioEnabled",
+ codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val pigeon_instanceArg = args[0] as androidx.camera.video.PendingRecording
+ val initialMutedArg = args[1] as Boolean
+ val wrapped: List =
+ try {
+ listOf(api.withAudioEnabled(pigeon_instanceArg, initialMutedArg))
+ } catch (exception: Throwable) {
+ CameraXLibraryPigeonUtils.wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
run {
val channel =
BasicMessageChannel(
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingProxyApi.java
index c7f294a0f59..14fef1b0ef9 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingProxyApi.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingProxyApi.java
@@ -4,6 +4,8 @@
package io.flutter.plugins.camerax;
+import android.Manifest;
+import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import androidx.camera.video.PendingRecording;
import androidx.camera.video.Recording;
@@ -25,6 +27,19 @@ public ProxyApiRegistrar getPigeonRegistrar() {
return (ProxyApiRegistrar) super.getPigeonRegistrar();
}
+ @NonNull
+ @Override
+ public PendingRecording withAudioEnabled(PendingRecording pigeonInstance, boolean initialMuted) {
+ if (!initialMuted
+ && ContextCompat.checkSelfPermission(
+ getPigeonRegistrar().getContext(), Manifest.permission.RECORD_AUDIO)
+ == PackageManager.PERMISSION_GRANTED) {
+ return pigeonInstance.withAudioEnabled(false);
+ }
+
+ return pigeonInstance.withAudioEnabled(true);
+ }
+
@NonNull
@Override
public Recording start(
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java
index 4aa83f9e66e..0f326281b9a 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java
@@ -4,15 +4,12 @@
package io.flutter.plugins.camerax;
-import android.Manifest;
-import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.video.FileOutputOptions;
import androidx.camera.video.PendingRecording;
import androidx.camera.video.QualitySelector;
import androidx.camera.video.Recorder;
-import androidx.core.content.ContextCompat;
import java.io.File;
/**
@@ -69,11 +66,6 @@ public PendingRecording prepareRecording(Recorder pigeonInstance, @NonNull Strin
final PendingRecording pendingRecording =
pigeonInstance.prepareRecording(getPigeonRegistrar().getContext(), fileOutputOptions);
- if (ContextCompat.checkSelfPermission(
- getPigeonRegistrar().getContext(), Manifest.permission.RECORD_AUDIO)
- == PackageManager.PERMISSION_GRANTED) {
- pendingRecording.withAudioEnabled();
- }
return pendingRecording;
}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java
index 23e8279a7a4..ad606cb5082 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java
@@ -5,10 +5,15 @@
package io.flutter.plugins.camerax;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
import androidx.camera.video.PendingRecording;
import androidx.camera.video.Recording;
import androidx.core.content.ContextCompat;
@@ -19,6 +24,65 @@
import org.mockito.stubbing.Answer;
public class PendingRecordingTest {
+ @Test
+ public void withAudioEnabled_enablesAudioWhenRequestedAndPermissionGranted() {
+ final PigeonApiPendingRecording api =
+ new TestProxyApiRegistrar().getPigeonApiPendingRecording();
+ final PendingRecording instance = mock(PendingRecording.class);
+ final PendingRecording newInstance = mock(PendingRecording.class);
+
+ try (MockedStatic mockedContextCompat =
+ Mockito.mockStatic(ContextCompat.class)) {
+ mockedContextCompat
+ .when(
+ () ->
+ ContextCompat.checkSelfPermission(
+ any(Context.class), eq(Manifest.permission.RECORD_AUDIO)))
+ .thenAnswer((Answer) invocation -> PackageManager.PERMISSION_GRANTED);
+
+ when(instance.withAudioEnabled(false)).thenReturn(newInstance);
+
+ assertEquals(api.withAudioEnabled(instance, false), newInstance);
+ verify(instance).withAudioEnabled(false);
+ }
+ }
+
+ @Test
+ public void withAudioEnabled_doesNotEnableAudioWhenRequestedAndPermissionNotGranted() {
+ final PigeonApiPendingRecording api =
+ new TestProxyApiRegistrar().getPigeonApiPendingRecording();
+ final PendingRecording instance = mock(PendingRecording.class);
+ final PendingRecording newInstance = mock(PendingRecording.class);
+
+ try (MockedStatic mockedContextCompat =
+ Mockito.mockStatic(ContextCompat.class)) {
+ mockedContextCompat
+ .when(
+ () ->
+ ContextCompat.checkSelfPermission(
+ any(Context.class), eq(Manifest.permission.RECORD_AUDIO)))
+ .thenAnswer((Answer) invocation -> PackageManager.PERMISSION_DENIED);
+
+ when(instance.withAudioEnabled(true)).thenReturn(newInstance);
+
+ assertEquals(api.withAudioEnabled(instance, false), newInstance);
+ verify(instance).withAudioEnabled(true);
+ }
+ }
+
+ @Test
+ public void withAudioEnabled_doesNotEnableAudioWhenAudioNotRequested() {
+ final PigeonApiPendingRecording api =
+ new TestProxyApiRegistrar().getPigeonApiPendingRecording();
+ final PendingRecording instance = mock(PendingRecording.class);
+ final PendingRecording newInstance = mock(PendingRecording.class);
+
+ when(instance.withAudioEnabled(true)).thenReturn(newInstance);
+
+ assertEquals(api.withAudioEnabled(instance, true), newInstance);
+ verify(instance).withAudioEnabled(true);
+ }
+
@Test
public void start_callsStartOnInstance() {
final PigeonApiPendingRecording api =
diff --git a/packages/camera/camera_android_camerax/example/lib/main.dart b/packages/camera/camera_android_camerax/example/lib/main.dart
index 07043549441..359f584fd76 100644
--- a/packages/camera/camera_android_camerax/example/lib/main.dart
+++ b/packages/camera/camera_android_camerax/example/lib/main.dart
@@ -661,11 +661,12 @@ class _CameraExampleHomeState extends State
final CameraController cameraController = CameraController(
cameraDescription,
- mediaSettings: const MediaSettings(
+ mediaSettings: MediaSettings(
resolutionPreset: ResolutionPreset.low,
fps: 15,
videoBitrate: 200000,
audioBitrate: 32000,
+ enableAudio: enableAudio,
),
imageFormatGroup: ImageFormatGroup.jpeg,
);
diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
index fdb83feddb7..795a8747227 100644
--- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
+++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
@@ -261,6 +261,11 @@ class AndroidCameraCameraX extends CameraPlatform {
/// This is expressed in terms of one of the [Surface] rotation constant.
late int _initialDefaultDisplayRotation;
+ /// Whether or not audio should be enabled for recording video if permission is
+ /// granted.
+ @visibleForTesting
+ late bool enableRecordingAudio;
+
/// Returns list of all available cameras and their descriptions.
@override
Future> availableCameras() async {
@@ -345,8 +350,9 @@ class AndroidCameraCameraX extends CameraPlatform {
CameraDescription cameraDescription,
MediaSettings? mediaSettings,
) async {
+ enableRecordingAudio = mediaSettings?.enableAudio ?? false;
final CameraPermissionsError? error = await systemServicesManager
- .requestCameraPermissions(mediaSettings?.enableAudio ?? false);
+ .requestCameraPermissions(enableRecordingAudio);
if (error != null) {
throw CameraException(error.errorCode, error.description);
@@ -1109,6 +1115,13 @@ class AndroidCameraCameraX extends CameraPlatform {
);
pendingRecording = await recorder!.prepareRecording(videoOutputPath!);
+ // Enable/disable recording audio as requested. If enabling audio is requested
+ // and permission was not granted when the camera was created, then recording
+ // audio will be disabled to respect the denied permission.
+ pendingRecording = await pendingRecording!.withAudioEnabled(
+ /* initialMuted */ !enableRecordingAudio,
+ );
+
recording = await pendingRecording!.start(_videoRecordingEventListener);
if (streamCallback != null) {
diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
index 1df922788e6..ba337d81f32 100644
--- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
@@ -4359,6 +4359,42 @@ class PendingRecording extends PigeonInternalProxyApiBaseClass {
}
}
+ /// Enables audio to be recorded for this recording.
+ Future withAudioEnabled(bool initialMuted) async {
+ final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec =
+ _pigeonVar_codecPendingRecording;
+ final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger;
+ const String pigeonVar_channelName =
+ 'dev.flutter.pigeon.camera_android_camerax.PendingRecording.withAudioEnabled';
+ final BasicMessageChannel