[image_picker] getMedia platform implementations (#4175)

Adds `getMedia` and `getMultipleMedia` methods to all image_picker
platforms.

~~waiting on https://github.com/flutter/packages/pull/4174~~

precursor to https://github.com/flutter/packages/pull/3892

part of https://github.com/flutter/flutter/issues/89159

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [relevant style guides] and ran the
auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages
repo does use `dart format`.)
- [x] I signed the [CLA].
- [x] The title of the PR starts with the name of the package surrounded
by square brackets, e.g. `[shared_preferences]`
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated `pubspec.yaml` with an appropriate new version according
to the [pub versioning philosophy], or this PR is [exempt from version
changes].
- [x] I updated `CHANGELOG.md` to add a description of the change,
[following repository CHANGELOG style].
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[relevant style guides]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
[pub versioning philosophy]: https://dart.dev/tools/pub/versioning
[exempt from version changes]:
https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#version-and-changelog-updates
[following repository CHANGELOG style]:
https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changelog-style
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md
index 97ccd9d..971cfe0 100644
--- a/packages/image_picker/image_picker_android/CHANGELOG.md
+++ b/packages/image_picker/image_picker_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.8.7
+
+* Adds `getMedia` method.
+
 ## 0.8.6+20
 
 * Bumps androidx.activity:activity from 1.7.0 to 1.7.1.
diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
index d216309..685534e 100644
--- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
+++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
@@ -79,6 +79,7 @@
   @VisibleForTesting static final int REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA = 2343;
   @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_VIDEO_FROM_GALLERY = 2352;
   @VisibleForTesting static final int REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA = 2353;
@@ -279,6 +280,52 @@
     return result.build();
   }
 
+  public void chooseMediaFromGallery(
+      @NonNull Messages.MediaSelectionOptions options,
+      @NonNull Messages.GeneralOptions generalOptions,
+      @NonNull Messages.Result<List<String>> result) {
+    if (!setPendingOptionsAndResult(options.getImageSelectionOptions(), null, result)) {
+      finishWithAlreadyActiveError(result);
+      return;
+    }
+
+    launchPickMediaFromGalleryIntent(generalOptions);
+  }
+
+  private void launchPickMediaFromGalleryIntent(Messages.GeneralOptions generalOptions) {
+    Intent pickMediaIntent;
+    if (generalOptions.getUsePhotoPicker() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+      if (generalOptions.getAllowMultiple()) {
+        pickMediaIntent =
+            new ActivityResultContracts.PickMultipleVisualMedia()
+                .createIntent(
+                    activity,
+                    new PickVisualMediaRequest.Builder()
+                        .setMediaType(
+                            ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE)
+                        .build());
+      } else {
+        pickMediaIntent =
+            new ActivityResultContracts.PickVisualMedia()
+                .createIntent(
+                    activity,
+                    new PickVisualMediaRequest.Builder()
+                        .setMediaType(
+                            ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE)
+                        .build());
+      }
+    } else {
+      pickMediaIntent = new Intent(Intent.ACTION_GET_CONTENT);
+      pickMediaIntent.setType("*/*");
+      String[] mimeTypes = {"video/*", "image/*"};
+      pickMediaIntent.putExtra("CONTENT_TYPE", mimeTypes);
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        pickMediaIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, generalOptions.getAllowMultiple());
+      }
+    }
+    activity.startActivityForResult(pickMediaIntent, REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY);
+  }
+
   public void chooseVideoFromGallery(
       @NonNull VideoSelectionOptions options,
       boolean usePhotoPicker,
@@ -291,9 +338,9 @@
     launchPickVideoFromGalleryIntent(usePhotoPicker);
   }
 
