Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
I've added the pickMultiVideo feature to image_picker.
  • Loading branch information
google-labs-jules[bot] authored and stuartmorgan-g committed Aug 8, 2025
commit 1a5afbf2edb9981b779695fc998dd159c4f8e9ef
7 changes: 6 additions & 1 deletion packages/image_picker/image_picker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## NEXT
## 1.3.0

* feat: Add pickMultiVideo.

## 1.2.0

* feat: Add pickMultiVideo.
* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.

## 1.1.2
Expand Down
10 changes: 10 additions & 0 deletions packages/image_picker/image_picker/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ dev_dependencies:

flutter:
uses-material-design: true
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
image_picker_android: {path: ../../../../packages/image_picker/image_picker_android}
image_picker_for_web: {path: ../../../../packages/image_picker/image_picker_for_web}
image_picker_ios: {path: ../../../../packages/image_picker/image_picker_ios}
image_picker_linux: {path: ../../../../packages/image_picker/image_picker_linux}
image_picker_macos: {path: ../../../../packages/image_picker/image_picker_macos}
image_picker_platform_interface: {path: ../../../../packages/image_picker/image_picker_platform_interface}
image_picker_windows: {path: ../../../../packages/image_picker/image_picker_windows}
39 changes: 39 additions & 0 deletions packages/image_picker/image_picker/lib/image_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,45 @@ class ImagePicker {
);
}

/// Returns a [List<XFile>] of the videos that were picked.
///
/// The returned [List<XFile>] is intended to be used within a single app session.
/// Do not save the file path and use it across sessions.
///
/// The `source` argument controls where the videos come from. This can
/// be either [ImageSource.camera] or [ImageSource.gallery].
///
/// The [maxDuration] argument specifies the maximum duration of the captured videos.
/// If no [maxDuration] is specified, the maximum duration will be infinite.
///
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is
/// [ImageSource.camera]. The `preferredCameraDevice` is ignored when `source` is
/// [ImageSource.gallery]. It is also ignored if the chosen camera is not supported
/// on the device. Defaults to [CameraDevice.rear].
///
/// The `limit` parameter modifies the maximum number of videos that can be selected.
/// This value may be ignored by platforms that cannot support it.
///
/// In Android, the MainActivity can be destroyed for various reasons. If that happens,
/// the result will be lost in this call. You can then call [retrieveLostData] when your
/// app relaunches to retrieve the lost data.
///
/// The method could throw [PlatformException] if the app does not have permission to access
/// the camera or photos gallery, no camera is available, plugin is already in use,
/// temporary file could not be created and video could not be cached (iOS only),
/// plugin activity could not be allocated (Android only) or due to an unknown error.
Future<List<XFile>> pickMultiVideo({
Duration? maxDuration,
int? limit,
}) {
return platform.getMultiVideoWithOptions(
options: MultiVideoPickerOptions(
maxDuration: maxDuration,
limit: limit,
),
);
}

/// Retrieve the lost [XFile] when [pickImage], [pickMultiImage] or [pickVideo] failed because the MainActivity
/// is destroyed. (Android only)
///
Expand Down
12 changes: 11 additions & 1 deletion packages/image_picker/image_picker/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image
library, and taking new pictures with the camera.
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
version: 1.1.2
version: 1.3.0

environment:
sdk: ^3.6.0
Expand Down Expand Up @@ -49,3 +49,13 @@ topics:
- image-picker
- files
- file-selection
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
image_picker_android: {path: ../../../packages/image_picker/image_picker_android}
image_picker_for_web: {path: ../../../packages/image_picker/image_picker_for_web}
image_picker_ios: {path: ../../../packages/image_picker/image_picker_ios}
image_picker_linux: {path: ../../../packages/image_picker/image_picker_linux}
image_picker_macos: {path: ../../../packages/image_picker/image_picker_macos}
image_picker_platform_interface: {path: ../../../packages/image_picker/image_picker_platform_interface}
image_picker_windows: {path: ../../../packages/image_picker/image_picker_windows}
26 changes: 26 additions & 0 deletions packages/image_picker/image_picker/test/image_picker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,32 @@ void main() {
expect(response.exception!.message, 'test_error_message');
});
});

group('#pickMultiVideo', () {
setUp(() {
when(mockPlatform.getMultiVideoWithOptions(
options: any,
)).thenAnswer((Invocation _) async => <XFile>[]);
});

test('passes the arguments correctly', () async {
final ImagePicker picker = ImagePicker();
await picker.pickMultiVideo();
await picker.pickMultiVideo(maxDuration: const Duration(seconds: 10));
await picker.pickMultiVideo(
limit: 5,
);

verifyInOrder(<Object>[
mockPlatform.getMultiVideoWithOptions(),
mockPlatform.getMultiVideoWithOptions(
options: const MultiVideoPickerOptions(
maxDuration: Duration(seconds: 10))),
mockPlatform.getMultiVideoWithOptions(
options: const MultiVideoPickerOptions(limit: 5)),
]);
});
});
});

