Photo / video library picker + library enumeration — a Mob plugin (extracted from mob core in Wave 2).
Two access modes, with different permission postures:
pick/2— the system picker. On iOS 14+ no permission is required (it runs out of process —PHPickerViewController); on Android the system Photo Picker (PickVisualMedia/PickMultipleVisualMedia) likewise runs out of process. The user chooses individual items; the app never sees the rest of the library.list_media/2— library enumeration. Lists the user's whole photo / video library with metadata (for an "AI Library Search" style screen). This reads the full MediaStore (AndroidContentResolver), so it genuinely requires a runtime permission:READ_MEDIA_IMAGES/READ_MEDIA_VIDEO(Android 33+) orREAD_EXTERNAL_STORAGE(API <= 32). Request it first withMob.Permissions.request(socket, :media)(this plugin registers the:mediacapability with the platform permission registry) — the result arrives as{:permission, :media, :granted | :denied}. Those Android permissions are declared in this plugin's manifest and merged into the host AndroidManifest at build time; iOS needsNSPhotoLibraryUsageDescriptioninInfo.plist(placeholder merged from the manifest — replace it). See the permissions guide for the cross-platform table.
Picker results
handle_info({:photos, :picked, items}, socket)
handle_info({:photos, :cancelled}, socket)Each item in items is:
%{path: "/tmp/mob_pick_xxx.jpg", type: :image | :video,
width: 1920, height: 1080}Platform parity notes (inherited from core, preserved by this plugin):
iOS items carry only path + type (type as an atom); Android items
carry all four keys but type is a string ("image"/"video") and
width/height are 0 (the picker doesn't probe dimensions).
iOS: PHPickerViewController. Android: PickMultipleVisualMedia.
Enumeration results
handle_info({:media, :listed, items}, socket)Each item in items is a map with atom keys. The native side builds a
JSON array of metadata; the delivery rides core's generic
{:mob_file_result, "media", "listed", json} path (the same decoder that
serves pick/2), which decodes the JSON and atomizes each item's keys before
your screen sees it:
%{uri: "content://media/external/images/media/42",
display_name: "IMG_0042.jpg",
size: 2_481_233,
date_added: 1_700_000_000, # unix seconds
mime_type: "image/jpeg",
type: "image"} # "image" | "video"On Android uri is a content:// URI (open it via the host's
contentResolver); the picker's path (a copied temp file) is a separate
concept — enumeration does NOT copy bytes, it only lists metadata. iOS is
not yet supported (list_media/2 returns {:error, :unsupported} there).
Summary
Functions
Enumerate the user's media library (images and/or videos) with metadata.
Build the option map passed to media_list/1. Pure function exposed so tests
can pin defaults + serialisation without going through the NIF.
Open the photo library picker.
Functions
@spec list_media( Mob.Socket.t(), keyword() ) :: Mob.Socket.t()
Enumerate the user's media library (images and/or videos) with metadata.
Asynchronous: the result is delivered to the calling process (call from
a Mob.Screen callback such as mount/3 or handle_info/2):
handle_info({:media, :listed, items}, socket)where each item is a string-keyed map (see the "Enumeration results" section
of the module doc). Requires the :media permission to be granted first
(Mob.Permissions.request(socket, :media)) — without it Android returns an
empty list, not an error.
Options:
type: :image | :video | :all(default:all) — which media kinds to listlimit: integer(default200) — maximum items returned, newest first (ordered bydate_addeddescending).0or a negative value means "no limit" — be cautious on large libraries (this is a synchronous query on the native side and the whole result is delivered as one message).
Returns the socket immediately. On iOS this is currently unsupported and the
NIF returns {:error, :unsupported} synchronously (no message is delivered);
the socket is still returned unchanged.
Build the option map passed to media_list/1. Pure function exposed so tests
can pin defaults + serialisation without going through the NIF.
@spec pick( Mob.Socket.t(), keyword() ) :: Mob.Socket.t()
Open the photo library picker.
Options:
max: integer(default1) — maximum number of items selectabletypes: [:image | :video](default[:image]) — currently ignored by both native sides (core parity: both pickers show images + videos)