-  private void launchPickVideoFromGalleryIntent(Boolean useAndroidPhotoPicker) {
+  private void launchPickVideoFromGalleryIntent(Boolean usePhotoPicker) {
     Intent pickVideoIntent;
-    if (useAndroidPhotoPicker && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+    if (usePhotoPicker && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
       pickVideoIntent =
           new ActivityResultContracts.PickVisualMedia()
               .createIntent(
@@ -389,9 +436,9 @@
     launchMultiPickImageFromGalleryIntent(usePhotoPicker);
   }
 
-  private void launchPickImageFromGalleryIntent(Boolean useAndroidPhotoPicker) {
+  private void launchPickImageFromGalleryIntent(Boolean usePhotoPicker) {
     Intent pickImageIntent;
-    if (useAndroidPhotoPicker && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+    if (usePhotoPicker && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
       pickImageIntent =
           new ActivityResultContracts.PickVisualMedia()
               .createIntent(
@@ -406,9 +453,9 @@
     activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY);
   }
 
-  private void launchMultiPickImageFromGalleryIntent(Boolean useAndroidPhotoPicker) {
+  private void launchMultiPickImageFromGalleryIntent(Boolean usePhotoPicker) {
     Intent pickMultiImageIntent;
-    if (useAndroidPhotoPicker && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+    if (usePhotoPicker && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
       pickMultiImageIntent =
           new ActivityResultContracts.PickMultipleVisualMedia()
               .createIntent(
@@ -563,6 +610,9 @@
       case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA:
         handlerRunnable = () -> handleCaptureImageResult(resultCode);
         break;
+      case REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY:
+        handlerRunnable = () -> handleChooseMediaResult(resultCode, data);
+        break;
       case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY:
         handlerRunnable = () -> handleChooseVideoResult(resultCode, data);
         break;
@@ -589,17 +639,59 @@
     finishWithSuccess(null);
   }
 
-  private void handleChooseMultiImageResult(int resultCode, Intent intent) {
+  public class MediaPath {
+    public MediaPath(@NonNull String path, @Nullable String mimeType) {
+      this.path = path;
+      this.mimeType = mimeType;
+    }
+
+    final String path;
+    final String mimeType;
+
+    public @NonNull String getPath() {
+      return path;
+    }
+
+    public @Nullable String getMimeType() {
+      return mimeType;
+    }
+  }
+
+  private void handleChooseMediaResult(int resultCode, Intent intent) {
     if (resultCode == Activity.RESULT_OK && intent != null) {
-      ArrayList<String> paths = new ArrayList<>();
+      ArrayList<MediaPath> paths = new ArrayList<>();
       if (intent.getClipData() != null) {
         for (int i = 0; i < intent.getClipData().getItemCount(); i++) {
-          paths.add(fileUtils.getPathFromUri(activity, intent.getClipData().getItemAt(i).getUri()));
+          Uri uri = intent.getClipData().getItemAt(i).getUri();
+          String path = fileUtils.getPathFromUri(activity, uri);
+          String mimeType = activity.getContentResolver().getType(uri);
+          paths.add(new MediaPath(path, mimeType));
         }
       } else {
-        paths.add(fileUtils.getPathFromUri(activity, intent.getData()));
+        paths.add(new MediaPath(fileUtils.getPathFromUri(activity, intent.getData()), null));
       }
-      handleMultiImageResult(paths);
+      handleMediaResult(paths);
+      return;
+    }
+
+    // User cancelled choosing a picture.
+    finishWithSuccess(null);
+  }
+
+  private void handleChooseMultiImageResult(int resultCode, Intent intent) {
+    if (resultCode == Activity.RESULT_OK && intent != null) {
+      ArrayList<MediaPath> paths = new ArrayList<>();
+      if (intent.getClipData() != null) {
+        for (int i = 0; i < intent.getClipData().getItemCount(); i++) {
+          paths.add(
+              new MediaPath(
+                  fileUtils.getPathFromUri(activity, intent.getClipData().getItemAt(i).getUri()),
+                  null));
+        }
+      } else {
+        paths.add(new MediaPath(fileUtils.getPathFromUri(activity, intent.getData()), null));
+      }
+      handleMediaResult(paths);
       return;
     }
 
@@ -649,26 +741,6 @@
     finishWithSuccess(null);
   }
 
-  private void handleMultiImageResult(ArrayList<String> paths) {
-    ImageSelectionOptions localImageOptions = null;
-    synchronized (pendingCallStateLock) {
-      if (pendingCallState != null) {
-        localImageOptions = pendingCallState.imageOptions;
-      }
-    }
-
-    if (localImageOptions != null) {
-      ArrayList<String> finalPath = new ArrayList<>();
-      for (int i = 0; i < paths.size(); i++) {
-        String finalImagePath = getResizedImagePath(paths.get(i), localImageOptions);
-        finalPath.add(i, finalImagePath);
-      }
-      finishWithListSuccess(finalPath);
-    } else {
-      finishWithListSuccess(paths);
-    }
-  }
-
   void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled) {
     ImageSelectionOptions localImageOptions = null;
     synchronized (pendingCallStateLock) {
@@ -679,7 +751,7 @@
 
     if (localImageOptions != null) {
       String finalImagePath = getResizedImagePath(path, localImageOptions);
-      //delete original file if scaled
+      // Delete original file if scaled.
       if (finalImagePath != null && !finalImagePath.equals(path) && shouldDeleteOriginalIfScaled) {
         new File(path).delete();
       }
@@ -697,7 +769,34 @@
         outputOptions.getQuality().intValue());
   }
 
-  void handleVideoResult(String path) {
+  private void handleMediaResult(@NonNull ArrayList<MediaPath> paths) {
+    ImageSelectionOptions localImageOptions = null;
+    synchronized (pendingCallStateLock) {
+      if (pendingCallState != null) {
+        localImageOptions = pendingCallState.imageOptions;
+      }
+    }
+
+    ArrayList<String> finalPaths = new ArrayList<>();
+    if (localImageOptions != null) {
+      for (int i = 0; i < paths.size(); i++) {
+        MediaPath path = paths.get(i);
+        String finalPath = path.path;
+        if (path.mimeType == null || !path.mimeType.startsWith("video/")) {
+          finalPath = getResizedImagePath(path.path, localImageOptions);
+        }
+        finalPaths.add(finalPath);
+      }
+      finishWithListSuccess(finalPaths);
+    } else {
+      for (int i = 0; i < paths.size(); i++) {
+        finalPaths.add(paths.get(i).path);
+      }
+      finishWithListSuccess(finalPaths);
+    }
+  }
+
+  private void handleVideoResult(String path) {
     finishWithSuccess(path);
   }
 
diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
index 31b2303..b5deb28 100644
--- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
+++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
@@ -19,10 +19,16 @@
 import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.PluginRegistry;
+import io.flutter.plugins.imagepicker.Messages.CacheRetrievalResult;
 import io.flutter.plugins.imagepicker.Messages.FlutterError;
+import io.flutter.plugins.imagepicker.Messages.GeneralOptions;
 import io.flutter.plugins.imagepicker.Messages.ImagePickerApi;
+import io.flutter.plugins.imagepicker.Messages.ImageSelectionOptions;
+import io.flutter.plugins.imagepicker.Messages.MediaSelectionOptions;
 import io.flutter.plugins.imagepicker.Messages.Result;
+import io.flutter.plugins.imagepicker.Messages.SourceCamera;
 import io.flutter.plugins.imagepicker.Messages.SourceSpecification;
+import io.flutter.plugins.imagepicker.Messages.VideoSelectionOptions;
 import java.util.List;
 
 @SuppressWarnings("deprecation")
@@ -279,7 +285,7 @@
 
   private void setCameraDevice(
       @NonNull ImagePickerDelegate delegate, @NonNull SourceSpecification source) {
-    Messages.SourceCamera camera = source.getCamera();
+    SourceCamera camera = source.getCamera();
     if (camera != null) {
       ImagePickerDelegate.CameraDevice device;
       switch (camera) {
@@ -298,9 +304,8 @@
   @Override
   public void pickImages(
       @NonNull SourceSpecification source,
-      @NonNull Messages.ImageSelectionOptions options,
-      @NonNull Boolean allowMultiple,
-      @NonNull Boolean usePhotoPicker,
+      @NonNull ImageSelectionOptions options,
+      @NonNull GeneralOptions generalOptions,
       @NonNull Result<List<String>> result) {
     ImagePickerDelegate delegate = getImagePickerDelegate();
     if (delegate == null) {
@@ -311,12 +316,12 @@
     }
 
     setCameraDevice(delegate, source);
-    if (allowMultiple) {
-      delegate.chooseMultiImageFromGallery(options, usePhotoPicker, result);
+    if (generalOptions.getAllowMultiple()) {
+      delegate.chooseMultiImageFromGallery(options, generalOptions.getUsePhotoPicker(), result);
     } else {
       switch (source.getType()) {
         case GALLERY:
-          delegate.chooseImageFromGallery(options, usePhotoPicker, result);
+          delegate.chooseImageFromGallery(options, generalOptions.getUsePhotoPicker(), result);
           break;
         case CAMERA:
           delegate.takeImageWithCamera(options, result);
@@ -326,11 +331,25 @@
   }
 
   @Override
+  public void pickMedia(
+      @NonNull MediaSelectionOptions mediaSelectionOptions,
+      @NonNull GeneralOptions generalOptions,
+      @NonNull Result<List<String>> result) {
+    ImagePickerDelegate delegate = getImagePickerDelegate();
+    if (delegate == null) {
+      result.error(
+          new FlutterError(
+              "no_activity", "image_picker plugin requires a foreground activity.", null));
+      return;
+    }
+    delegate.chooseMediaFromGallery(mediaSelectionOptions, generalOptions, result);
+  }
+
+  @Override
   public void pickVideos(
       @NonNull SourceSpecification source,
-      @NonNull Messages.VideoSelectionOptions options,
-      @NonNull Boolean allowMultiple,
-      @NonNull Boolean usePhotoPicker,
+      @NonNull VideoSelectionOptions options,
+      @NonNull GeneralOptions generalOptions,
       @NonNull Result<List<String>> result) {
     ImagePickerDelegate delegate = getImagePickerDelegate();
     if (delegate == null) {
@@ -341,12 +360,12 @@
     }
 
     setCameraDevice(delegate, source);
-    if (allowMultiple) {
+    if (generalOptions.getAllowMultiple()) {
       result.error(new RuntimeException("Multi-video selection is not implemented"));
     } else {
       switch (source.getType()) {
         case GALLERY:
-          delegate.chooseVideoFromGallery(options, usePhotoPicker, result);
+          delegate.chooseVideoFromGallery(options, generalOptions.getUsePhotoPicker(), result);
           break;
         case CAMERA:
           delegate.takeVideoWithCamera(options, result);
@@ -357,7 +376,7 @@
 
   @Nullable
   @Override
-  public Messages.CacheRetrievalResult retrieveLostResults() {
+  public CacheRetrievalResult retrieveLostResults() {
     ImagePickerDelegate delegate = getImagePickerDelegate();
     if (delegate == null) {
       throw new FlutterError(
diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java
index 17390ac..8a19cfd 100644
--- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java
+++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java
@@ -88,6 +88,79 @@
     }
   }
 
+  /** Generated class from Pigeon that represents data sent in messages. */
+  public static final class GeneralOptions {
+    private @NonNull Boolean allowMultiple;
+
+    public @NonNull Boolean getAllowMultiple() {
+      return allowMultiple;
+    }
+
+    public void setAllowMultiple(@NonNull Boolean setterArg) {
+      if (setterArg == null) {
+        throw new IllegalStateException("Nonnull field \"allowMultiple\" is null.");
+      }
+      this.allowMultiple = setterArg;
+    }
+
+    private @NonNull Boolean usePhotoPicker;
+
+    public @NonNull Boolean getUsePhotoPicker() {
+      return usePhotoPicker;
+    }
+
+    public void setUsePhotoPicker(@NonNull Boolean setterArg) {
+      if (setterArg == null) {
+        throw new IllegalStateException("Nonnull field \"usePhotoPicker\" is null.");
+      }
+      this.usePhotoPicker = setterArg;
+    }
+
+    /** Constructor is non-public to enforce null safety; use Builder. */
+    GeneralOptions() {}
+
+    public static final class Builder {
+
+      private @Nullable Boolean allowMultiple;
+
+      public @NonNull Builder setAllowMultiple(@NonNull Boolean setterArg) {
+        this.allowMultiple = setterArg;
+        return this;
+      }
+
+      private @Nullable Boolean usePhotoPicker;
+
+      public @NonNull Builder setUsePhotoPicker(@NonNull Boolean setterArg) {
+        this.usePhotoPicker = setterArg;
+        return this;
+      }
+
+      public @NonNull GeneralOptions build() {
+        GeneralOptions pigeonReturn = new GeneralOptions();
+        pigeonReturn.setAllowMultiple(allowMultiple);
+        pigeonReturn.setUsePhotoPicker(usePhotoPicker);
+        return pigeonReturn;
+      }
+    }
+
+    @NonNull
+    ArrayList<Object> toList() {
+      ArrayList<Object> toListResult = new ArrayList<Object>(2);
+      toListResult.add(allowMultiple);
+      toListResult.add(usePhotoPicker);
+      return toListResult;
+    }
+
+    static @NonNull GeneralOptions fromList(@NonNull ArrayList<Object> list) {
+      GeneralOptions pigeonResult = new GeneralOptions();
+      Object allowMultiple = list.get(0);
+      pigeonResult.setAllowMultiple((Boolean) allowMultiple);
+      Object usePhotoPicker = list.get(1);
+      pigeonResult.setUsePhotoPicker((Boolean) usePhotoPicker);
+      return pigeonResult;
+    }
+  }
+
   /**
    * Options for image selection and output.
    *
@@ -193,6 +266,58 @@
     }
   }
 
+  /** Generated class from Pigeon that represents data sent in messages. */
+  public static final class MediaSelectionOptions {
+    private @NonNull ImageSelectionOptions imageSelectionOptions;
+
+    public @NonNull ImageSelectionOptions getImageSelectionOptions() {
+      return imageSelectionOptions;
+    }
+
+    public void setImageSelectionOptions(@NonNull ImageSelectionOptions setterArg) {
+      if (setterArg == null) {
+        throw new IllegalStateException("Nonnull field \"imageSelectionOptions\" is null.");
+      }
+      this.imageSelectionOptions = setterArg;
+    }
+
+    /** Constructor is non-public to enforce null safety; use Builder. */
+    MediaSelectionOptions() {}
+
+    public static final class Builder {
+
+      private @Nullable ImageSelectionOptions imageSelectionOptions;
+
+      public @NonNull Builder setImageSelectionOptions(@NonNull ImageSelectionOptions setterArg) {
+        this.imageSelectionOptions = setterArg;
+        return this;
+      }
+
+      public @NonNull MediaSelectionOptions build() {
+        MediaSelectionOptions pigeonReturn = new MediaSelectionOptions();
+        pigeonReturn.setImageSelectionOptions(imageSelectionOptions);
+        return pigeonReturn;
+      }
+    }
+
+    @NonNull
+    ArrayList<Object> toList() {
+      ArrayList<Object> toListResult = new ArrayList<Object>(1);
+      toListResult.add((imageSelectionOptions == null) ? null : imageSelectionOptions.toList());
+      return toListResult;
+    }
+
+    static @NonNull MediaSelectionOptions fromList(@NonNull ArrayList<Object> list) {
+      MediaSelectionOptions pigeonResult = new MediaSelectionOptions();
+      Object imageSelectionOptions = list.get(0);
+      pigeonResult.setImageSelectionOptions(
+          (imageSelectionOptions == null)
+              ? null
+              : ImageSelectionOptions.fromList((ArrayList<Object>) imageSelectionOptions));
+      return pigeonResult;
+    }
+  }
+
   /**
    * Options for image selection and output.
    *
@@ -523,10 +648,14 @@
         case (byte) 129:
           return CacheRetrievalResult.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 130:
-          return ImageSelectionOptions.fromList((ArrayList<Object>) readValue(buffer));
+          return GeneralOptions.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 131:
-          return SourceSpecification.fromList((ArrayList<Object>) readValue(buffer));
+          return ImageSelectionOptions.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 132:
+          return MediaSelectionOptions.fromList((ArrayList<Object>) readValue(buffer));
+        case (byte) 133:
+          return SourceSpecification.fromList((ArrayList<Object>) readValue(buffer));
+        case (byte) 134:
           return VideoSelectionOptions.fromList((ArrayList<Object>) readValue(buffer));
         default:
           return super.readValueOfType(type, buffer);
@@ -541,14 +670,20 @@
       } else if (value instanceof CacheRetrievalResult) {
         stream.write(129);
         writeValue(stream, ((CacheRetrievalResult) value).toList());
-      } else if (value instanceof ImageSelectionOptions) {
+      } else if (value instanceof GeneralOptions) {
         stream.write(130);
-        writeValue(stream, ((ImageSelectionOptions) value).toList());
-      } else if (value instanceof SourceSpecification) {
+        writeValue(stream, ((GeneralOptions) value).toList());
+      } else if (value instanceof ImageSelectionOptions) {
         stream.write(131);
+        writeValue(stream, ((ImageSelectionOptions) value).toList());
+      } else if (value instanceof MediaSelectionOptions) {
+        stream.write(132);
+        writeValue(stream, ((MediaSelectionOptions) value).toList());
+      } else if (value instanceof SourceSpecification) {
+        stream.write(133);
         writeValue(stream, ((SourceSpecification) value).toList());
       } else if (value instanceof VideoSelectionOptions) {
-        stream.write(132);
+        stream.write(134);
         writeValue(stream, ((VideoSelectionOptions) value).toList());
       } else {
         super.writeValue(stream, value);
@@ -567,8 +702,7 @@
     void pickImages(
         @NonNull SourceSpecification source,
         @NonNull ImageSelectionOptions options,
-        @NonNull Boolean allowMultiple,
-        @NonNull Boolean usePhotoPicker,
+        @NonNull GeneralOptions generalOptions,
         @NonNull Result<List<String>> result);
     /**
      * Selects video and returns their paths.
@@ -579,8 +713,17 @@
     void pickVideos(
         @NonNull SourceSpecification source,
         @NonNull VideoSelectionOptions options,
-        @NonNull Boolean allowMultiple,
-        @NonNull Boolean usePhotoPicker,
+        @NonNull GeneralOptions generalOptions,
+        @NonNull Result<List<String>> result);
+    /**
+     * Selects images and videos and returns their paths.
+     *
+     * <p>Elements must not be null, by convention. See
+     * https://github.com/flutter/flutter/issues/97848
+     */
+    void pickMedia(
+        @NonNull MediaSelectionOptions mediaSelectionOptions,
+        @NonNull GeneralOptions generalOptions,
         @NonNull Result<List<String>> result);
     /** Returns results from a previous app session, if any. */
     @Nullable
@@ -607,8 +750,7 @@
                 ArrayList<Object> args = (ArrayList<Object>) message;
                 SourceSpecification sourceArg = (SourceSpecification) args.get(0);
                 ImageSelectionOptions optionsArg = (ImageSelectionOptions) args.get(1);
-                Boolean allowMultipleArg = (Boolean) args.get(2);
-                Boolean usePhotoPickerArg = (Boolean) args.get(3);
+                GeneralOptions generalOptionsArg = (GeneralOptions) args.get(2);
                 Result<List<String>> resultCallback =
                     new Result<List<String>>() {
                       public void success(List<String> result) {
@@ -622,8 +764,7 @@
                       }
                     };
 
-                api.pickImages(
-                    sourceArg, optionsArg, allowMultipleArg, usePhotoPickerArg, resultCallback);
+                api.pickImages(sourceArg, optionsArg, generalOptionsArg, resultCallback);
               });
         } else {
           channel.setMessageHandler(null);
@@ -644,8 +785,7 @@
                 ArrayList<Object> args = (ArrayList<Object>) message;
                 SourceSpecification sourceArg = (SourceSpecification) args.get(0);
                 VideoSelectionOptions optionsArg = (VideoSelectionOptions) args.get(1);
-                Boolean allowMultipleArg = (Boolean) args.get(2);
-                Boolean usePhotoPickerArg = (Boolean) args.get(3);
+                GeneralOptions generalOptionsArg = (GeneralOptions) args.get(2);
                 Result<List<String>> resultCallback =
                     new Result<List<String>>() {
                       public void success(List<String> result) {
@@ -659,8 +799,38 @@
                       }
                     };
 
-                api.pickVideos(
-                    sourceArg, optionsArg, allowMultipleArg, usePhotoPickerArg, resultCallback);
+                api.pickVideos(sourceArg, optionsArg, generalOptionsArg, resultCallback);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger, "dev.flutter.pigeon.ImagePickerApi.pickMedia", getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                ArrayList<Object> wrapped = new ArrayList<Object>();
+                ArrayList<Object> args = (ArrayList<Object>) message;
+                MediaSelectionOptions mediaSelectionOptionsArg =
+                    (MediaSelectionOptions) args.get(0);
+                GeneralOptions generalOptionsArg = (GeneralOptions) args.get(1);
+                Result<List<String>> resultCallback =
+                    new Result<List<String>>() {
+                      public void success(List<String> result) {
+                        wrapped.add(0, result);
+                        reply.reply(wrapped);
+                      }
+
+                      public void error(Throwable error) {
+                        ArrayList<Object> wrappedError = wrapError(error);
+                        reply.reply(wrappedError);
+                      }
+                    };
+
+                api.pickMedia(mediaSelectionOptionsArg, generalOptionsArg, resultCallback);
               });
         } else {
           channel.setMessageHandler(null);
diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
index efdbbae..73ee5a0 100644
--- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
+++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
@@ -29,7 +29,9 @@
 import android.net.Uri;
 import androidx.annotation.Nullable;
 import io.flutter.plugins.imagepicker.Messages.FlutterError;
+import io.flutter.plugins.imagepicker.Messages.GeneralOptions;
 import io.flutter.plugins.imagepicker.Messages.ImageSelectionOptions;
+import io.flutter.plugins.imagepicker.Messages.MediaSelectionOptions;
 import io.flutter.plugins.imagepicker.Messages.VideoSelectionOptions;
 import java.io.File;
 import java.io.IOException;
@@ -61,6 +63,8 @@
       new ImageSelectionOptions.Builder().setQuality((long) 100).setMaxWidth(WIDTH).build();
   private static final VideoSelectionOptions DEFAULT_VIDEO_OPTIONS =
       new VideoSelectionOptions.Builder().build();
+  private static final MediaSelectionOptions DEFAULT_MEDIA_OPTIONS =
+      new MediaSelectionOptions.Builder().setImageSelectionOptions(DEFAULT_IMAGE_OPTIONS).build();
 
   @Mock Activity mockActivity;
   @Mock ImageResizer mockImageResizer;
@@ -162,6 +166,18 @@
   }
 
   @Test
+  public void chooseMediaFromGallery_whenPendingResultExists_finishesWithAlreadyActiveError() {
+    ImagePickerDelegate delegate =
+        createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null);
+    GeneralOptions generalOptions =
+        new GeneralOptions.Builder().setAllowMultiple(true).setUsePhotoPicker(true).build();
+    delegate.chooseMediaFromGallery(DEFAULT_MEDIA_OPTIONS, generalOptions, mockResult);
+
+    verifyFinishedWithAlreadyActiveError();
+    verifyNoMoreInteractions(mockResult);
+  }
+
+  @Test
   @Config(sdk = 30)
   public void chooseImageFromGallery_launchesChooseFromGalleryIntent() {
 
@@ -632,6 +648,19 @@
   }
 
   @Test
+  public void onActivityResult_whenMediaPickedFromGallery_returnsTrue() {
+    ImagePickerDelegate delegate = createDelegate();
+
+    boolean isHandled =
+        delegate.onActivityResult(
+            ImagePickerDelegate.REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY,
+            Activity.RESULT_OK,
+            mockIntent);
+
+    assertTrue(isHandled);
+  }
+
+  @Test
   public void onActivityResult_whenVideoPickerFromGallery_returnsTrue() {
     ImagePickerDelegate delegate = createDelegate();
 
diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java
index cd408c5..b2c281c 100644
--- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java
+++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java
@@ -24,7 +24,9 @@
 import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugins.imagepicker.Messages.FlutterError;
+import io.flutter.plugins.imagepicker.Messages.GeneralOptions;
 import io.flutter.plugins.imagepicker.Messages.ImageSelectionOptions;
+import io.flutter.plugins.imagepicker.Messages.MediaSelectionOptions;
 import io.flutter.plugins.imagepicker.Messages.SourceSpecification;
 import io.flutter.plugins.imagepicker.Messages.VideoSelectionOptions;
 import java.util.List;
@@ -40,6 +42,16 @@
       new ImageSelectionOptions.Builder().setQuality((long) 100).build();
   private static final VideoSelectionOptions DEFAULT_VIDEO_OPTIONS =
       new VideoSelectionOptions.Builder().build();
+  private static final MediaSelectionOptions DEFAULT_MEDIA_OPTIONS =
+      new MediaSelectionOptions.Builder().setImageSelectionOptions(DEFAULT_IMAGE_OPTIONS).build();
+  private static final GeneralOptions GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER =
+      new GeneralOptions.Builder().setUsePhotoPicker(true).setAllowMultiple(true).build();
+  private static final GeneralOptions GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_USE_PHOTO_PICKER =
+      new GeneralOptions.Builder().setUsePhotoPicker(true).setAllowMultiple(false).build();
+  private static final GeneralOptions GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER =
+      new GeneralOptions.Builder().setUsePhotoPicker(false).setAllowMultiple(false).build();
+  private static final GeneralOptions GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER =
+      new GeneralOptions.Builder().setUsePhotoPicker(false).setAllowMultiple(true).build();
   private static final SourceSpecification SOURCE_GALLERY =
       new SourceSpecification.Builder().setType(Messages.SourceType.GALLERY).build();
   private static final SourceSpecification SOURCE_CAMERA_FRONT =
@@ -88,7 +100,10 @@
     ImagePickerPlugin imagePickerPluginWithNullActivity =
         new ImagePickerPlugin(mockImagePickerDelegate, null);
     imagePickerPluginWithNullActivity.pickImages(
-        SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, false, false, mockResult);
+        SOURCE_GALLERY,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER,
+        mockResult);
 
     ArgumentCaptor<FlutterError> errorCaptor = ArgumentCaptor.forClass(FlutterError.class);
     verify(mockResult).error(errorCaptor.capture());
@@ -103,7 +118,10 @@
     ImagePickerPlugin imagePickerPluginWithNullActivity =
         new ImagePickerPlugin(mockImagePickerDelegate, null);
     imagePickerPluginWithNullActivity.pickVideos(
-        SOURCE_CAMERA_REAR, DEFAULT_VIDEO_OPTIONS, false, false, mockResult);
+        SOURCE_CAMERA_REAR,
+        DEFAULT_VIDEO_OPTIONS,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
 
     ArgumentCaptor<FlutterError> errorCaptor = ArgumentCaptor.forClass(FlutterError.class);
     verify(mockResult).error(errorCaptor.capture());
@@ -126,60 +144,126 @@
 
   @Test
   public void pickImages_whenSourceIsGallery_invokesChooseImageFromGallery() {
-    plugin.pickImages(SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, false, false, mockResult);
+    plugin.pickImages(
+        SOURCE_GALLERY,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).chooseImageFromGallery(any(), eq(false), any());
     verifyNoInteractions(mockResult);
   }
 
   @Test
   public void pickImages_whenSourceIsGalleryUsingPhotoPicker_invokesChooseImageFromGallery() {
-    plugin.pickImages(SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, false, true, mockResult);
+    plugin.pickImages(
+        SOURCE_GALLERY,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).chooseImageFromGallery(any(), eq(true), any());
     verifyNoInteractions(mockResult);
   }
 
   @Test
   public void pickImages_invokesChooseMultiImageFromGallery() {
-    plugin.pickImages(SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, true, false, mockResult);
+    plugin.pickImages(
+        SOURCE_GALLERY,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).chooseMultiImageFromGallery(any(), eq(false), any());
     verifyNoInteractions(mockResult);
   }
 
   @Test
   public void pickImages_usingPhotoPicker_invokesChooseMultiImageFromGallery() {
-    plugin.pickImages(SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, true, true, mockResult);
+    plugin.pickImages(
+        SOURCE_GALLERY,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).chooseMultiImageFromGallery(any(), eq(true), any());
     verifyNoInteractions(mockResult);
   }
 
   @Test
+  public void pickMedia_invokesChooseMediaFromGallery() {
+    MediaSelectionOptions mediaSelectionOptions =
+        new MediaSelectionOptions.Builder().setImageSelectionOptions(DEFAULT_IMAGE_OPTIONS).build();
+    plugin.pickMedia(
+        mediaSelectionOptions,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
+    verify(mockImagePickerDelegate)
+        .chooseMediaFromGallery(
+            eq(mediaSelectionOptions),
+            eq(GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER),
+            any());
+    verifyNoInteractions(mockResult);
+  }
+
+  @Test
+  public void pickMedia_usingPhotoPicker_invokesChooseMediaFromGallery() {
+    MediaSelectionOptions mediaSelectionOptions =
+        new MediaSelectionOptions.Builder().setImageSelectionOptions(DEFAULT_IMAGE_OPTIONS).build();
+    plugin.pickMedia(
+        mediaSelectionOptions, GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, mockResult);
+    verify(mockImagePickerDelegate)
+        .chooseMediaFromGallery(
+            eq(mediaSelectionOptions),
+            eq(GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER),
+            any());
+    verifyNoInteractions(mockResult);
+  }
+
+  @Test
   public void pickImages_whenSourceIsCamera_invokesTakeImageWithCamera() {
-    plugin.pickImages(SOURCE_CAMERA_REAR, DEFAULT_IMAGE_OPTIONS, false, false, mockResult);
+    plugin.pickImages(
+        SOURCE_CAMERA_REAR,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).takeImageWithCamera(any(), any());
     verifyNoInteractions(mockResult);
   }
 
   @Test
   public void pickImages_whenSourceIsCamera_invokesTakeImageWithCamera_RearCamera() {
-    plugin.pickImages(SOURCE_CAMERA_REAR, DEFAULT_IMAGE_OPTIONS, false, false, mockResult);
+    plugin.pickImages(
+        SOURCE_CAMERA_REAR,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.REAR));
   }
 
   @Test
   public void pickImages_whenSourceIsCamera_invokesTakeImageWithCamera_FrontCamera() {
-    plugin.pickImages(SOURCE_CAMERA_FRONT, DEFAULT_IMAGE_OPTIONS, false, false, mockResult);
+    plugin.pickImages(
+        SOURCE_CAMERA_FRONT,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.FRONT));
   }
 
   @Test
   public void pickVideos_whenSourceIsCamera_invokesTakeImageWithCamera_RearCamera() {
-    plugin.pickVideos(SOURCE_CAMERA_REAR, DEFAULT_VIDEO_OPTIONS, false, false, mockResult);
+    plugin.pickVideos(
+        SOURCE_CAMERA_REAR,
+        DEFAULT_VIDEO_OPTIONS,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.REAR));
   }
 
   @Test
   public void pickVideos_whenSourceIsCamera_invokesTakeImageWithCamera_FrontCamera() {
-    plugin.pickVideos(SOURCE_CAMERA_FRONT, DEFAULT_VIDEO_OPTIONS, false, false, mockResult);
+    plugin.pickVideos(
+        SOURCE_CAMERA_FRONT,
+        DEFAULT_VIDEO_OPTIONS,
+        GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
+        mockResult);
     verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.FRONT));
   }
 
diff --git a/packages/image_picker/image_picker_android/example/lib/main.dart b/packages/image_picker/image_picker_android/example/lib/main.dart
index fa87587..7d58a2a 100755
--- a/packages/image_picker/image_picker_android/example/lib/main.dart
+++ b/packages/image_picker/image_picker_android/example/lib/main.dart
@@ -14,6 +14,7 @@
 import 'package:image_picker_android/image_picker_android.dart';
 import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
 // #enddocregion photo-picker-example
+import 'package:mime/mime.dart';
 import 'package:video_player/video_player.dart';
 
 void appMain() {
@@ -55,14 +56,14 @@
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  List<XFile>? _imageFileList;
+  List<XFile>? _mediaFileList;
 
   void _setImageFileListFromFile(XFile? value) {
-    _imageFileList = value == null ? null : <XFile>[value];
+    _mediaFileList = value == null ? null : <XFile>[value];
   }
 
   dynamic _pickImageError;
-  bool isVideo = false;
+  bool _isVideo = false;
 
   VideoPlayerController? _controller;
   VideoPlayerController? _toBeDisposed;
@@ -77,18 +78,10 @@
     if (file != null && mounted) {
       await _disposeVideoController();
       late VideoPlayerController controller;
-      if (kIsWeb) {
-        controller = VideoPlayerController.network(file.path);
-      } else {
-        controller = VideoPlayerController.file(File(file.path));
-      }
+
+      controller = VideoPlayerController.file(File(file.path));
       _controller = controller;
-      // In web, most browsers won't honor a programmatic call to .play
-      // if the video has a sound track (and is not muted).
-      // Mute the video so it auto-plays in web!
-      // This is not needed if the call to .play is the result of user
-      // interaction (clicking on a "play" button, for example).
-      const double volume = kIsWeb ? 0.0 : 1.0;
+      const double volume = 1.0;
       await controller.setVolume(volume);
       await controller.initialize();
       await controller.setLooping(true);
@@ -101,12 +94,13 @@
     ImageSource source, {
     required BuildContext context,
     bool isMultiImage = false,
+    bool isMedia = false,
   }) async {
     if (_controller != null) {
       await _controller!.setVolume(0.0);
     }
     if (context.mounted) {
-      if (isVideo) {
+      if (_isVideo) {
         final XFile? file = await _picker.getVideo(
             source: source, maxDuration: const Duration(seconds: 10));
         if (file != null && context.mounted) {
@@ -117,15 +111,54 @@
         await _displayPickImageDialog(context,
             (double? maxWidth, double? maxHeight, int? quality) async {
           try {
-            final List<XFile>? pickedFileList = await _picker.getMultiImage(
-              maxWidth: maxWidth,
-              maxHeight: maxHeight,
-              imageQuality: quality,
-            );
+            final List<XFile>? pickedFileList = isMedia
+                ? await _picker.getMedia(
+                    options: MediaOptions(
+                        allowMultiple: isMultiImage,
+                        imageOptions: ImageOptions(
+                          maxWidth: maxWidth,
+                          maxHeight: maxHeight,
+                          imageQuality: quality,
+                        )),
+                  )
+                : await _picker.getMultiImage(
+                    maxWidth: maxWidth,
+                    maxHeight: maxHeight,
+                    imageQuality: quality,
+                  );
             if (pickedFileList != null && context.mounted) {
               _showPickedSnackBar(context, pickedFileList);
             }
-            setState(() => _imageFileList = pickedFileList);
+            setState(() {
+              _mediaFileList = pickedFileList;
+            });
+          } catch (e) {
+            setState(() {
+              _pickImageError = e;
+            });
+          }
+        });
+      } else if (isMedia) {
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final List<XFile> pickedFileList = <XFile>[];
+            final XFile? media = _firstOrNull(await _picker.getMedia(
+              options: MediaOptions(
+                  allowMultiple: isMultiImage,
+                  imageOptions: ImageOptions(
+                    maxWidth: maxWidth,
+                    maxHeight: maxHeight,
+                    imageQuality: quality,
+                  )),
+            ));
+
+            if (media != null) {
+              pickedFileList.add(media);
+              setState(() {
+                _mediaFileList = pickedFileList;
+              });
+            }
           } catch (e) {
             setState(() => _pickImageError = e);
           }
@@ -200,30 +233,37 @@
     if (retrieveError != null) {
       return retrieveError;
     }
-    if (_imageFileList != null) {
+    if (_mediaFileList != null) {
       return Semantics(
         label: 'image_picker_example_picked_images',
         child: ListView.builder(
           key: UniqueKey(),
           itemBuilder: (BuildContext context, int index) {
-            final XFile image = _imageFileList![index];
+            final XFile image = _mediaFileList![index];
+            final String? mime = lookupMimeType(_mediaFileList![index].path);
             return Column(
               mainAxisSize: MainAxisSize.min,
               children: <Widget>[
                 Text(image.name,
                     key: const Key('image_picker_example_picked_image_name')),
-                // Why network for web?
-                // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform
                 Semantics(
                   label: 'image_picker_example_picked_image',
-                  child: kIsWeb
-                      ? Image.network(image.path)
-                      : Image.file(File(image.path)),
+                  child: mime == null || mime.startsWith('image/')
+                      ? Image.file(
+                          File(_mediaFileList![index].path),
+                          errorBuilder: (BuildContext context, Object error,
+                              StackTrace? stackTrace) {
+                            return const Center(
+                                child:
+                                    Text('This image type is not supported'));
+                          },
+                        )
+                      : _buildInlineVideoPlayer(index),
                 ),
               ],
             );
           },
-          itemCount: _imageFileList!.length,
+          itemCount: _mediaFileList!.length,
         ),
       );
     } else if (_pickImageError != null) {
@@ -239,8 +279,19 @@
     }
   }
 
+  Widget _buildInlineVideoPlayer(int index) {
+    final VideoPlayerController controller =
+        VideoPlayerController.file(File(_mediaFileList![index].path));
+    const double volume = 1.0;
+    controller.setVolume(volume);
+    controller.initialize();
+    controller.setLooping(true);
+    controller.play();
+    return Center(child: AspectRatioVideo(controller));
+  }
+
   Widget _handlePreview() {
-    if (isVideo) {
+    if (_isVideo) {
       return _previewVideo();
     } else {
       return _previewImages();
@@ -254,15 +305,15 @@
     }
     if (response.file != null) {
       if (response.type == RetrieveType.video) {
-        isVideo = true;
+        _isVideo = true;
         await _playVideo(response.file);
       } else {
-        isVideo = false;
+        _isVideo = false;
         setState(() {
           if (response.files == null) {
             _setImageFileListFromFile(response.file);
           } else {
-            _imageFileList = response.files;
+            _mediaFileList = response.files;
           }
         });
       }
@@ -316,7 +367,7 @@
             child: FloatingActionButton(
               key: const Key('image_picker_example_from_gallery'),
               onPressed: () {
-                isVideo = false;
+                _isVideo = false;
                 _onImageButtonPressed(ImageSource.gallery, context: context);
               },
               heroTag: 'image0',
@@ -328,7 +379,40 @@
             padding: const EdgeInsets.only(top: 16.0),
             child: FloatingActionButton(
               onPressed: () {
-                isVideo = false;
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMultiImage: true,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'multipleMedia',
+              tooltip: 'Pick Multiple Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'media',
+              tooltip: 'Pick Single Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
                 _onImageButtonPressed(
                   ImageSource.gallery,
                   context: context,
@@ -344,7 +428,7 @@
             padding: const EdgeInsets.only(top: 16.0),
             child: FloatingActionButton(
               onPressed: () {
-                isVideo = false;
+                _isVideo = false;
                 _onImageButtonPressed(ImageSource.camera, context: context);
               },
               heroTag: 'image2',
@@ -357,7 +441,7 @@
             child: FloatingActionButton(
               backgroundColor: Colors.red,
               onPressed: () {
-                isVideo = true;
+                _isVideo = true;
                 _onImageButtonPressed(ImageSource.gallery, context: context);
               },
               heroTag: 'video0',
@@ -370,7 +454,7 @@
             child: FloatingActionButton(
               backgroundColor: Colors.red,
               onPressed: () {
-                isVideo = true;
+                _isVideo = true;
                 _onImageButtonPressed(ImageSource.camera, context: context);
               },
               heroTag: 'video1',
@@ -510,3 +594,7 @@
     }
   }
 }
+
+T? _firstOrNull<T>(List<T> list) {
+  return list.isEmpty ? null : list.first;
+}
diff --git a/packages/image_picker/image_picker_android/example/pubspec.yaml b/packages/image_picker/image_picker_android/example/pubspec.yaml
index 8921a37..1cce21a 100644
--- a/packages/image_picker/image_picker_android/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_android/example/pubspec.yaml
@@ -19,7 +19,8 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  image_picker_platform_interface: ^2.3.0
+  image_picker_platform_interface: ^2.8.0
+  mime: ^1.0.4
   video_player: ^2.1.4
 
 dev_dependencies:
diff --git a/packages/image_picker/image_picker_android/lib/image_picker_android.dart b/packages/image_picker/image_picker_android/lib/image_picker_android.dart
index fbc7fa7..c9e2c87 100644
--- a/packages/image_picker/image_picker_android/lib/image_picker_android.dart
+++ b/packages/image_picker/image_picker_android/lib/image_picker_android.dart
@@ -11,12 +11,17 @@
 
 /// An Android implementation of [ImagePickerPlatform].
 class ImagePickerAndroid extends ImagePickerPlatform {
-  /// Creates a new plugin implemenation instance.
+  /// Creates a new plugin implementation instance.
   ImagePickerAndroid({@visibleForTesting ImagePickerApi? api})
       : _hostApi = api ?? ImagePickerApi();
 
   final ImagePickerApi _hostApi;
 
+  /// Sets [ImagePickerAndroid] to use Android 13 Photo Picker.
+  ///
+  /// Currently defaults to false, but the default is subject to change.
+  bool useAndroidPhotoPicker = false;
+
   /// Registers this class as the default platform implementation.
   static void registerWith() {
     ImagePickerPlatform.instance = ImagePickerAndroid();
@@ -77,13 +82,14 @@
     }
 
     return _hostApi.pickImages(
-        SourceSpecification(type: SourceType.gallery),
-        ImageSelectionOptions(
-            maxWidth: maxWidth,
-            maxHeight: maxHeight,
-            quality: imageQuality ?? 100),
-        /* allowMultiple */ true,
-        useAndroidPhotoPicker);
+      SourceSpecification(type: SourceType.gallery),
+      ImageSelectionOptions(
+          maxWidth: maxWidth,
+          maxHeight: maxHeight,
+          quality: imageQuality ?? 100),
+      GeneralOptions(
+          allowMultiple: true, usePhotoPicker: useAndroidPhotoPicker),
+    );
   }
 
   Future<String?> _getImagePath({
@@ -108,13 +114,16 @@
     }
 
     final List<String?> paths = await _hostApi.pickImages(
-        _buildSourceSpec(source, preferredCameraDevice),
-        ImageSelectionOptions(
-            maxWidth: maxWidth,
-            maxHeight: maxHeight,
-            quality: imageQuality ?? 100),
-        /* allowMultiple */ false,
-        useAndroidPhotoPicker);
+      _buildSourceSpec(source, preferredCameraDevice),
+      ImageSelectionOptions(
+          maxWidth: maxWidth,
+          maxHeight: maxHeight,
+          quality: imageQuality ?? 100),
+      GeneralOptions(
+        allowMultiple: false,
+        usePhotoPicker: useAndroidPhotoPicker,
+      ),
+    );
     return paths.isEmpty ? null : paths.first;
   }
 
@@ -138,10 +147,13 @@
     Duration? maxDuration,
   }) async {
     final List<String?> paths = await _hostApi.pickVideos(
-        _buildSourceSpec(source, preferredCameraDevice),
-        VideoSelectionOptions(maxDurationSeconds: maxDuration?.inSeconds),
-        /* allowMultiple */ false,
-        useAndroidPhotoPicker);
+      _buildSourceSpec(source, preferredCameraDevice),
+      VideoSelectionOptions(maxDurationSeconds: maxDuration?.inSeconds),
+      GeneralOptions(
+        allowMultiple: false,
+        usePhotoPicker: useAndroidPhotoPicker,
+      ),
+    );
     return paths.isEmpty ? null : paths.first;
   }
 
@@ -198,6 +210,21 @@
   }
 
   @override
+  Future<List<XFile>> getMedia({
+    required MediaOptions options,
+  }) async {
+    return (await _hostApi.pickMedia(
+      _mediaOptionsToMediaSelectionOptions(options),
+      GeneralOptions(
+        allowMultiple: options.allowMultiple,
+        usePhotoPicker: useAndroidPhotoPicker,
+      ),
+    ))
+        .map((String? path) => XFile(path!))
+        .toList();
+  }
+
+  @override
   Future<XFile?> getVideo({
     required ImageSource source,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
@@ -211,6 +238,38 @@
     return path != null ? XFile(path) : null;
   }
 
+  MediaSelectionOptions _mediaOptionsToMediaSelectionOptions(
+      MediaOptions mediaOptions) {
+    final ImageSelectionOptions imageSelectionOptions =
+        _imageOptionsToImageSelectionOptionsWithValidator(
+            mediaOptions.imageOptions);
+    return MediaSelectionOptions(
+      imageSelectionOptions: imageSelectionOptions,
+    );
+  }
+
+  ImageSelectionOptions _imageOptionsToImageSelectionOptionsWithValidator(
+      ImageOptions? imageOptions) {
+    final double? maxHeight = imageOptions?.maxHeight;
+    final double? maxWidth = imageOptions?.maxWidth;
+    final int? imageQuality = imageOptions?.imageQuality;
+
+    if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
+      throw ArgumentError.value(
+          imageQuality, 'imageQuality', 'must be between 0 and 100');
+    }
+
+    if (maxWidth != null && maxWidth < 0) {
+      throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
+    }
+
+    if (maxHeight != null && maxHeight < 0) {
+      throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
+    }
+    return ImageSelectionOptions(
+        quality: imageQuality ?? 100, maxHeight: maxHeight, maxWidth: maxWidth);
+  }
+
   @override
   Future<LostData> retrieveLostData() async {
     final LostDataResponse result = await getLostData();
@@ -243,7 +302,7 @@
         : PlatformException(code: error.code, message: error.message);
 
     // Entries are guaranteed not to be null, even though that's not currently
-    // expressable in Pigeon.
+    // expressible in Pigeon.
     final List<XFile> pickedFileList =
         result.paths.map((String? path) => XFile(path!)).toList();
 
@@ -309,9 +368,4 @@
     // ignore: dead_code
     return RetrieveType.image;
   }
-
-  /// Sets [ImagePickerAndroid] to use Android 13 Photo Picker.
-  ///
-  /// Currently defaults to false, but the default is subject to change.
-  bool useAndroidPhotoPicker = false;
 }
diff --git a/packages/image_picker/image_picker_android/lib/src/messages.g.dart b/packages/image_picker/image_picker_android/lib/src/messages.g.dart
index a4f15c8..476e80d 100644
--- a/packages/image_picker/image_picker_android/lib/src/messages.g.dart
+++ b/packages/image_picker/image_picker_android/lib/src/messages.g.dart
@@ -26,6 +26,32 @@
   video,
 }
 
+class GeneralOptions {
+  GeneralOptions({
+    required this.allowMultiple,
+    required this.usePhotoPicker,
+  });
+
+  bool allowMultiple;
+
+  bool usePhotoPicker;
+
+  Object encode() {
+    return <Object?>[
+      allowMultiple,
+      usePhotoPicker,
+    ];
+  }
+
+  static GeneralOptions decode(Object result) {
+    result as List<Object?>;
+    return GeneralOptions(
+      allowMultiple: result[0]! as bool,
+      usePhotoPicker: result[1]! as bool,
+    );
+  }
+}
+
 /// Options for image selection and output.
 class ImageSelectionOptions {
   ImageSelectionOptions({
@@ -63,6 +89,28 @@
   }
 }
 
+class MediaSelectionOptions {
+  MediaSelectionOptions({
+    required this.imageSelectionOptions,
+  });
+
+  ImageSelectionOptions imageSelectionOptions;
+
+  Object encode() {
+    return <Object?>[
+      imageSelectionOptions.encode(),
+    ];
+  }
+
+  static MediaSelectionOptions decode(Object result) {
+    result as List<Object?>;
+    return MediaSelectionOptions(
+      imageSelectionOptions:
+          ImageSelectionOptions.decode(result[0]! as List<Object?>),
+    );
+  }
+}
+
 /// Options for image selection and output.
 class VideoSelectionOptions {
   VideoSelectionOptions({
@@ -192,15 +240,21 @@
     } else if (value is CacheRetrievalResult) {
       buffer.putUint8(129);
       writeValue(buffer, value.encode());
-    } else if (value is ImageSelectionOptions) {
+    } else if (value is GeneralOptions) {
       buffer.putUint8(130);
       writeValue(buffer, value.encode());
-    } else if (value is SourceSpecification) {
+    } else if (value is ImageSelectionOptions) {
       buffer.putUint8(131);
       writeValue(buffer, value.encode());
-    } else if (value is VideoSelectionOptions) {
+    } else if (value is MediaSelectionOptions) {
       buffer.putUint8(132);
       writeValue(buffer, value.encode());
+    } else if (value is SourceSpecification) {
+      buffer.putUint8(133);
+      writeValue(buffer, value.encode());
+    } else if (value is VideoSelectionOptions) {
+      buffer.putUint8(134);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -214,10 +268,14 @@
       case 129:
         return CacheRetrievalResult.decode(readValue(buffer)!);
       case 130:
-        return ImageSelectionOptions.decode(readValue(buffer)!);
+        return GeneralOptions.decode(readValue(buffer)!);
       case 131:
-        return SourceSpecification.decode(readValue(buffer)!);
+        return ImageSelectionOptions.decode(readValue(buffer)!);
       case 132:
+        return MediaSelectionOptions.decode(readValue(buffer)!);
+      case 133:
+        return SourceSpecification.decode(readValue(buffer)!);
+      case 134:
         return VideoSelectionOptions.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
@@ -242,17 +300,13 @@
   Future<List<String?>> pickImages(
       SourceSpecification arg_source,
       ImageSelectionOptions arg_options,
-      bool arg_allowMultiple,
-      bool arg_usePhotoPicker) async {
+      GeneralOptions arg_generalOptions) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.ImagePickerApi.pickImages', codec,
         binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel.send(<Object?>[
-      arg_source,
-      arg_options,
-      arg_allowMultiple,
-      arg_usePhotoPicker
-    ]) as List<Object?>?;
+    final List<Object?>? replyList = await channel
+            .send(<Object?>[arg_source, arg_options, arg_generalOptions])
+        as List<Object?>?;
     if (replyList == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -281,17 +335,47 @@
   Future<List<String?>> pickVideos(
       SourceSpecification arg_source,
       VideoSelectionOptions arg_options,
-      bool arg_allowMultiple,
-      bool arg_usePhotoPicker) async {
+      GeneralOptions arg_generalOptions) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.ImagePickerApi.pickVideos', codec,
         binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel.send(<Object?>[
-      arg_source,
-      arg_options,
-      arg_allowMultiple,
-      arg_usePhotoPicker
-    ]) as List<Object?>?;
+    final List<Object?>? replyList = await channel
+            .send(<Object?>[arg_source, arg_options, arg_generalOptions])
+        as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else if (replyList[0] == null) {
+      throw PlatformException(
+        code: 'null-error',
+        message: 'Host platform returned null value for non-null return value.',
+      );
+    } else {
+      return (replyList[0] as List<Object?>?)!.cast<String?>();
+    }
+  }
+
+  /// Selects images and videos and returns their paths.
+  ///
+  /// Elements must not be null, by convention. See
+  /// https://github.com/flutter/flutter/issues/97848
+  Future<List<String?>> pickMedia(
+      MediaSelectionOptions arg_mediaSelectionOptions,
+      GeneralOptions arg_generalOptions) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList = await channel
+            .send(<Object?>[arg_mediaSelectionOptions, arg_generalOptions])
+        as List<Object?>?;
     if (replyList == null) {
       throw PlatformException(
         code: 'channel-error',
diff --git a/packages/image_picker/image_picker_android/pigeons/messages.dart b/packages/image_picker/image_picker_android/pigeons/messages.dart
index 31ff22f..9d264b5 100644
--- a/packages/image_picker/image_picker_android/pigeons/messages.dart
+++ b/packages/image_picker/image_picker_android/pigeons/messages.dart
@@ -13,6 +13,11 @@
   ),
   copyrightHeader: 'pigeons/copyright.txt',
 ))
+class GeneralOptions {
+  GeneralOptions(this.allowMultiple, this.usePhotoPicker);
+  bool allowMultiple;
+  bool usePhotoPicker;
+}
 
 /// Options for image selection and output.
 class ImageSelectionOptions {
@@ -30,6 +35,14 @@
   int quality;
 }
 
+class MediaSelectionOptions {
+  MediaSelectionOptions({
+    required this.imageSelectionOptions,
+  });
+
+  ImageSelectionOptions imageSelectionOptions;
+}
+
 /// Options for image selection and output.
 class VideoSelectionOptions {
   VideoSelectionOptions({this.maxDurationSeconds});
@@ -89,8 +102,11 @@
   /// https://github.com/flutter/flutter/issues/97848
   @TaskQueue(type: TaskQueueType.serialBackgroundThread)
   @async
-  List<String?> pickImages(SourceSpecification source,
-      ImageSelectionOptions options, bool allowMultiple, bool usePhotoPicker);
+  List<String?> pickImages(
+    SourceSpecification source,
+    ImageSelectionOptions options,
+    GeneralOptions generalOptions,
+  );
 
   /// Selects video and returns their paths.
   ///
@@ -98,8 +114,21 @@
   /// https://github.com/flutter/flutter/issues/97848
   @TaskQueue(type: TaskQueueType.serialBackgroundThread)
   @async
-  List<String?> pickVideos(SourceSpecification source,
-      VideoSelectionOptions options, bool allowMultiple, bool usePhotoPicker);
+  List<String?> pickVideos(
+    SourceSpecification source,
+    VideoSelectionOptions options,
+    GeneralOptions generalOptions,
+  );
+
+  /// Selects images and videos and returns their paths.
+  ///
+  /// Elements must not be null, by convention. See
+  /// https://github.com/flutter/flutter/issues/97848
+  @async
+  List<String?> pickMedia(
+    MediaSelectionOptions mediaSelectionOptions,
+    GeneralOptions generalOptions,
+  );
 
   /// Returns results from a previous app session, if any.
   @TaskQueue(type: TaskQueueType.serialBackgroundThread)
diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml
index 8c61648..ed7c8db 100755
--- a/packages/image_picker/image_picker_android/pubspec.yaml
+++ b/packages/image_picker/image_picker_android/pubspec.yaml
@@ -3,7 +3,7 @@
 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.6+20
+version: 0.8.7
 
 environment:
   sdk: ">=2.18.0 <4.0.0"
@@ -22,7 +22,7 @@
   flutter:
     sdk: flutter
   flutter_plugin_android_lifecycle: ^2.0.1
-  image_picker_platform_interface: ^2.5.0
+  image_picker_platform_interface: ^2.8.0
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart
index f17d078..0b0cab4 100644
--- a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart
+++ b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart
@@ -654,6 +654,129 @@
     });
   });
 
+  group('#getMedia', () {
+    test('calls the method correctly', () async {
+      const List<String> fakePaths = <String>['/foo.jgp', 'bar.jpg'];
+      api.returnValue = fakePaths;
+
+      final List<XFile> files = await picker.getMedia(
+        options: const MediaOptions(
+          allowMultiple: true,
+        ),
+      );
+
+      expect(api.lastCall, _LastPickType.image);
+      expect(files.length, 2);
+      expect(files[0].path, fakePaths[0]);
+      expect(files[1].path, fakePaths[1]);
+    });
+
+    test('passes default image options', () async {
+      await picker.getMedia(
+        options: const MediaOptions(
+          allowMultiple: true,
+        ),
+      );
+
+      expect(api.passedImageOptions?.maxWidth, null);
+      expect(api.passedImageOptions?.maxHeight, null);
+      expect(api.passedImageOptions?.quality, 100);
+    });
+
+    test('passes image option arguments correctly', () async {
+      await picker.getMedia(
+          options: const MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions(
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+          imageQuality: 70,
+        ),
+      ));
+
+      expect(api.passedImageOptions?.maxWidth, 10.0);
+      expect(api.passedImageOptions?.maxHeight, 20.0);
+      expect(api.passedImageOptions?.quality, 70);
+    });
+
+    test('does not accept a negative width or height argument', () {
+      expect(
+        () => picker.getMedia(
+          options: const MediaOptions(
+            allowMultiple: true,
+            imageOptions: ImageOptions(maxWidth: -1.0),
+          ),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMedia(
+          options: const MediaOptions(
+            allowMultiple: true,
+            imageOptions: ImageOptions(maxHeight: -1.0),
+          ),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept an invalid imageQuality argument', () {
+      expect(
+        () => picker.getMedia(
+          options: const MediaOptions(
+            allowMultiple: true,
+            imageOptions: ImageOptions(imageQuality: -1),
+          ),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMedia(
+          options: const MediaOptions(
+            allowMultiple: true,
+            imageOptions: ImageOptions(imageQuality: 101),
+          ),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles an empty path response gracefully', () async {
+      api.returnValue = <String>[];
+
+      expect(
+          await picker.getMedia(
+            options: const MediaOptions(
+              allowMultiple: true,
+            ),
+          ),
+          <String>[]);
+    });
+
+    test('defaults to not using Android Photo Picker', () async {
+      await picker.getMedia(
+        options: const MediaOptions(
+          allowMultiple: true,
+        ),
+      );
+
+      expect(api.passedPhotoPickerFlag, false);
+    });
+
+    test('allows using Android Photo Picker', () async {
+      picker.useAndroidPhotoPicker = true;
+      await picker.getMedia(
+        options: const MediaOptions(
+          allowMultiple: true,
+        ),
+      );
+
+      expect(api.passedPhotoPickerFlag, true);
+    });
+  });
+
   group('#getImageFromSource', () {
     test('calls the method correctly', () async {
       const String fakePath = '/foo.jpg';
@@ -807,29 +930,41 @@
 
   @override
   Future<List<String?>> pickImages(
-      SourceSpecification source,
-      ImageSelectionOptions options,
-      bool allowMultiple,
-      bool usePhotoPicker) async {
+    SourceSpecification source,
+    ImageSelectionOptions options,
+    GeneralOptions generalOptions,
+  ) async {
     lastCall = _LastPickType.image;
     passedSource = source;
     passedImageOptions = options;
-    passedAllowMultiple = allowMultiple;
-    passedPhotoPickerFlag = usePhotoPicker;
+    passedAllowMultiple = generalOptions.allowMultiple;
+    passedPhotoPickerFlag = generalOptions.usePhotoPicker;
+    return returnValue as List<String?>? ?? <String>[];
+  }
+
+  @override
+  Future<List<String?>> pickMedia(
+    MediaSelectionOptions options,
+    GeneralOptions generalOptions,
+  ) async {
+    lastCall = _LastPickType.image;
+    passedImageOptions = options.imageSelectionOptions;
+    passedPhotoPickerFlag = generalOptions.usePhotoPicker;
+    passedAllowMultiple = generalOptions.allowMultiple;
     return returnValue as List<String?>? ?? <String>[];
   }
 
   @override
   Future<List<String?>> pickVideos(
-      SourceSpecification source,
-      VideoSelectionOptions options,
-      bool allowMultiple,
-      bool usePhotoPicker) async {
+    SourceSpecification source,
+    VideoSelectionOptions options,
+    GeneralOptions generalOptions,
+  ) async {
     lastCall = _LastPickType.video;
     passedSource = source;
     passedVideoOptions = options;
-    passedAllowMultiple = allowMultiple;
-    passedPhotoPickerFlag = usePhotoPicker;
+    passedAllowMultiple = generalOptions.allowMultiple;
+    passedPhotoPickerFlag = generalOptions.usePhotoPicker;
     return returnValue as List<String?>? ?? <String>[];
   }
 
diff --git a/packages/image_picker/image_picker_android/test/test_api.g.dart b/packages/image_picker/image_picker_android/test/test_api.g.dart
index dbb6b14..d3b6891 100644
--- a/packages/image_picker/image_picker_android/test/test_api.g.dart
+++ b/packages/image_picker/image_picker_android/test/test_api.g.dart
@@ -23,15 +23,21 @@
     } else if (value is CacheRetrievalResult) {
       buffer.putUint8(129);
       writeValue(buffer, value.encode());
-    } else if (value is ImageSelectionOptions) {
+    } else if (value is GeneralOptions) {
       buffer.putUint8(130);
       writeValue(buffer, value.encode());
-    } else if (value is SourceSpecification) {
+    } else if (value is ImageSelectionOptions) {
       buffer.putUint8(131);
       writeValue(buffer, value.encode());
-    } else if (value is VideoSelectionOptions) {
+    } else if (value is MediaSelectionOptions) {
       buffer.putUint8(132);
       writeValue(buffer, value.encode());
+    } else if (value is SourceSpecification) {
+      buffer.putUint8(133);
+      writeValue(buffer, value.encode());
+    } else if (value is VideoSelectionOptions) {
+      buffer.putUint8(134);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -45,10 +51,14 @@
       case 129:
         return CacheRetrievalResult.decode(readValue(buffer)!);
       case 130:
-        return ImageSelectionOptions.decode(readValue(buffer)!);
+        return GeneralOptions.decode(readValue(buffer)!);
       case 131:
-        return SourceSpecification.decode(readValue(buffer)!);
+        return ImageSelectionOptions.decode(readValue(buffer)!);
       case 132:
+        return MediaSelectionOptions.decode(readValue(buffer)!);
+      case 133:
+        return SourceSpecification.decode(readValue(buffer)!);
+      case 134:
         return VideoSelectionOptions.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
@@ -66,14 +76,21 @@
   /// Elements must not be null, by convention. See
   /// https://github.com/flutter/flutter/issues/97848
   Future<List<String?>> pickImages(SourceSpecification source,
-      ImageSelectionOptions options, bool allowMultiple, bool usePhotoPicker);
+      ImageSelectionOptions options, GeneralOptions generalOptions);
 
   /// Selects video and returns their paths.
   ///
   /// Elements must not be null, by convention. See
   /// https://github.com/flutter/flutter/issues/97848
   Future<List<String?>> pickVideos(SourceSpecification source,
-      VideoSelectionOptions options, bool allowMultiple, bool usePhotoPicker);
+      VideoSelectionOptions options, GeneralOptions generalOptions);
+
+  /// Selects images and videos and returns their paths.
+  ///
+  /// Elements must not be null, by convention. See
+  /// https://github.com/flutter/flutter/issues/97848
+  Future<List<String?>> pickMedia(MediaSelectionOptions mediaSelectionOptions,
+      GeneralOptions generalOptions);
 
   /// Returns results from a previous app session, if any.
   CacheRetrievalResult? retrieveLostResults();
@@ -102,14 +119,12 @@
               (args[1] as ImageSelectionOptions?);
           assert(arg_options != null,
               'Argument for dev.flutter.pigeon.ImagePickerApi.pickImages was null, expected non-null ImageSelectionOptions.');
-          final bool? arg_allowMultiple = (args[2] as bool?);
-          assert(arg_allowMultiple != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickImages was null, expected non-null bool.');
-          final bool? arg_usePhotoPicker = (args[3] as bool?);
-          assert(arg_usePhotoPicker != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickImages was null, expected non-null bool.');
-          final List<String?> output = await api.pickImages(arg_source!,
-              arg_options!, arg_allowMultiple!, arg_usePhotoPicker!);
+          final GeneralOptions? arg_generalOptions =
+              (args[2] as GeneralOptions?);
+          assert(arg_generalOptions != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickImages was null, expected non-null GeneralOptions.');
+          final List<String?> output = await api.pickImages(
+              arg_source!, arg_options!, arg_generalOptions!);
           return <Object?>[output];
         });
       }
@@ -136,14 +151,40 @@
               (args[1] as VideoSelectionOptions?);
           assert(arg_options != null,
               'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideos was null, expected non-null VideoSelectionOptions.');
-          final bool? arg_allowMultiple = (args[2] as bool?);
-          assert(arg_allowMultiple != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideos was null, expected non-null bool.');
-          final bool? arg_usePhotoPicker = (args[3] as bool?);
-          assert(arg_usePhotoPicker != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideos was null, expected non-null bool.');
-          final List<String?> output = await api.pickVideos(arg_source!,
-              arg_options!, arg_allowMultiple!, arg_usePhotoPicker!);
+          final GeneralOptions? arg_generalOptions =
+              (args[2] as GeneralOptions?);
+          assert(arg_generalOptions != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideos was null, expected non-null GeneralOptions.');
+          final List<String?> output = await api.pickVideos(
+              arg_source!, arg_options!, arg_generalOptions!);
+          return <Object?>[output];
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        _testBinaryMessengerBinding!.defaultBinaryMessenger
+            .setMockDecodedMessageHandler<Object?>(channel, null);
+      } else {
+        _testBinaryMessengerBinding!.defaultBinaryMessenger
+            .setMockDecodedMessageHandler<Object?>(channel,
+                (Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final MediaSelectionOptions? arg_mediaSelectionOptions =
+              (args[0] as MediaSelectionOptions?);
+          assert(arg_mediaSelectionOptions != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null MediaSelectionOptions.');
+          final GeneralOptions? arg_generalOptions =
+              (args[1] as GeneralOptions?);
+          assert(arg_generalOptions != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null GeneralOptions.');
+          final List<String?> output = await api.pickMedia(
+              arg_mediaSelectionOptions!, arg_generalOptions!);
           return <Object?>[output];
         });
       }
diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md
index 100a9b0..8230dd7 100644
--- a/packages/image_picker/image_picker_for_web/CHANGELOG.md
+++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.2.0
 
+* Adds `getMedia` method.
 * Updates minimum supported SDK version to Flutter 3.3/Dart 2.18.
 
 ## 2.1.12
diff --git a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart
index 9fe40da..256fe34 100644
--- a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart
+++ b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart
@@ -87,7 +87,8 @@
         ));
   });
 
-  testWidgets('Can select multiple files', (WidgetTester tester) async {
+  testWidgets('getMultiImage can select multiple files',
+      (WidgetTester tester) async {
     final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
 
     final ImagePickerPluginTestOverrides overrides =
@@ -117,6 +118,38 @@
     expect(secondFile.length(), completion(secondTextFile.size));
   });
 
+  testWidgets('getMedia can select multiple files',
+      (WidgetTester tester) async {
+    final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
+
+    final ImagePickerPluginTestOverrides overrides =
+        ImagePickerPluginTestOverrides()
+          ..createInputElement = ((_, __) => mockInput)
+          ..getMultipleFilesFromInput =
+              ((_) => <html.File>[textFile, secondTextFile]);
+
+    final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides);
+
+    // Init the pick file dialog...
+    final Future<List<XFile>> files =
+        plugin.getMedia(options: const MediaOptions(allowMultiple: true));
+
+    // Mock the browser behavior of selecting a file...
+    mockInput.dispatchEvent(html.Event('change'));
+
+    // Now the file should be available
+    expect(files, completes);
+
+    // And readable
+    expect((await files).first.readAsBytes(), completion(isNotEmpty));
+
+    // Peek into the second file...
+    final XFile secondFile = (await files).elementAt(1);
+    expect(secondFile.readAsBytes(), completion(isNotEmpty));
+    expect(secondFile.name, secondTextFile.name);
+    expect(secondFile.length(), completion(secondTextFile.size));
+  });
+
   // There's no good way of detecting when the user has "aborted" the selection.
 
   testWidgets('computeCaptureAttribute', (WidgetTester tester) async {
diff --git a/packages/image_picker/image_picker_for_web/example/pubspec.yaml b/packages/image_picker/image_picker_for_web/example/pubspec.yaml
index 9c431bd..433a160 100644
--- a/packages/image_picker/image_picker_for_web/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_for_web/example/pubspec.yaml
@@ -10,7 +10,7 @@
     sdk: flutter
   image_picker_for_web:
     path: ../
-  image_picker_platform_interface: ^2.2.0
+  image_picker_platform_interface: ^2.8.0
 
 dev_dependencies:
   flutter_driver:
diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
index bb261f7..fb88c96 100644
--- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
+++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
@@ -8,6 +8,7 @@
 import 'package:flutter/foundation.dart' show visibleForTesting;
 import 'package:flutter_web_plugins/flutter_web_plugins.dart';
 import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+import 'package:mime/mime.dart' as mime;
 
 import 'src/image_resizer.dart';
 
@@ -166,7 +167,7 @@
     return files.first;
   }
 
-  /// Injects a file input, and returns a list of XFile that the user selected locally.
+  /// Injects a file input, and returns a list of XFile images that the user selected locally.
   @override
   Future<List<XFile>> getMultiImage({
     double? maxWidth,
@@ -189,6 +190,30 @@
     return Future.wait<XFile>(resized);
   }
 
+  /// Injects a file input, and returns a list of XFile media that the user selected locally.
+  @override
+  Future<List<XFile>> getMedia({
+    required MediaOptions options,
+  }) async {
+    final List<XFile> images = await getFiles(
+      accept: '$_kAcceptImageMimeType,$_kAcceptVideoMimeType',
+      multiple: options.allowMultiple,
+    );
+    final Iterable<Future<XFile>> resized = images.map((XFile media) {
+      if (mime.lookupMimeType(media.path)?.startsWith('image/') ?? false) {
+        return _imageResizer.resizeImageIfNeeded(
+          media,
+          options.imageOptions.maxWidth,
+          options.imageOptions.maxHeight,
+          options.imageOptions.imageQuality,
+        );
+      }
+      return Future<XFile>.value(media);
+    });
+
+    return Future.wait<XFile>(resized);
+  }
+
   /// Injects a file input with the specified accept+capture attributes, and
   /// returns a list of XFile that the user selected locally.
   ///
diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml
index 06a7093..a61a5b8 100644
--- a/packages/image_picker/image_picker_for_web/pubspec.yaml
+++ b/packages/image_picker/image_picker_for_web/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Web platform implementation of image_picker
 repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_for_web
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 2.1.12
+version: 2.2.0
 
 environment:
   sdk: ">=2.18.0 <4.0.0"
@@ -21,7 +21,8 @@
     sdk: flutter
   flutter_web_plugins:
     sdk: flutter
-  image_picker_platform_interface: ^2.2.0
+  image_picker_platform_interface: ^2.8.0
+  mime: ^1.0.4
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md
index 1173ddf..78805ad 100644
--- a/packages/image_picker/image_picker_ios/CHANGELOG.md
+++ b/packages/image_picker/image_picker_ios/CHANGELOG.md
@@ -1,4 +1,9 @@
+## 0.8.8
+
+* Adds `getMedia` and `getMultipleMedia` methods.
+
 ## 0.8.7+4
+
 * Fixes `BuildContext` handling in example.
 * Updates metadata unit test to work on iOS 16.2.
 
diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
index ede6233..cc22621 100644
--- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
+++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
@@ -182,6 +182,32 @@
             [mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]);
 }
 
+- (void)testPickMediaShouldUseUIImagePickerControllerOnPreiOS14 {
+  if (@available(iOS 14, *)) {
+    return;
+  }
+
+  id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
+  id photoLibrary = OCMClassMock([PHPhotoLibrary class]);
+  OCMStub(ClassMethod([photoLibrary authorizationStatus]))
+      .andReturn(PHAuthorizationStatusAuthorized);
+
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
+  FLTMediaSelectionOptions *mediaSelectionOptions =
+      [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
+                                   imageQuality:@(50)
+                            requestFullMetadata:@YES
+                                  allowMultiple:@YES];
+
+  [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions
+                                  completion:^(NSArray<NSString *> *_Nullable result,
+                                               FlutterError *_Nullable error){
+                                  }];
+  OCMVerify(times(1),
+            [mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]);
+}
+
 - (void)testPickImageWithoutFullMetadata {
   id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
   id photoLibrary = OCMClassMock([PHPhotoLibrary class]);
@@ -217,6 +243,28 @@
   OCMVerify(times(0), [photoLibrary authorizationStatus]);
 }
 
+- (void)testPickMediaWithoutFullMetadata {
+  id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
+  id photoLibrary = OCMClassMock([PHPhotoLibrary class]);
+
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
+
+  FLTMediaSelectionOptions *mediaSelectionOptions =
+      [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
+                                   imageQuality:@(50)
+                            requestFullMetadata:@YES
+                                  allowMultiple:@YES];
+
+  [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions
+
+                                  completion:^(NSArray<NSString *> *_Nullable result,
+                                               FlutterError *_Nullable error){
+                                  }];
+
+  OCMVerify(times(0), [photoLibrary authorizationStatus]);
+}
+
 #pragma mark - Test camera devices, no op on simulators
 
 - (void)testPluginPickImageDeviceCancelClickMultipleTimes {
@@ -298,6 +346,36 @@
   [self waitForExpectationsWithTimeout:30 handler:nil];
 }
 
+- (void)testPluginMediaPathHasNoItem {
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+
+  XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+  plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
+      initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
+        XCTAssertEqualObjects(result, @[]);
+        [resultExpectation fulfill];
+      }];
+  [plugin sendCallResultWithSavedPathList:@[]];
+
+  [self waitForExpectationsWithTimeout:30 handler:nil];
+}
+
+- (void)testPluginMediaPathHasItem {
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  NSArray *pathList = @[ @"test" ];
+
+  XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+  plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
+      initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
+        XCTAssertEqualObjects(result, pathList);
+        [resultExpectation fulfill];
+      }];
+  [plugin sendCallResultWithSavedPathList:pathList];
+
+  [self waitForExpectationsWithTimeout:30 handler:nil];
+}
+
 - (void)testSendsImageInvalidSourceError API_AVAILABLE(ios(14)) {
   id mockPickerViewController = OCMClassMock([PHPickerViewController class]);
 
diff --git a/packages/image_picker/image_picker_ios/example/lib/main.dart b/packages/image_picker/image_picker_ios/example/lib/main.dart
index 76076a5..0f42b58 100755
--- a/packages/image_picker/image_picker_ios/example/lib/main.dart
+++ b/packages/image_picker/image_picker_ios/example/lib/main.dart
@@ -10,6 +10,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+import 'package:mime/mime.dart';
 import 'package:video_player/video_player.dart';
 
 void main() {
@@ -38,14 +39,14 @@
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  List<XFile>? _imageFileList;
+  List<XFile>? _mediaFileList;
 
   void _setImageFileListFromFile(XFile? value) {
-    _imageFileList = value == null ? null : <XFile>[value];
+    _mediaFileList = value == null ? null : <XFile>[value];
   }
 
   dynamic _pickImageError;
-  bool isVideo = false;
+  bool _isVideo = false;
 
   VideoPlayerController? _controller;
   VideoPlayerController? _toBeDisposed;
@@ -60,18 +61,10 @@
     if (file != null && mounted) {
       await _disposeVideoController();
       late VideoPlayerController controller;
-      if (kIsWeb) {
-        controller = VideoPlayerController.network(file.path);
-      } else {
-        controller = VideoPlayerController.file(File(file.path));
-      }
+
+      controller = VideoPlayerController.file(File(file.path));
       _controller = controller;
-      // In web, most browsers won't honor a programmatic call to .play
-      // if the video has a sound track (and is not muted).
-      // Mute the video so it auto-plays in web!
-      // This is not needed if the call to .play is the result of user
-      // interaction (clicking on a "play" button, for example).
-      const double volume = kIsWeb ? 0.0 : 1.0;
+      const double volume = 1.0;
       await controller.setVolume(volume);
       await controller.initialize();
       await controller.setLooping(true);
@@ -80,13 +73,17 @@
     }
   }
 
-  Future<void> _onImageButtonPressed(ImageSource source,
-      {required BuildContext context, bool isMultiImage = false}) async {
+  Future<void> _onImageButtonPressed(
+    ImageSource source, {
+    required BuildContext context,
+    bool isMultiImage = false,
+    bool isMedia = false,
+  }) async {
     if (_controller != null) {
       await _controller!.setVolume(0.0);
     }
     if (context.mounted) {
-      if (isVideo) {
+      if (_isVideo) {
         final XFile? file = await _picker.getVideo(
             source: source, maxDuration: const Duration(seconds: 10));
         await _playVideo(file);
@@ -94,18 +91,27 @@
         await _displayPickImageDialog(context,
             (double? maxWidth, double? maxHeight, int? quality) async {
           try {
-            final List<XFile> pickedFileList =
-                await _picker.getMultiImageWithOptions(
-              options: MultiImagePickerOptions(
-                imageOptions: ImageOptions(
-                  maxWidth: maxWidth,
-                  maxHeight: maxHeight,
-                  imageQuality: quality,
-                ),
-              ),
-            );
+            final List<XFile> pickedFileList = isMedia
+                ? await _picker.getMedia(
+                    options: MediaOptions(
+                        allowMultiple: isMultiImage,
+                        imageOptions: ImageOptions(
+                          maxWidth: maxWidth,
+                          maxHeight: maxHeight,
+                          imageQuality: quality,
+                        )),
+                  )
+                : await _picker.getMultiImageWithOptions(
+                    options: MultiImagePickerOptions(
+                      imageOptions: ImageOptions(
+                        maxWidth: maxWidth,
+                        maxHeight: maxHeight,
+                        imageQuality: quality,
+                      ),
+                    ),
+                  );
             setState(() {
-              _imageFileList = pickedFileList;
+              _mediaFileList = pickedFileList;
             });
           } catch (e) {
             setState(() {
@@ -113,6 +119,31 @@
             });
           }
         });
+      } else if (isMedia) {
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final List<XFile> pickedFileList = <XFile>[];
+            final XFile? media = _firstOrNull(await _picker.getMedia(
+              options: MediaOptions(
+                  allowMultiple: isMultiImage,
+                  imageOptions: ImageOptions(
+                    maxWidth: maxWidth,
+                    maxHeight: maxHeight,
+                    imageQuality: quality,
+                  )),
+            ));
+
+            if (media != null) {
+              pickedFileList.add(media);
+              setState(() {
+                _mediaFileList = pickedFileList;
+              });
+            }
+          } catch (e) {
+            setState(() => _pickImageError = e);
+          }
+        });
       } else {
         await _displayPickImageDialog(context,
             (double? maxWidth, double? maxHeight, int? quality) async {
@@ -186,22 +217,28 @@
     if (retrieveError != null) {
       return retrieveError;
     }
-    if (_imageFileList != null) {
+    if (_mediaFileList != null) {
       return Semantics(
         label: 'image_picker_example_picked_images',
         child: ListView.builder(
           key: UniqueKey(),
           itemBuilder: (BuildContext context, int index) {
-            // Why network for web?
-            // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform
+            final String? mime = lookupMimeType(_mediaFileList![index].path);
             return Semantics(
               label: 'image_picker_example_picked_image',
-              child: kIsWeb
-                  ? Image.network(_imageFileList![index].path)
-                  : Image.file(File(_imageFileList![index].path)),
+              child: mime == null || mime.startsWith('image/')
+                  ? Image.file(
+                      File(_mediaFileList![index].path),
+                      errorBuilder: (BuildContext context, Object error,
+                          StackTrace? stackTrace) {
+                        return const Center(
+                            child: Text('This image type is not supported'));
+                      },
+                    )
+                  : _buildInlineVideoPlayer(index),
             );
           },
-          itemCount: _imageFileList!.length,
+          itemCount: _mediaFileList!.length,
         ),
       );
     } else if (_pickImageError != null) {
@@ -217,8 +254,19 @@
     }
   }
 
+  Widget _buildInlineVideoPlayer(int index) {
+    final VideoPlayerController controller =
+        VideoPlayerController.file(File(_mediaFileList![index].path));
+    const double volume = kIsWeb ? 0.0 : 1.0;
+    controller.setVolume(volume);
+    controller.initialize();
+    controller.setLooping(true);
+    controller.play();
+    return Center(child: AspectRatioVideo(controller));
+  }
+
   Widget _handlePreview() {
-    if (isVideo) {
+    if (_isVideo) {
       return _previewVideo();
     } else {
       return _previewImages();
@@ -240,8 +288,9 @@
           Semantics(
             label: 'image_picker_example_from_gallery',
             child: FloatingActionButton(
+              key: const Key('image_picker_example_from_gallery'),
               onPressed: () {
-                isVideo = false;
+                _isVideo = false;
                 _onImageButtonPressed(ImageSource.gallery, context: context);
               },
               heroTag: 'image0',
@@ -253,7 +302,40 @@
             padding: const EdgeInsets.only(top: 16.0),
             child: FloatingActionButton(
               onPressed: () {
-                isVideo = false;
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMultiImage: true,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'multipleMedia',
+              tooltip: 'Pick Multiple Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'media',
+              tooltip: 'Pick Single Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
                 _onImageButtonPressed(
                   ImageSource.gallery,
                   context: context,
@@ -269,7 +351,7 @@
             padding: const EdgeInsets.only(top: 16.0),
             child: FloatingActionButton(
               onPressed: () {
-                isVideo = false;
+                _isVideo = false;
                 _onImageButtonPressed(ImageSource.camera, context: context);
               },
               heroTag: 'image2',
@@ -282,7 +364,7 @@
             child: FloatingActionButton(
               backgroundColor: Colors.red,
               onPressed: () {
-                isVideo = true;
+                _isVideo = true;
                 _onImageButtonPressed(ImageSource.gallery, context: context);
               },
               heroTag: 'video0',
@@ -295,7 +377,7 @@
             child: FloatingActionButton(
               backgroundColor: Colors.red,
               onPressed: () {
-                isVideo = true;
+                _isVideo = true;
                 _onImageButtonPressed(ImageSource.camera, context: context);
               },
               heroTag: 'video1',
@@ -428,3 +510,7 @@
     }
   }
 }
+
+T? _firstOrNull<T>(List<T> list) {
+  return list.isEmpty ? null : list.first;
+}
diff --git a/packages/image_picker/image_picker_ios/example/pubspec.yaml b/packages/image_picker/image_picker_ios/example/pubspec.yaml
index d0bca04..9d08635 100755
--- a/packages/image_picker/image_picker_ios/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_ios/example/pubspec.yaml
@@ -16,7 +16,8 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  image_picker_platform_interface: ^2.6.1
+  image_picker_platform_interface: ^2.8.0
+  mime: ^1.0.4
   video_player: ^2.1.4
 
 dev_dependencies:
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.h
index 0016765..212f092 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.h
@@ -16,7 +16,10 @@
 
 + (nullable PHAsset *)getAssetFromPHPickerResult:(PHPickerResult *)result API_AVAILABLE(ios(14));
 
-// Save image with correct meta data and extention copied from the original asset.
+// Saves video to temporary URL. Returns nil on failure;
++ (NSURL *)saveVideoFromURL:(NSURL *)videoURL;
+
+// Saves image with correct meta data and extention copied from the original asset.
 // maxWidth and maxHeight are used only for GIF images.
 + (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData
                                        image:(UIImage *)image
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m
index bf712cd..294bbc7 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m
@@ -20,6 +20,20 @@
   return fetchResult.firstObject;
 }
 
++ (NSURL *)saveVideoFromURL:(NSURL *)videoURL {
+  if (![[NSFileManager defaultManager] isReadableFileAtPath:[videoURL path]]) {
+    return nil;
+  }
+  NSString *fileName = [videoURL lastPathComponent];
+  NSURL *destination = [NSURL fileURLWithPath:[self temporaryFilePath:fileName]];
+  NSError *error;
+  [[NSFileManager defaultManager] copyItemAtURL:videoURL toURL:destination error:&error];
+  if (error) {
+    return nil;
+  }
+  return destination;
+}
+
 + (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData
                                        image:(UIImage *)image
                                     maxWidth:(NSNumber *)maxWidth
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
index 5aadecd..c812e35 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
@@ -109,7 +109,13 @@
   PHPickerConfiguration *config =
       [[PHPickerConfiguration alloc] initWithPhotoLibrary:PHPhotoLibrary.sharedPhotoLibrary];
   config.selectionLimit = context.maxImageCount;
-  config.filter = [PHPickerFilter imagesFilter];
+  if (context.includeVideo) {
+    config.filter = [PHPickerFilter anyFilterMatchingSubfilters:@[
+      [PHPickerFilter imagesFilter], [PHPickerFilter videosFilter]
+    ]];
+  } else {
+    config.filter = [PHPickerFilter imagesFilter];
+  }
 
   _pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config];
   _pickerViewController.delegate = self;
@@ -128,7 +134,12 @@
   UIImagePickerController *imagePickerController = [self createImagePickerController];
   imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
   imagePickerController.delegate = self;
-  imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ];
+  if (context.includeVideo) {
+    imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage, (NSString *)kUTTypeMovie ];
+
+  } else {
+    imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ];
+  }
   self.callContext = context;
 
   switch (source.type) {
@@ -206,6 +217,29 @@
   }
 }
 
+- (void)pickMediaWithMediaSelectionOptions:(nonnull FLTMediaSelectionOptions *)mediaSelectionOptions
+                                completion:(nonnull void (^)(NSArray<NSString *> *_Nullable,
+                                                             FlutterError *_Nullable))completion {
+  FLTImagePickerMethodCallContext *context =
+      [[FLTImagePickerMethodCallContext alloc] initWithResult:completion];
+  context.maxSize = [mediaSelectionOptions maxSize];
+  context.imageQuality = [mediaSelectionOptions imageQuality];
+  context.requestFullMetadata = [mediaSelectionOptions requestFullMetadata];
+  context.includeVideo = YES;
+  if (![[mediaSelectionOptions allowMultiple] boolValue]) {
+    context.maxImageCount = 1;
+  }
+
+  if (@available(iOS 14, *)) {
+    [self launchPHPickerWithContext:context];
+  } else {
+    // Camera is ignored for gallery mode, so the value here is arbitrary.
+    [self launchUIImagePickerWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery
+                                                                      camera:FLTSourceCameraRear]
+                                context:context];
+  }
+}
+
 - (void)pickVideoWithSource:(nonnull FLTSourceSpecification *)source
                 maxDuration:(nullable NSNumber *)maxDurationSeconds
                  completion:
@@ -538,25 +572,16 @@
   }
   if (videoURL != nil) {
     if (@available(iOS 13.0, *)) {
-      NSString *fileName = [videoURL lastPathComponent];
-      NSURL *destination =
-          [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
-
-      if ([[NSFileManager defaultManager] isReadableFileAtPath:[videoURL path]]) {
-        NSError *error;
-        if (![[videoURL path] isEqualToString:[destination path]]) {
-          [[NSFileManager defaultManager] copyItemAtURL:videoURL toURL:destination error:&error];
-
-          if (error) {
-            [self sendCallResultWithError:[FlutterError
-                                              errorWithCode:@"flutter_image_picker_copy_video_error"
-                                                    message:@"Could not cache the video file."
-                                                    details:nil]];
-            return;
-          }
-        }
-        videoURL = destination;
+      NSURL *destination = [FLTImagePickerPhotoAssetUtil saveVideoFromURL:videoURL];
+      if (destination == nil) {
+        [self sendCallResultWithError:[FlutterError
+                                          errorWithCode:@"flutter_image_picker_copy_video_error"
+                                                message:@"Could not cache the video file."
+                                                details:nil]];
+        return;
       }
+
+      videoURL = destination;
     }
     [self sendCallResultWithSavedPathList:@[ videoURL.path ]];
   } else {
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
index f849211..99d3ef6 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
@@ -11,7 +11,7 @@
 NS_ASSUME_NONNULL_BEGIN
 
 /**
- * The return hander used for all method calls, which internally adapts the provided result list
+ * The return handler used for all method calls, which internally adapts the provided result list
  * to return either a list or a single element depending on the original call.
  */
 typedef void (^FlutterResultAdapter)(NSArray<NSString *> *_Nullable, FlutterError *_Nullable);
@@ -49,6 +49,9 @@
 /** Whether the image should be picked with full metadata (requires gallery permissions) */
 @property(nonatomic, assign) BOOL requestFullMetadata;
 
+/** Whether the picker should include videos in the list*/
+@property(nonatomic, assign) BOOL includeVideo;
+
 @end
 
 #pragma mark -
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
index 80e03dd..3476721 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
@@ -107,9 +107,15 @@
                                     [self completeOperationWithPath:nil error:flutterError];
                                   }
                                 }];
+    } else if ([self.result.itemProvider
+                   // This supports uniform types that conform to UTTypeMovie.
+                   // This includes kUTTypeVideo, kUTTypeMPEG4, public.3gpp, kUTTypeMPEG,
+                   // public.3gpp2, public.avi, kUTTypeQuickTimeMovie.
+                   hasItemConformingToTypeIdentifier:UTTypeMovie.identifier]) {
+      [self processVideo];
     } else {
       FlutterError *flutterError = [FlutterError errorWithCode:@"invalid_source"
-                                                       message:@"Invalid image source."
+                                                       message:@"Invalid media source."
                                                        details:nil];
       [self completeOperationWithPath:nil error:flutterError];
     }
@@ -184,4 +190,41 @@
   }
 }
 
+/**
+ * Processes the video.
+ */
+- (void)processVideo API_AVAILABLE(ios(14)) {
+  NSString *typeIdentifier = self.result.itemProvider.registeredTypeIdentifiers.firstObject;
+  [self.result.itemProvider
+      loadFileRepresentationForTypeIdentifier:typeIdentifier
+                            completionHandler:^(NSURL *_Nullable videoURL,
+                                                NSError *_Nullable error) {
+                              if (error != nil) {
+                                FlutterError *flutterError =
+                                    [FlutterError errorWithCode:@"invalid_image"
+                                                        message:error.localizedDescription
+                                                        details:error.domain];
+                                [self completeOperationWithPath:nil error:flutterError];
+                                return;
+                              }
+
+                              NSURL *destination =
+                                  [FLTImagePickerPhotoAssetUtil saveVideoFromURL:videoURL];
+                              if (destination == nil) {
+                                [self
+                                    completeOperationWithPath:nil
+                                                        error:[FlutterError
+                                                                  errorWithCode:
+                                                                      @"flutter_image_picker_copy_"
+                                                                      @"video_error"
+                                                                        message:@"Could not cache "
+                                                                                @"the video file."
+                                                                        details:nil]];
+                                return;
+                              }
+
+                              [self completeOperationWithPath:[destination path] error:nil];
+                            }];
+}
+
 @end
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
index cdde03d..4e2c4b2 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v9.2.4), do not edit directly.
+// Autogenerated from Pigeon (v9.2.5), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #import <Foundation/Foundation.h>
@@ -24,6 +24,7 @@
 };
 
 @class FLTMaxSize;
+@class FLTMediaSelectionOptions;
 @class FLTSourceSpecification;
 
 @interface FLTMaxSize : NSObject
@@ -32,6 +33,19 @@
 @property(nonatomic, strong, nullable) NSNumber *height;
 @end
 
+@interface FLTMediaSelectionOptions : NSObject
+/// `init` unavailable to enforce nonnull fields, see the `make` class method.
+- (instancetype)init NS_UNAVAILABLE;
++ (instancetype)makeWithMaxSize:(FLTMaxSize *)maxSize
+                   imageQuality:(nullable NSNumber *)imageQuality
+            requestFullMetadata:(NSNumber *)requestFullMetadata
+                  allowMultiple:(NSNumber *)allowMultiple;
+@property(nonatomic, strong) FLTMaxSize *maxSize;
+@property(nonatomic, strong, nullable) NSNumber *imageQuality;
+@property(nonatomic, strong) NSNumber *requestFullMetadata;
+@property(nonatomic, strong) NSNumber *allowMultiple;
+@end
+
 @interface FLTSourceSpecification : NSObject
 /// `init` unavailable to enforce nonnull fields, see the `make` class method.
 - (instancetype)init NS_UNAVAILABLE;
@@ -57,6 +71,10 @@
 - (void)pickVideoWithSource:(FLTSourceSpecification *)source
                 maxDuration:(nullable NSNumber *)maxDurationSeconds
                  completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion;
+/// Selects images and videos and returns their paths.
+- (void)pickMediaWithMediaSelectionOptions:(FLTMediaSelectionOptions *)mediaSelectionOptions
+                                completion:(void (^)(NSArray<NSString *> *_Nullable,
+                                                     FlutterError *_Nullable))completion;
 @end
 
 extern void FLTImagePickerApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
index a1d8636..2a24f83 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v9.2.4), do not edit directly.
+// Autogenerated from Pigeon (v9.2.5), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #import "messages.g.h"
@@ -30,6 +30,12 @@
 - (NSArray *)toList;
 @end
 
+@interface FLTMediaSelectionOptions ()
++ (FLTMediaSelectionOptions *)fromList:(NSArray *)list;
++ (nullable FLTMediaSelectionOptions *)nullableFromList:(NSArray *)list;
+- (NSArray *)toList;
+@end
+
 @interface FLTSourceSpecification ()
 + (FLTSourceSpecification *)fromList:(NSArray *)list;
 + (nullable FLTSourceSpecification *)nullableFromList:(NSArray *)list;
@@ -60,6 +66,42 @@
 }
 @end
 
+@implementation FLTMediaSelectionOptions
++ (instancetype)makeWithMaxSize:(FLTMaxSize *)maxSize
+                   imageQuality:(nullable NSNumber *)imageQuality
+            requestFullMetadata:(NSNumber *)requestFullMetadata
+                  allowMultiple:(NSNumber *)allowMultiple {
+  FLTMediaSelectionOptions *pigeonResult = [[FLTMediaSelectionOptions alloc] init];
+  pigeonResult.maxSize = maxSize;
+  pigeonResult.imageQuality = imageQuality;
+  pigeonResult.requestFullMetadata = requestFullMetadata;
+  pigeonResult.allowMultiple = allowMultiple;
+  return pigeonResult;
+}
++ (FLTMediaSelectionOptions *)fromList:(NSArray *)list {
+  FLTMediaSelectionOptions *pigeonResult = [[FLTMediaSelectionOptions alloc] init];
+  pigeonResult.maxSize = [FLTMaxSize nullableFromList:(GetNullableObjectAtIndex(list, 0))];
+  NSAssert(pigeonResult.maxSize != nil, @"");
+  pigeonResult.imageQuality = GetNullableObjectAtIndex(list, 1);
+  pigeonResult.requestFullMetadata = GetNullableObjectAtIndex(list, 2);
+  NSAssert(pigeonResult.requestFullMetadata != nil, @"");
+  pigeonResult.allowMultiple = GetNullableObjectAtIndex(list, 3);
+  NSAssert(pigeonResult.allowMultiple != nil, @"");
+  return pigeonResult;
+}
++ (nullable FLTMediaSelectionOptions *)nullableFromList:(NSArray *)list {
+  return (list) ? [FLTMediaSelectionOptions fromList:list] : nil;
+}
+- (NSArray *)toList {
+  return @[
+    (self.maxSize ? [self.maxSize toList] : [NSNull null]),
+    (self.imageQuality ?: [NSNull null]),
+    (self.requestFullMetadata ?: [NSNull null]),
+    (self.allowMultiple ?: [NSNull null]),
+  ];
+}
+@end
+
 @implementation FLTSourceSpecification
 + (instancetype)makeWithType:(FLTSourceType)type camera:(FLTSourceCamera)camera {
   FLTSourceSpecification *pigeonResult = [[FLTSourceSpecification alloc] init];
@@ -92,6 +134,8 @@
     case 128:
       return [FLTMaxSize fromList:[self readValue]];
     case 129:
+      return [FLTMediaSelectionOptions fromList:[self readValue]];
+    case 130:
       return [FLTSourceSpecification fromList:[self readValue]];
     default:
       return [super readValueOfType:type];
@@ -106,9 +150,12 @@
   if ([value isKindOfClass:[FLTMaxSize class]]) {
     [self writeByte:128];
     [self writeValue:[value toList]];
-  } else if ([value isKindOfClass:[FLTSourceSpecification class]]) {
+  } else if ([value isKindOfClass:[FLTMediaSelectionOptions class]]) {
     [self writeByte:129];
     [self writeValue:[value toList]];
+  } else if ([value isKindOfClass:[FLTSourceSpecification class]]) {
+    [self writeByte:130];
+    [self writeValue:[value toList]];
   } else {
     [super writeValue:value];
   }
@@ -220,4 +267,28 @@
       [channel setMessageHandler:nil];
     }
   }
+  /// Selects images and videos and returns their paths.
+  {
+    FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+           initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickMedia"
+        binaryMessenger:binaryMessenger
+                  codec:FLTImagePickerApiGetCodec()];
+    if (api) {
+      NSCAssert([api respondsToSelector:@selector(pickMediaWithMediaSelectionOptions:completion:)],
+                @"FLTImagePickerApi api (%@) doesn't respond to "
+                @"@selector(pickMediaWithMediaSelectionOptions:completion:)",
+                api);
+      [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+        NSArray *args = message;
+        FLTMediaSelectionOptions *arg_mediaSelectionOptions = GetNullableObjectAtIndex(args, 0);
+        [api pickMediaWithMediaSelectionOptions:arg_mediaSelectionOptions
+                                     completion:^(NSArray<NSString *> *_Nullable output,
+                                                  FlutterError *_Nullable error) {
+                                       callback(wrapResult(output, error));
+                                     }];
+      }];
+    } else {
+      [channel setMessageHandler:nil];
+    }
+  }
 }
diff --git a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
index 3f76784..02105f9 100644
--- a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
+++ b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
@@ -176,6 +176,51 @@
   }
 
   @override
+  Future<List<XFile>> getMedia({
+    required MediaOptions options,
+  }) async {
+    final MediaSelectionOptions mediaSelectionOptions =
+        _mediaOptionsToMediaSelectionOptions(options);
+
+    return (await _hostApi.pickMedia(mediaSelectionOptions))
+        .map((String? path) => XFile(path!))
+        .toList();
+  }
+
+  MaxSize _imageOptionsToMaxSizeWithValidation(ImageOptions imageOptions) {
+    final double? maxHeight = imageOptions.maxHeight;
+    final double? maxWidth = imageOptions.maxWidth;
+    final int? imageQuality = imageOptions.imageQuality;
+
+    if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
+      throw ArgumentError.value(
+          imageQuality, 'imageQuality', 'must be between 0 and 100');
+    }
+
+    if (maxWidth != null && maxWidth < 0) {
+      throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
+    }
+
+    if (maxHeight != null && maxHeight < 0) {
+      throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
+    }
+
+    return MaxSize(width: maxWidth, height: maxHeight);
+  }
+
+  MediaSelectionOptions _mediaOptionsToMediaSelectionOptions(
+      MediaOptions mediaOptions) {
+    final MaxSize maxSize =
+        _imageOptionsToMaxSizeWithValidation(mediaOptions.imageOptions);
+    return MediaSelectionOptions(
+      maxSize: maxSize,
+      imageQuality: mediaOptions.imageOptions.imageQuality,
+      requestFullMetadata: mediaOptions.imageOptions.requestFullMetadata,
+      allowMultiple: mediaOptions.allowMultiple,
+    );
+  }
+
+  @override
   Future<PickedFile?> pickVideo({
     required ImageSource source,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
diff --git a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
index 87596b7..91dde82 100644
--- a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
+++ b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v9.2.4), do not edit directly.
+// Autogenerated from Pigeon (v9.2.5), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
@@ -47,6 +47,42 @@
   }
 }
 
+class MediaSelectionOptions {
+  MediaSelectionOptions({
+    required this.maxSize,
+    this.imageQuality,
+    required this.requestFullMetadata,
+    required this.allowMultiple,
+  });
+
+  MaxSize maxSize;
+
+  int? imageQuality;
+
+  bool requestFullMetadata;
+
+  bool allowMultiple;
+
+  Object encode() {
+    return <Object?>[
+      maxSize.encode(),
+      imageQuality,
+      requestFullMetadata,
+      allowMultiple,
+    ];
+  }
+
+  static MediaSelectionOptions decode(Object result) {
+    result as List<Object?>;
+    return MediaSelectionOptions(
+      maxSize: MaxSize.decode(result[0]! as List<Object?>),
+      imageQuality: result[1] as int?,
+      requestFullMetadata: result[2]! as bool,
+      allowMultiple: result[3]! as bool,
+    );
+  }
+}
+
 class SourceSpecification {
   SourceSpecification({
     required this.type,
@@ -80,9 +116,12 @@
     if (value is MaxSize) {
       buffer.putUint8(128);
       writeValue(buffer, value.encode());
-    } else if (value is SourceSpecification) {
+    } else if (value is MediaSelectionOptions) {
       buffer.putUint8(129);
       writeValue(buffer, value.encode());
+    } else if (value is SourceSpecification) {
+      buffer.putUint8(130);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -94,6 +133,8 @@
       case 128:
         return MaxSize.decode(readValue(buffer)!);
       case 129:
+        return MediaSelectionOptions.decode(readValue(buffer)!);
+      case 130:
         return SourceSpecification.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
@@ -184,4 +225,33 @@
       return (replyList[0] as String?);
     }
   }
+
+  /// Selects images and videos and returns their paths.
+  Future<List<String?>> pickMedia(
+      MediaSelectionOptions arg_mediaSelectionOptions) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList = await channel
+        .send(<Object?>[arg_mediaSelectionOptions]) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else if (replyList[0] == null) {
+      throw PlatformException(
+        code: 'null-error',
+        message: 'Host platform returned null value for non-null return value.',
+      );
+    } else {
+      return (replyList[0] as List<Object?>?)!.cast<String?>();
+    }
+  }
 }
diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart
index d04841b..fb69a6d 100644
--- a/packages/image_picker/image_picker_ios/pigeons/messages.dart
+++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart
@@ -20,6 +20,20 @@
   double? height;
 }
 
+class MediaSelectionOptions {
+  MediaSelectionOptions({
+    required this.maxSize,
+    this.imageQuality,
+    required this.requestFullMetadata,
+    required this.allowMultiple,
+  });
+
+  MaxSize maxSize;
+  int? imageQuality;
+  bool requestFullMetadata;
+  bool allowMultiple;
+}
+
 // Corresponds to `CameraDevice` from the platform interface package.
 enum SourceCamera { rear, front }
 
@@ -45,4 +59,9 @@
   @async
   @ObjCSelector('pickVideoWithSource:maxDuration:')
   String? pickVideo(SourceSpecification source, int? maxDurationSeconds);
+
+  /// Selects images and videos and returns their paths.
+  @async
+  @ObjCSelector('pickMediaWithMediaSelectionOptions:')
+  List<String?> pickMedia(MediaSelectionOptions mediaSelectionOptions);
 }
diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml
index 90d6dff..a924689 100755
--- a/packages/image_picker/image_picker_ios/pubspec.yaml
+++ b/packages/image_picker/image_picker_ios/pubspec.yaml
@@ -2,7 +2,7 @@
 description: iOS implementation of the image_picker plugin.
 repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_ios
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 0.8.7+4
+version: 0.8.8
 
 environment:
   sdk: ">=2.18.0 <4.0.0"
@@ -19,7 +19,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  image_picker_platform_interface: ^2.6.1
+  image_picker_platform_interface: ^2.8.0
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
index 2c9d525..da74e31 100644
--- a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
+++ b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
@@ -72,6 +72,19 @@
   }
 
   @override
+  Future<List<String?>> pickMedia(
+      MediaSelectionOptions mediaSelectionOptions) async {
+    calls.add(_LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+      'maxWidth': mediaSelectionOptions.maxSize.width,
+      'maxHeight': mediaSelectionOptions.maxSize.height,
+      'imageQuality': mediaSelectionOptions.imageQuality,
+      'requestFullMetadata': mediaSelectionOptions.requestFullMetadata,
+      'allowMultiple': mediaSelectionOptions.allowMultiple,
+    }));
+    return returnValue as List<String?>;
+  }
+
+  @override
   Future<String?> pickVideo(
       SourceSpecification source, int? maxDurationSeconds) async {
     calls.add(_LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
@@ -878,6 +891,227 @@
     });
   });
 
+  group('#getMedia', () {
+    test('calls the method correctly', () async {
+      log.returnValue = <String>['0', '1'];
+      await picker.getMedia(options: const MediaOptions(allowMultiple: true));
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'requestFullMetadata': true,
+            'allowMultiple': true
+          }),
+        ],
+      );
+    });
+
+    test('passes the width and height arguments correctly', () async {
+      log.returnValue = <String>['0', '1'];
+      await picker.getMedia(options: const MediaOptions(allowMultiple: true));
+      await picker.getMedia(
+          options: MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions.createAndValidate(
+          maxWidth: 10.0,
+        ),
+      ));
+      await picker.getMedia(
+          options: MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions.createAndValidate(
+          maxHeight: 10.0,
+        ),
+      ));
+      await picker.getMedia(
+          options: MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions.createAndValidate(
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+        ),
+      ));
+      await picker.getMedia(
+          options: MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions.createAndValidate(
+          maxWidth: 10.0,
+          imageQuality: 70,
+        ),
+      ));
+      await picker.getMedia(
+          options: MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions.createAndValidate(
+          maxHeight: 10.0,
+          imageQuality: 70,
+        ),
+      ));
+      await picker.getMedia(
+          options: MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions.createAndValidate(
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+          imageQuality: 70,
+        ),
+      ));
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'requestFullMetadata': true,
+            'allowMultiple': true
+          }),
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': null,
+            'requestFullMetadata': true,
+            'allowMultiple': true
+          }),
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': null,
+            'requestFullMetadata': true,
+            'allowMultiple': true
+          }),
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': null,
+            'requestFullMetadata': true,
+            'allowMultiple': true
+          }),
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': 70,
+            'requestFullMetadata': true,
+            'allowMultiple': true
+          }),
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': 70,
+            'requestFullMetadata': true,
+            'allowMultiple': true
+          }),
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': 70,
+            'requestFullMetadata': true,
+            'allowMultiple': true
+          }),
+        ],
+      );
+    });
+
+    test('passes request metadata argument correctly', () async {
+      log.returnValue = <String>['0', '1'];
+      await picker.getMedia(
+          options: const MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions(requestFullMetadata: false),
+      ));
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'requestFullMetadata': false,
+            'allowMultiple': true
+          }),
+        ],
+      );
+    });
+
+    test('passes allowMultiple argument correctly', () async {
+      log.returnValue = <String>['0', '1'];
+      await picker.getMedia(
+          options: const MediaOptions(
+        allowMultiple: false,
+      ));
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'requestFullMetadata': true,
+            'allowMultiple': false
+          }),
+        ],
+      );
+    });
+
+    test('does not accept a negative width or height argument', () {
+      log.returnValue = <String>['0', '1'];
+      expect(
+        () => picker.getMedia(
+            options: MediaOptions(
+          allowMultiple: true,
+          imageOptions: ImageOptions.createAndValidate(maxWidth: -1.0),
+        )),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMedia(
+            options: MediaOptions(
+          allowMultiple: true,
+          imageOptions: ImageOptions.createAndValidate(maxHeight: -1.0),
+        )),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept a invalid imageQuality argument', () {
+      log.returnValue = <String>['0', '1'];
+      expect(
+        () => picker.getMedia(
+            options: MediaOptions(
+          allowMultiple: true,
+          imageOptions: ImageOptions.createAndValidate(imageQuality: -1),
+        )),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMedia(
+            options: MediaOptions(
+          allowMultiple: true,
+          imageOptions: ImageOptions.createAndValidate(imageQuality: 101),
+        )),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles a empty path response gracefully', () async {
+      log.returnValue = <String>[];
+
+      expect(
+          await picker.getMedia(
+              options: const MediaOptions(allowMultiple: true)),
+          <String>[]);
+    });
+  });
+
   group('#getVideo', () {
     test('passes the image source argument correctly', () async {
       await picker.getVideo(source: ImageSource.camera);
diff --git a/packages/image_picker/image_picker_ios/test/test_api.g.dart b/packages/image_picker/image_picker_ios/test/test_api.g.dart
index 4ac6195..6da0400 100644
--- a/packages/image_picker/image_picker_ios/test/test_api.g.dart
+++ b/packages/image_picker/image_picker_ios/test/test_api.g.dart
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v9.2.4), do not edit directly.
+// Autogenerated from Pigeon (v9.2.5), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import
 // ignore_for_file: avoid_relative_lib_imports
@@ -20,9 +20,12 @@
     if (value is MaxSize) {
       buffer.putUint8(128);
       writeValue(buffer, value.encode());
-    } else if (value is SourceSpecification) {
+    } else if (value is MediaSelectionOptions) {
       buffer.putUint8(129);
       writeValue(buffer, value.encode());
+    } else if (value is SourceSpecification) {
+      buffer.putUint8(130);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -34,6 +37,8 @@
       case 128:
         return MaxSize.decode(readValue(buffer)!);
       case 129:
+        return MediaSelectionOptions.decode(readValue(buffer)!);
+      case 130:
         return SourceSpecification.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
@@ -55,6 +60,9 @@
   Future<String?> pickVideo(
       SourceSpecification source, int? maxDurationSeconds);
 
+  /// Selects images and videos and returns their paths.
+  Future<List<String?>> pickMedia(MediaSelectionOptions mediaSelectionOptions);
+
   static void setup(TestHostImagePickerApi? api,
       {BinaryMessenger? binaryMessenger}) {
     {
@@ -140,5 +148,29 @@
         });
       }
     }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        _testBinaryMessengerBinding!.defaultBinaryMessenger
+            .setMockDecodedMessageHandler<Object?>(channel, null);
+      } else {
+        _testBinaryMessengerBinding!.defaultBinaryMessenger
+            .setMockDecodedMessageHandler<Object?>(channel,
+                (Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final MediaSelectionOptions? arg_mediaSelectionOptions =
+              (args[0] as MediaSelectionOptions?);
+          assert(arg_mediaSelectionOptions != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null MediaSelectionOptions.');
+          final List<String?> output =
+              await api.pickMedia(arg_mediaSelectionOptions!);
+          return <Object?>[output];
+        });
+      }
+    }
   }
 }