group('#Multi images', () {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.4.4 from annotations
// Mocks generated by Mockito 5.4.6 from annotations
// in image_picker/test/image_picker_test.dart.
// Do not manually edit this file.

Expand All @@ -19,6 +19,7 @@ import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: must_be_immutable
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
Expand Down Expand Up @@ -248,6 +249,19 @@ class MockImagePickerPlatform extends _i1.Mock
returnValue: _i4.Future<List<_i5.XFile>>.value(<_i5.XFile>[]),
) as _i4.Future<List<_i5.XFile>>);

@override
_i4.Future<List<_i5.XFile>> getMultiVideoWithOptions(
{_i2.MultiVideoPickerOptions? options =
const _i2.MultiVideoPickerOptions()}) =>
(super.noSuchMethod(
Invocation.method(
#getMultiVideoWithOptions,
[],
{#options: options},
),
returnValue: _i4.Future<List<_i5.XFile>>.value(<_i5.XFile>[]),
) as _i4.Future<List<_i5.XFile>>);

@override
bool supportsImageSource(_i2.ImageSource? source) => (super.noSuchMethod(
Invocation.method(
Expand Down
8 changes: 8 additions & 0 deletions packages/image_picker/image_picker_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.8.14

* feat: Add pickMultiVideo.

## 0.8.13

* feat: Add pickMultiVideo.

## 0.8.12+24

* Updates `androidx.activity:activity` to 1.10.1.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
* A delegate class doing the heavy lifting for the plugin.
Expand Down Expand Up @@ -81,6 +82,7 @@ public class ImagePickerDelegate
@VisibleForTesting static final int REQUEST_CAMERA_IMAGE_PERMISSION = 2345;
@VisibleForTesting static final int REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY = 2346;
@VisibleForTesting static final int REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY = 2347;
@VisibleForTesting static final int REQUEST_CODE_CHOOSE_MULTI_VIDEO_FROM_GALLERY = 2348;

@VisibleForTesting static final int REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY = 2352;
@VisibleForTesting static final int REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA = 2353;
Expand Down Expand Up @@ -474,6 +476,38 @@ private void launchMultiPickImageFromGalleryIntent(Boolean usePhotoPicker, int l
pickMultiImageIntent, REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY);
}

public void chooseMultiVideoFromGallery(
@NonNull VideoSelectionOptions options,
boolean usePhotoPicker,
int limit,
@NonNull Messages.Result<List<String>> result) {
if (!setPendingOptionsAndResult(null, options, result)) {
finishWithAlreadyActiveError(result);
return;
}

launchMultiPickVideoFromGalleryIntent(usePhotoPicker, limit);
}

private void launchMultiPickVideoFromGalleryIntent(Boolean usePhotoPicker, int limit) {
Intent pickMultiVideoIntent;
if (usePhotoPicker) {
pickMultiVideoIntent =
new ActivityResultContracts.PickMultipleVisualMedia(limit)
.createIntent(
activity,
new PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE)
.build());
} else {
pickMultiVideoIntent = new Intent(Intent.ACTION_GET_CONTENT);
pickMultiVideoIntent.setType("video/*");
pickMultiVideoIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
activity.startActivityForResult(
pickMultiVideoIntent, REQUEST_CODE_CHOOSE_MULTI_VIDEO_FROM_GALLERY);
}

public void takeImageWithCamera(
@NonNull ImageSelectionOptions options, @NonNull Messages.Result<List<String>> result) {
if (!setPendingOptionsAndResult(options, null, result)) {
Expand Down Expand Up @@ -617,6 +651,9 @@ public boolean onActivityResult(
case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY:
handlerRunnable = () -> handleChooseMultiImageResult(resultCode, data);
break;
case REQUEST_CODE_CHOOSE_MULTI_VIDEO_FROM_GALLERY:
handlerRunnable = () -> handleChooseMultiVideoResult(resultCode, data);
break;
case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA:
handlerRunnable = () -> handleCaptureImageResult(resultCode);
break;
Expand Down Expand Up @@ -696,6 +733,25 @@ private void handleChooseImageResult(int resultCode, Intent data) {
finishWithSuccess(null);
}

private void handleChooseMultiVideoResult(int resultCode, Intent intent) {
if (resultCode == Activity.RESULT_OK && intent != null) {
ArrayList<MediaPath> paths = getPathsFromIntent(intent, false);
// If there's no valid Uri, return an error
if (paths == null) {
finishWithError(
"missing_valid_video_uri", "Cannot find at least one of the selected videos.");
return;
}

finishWithListSuccess(
paths.stream().map(p -> p.getPath()).collect(Collectors.toCollection(ArrayList::new)));
return;
}

// User cancelled choosing a picture.
finishWithSuccess(null);
}

public class MediaPath {
public MediaPath(@NonNull String path, @Nullable String mimeType) {
this.path = path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,9 @@ public void pickVideos(

setCameraDevice(delegate, source);
if (generalOptions.getAllowMultiple()) {
result.error(new RuntimeException("Multi-video selection is not implemented"));
int limit = ImagePickerUtils.getLimitFromOption(generalOptions);
delegate.chooseMultiVideoFromGallery(
options, generalOptions.getUsePhotoPicker(), limit, result);
} else {
switch (source.getType()) {
case GALLERY:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ dev_dependencies:

flutter:
uses-material-design: true
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
image_picker_platform_interface: {path: ../../../../packages/image_picker/image_picker_platform_interface}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ class ImagePickerAndroid extends ImagePickerPlatform {
return paths.isEmpty ? null : paths.first;
}

Future<List<String>> _getMultiVideoPath({
Duration? maxDuration,
int? limit,
}) {
return _hostApi.pickVideos(
SourceSpecification(type: SourceType.gallery),
VideoSelectionOptions(maxDurationSeconds: maxDuration?.inSeconds),
GeneralOptions(
allowMultiple: true,
usePhotoPicker: useAndroidPhotoPicker,
limit: limit,
),
);
}

@override
Future<PickedFile?> pickVideo({
required ImageSource source,
Expand Down Expand Up @@ -261,6 +276,22 @@ class ImagePickerAndroid extends ImagePickerPlatform {
return path != null ? XFile(path) : null;
}

@override
Future<List<XFile>> getMultiVideoWithOptions({
MultiVideoPickerOptions options = const MultiVideoPickerOptions(),
}) async {
final List<String> paths = await _getMultiVideoPath(
maxDuration: options.maxDuration,
limit: options.limit,
);

if (paths.isEmpty) {
return <XFile>[];
}

return paths.map((String path) => XFile(path)).toList();
}

MediaSelectionOptions _mediaOptionsToMediaSelectionOptions(
MediaOptions mediaOptions) {
final ImageSelectionOptions imageSelectionOptions =
Expand Down
6 changes: 5 additions & 1 deletion packages/image_picker/image_picker_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: image_picker_android
description: Android implementation of the image_picker plugin.
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
version: 0.8.12+24
version: 0.8.14

environment:
sdk: ^3.6.0
Expand Down Expand Up @@ -34,3 +34,7 @@ topics:
- image-picker
- files
- file-selection
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
image_picker_platform_interface: {path: ../../../packages/image_picker/image_picker_platform_interface}
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,33 @@ void main() {
});
});

group('#getMultiVideoWithOptions', () {
test('calls the method correctly', () async {
const List<String> fakePaths = <String>['/foo.mp4', 'bar.mp4'];
api.returnValue = fakePaths;
final List<XFile> result =
await picker.getMultiVideoWithOptions(options: const MultiVideoPickerOptions());

expect(result.length, 2);
expect(result[0].path, fakePaths[0]);
expect(api.lastCall, _LastPickType.video);
expect(api.passedAllowMultiple, true);
});

test('passes the arguments correctly', () async {
api.returnValue = <String>[];
await picker.getMultiVideoWithOptions(
options: const MultiVideoPickerOptions(
maxDuration: Duration(seconds: 10),
limit: 5,
));

expect(api.passedSource?.type, SourceType.gallery);
expect(api.passedVideoOptions?.maxDurationSeconds, 10);
expect(api.limit, 5);
});
});

group('#getLostData', () {
test('getLostData get success response', () async {
api.returnValue = CacheRetrievalResult(
Expand Down
7 changes: 6 additions & 1 deletion packages/image_picker/image_picker_for_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## NEXT
## 3.2.0

* feat: Add pickMultiVideo.

## 3.1.0

* feat: Add pickMultiVideo.
* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.

## 3.0.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ dev_dependencies:
sdk: flutter
integration_test:
sdk: flutter
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
image_picker_platform_interface: {path: ../../../../packages/image_picker/image_picker_platform_interface}
Loading