diff --git a/packages/image_picker/image_picker_linux/CHANGELOG.md b/packages/image_picker/image_picker_linux/CHANGELOG.md
index d3bfbf9..9f14cc7 100644
--- a/packages/image_picker/image_picker_linux/CHANGELOG.md
+++ b/packages/image_picker/image_picker_linux/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.2.1
+
+* Adds `getMedia` method.
+
 ## 0.2.0
 
 * Implements initial Linux support.
diff --git a/packages/image_picker/image_picker_linux/example/lib/main.dart b/packages/image_picker/image_picker_linux/example/lib/main.dart
index 9e22c71..8f48870 100644
--- a/packages/image_picker/image_picker_linux/example/lib/main.dart
+++ b/packages/image_picker/image_picker_linux/example/lib/main.dart
@@ -9,6 +9,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+import 'package:mime/mime.dart';
 import 'package:video_player/video_player.dart';
 
 void main() {
@@ -37,11 +38,11 @@
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  List<XFile>? _imageFileList;
+  List<XFile>? _mediaFileList;
 
   // This must be called from within a setState() callback
   void _setImageFileListFromFile(XFile? value) {
-    _imageFileList = value == null ? null : <XFile>[value];
+    _mediaFileList = value == null ? null : <XFile>[value];
   }
 
   dynamic _pickImageError;
@@ -70,52 +71,12 @@
     }
   }
 
-  Future<void> _handleMultiImagePicked(BuildContext context) async {
-    await _displayPickImageDialog(context,
-        (double? maxWidth, double? maxHeight, int? quality) async {
-      try {
-        final List<XFile>? pickedFileList = await _picker.getMultiImage(
-          maxWidth: maxWidth,
-          maxHeight: maxHeight,
-          imageQuality: quality,
-        );
-        setState(() {
-          _imageFileList = pickedFileList;
-        });
-      } catch (e) {
-        setState(() {
-          _pickImageError = e;
-        });
-      }
-    });
-  }
-
-  Future<void> _handleSingleImagePicked(
-      BuildContext context, ImageSource source) async {
-    await _displayPickImageDialog(context,
-        (double? maxWidth, double? maxHeight, int? quality) async {
-      try {
-        final XFile? pickedFile = await _picker.getImageFromSource(
-          source: source,
-          options: ImagePickerOptions(
-            maxWidth: maxWidth,
-            maxHeight: maxHeight,
-            imageQuality: quality,
-          ),
-        );
-        setState(() {
-          _setImageFileListFromFile(pickedFile);
-        });
-      } catch (e) {
-        setState(() {
-          _pickImageError = e;
-        });
-      }
-    });
-  }
-
-  Future<void> _onImageButtonPressed(ImageSource source,
-      {required BuildContext context, bool isMultiImage = false}) async {
+  Future<void> _onImageButtonPressed(
+    ImageSource source, {
+    required BuildContext context,
+    bool isMultiImage = false,
+    bool isMedia = false,
+  }) async {
     if (_controller != null) {
       await _controller!.setVolume(0.0);
     }
@@ -125,9 +86,83 @@
             source: source, maxDuration: const Duration(seconds: 10));
         await _playVideo(file);
       } else if (isMultiImage) {
-        await _handleMultiImagePicked(context);
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final List<XFile> pickedFileList = isMedia
+                ? await _picker.getMedia(
+                    options: MediaOptions(
+                        allowMultiple: isMultiImage,
+                        imageOptions: ImageOptions(
+                          maxWidth: maxWidth,
+                          maxHeight: maxHeight,
+                          imageQuality: quality,
+                        )),
+                  )
+                : await _picker.getMultiImageWithOptions(
+                    options: MultiImagePickerOptions(
+                      imageOptions: ImageOptions(
+                        maxWidth: maxWidth,
+                        maxHeight: maxHeight,
+                        imageQuality: quality,
+                      ),
+                    ),
+                  );
+            setState(() {
+              _mediaFileList = pickedFileList;
+            });
+          } catch (e) {
+            setState(() {
+              _pickImageError = e;
+            });
+          }
+        });
+      } else if (isMedia) {
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final List<XFile> pickedFileList = <XFile>[];
+            final XFile? media = _firstOrNull(await _picker.getMedia(
+              options: MediaOptions(
+                  allowMultiple: isMultiImage,
+                  imageOptions: ImageOptions(
+                    maxWidth: maxWidth,
+                    maxHeight: maxHeight,
+                    imageQuality: quality,
+                  )),
+            ));
+
+            if (media != null) {
+              pickedFileList.add(media);
+              setState(() {
+                _mediaFileList = pickedFileList;
+              });
+            }
+          } catch (e) {
+            setState(() => _pickImageError = e);
+          }
+        });
       } else {
-        await _handleSingleImagePicked(context, source);
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final XFile? pickedFile = await _picker.getImageFromSource(
+              source: source,
+              options: ImagePickerOptions(
+                maxWidth: maxWidth,
+                maxHeight: maxHeight,
+                imageQuality: quality,
+              ),
+            );
+            setState(() {
+              _setImageFileListFromFile(pickedFile);
+            });
+          } catch (e) {
+            setState(() {
+              _pickImageError = e;
+            });
+          }
+        });
       }
     }
   }
@@ -180,18 +215,28 @@
     if (retrieveError != null) {
       return retrieveError;
     }
-    if (_imageFileList != null) {
+    if (_mediaFileList != null) {
       return Semantics(
         label: 'image_picker_example_picked_images',
         child: ListView.builder(
           key: UniqueKey(),
           itemBuilder: (BuildContext context, int index) {
+            final String? mime = lookupMimeType(_mediaFileList![index].path);
             return Semantics(
               label: 'image_picker_example_picked_image',
-              child: Image.file(File(_imageFileList![index].path)),
+              child: mime == null || mime.startsWith('image/')
+                  ? Image.file(
+                      File(_mediaFileList![index].path),
+                      errorBuilder: (BuildContext context, Object error,
+                          StackTrace? stackTrace) {
+                        return const Center(
+                            child: Text('This image type is not supported'));
+                      },
+                    )
+                  : _buildInlineVideoPlayer(index),
             );
           },
-          itemCount: _imageFileList!.length,
+          itemCount: _mediaFileList!.length,
         ),
       );
     } else if (_pickImageError != null) {
@@ -207,6 +252,17 @@
     }
   }
 
+  Widget _buildInlineVideoPlayer(int index) {
+    final VideoPlayerController controller =
+        VideoPlayerController.file(File(_mediaFileList![index].path));
+    const double volume = 1.0;
+    controller.setVolume(volume);
+    controller.initialize();
+    controller.setLooping(true);
+    controller.play();
+    return Center(child: AspectRatioVideo(controller));
+  }
+
   Widget _handlePreview() {
     if (_isVideo) {
       return _previewVideo();
@@ -230,6 +286,7 @@
           Semantics(
             label: 'image_picker_example_from_gallery',
             child: FloatingActionButton(
+              key: const Key('image_picker_example_from_gallery'),
               onPressed: () {
                 _isVideo = false;
                 _onImageButtonPressed(ImageSource.gallery, context: context);
@@ -248,6 +305,39 @@
                   ImageSource.gallery,
                   context: context,
                   isMultiImage: true,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'multipleMedia',
+              tooltip: 'Pick Multiple Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'media',
+              tooltip: 'Pick Single Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMultiImage: true,
                 );
               },
               heroTag: 'image1',
@@ -420,3 +510,7 @@
     }
   }
 }
+
+T? _firstOrNull<T>(List<T> list) {
+  return list.isEmpty ? null : list.first;
+}
diff --git a/packages/image_picker/image_picker_linux/example/pubspec.yaml b/packages/image_picker/image_picker_linux/example/pubspec.yaml
index 54beb76..76e8f25 100644
--- a/packages/image_picker/image_picker_linux/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_linux/example/pubspec.yaml
@@ -17,7 +17,8 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ..
-  image_picker_platform_interface: ^2.7.0
+  image_picker_platform_interface: ^2.8.0
+  mime: ^1.0.4
   video_player: ^2.1.4
 
 dev_dependencies:
diff --git a/packages/image_picker/image_picker_linux/lib/image_picker_linux.dart b/packages/image_picker/image_picker_linux/lib/image_picker_linux.dart
index f932a02..72596ea 100644
--- a/packages/image_picker/image_picker_linux/lib/image_picker_linux.dart
+++ b/packages/image_picker/image_picker_linux/lib/image_picker_linux.dart
@@ -154,4 +154,27 @@
         .openFiles(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
     return files;
   }
+
+  // `maxWidth`, `maxHeight`, and `imageQuality` arguments are not currently
+  // supported. If any of these arguments are supplied, they will be silently
+  // ignored.
+  @override
+  Future<List<XFile>> getMedia({required MediaOptions options}) async {
+    const XTypeGroup typeGroup = XTypeGroup(
+        label: 'images and videos', extensions: <String>['image/*', 'video/*']);
+
+    List<XFile> files;
+
+    if (options.allowMultiple) {
+      files = await fileSelector
+          .openFiles(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
+    } else {
+      final XFile? file = await fileSelector
+          .openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
+      files = <XFile>[
+        if (file != null) file,
+      ];
+    }
+    return files;
+  }
 }
diff --git a/packages/image_picker/image_picker_linux/pubspec.yaml b/packages/image_picker/image_picker_linux/pubspec.yaml
index dcfd675..9698991 100644
--- a/packages/image_picker/image_picker_linux/pubspec.yaml
+++ b/packages/image_picker/image_picker_linux/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Linux platform implementation of image_picker
 repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_linux
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 0.2.0
+version: 0.2.1
 
 environment:
   sdk: ">=2.18.0 <4.0.0"
@@ -20,7 +20,7 @@
   file_selector_platform_interface: ^2.2.0
   flutter:
     sdk: flutter
-  image_picker_platform_interface: ^2.7.0
+  image_picker_platform_interface: ^2.8.0
 
 dev_dependencies:
   build_runner: ^2.1.5
diff --git a/packages/image_picker/image_picker_linux/test/image_picker_linux_test.dart b/packages/image_picker/image_picker_linux/test/image_picker_linux_test.dart
index 32c3d45..004bfcc 100644
--- a/packages/image_picker/image_picker_linux/test/image_picker_linux_test.dart
+++ b/packages/image_picker/image_picker_linux/test/image_picker_linux_test.dart
@@ -125,6 +125,38 @@
           plugin.getVideo(source: ImageSource.camera), throwsStateError);
     });
   });
+
+  group('media', () {
+    test('getMedia passes the accepted type groups correctly', () async {
+      await plugin.getMedia(options: const MediaOptions(allowMultiple: true));
+
+      final VerificationResult result = verify(
+          mockFileSelectorPlatform.openFiles(
+              acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups')));
+      expect(capturedTypeGroups(result)[0].extensions,
+          <String>['image/*', 'video/*']);
+    });
+
+    test('multiple media handles an empty path response gracefully', () async {
+      expect(
+          await plugin.getMedia(
+            options: const MediaOptions(
+              allowMultiple: true,
+            ),
+          ),
+          <String>[]);
+    });
+
+    test('single media handles an empty path response gracefully', () async {
+      expect(
+          await plugin.getMedia(
+            options: const MediaOptions(
+              allowMultiple: false,
+            ),
+          ),
+          <String>[]);
+    });
+  });
 }
 
 class FakeCameraDelegate extends ImagePickerCameraDelegate {
diff --git a/packages/image_picker/image_picker_macos/CHANGELOG.md b/packages/image_picker/image_picker_macos/CHANGELOG.md
index 94ce98b..bd79a86 100644
--- a/packages/image_picker/image_picker_macos/CHANGELOG.md
+++ b/packages/image_picker/image_picker_macos/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.2.1
+
+* Adds `getMedia` method.
+
 ## 0.2.0
 
 * Implements initial macOS support.
diff --git a/packages/image_picker/image_picker_macos/example/lib/main.dart b/packages/image_picker/image_picker_macos/example/lib/main.dart
index 9e22c71..8f48870 100644
--- a/packages/image_picker/image_picker_macos/example/lib/main.dart
+++ b/packages/image_picker/image_picker_macos/example/lib/main.dart
@@ -9,6 +9,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+import 'package:mime/mime.dart';
 import 'package:video_player/video_player.dart';
 
 void main() {
@@ -37,11 +38,11 @@
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  List<XFile>? _imageFileList;
+  List<XFile>? _mediaFileList;
 
   // This must be called from within a setState() callback
   void _setImageFileListFromFile(XFile? value) {
-    _imageFileList = value == null ? null : <XFile>[value];
+    _mediaFileList = value == null ? null : <XFile>[value];
   }
 
   dynamic _pickImageError;
@@ -70,52 +71,12 @@
     }
   }
 
-  Future<void> _handleMultiImagePicked(BuildContext context) async {
-    await _displayPickImageDialog(context,
-        (double? maxWidth, double? maxHeight, int? quality) async {
-      try {
-        final List<XFile>? pickedFileList = await _picker.getMultiImage(
-          maxWidth: maxWidth,
-          maxHeight: maxHeight,
-          imageQuality: quality,
-        );
-        setState(() {
-          _imageFileList = pickedFileList;
-        });
-      } catch (e) {
-        setState(() {
-          _pickImageError = e;
-        });
-      }
-    });
-  }
-
-  Future<void> _handleSingleImagePicked(
-      BuildContext context, ImageSource source) async {
-    await _displayPickImageDialog(context,
-        (double? maxWidth, double? maxHeight, int? quality) async {
-      try {
-        final XFile? pickedFile = await _picker.getImageFromSource(
-          source: source,
-          options: ImagePickerOptions(
-            maxWidth: maxWidth,
-            maxHeight: maxHeight,
-            imageQuality: quality,
-          ),
-        );
-        setState(() {
-          _setImageFileListFromFile(pickedFile);
-        });
-      } catch (e) {
-        setState(() {
-          _pickImageError = e;
-        });
-      }
-    });
-  }
-
-  Future<void> _onImageButtonPressed(ImageSource source,
-      {required BuildContext context, bool isMultiImage = false}) async {
+  Future<void> _onImageButtonPressed(
+    ImageSource source, {
+    required BuildContext context,
+    bool isMultiImage = false,
+    bool isMedia = false,
+  }) async {
     if (_controller != null) {
       await _controller!.setVolume(0.0);
     }
@@ -125,9 +86,83 @@
             source: source, maxDuration: const Duration(seconds: 10));
         await _playVideo(file);
       } else if (isMultiImage) {
-        await _handleMultiImagePicked(context);
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final List<XFile> pickedFileList = isMedia
+                ? await _picker.getMedia(
+                    options: MediaOptions(
+                        allowMultiple: isMultiImage,
+                        imageOptions: ImageOptions(
+                          maxWidth: maxWidth,
+                          maxHeight: maxHeight,
+                          imageQuality: quality,
+                        )),
+                  )
+                : await _picker.getMultiImageWithOptions(
+                    options: MultiImagePickerOptions(
+                      imageOptions: ImageOptions(
+                        maxWidth: maxWidth,
+                        maxHeight: maxHeight,
+                        imageQuality: quality,
+                      ),
+                    ),
+                  );
+            setState(() {
+              _mediaFileList = pickedFileList;
+            });
+          } catch (e) {
+            setState(() {
+              _pickImageError = e;
+            });
+          }
+        });
+      } else if (isMedia) {
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final List<XFile> pickedFileList = <XFile>[];
+            final XFile? media = _firstOrNull(await _picker.getMedia(
+              options: MediaOptions(
+                  allowMultiple: isMultiImage,
+                  imageOptions: ImageOptions(
+                    maxWidth: maxWidth,
+                    maxHeight: maxHeight,
+                    imageQuality: quality,
+                  )),
+            ));
+
+            if (media != null) {
+              pickedFileList.add(media);
+              setState(() {
+                _mediaFileList = pickedFileList;
+              });
+            }
+          } catch (e) {
+            setState(() => _pickImageError = e);
+          }
+        });
       } else {
-        await _handleSingleImagePicked(context, source);
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final XFile? pickedFile = await _picker.getImageFromSource(
+              source: source,
+              options: ImagePickerOptions(
+                maxWidth: maxWidth,
+                maxHeight: maxHeight,
+                imageQuality: quality,
+              ),
+            );
+            setState(() {
+              _setImageFileListFromFile(pickedFile);
+            });
+          } catch (e) {
+            setState(() {
+              _pickImageError = e;
+            });
+          }
+        });
       }
     }
   }
@@ -180,18 +215,28 @@
     if (retrieveError != null) {
       return retrieveError;
     }
-    if (_imageFileList != null) {
+    if (_mediaFileList != null) {
       return Semantics(
         label: 'image_picker_example_picked_images',
         child: ListView.builder(
           key: UniqueKey(),
           itemBuilder: (BuildContext context, int index) {
+            final String? mime = lookupMimeType(_mediaFileList![index].path);
             return Semantics(
               label: 'image_picker_example_picked_image',
-              child: Image.file(File(_imageFileList![index].path)),
+              child: mime == null || mime.startsWith('image/')
+                  ? Image.file(
+                      File(_mediaFileList![index].path),
+                      errorBuilder: (BuildContext context, Object error,
+                          StackTrace? stackTrace) {
+                        return const Center(
+                            child: Text('This image type is not supported'));
+                      },
+                    )
+                  : _buildInlineVideoPlayer(index),
             );
           },
-          itemCount: _imageFileList!.length,
+          itemCount: _mediaFileList!.length,
         ),
       );
     } else if (_pickImageError != null) {
@@ -207,6 +252,17 @@
     }
   }
 
+  Widget _buildInlineVideoPlayer(int index) {
+    final VideoPlayerController controller =
+        VideoPlayerController.file(File(_mediaFileList![index].path));
+    const double volume = 1.0;
+    controller.setVolume(volume);
+    controller.initialize();
+    controller.setLooping(true);
+    controller.play();
+    return Center(child: AspectRatioVideo(controller));
+  }
+
   Widget _handlePreview() {
     if (_isVideo) {
       return _previewVideo();
@@ -230,6 +286,7 @@
           Semantics(
             label: 'image_picker_example_from_gallery',
             child: FloatingActionButton(
+              key: const Key('image_picker_example_from_gallery'),
               onPressed: () {
                 _isVideo = false;
                 _onImageButtonPressed(ImageSource.gallery, context: context);
@@ -248,6 +305,39 @@
                   ImageSource.gallery,
                   context: context,
                   isMultiImage: true,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'multipleMedia',
+              tooltip: 'Pick Multiple Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'media',
+              tooltip: 'Pick Single Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMultiImage: true,
                 );
               },
               heroTag: 'image1',
@@ -420,3 +510,7 @@
     }
   }
 }
+
+T? _firstOrNull<T>(List<T> list) {
+  return list.isEmpty ? null : list.first;
+}
diff --git a/packages/image_picker/image_picker_macos/example/pubspec.yaml b/packages/image_picker/image_picker_macos/example/pubspec.yaml
index e76c492..785a2af 100644
--- a/packages/image_picker/image_picker_macos/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_macos/example/pubspec.yaml
@@ -17,7 +17,8 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ..
-  image_picker_platform_interface: ^2.7.0
+  image_picker_platform_interface: ^2.8.0
+  mime: ^1.0.4
   video_player: ^2.1.4
 
 dev_dependencies:
diff --git a/packages/image_picker/image_picker_macos/lib/image_picker_macos.dart b/packages/image_picker/image_picker_macos/lib/image_picker_macos.dart
index 7a7e927..9e9447a 100644
--- a/packages/image_picker/image_picker_macos/lib/image_picker_macos.dart
+++ b/packages/image_picker/image_picker_macos/lib/image_picker_macos.dart
@@ -159,4 +159,28 @@
         .openFiles(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
     return files;
   }
+
+  // `maxWidth`, `maxHeight`, and `imageQuality` arguments are not currently
+  // supported. If any of these arguments are supplied, they will be silently
+  // ignored.
+  @override
+  Future<List<XFile>> getMedia({required MediaOptions options}) async {
+    const XTypeGroup typeGroup = XTypeGroup(
+        label: 'images and videos',
+        extensions: <String>['public.image', 'public.movie']);
+
+    List<XFile> files;
+
+    if (options.allowMultiple) {
+      files = await fileSelector
+          .openFiles(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
+    } else {
+      final XFile? file = await fileSelector
+          .openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
+      files = <XFile>[
+        if (file != null) file,
+      ];
+    }
+    return files;
+  }
 }
diff --git a/packages/image_picker/image_picker_macos/pubspec.yaml b/packages/image_picker/image_picker_macos/pubspec.yaml
index ef97bd4..9ace885 100644
--- a/packages/image_picker/image_picker_macos/pubspec.yaml
+++ b/packages/image_picker/image_picker_macos/pubspec.yaml
@@ -2,7 +2,7 @@
 description: macOS platform implementation of image_picker
 repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_macos
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 0.2.0
+version: 0.2.1
 
 environment:
   sdk: ">=2.18.0 <4.0.0"
@@ -20,7 +20,7 @@
   file_selector_platform_interface: ^2.3.0
   flutter:
     sdk: flutter
-  image_picker_platform_interface: ^2.7.0
+  image_picker_platform_interface: ^2.8.0
 
 dev_dependencies:
   build_runner: ^2.1.5
diff --git a/packages/image_picker/image_picker_macos/test/image_picker_macos_test.dart b/packages/image_picker/image_picker_macos/test/image_picker_macos_test.dart
index f2b45cf..7e94161 100644
--- a/packages/image_picker/image_picker_macos/test/image_picker_macos_test.dart
+++ b/packages/image_picker/image_picker_macos/test/image_picker_macos_test.dart
@@ -131,6 +131,38 @@
           plugin.getVideo(source: ImageSource.camera), throwsStateError);
     });
   });
+
+  group('media', () {
+    test('getMedia passes the accepted type groups correctly', () async {
+      await plugin.getMedia(options: const MediaOptions(allowMultiple: true));
+
+      final VerificationResult result = verify(
+          mockFileSelectorPlatform.openFiles(
+              acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups')));
+      expect(capturedTypeGroups(result)[0].extensions,
+          <String>['public.image', 'public.movie']);
+    });
+
+    test('multiple media handles an empty path response gracefully', () async {
+      expect(
+          await plugin.getMedia(
+            options: const MediaOptions(
+              allowMultiple: true,
+            ),
+          ),
+          <String>[]);
+    });
+
+    test('single media handles an empty path response gracefully', () async {
+      expect(
+          await plugin.getMedia(
+            options: const MediaOptions(
+              allowMultiple: false,
+            ),
+          ),
+          <String>[]);
+    });
+  });
 }
 
 class FakeCameraDelegate extends ImagePickerCameraDelegate {
diff --git a/packages/image_picker/image_picker_windows/CHANGELOG.md b/packages/image_picker/image_picker_windows/CHANGELOG.md
index 2159d87..bd881d1 100644
--- a/packages/image_picker/image_picker_windows/CHANGELOG.md
+++ b/packages/image_picker/image_picker_windows/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.2.1
+
+* Adds `getMedia` method.
+
 ## 0.2.0
 
 * Updates minimum Flutter version to 3.3.
diff --git a/packages/image_picker/image_picker_windows/example/lib/main.dart b/packages/image_picker/image_picker_windows/example/lib/main.dart
index 9e22c71..8f48870 100644
--- a/packages/image_picker/image_picker_windows/example/lib/main.dart
+++ b/packages/image_picker/image_picker_windows/example/lib/main.dart
@@ -9,6 +9,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+import 'package:mime/mime.dart';
 import 'package:video_player/video_player.dart';
 
 void main() {
@@ -37,11 +38,11 @@
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  List<XFile>? _imageFileList;
+  List<XFile>? _mediaFileList;
 
   // This must be called from within a setState() callback
   void _setImageFileListFromFile(XFile? value) {
-    _imageFileList = value == null ? null : <XFile>[value];
+    _mediaFileList = value == null ? null : <XFile>[value];
   }
 
   dynamic _pickImageError;
@@ -70,52 +71,12 @@
     }
   }
 
-  Future<void> _handleMultiImagePicked(BuildContext context) async {
-    await _displayPickImageDialog(context,
-        (double? maxWidth, double? maxHeight, int? quality) async {
-      try {
-        final List<XFile>? pickedFileList = await _picker.getMultiImage(
-          maxWidth: maxWidth,
-          maxHeight: maxHeight,
-          imageQuality: quality,
-        );
-        setState(() {
-          _imageFileList = pickedFileList;
-        });
-      } catch (e) {
-        setState(() {
-          _pickImageError = e;
-        });
-      }
-    });
-  }
-
-  Future<void> _handleSingleImagePicked(
-      BuildContext context, ImageSource source) async {
-    await _displayPickImageDialog(context,
-        (double? maxWidth, double? maxHeight, int? quality) async {
-      try {
-        final XFile? pickedFile = await _picker.getImageFromSource(
-          source: source,
-          options: ImagePickerOptions(
-            maxWidth: maxWidth,
-            maxHeight: maxHeight,
-            imageQuality: quality,
-          ),
-        );
-        setState(() {
-          _setImageFileListFromFile(pickedFile);
-        });
-      } catch (e) {
-        setState(() {
-          _pickImageError = e;
-        });
-      }
-    });
-  }
-
-  Future<void> _onImageButtonPressed(ImageSource source,
-      {required BuildContext context, bool isMultiImage = false}) async {
+  Future<void> _onImageButtonPressed(
+    ImageSource source, {
+    required BuildContext context,
+    bool isMultiImage = false,
+    bool isMedia = false,
+  }) async {
     if (_controller != null) {
       await _controller!.setVolume(0.0);
     }
@@ -125,9 +86,83 @@
             source: source, maxDuration: const Duration(seconds: 10));
         await _playVideo(file);
       } else if (isMultiImage) {
-        await _handleMultiImagePicked(context);
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final List<XFile> pickedFileList = isMedia
+                ? await _picker.getMedia(
+                    options: MediaOptions(
+                        allowMultiple: isMultiImage,
+                        imageOptions: ImageOptions(
+                          maxWidth: maxWidth,
+                          maxHeight: maxHeight,
+                          imageQuality: quality,
+                        )),
+                  )
+                : await _picker.getMultiImageWithOptions(
+                    options: MultiImagePickerOptions(
+                      imageOptions: ImageOptions(
+                        maxWidth: maxWidth,
+                        maxHeight: maxHeight,
+                        imageQuality: quality,
+                      ),
+                    ),
+                  );
+            setState(() {
+              _mediaFileList = pickedFileList;
+            });
+          } catch (e) {
+            setState(() {
+              _pickImageError = e;
+            });
+          }
+        });
+      } else if (isMedia) {
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final List<XFile> pickedFileList = <XFile>[];
+            final XFile? media = _firstOrNull(await _picker.getMedia(
+              options: MediaOptions(
+                  allowMultiple: isMultiImage,
+                  imageOptions: ImageOptions(
+                    maxWidth: maxWidth,
+                    maxHeight: maxHeight,
+                    imageQuality: quality,
+                  )),
+            ));
+
+            if (media != null) {
+              pickedFileList.add(media);
+              setState(() {
+                _mediaFileList = pickedFileList;
+              });
+            }
+          } catch (e) {
+            setState(() => _pickImageError = e);
+          }
+        });
       } else {
-        await _handleSingleImagePicked(context, source);
+        await _displayPickImageDialog(context,
+            (double? maxWidth, double? maxHeight, int? quality) async {
+          try {
+            final XFile? pickedFile = await _picker.getImageFromSource(
+              source: source,
+              options: ImagePickerOptions(
+                maxWidth: maxWidth,
+                maxHeight: maxHeight,
+                imageQuality: quality,
+              ),
+            );
+            setState(() {
+              _setImageFileListFromFile(pickedFile);
+            });
+          } catch (e) {
+            setState(() {
+              _pickImageError = e;
+            });
+          }
+        });
       }
     }
   }
@@ -180,18 +215,28 @@
     if (retrieveError != null) {
       return retrieveError;
     }
-    if (_imageFileList != null) {
+    if (_mediaFileList != null) {
       return Semantics(
         label: 'image_picker_example_picked_images',
         child: ListView.builder(
           key: UniqueKey(),
           itemBuilder: (BuildContext context, int index) {
+            final String? mime = lookupMimeType(_mediaFileList![index].path);
             return Semantics(
               label: 'image_picker_example_picked_image',
-              child: Image.file(File(_imageFileList![index].path)),
+              child: mime == null || mime.startsWith('image/')
+                  ? Image.file(
+                      File(_mediaFileList![index].path),
+                      errorBuilder: (BuildContext context, Object error,
+                          StackTrace? stackTrace) {
+                        return const Center(
+                            child: Text('This image type is not supported'));
+                      },
+                    )
+                  : _buildInlineVideoPlayer(index),
             );
           },
-          itemCount: _imageFileList!.length,
+          itemCount: _mediaFileList!.length,
         ),
       );
     } else if (_pickImageError != null) {
@@ -207,6 +252,17 @@
     }
   }
 
+  Widget _buildInlineVideoPlayer(int index) {
+    final VideoPlayerController controller =
+        VideoPlayerController.file(File(_mediaFileList![index].path));
+    const double volume = 1.0;
+    controller.setVolume(volume);
+    controller.initialize();
+    controller.setLooping(true);
+    controller.play();
+    return Center(child: AspectRatioVideo(controller));
+  }
+
   Widget _handlePreview() {
     if (_isVideo) {
       return _previewVideo();
@@ -230,6 +286,7 @@
           Semantics(
             label: 'image_picker_example_from_gallery',
             child: FloatingActionButton(
+              key: const Key('image_picker_example_from_gallery'),
               onPressed: () {
                 _isVideo = false;
                 _onImageButtonPressed(ImageSource.gallery, context: context);
@@ -248,6 +305,39 @@
                   ImageSource.gallery,
                   context: context,
                   isMultiImage: true,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'multipleMedia',
+              tooltip: 'Pick Multiple Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMedia: true,
+                );
+              },
+              heroTag: 'media',
+              tooltip: 'Pick Single Media from gallery',
+              child: const Icon(Icons.photo_library),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.only(top: 16.0),
+            child: FloatingActionButton(
+              onPressed: () {
+                _isVideo = false;
+                _onImageButtonPressed(
+                  ImageSource.gallery,
+                  context: context,
+                  isMultiImage: true,
                 );
               },
               heroTag: 'image1',
@@ -420,3 +510,7 @@
     }
   }
 }
+
+T? _firstOrNull<T>(List<T> list) {
+  return list.isEmpty ? null : list.first;
+}
diff --git a/packages/image_picker/image_picker_windows/example/pubspec.yaml b/packages/image_picker/image_picker_windows/example/pubspec.yaml
index a645670..6515d50 100644
--- a/packages/image_picker/image_picker_windows/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_windows/example/pubspec.yaml
@@ -10,7 +10,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  image_picker_platform_interface: ^2.7.0
+  image_picker_platform_interface: ^2.8.0
   image_picker_windows:
     # When depending on this package from a real application you should use:
     #   image_picker_windows: ^x.y.z
@@ -18,6 +18,7 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ..
+  mime: ^1.0.4
   video_player: ^2.1.4
 
 dev_dependencies:
diff --git a/packages/image_picker/image_picker_windows/lib/image_picker_windows.dart b/packages/image_picker/image_picker_windows/lib/image_picker_windows.dart
index ba7ff4d..e9e4146 100644
--- a/packages/image_picker/image_picker_windows/lib/image_picker_windows.dart
+++ b/packages/image_picker/image_picker_windows/lib/image_picker_windows.dart
@@ -183,4 +183,28 @@
         .openFiles(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
     return files;
   }
+
+  // `maxWidth`, `maxHeight`, and `imageQuality` arguments are not
+  // supported on Windows. If any of these arguments is supplied,
+  // they will be silently ignored by the Windows version of the plugin.
+  @override
+  Future<List<XFile>> getMedia({required MediaOptions options}) async {
+    const XTypeGroup typeGroup = XTypeGroup(
+        label: 'images and videos',
+        extensions: <String>[...imageFormats, ...videoFormats]);
+
+    List<XFile> files;
+
+    if (options.allowMultiple) {
+      files = await fileSelector
+          .openFiles(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
+    } else {
+      final XFile? file = await fileSelector
+          .openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
+      files = <XFile>[
+        if (file != null) file,
+      ];
+    }
+    return files;
+  }
 }
diff --git a/packages/image_picker/image_picker_windows/pubspec.yaml b/packages/image_picker/image_picker_windows/pubspec.yaml
index 2ca2fc5..e16ecbd 100644
--- a/packages/image_picker/image_picker_windows/pubspec.yaml
+++ b/packages/image_picker/image_picker_windows/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Windows platform implementation of image_picker
 repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_windows
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 0.2.0
+version: 0.2.1
 
 environment:
   sdk: ">=2.18.0 <4.0.0"
@@ -20,7 +20,7 @@
   file_selector_windows: ^0.9.0
   flutter:
     sdk: flutter
-  image_picker_platform_interface: ^2.7.0
+  image_picker_platform_interface: ^2.8.0
 
 dev_dependencies:
   build_runner: ^2.1.5
diff --git a/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart b/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart
index d680d78..6da0873 100644
--- a/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart
+++ b/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart
@@ -128,6 +128,41 @@
             plugin.getVideo(source: ImageSource.camera), throwsStateError);
       });
     });
+
+    group('media', () {
+      test('getMedia passes the accepted type groups correctly', () async {
+        await plugin.getMedia(options: const MediaOptions(allowMultiple: true));
+
+        final VerificationResult result = verify(
+            mockFileSelectorPlatform.openFiles(
+                acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups')));
+        expect(capturedTypeGroups(result)[0].extensions, <String>[
+          ...ImagePickerWindows.imageFormats,
+          ...ImagePickerWindows.videoFormats
+        ]);
+      });
+
+      test('multiple media handles an empty path response gracefully',
+          () async {
+        expect(
+            await plugin.getMedia(
+              options: const MediaOptions(
+                allowMultiple: true,
+              ),
+            ),
+            <String>[]);
+      });
+
+      test('single media handles an empty path response gracefully', () async {
+        expect(
+            await plugin.getMedia(
+              options: const MediaOptions(
+                allowMultiple: false,
+              ),
+            ),
+            <String>[]);
+      });
+    });
   });
 }
 
diff --git a/script/configs/allowed_unpinned_deps.yaml b/script/configs/allowed_unpinned_deps.yaml
index 1cf35c8..d027396 100644
--- a/script/configs/allowed_unpinned_deps.yaml
+++ b/script/configs/allowed_unpinned_deps.yaml
@@ -48,6 +48,7 @@
 - logging
 - markdown
 - meta
+- mime
 - path
 - shelf
 - shelf_static