@symbiote-native/media-library 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +284 -0
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +1 -0
- package/build/core/index.d.ts +3 -0
- package/build/core/index.js +3 -0
- package/build/core/media-library.d.ts +94 -0
- package/build/core/media-library.js +284 -0
- package/build/core/native-module.d.ts +29 -0
- package/build/core/native-module.js +3 -0
- package/build/core/types.d.ts +126 -0
- package/build/core/types.js +1 -0
- package/build/next/album.d.ts +9 -0
- package/build/next/album.js +12 -0
- package/build/next/asset.d.ts +13 -0
- package/build/next/asset.js +34 -0
- package/build/next/index.d.ts +24 -0
- package/build/next/index.js +44 -0
- package/build/next/native-module.d.ts +78 -0
- package/build/next/native-module.js +2 -0
- package/build/next/query.d.ts +13 -0
- package/build/next/query.js +16 -0
- package/build/next/types.d.ts +92 -0
- package/build/next/types.js +30 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +2 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/next/album.d.ts +9 -0
- package/build-ngc/next/album.js +13 -0
- package/build-ngc/next/album.js.map +1 -0
- package/build-ngc/next/asset.d.ts +13 -0
- package/build-ngc/next/asset.js +35 -0
- package/build-ngc/next/asset.js.map +1 -0
- package/build-ngc/next/index.d.ts +24 -0
- package/build-ngc/next/index.js +45 -0
- package/build-ngc/next/index.js.map +1 -0
- package/build-ngc/next/native-module.d.ts +78 -0
- package/build-ngc/next/native-module.js +3 -0
- package/build-ngc/next/native-module.js.map +1 -0
- package/build-ngc/next/query.d.ts +13 -0
- package/build-ngc/next/query.js +17 -0
- package/build-ngc/next/query.js.map +1 -0
- package/build-ngc/next/types.d.ts +92 -0
- package/build-ngc/next/types.js +31 -0
- package/build-ngc/next/types.js.map +1 -0
- package/native-link.json +34 -0
- package/package.json +154 -0
- package/src/angular/index.ts +1 -0
- package/src/core/index.ts +7 -0
- package/src/core/media-library.ts +443 -0
- package/src/core/native-module.ts +80 -0
- package/src/core/types.ts +163 -0
- package/src/next/album.ts +13 -0
- package/src/next/asset.ts +51 -0
- package/src/next/index.ts +82 -0
- package/src/next/native-module.ts +116 -0
- package/src/next/query.ts +17 -0
- package/src/next/types.ts +106 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { createPermissionHook } from 'expo-modules-core';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import { expoMediaLibrary } from './native-module.js';
|
|
4
|
+
function arrayize(item) {
|
|
5
|
+
if (Array.isArray(item))
|
|
6
|
+
return item;
|
|
7
|
+
return item ? [item] : [];
|
|
8
|
+
}
|
|
9
|
+
function getId(ref) {
|
|
10
|
+
if (typeof ref === 'string')
|
|
11
|
+
return ref;
|
|
12
|
+
return ref ? ref.id : undefined;
|
|
13
|
+
}
|
|
14
|
+
function assertAssetIds(assetIds) {
|
|
15
|
+
if (assetIds.some(id => !id || typeof id !== 'string')) {
|
|
16
|
+
throw new Error('Asset ID must be a string!');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function assertAlbumIds(albumIds) {
|
|
20
|
+
if (albumIds.some(id => !id || typeof id !== 'string')) {
|
|
21
|
+
throw new Error('Album ID must be a string!');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function assertMediaType(mediaType) {
|
|
25
|
+
if (!Object.values(MediaType).includes(mediaType)) {
|
|
26
|
+
throw new Error(`Invalid mediaType: ${String(mediaType)}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function assertSortByKey(sortBy) {
|
|
30
|
+
if (!Object.values(SortBy).includes(sortBy)) {
|
|
31
|
+
throw new Error(`Invalid sortBy key: ${String(sortBy)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function assertSortBy(sortBy) {
|
|
35
|
+
if (Array.isArray(sortBy)) {
|
|
36
|
+
assertSortByKey(sortBy[0]);
|
|
37
|
+
if (typeof sortBy[1] !== 'boolean') {
|
|
38
|
+
throw new Error('Invalid sortBy array argument. Second item must be a boolean!');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
assertSortByKey(sortBy);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function sortByOptionToString(sortBy) {
|
|
46
|
+
assertSortBy(sortBy);
|
|
47
|
+
if (Array.isArray(sortBy)) {
|
|
48
|
+
return `${sortBy[0]} ${sortBy[1] ? 'ASC' : 'DESC'}`;
|
|
49
|
+
}
|
|
50
|
+
return `${sortBy} DESC`;
|
|
51
|
+
}
|
|
52
|
+
function dateToNumber(value) {
|
|
53
|
+
return value instanceof Date ? value.getTime() : value;
|
|
54
|
+
}
|
|
55
|
+
export const MediaType = expoMediaLibrary.MediaType;
|
|
56
|
+
export const SortBy = expoMediaLibrary.SortBy;
|
|
57
|
+
export async function isAvailableAsync() {
|
|
58
|
+
return !!expoMediaLibrary && 'getAssetsAsync' in expoMediaLibrary;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @param granularPermissions Only has an effect on Android 13+. Defaults to every possible
|
|
62
|
+
* permission — make sure your app manifest requests everything you pass here.
|
|
63
|
+
*/
|
|
64
|
+
export async function requestPermissionsAsync(writeOnly = false, granularPermissions) {
|
|
65
|
+
if (Platform.OS === 'android') {
|
|
66
|
+
return expoMediaLibrary.requestPermissionsAsync(writeOnly, granularPermissions);
|
|
67
|
+
}
|
|
68
|
+
return expoMediaLibrary.requestPermissionsAsync(writeOnly);
|
|
69
|
+
}
|
|
70
|
+
export async function getPermissionsAsync(writeOnly = false, granularPermissions) {
|
|
71
|
+
if (Platform.OS === 'android') {
|
|
72
|
+
return expoMediaLibrary.getPermissionsAsync(writeOnly, granularPermissions);
|
|
73
|
+
}
|
|
74
|
+
return expoMediaLibrary.getPermissionsAsync(writeOnly);
|
|
75
|
+
}
|
|
76
|
+
export const usePermissions = createPermissionHook({
|
|
77
|
+
getMethod: (options) => getPermissionsAsync(options?.writeOnly, options?.granularPermissions),
|
|
78
|
+
requestMethod: (options) => requestPermissionsAsync(options?.writeOnly, options?.granularPermissions),
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* Lets the user update which assets your app has access to. Only a no-op unless the user
|
|
82
|
+
* previously granted `'limited'` access.
|
|
83
|
+
* @platform android 14+
|
|
84
|
+
* @platform ios
|
|
85
|
+
*/
|
|
86
|
+
export async function presentPermissionsPickerAsync(mediaTypes = ['photo', 'video']) {
|
|
87
|
+
if (Platform.OS === 'android' && Platform.Version >= 34) {
|
|
88
|
+
await expoMediaLibrary.requestPermissionsAsync(false, mediaTypes);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!expoMediaLibrary.presentPermissionsPickerAsync) {
|
|
92
|
+
throw new Error('presentPermissionsPickerAsync is not available on this platform.');
|
|
93
|
+
}
|
|
94
|
+
return expoMediaLibrary.presentPermissionsPickerAsync();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Creates an asset from an existing local file — the common case is a picture just taken by a
|
|
98
|
+
* camera. Requires write permission.
|
|
99
|
+
* @param localUri Must contain an extension. On Android it must be a local path (`file:///…`).
|
|
100
|
+
* @param album If provided, the asset is added to this album on creation.
|
|
101
|
+
*/
|
|
102
|
+
export async function createAssetAsync(localUri, album) {
|
|
103
|
+
if (!localUri || typeof localUri !== 'string') {
|
|
104
|
+
throw new Error('Invalid argument "localUri". It must be a string!');
|
|
105
|
+
}
|
|
106
|
+
const asset = await expoMediaLibrary.createAssetAsync(localUri, getId(album));
|
|
107
|
+
return Array.isArray(asset) ? asset[0] : asset;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Saves the file at `localUri` to the user's library. Unlike `createAssetAsync`, does not
|
|
111
|
+
* return the created asset. On iOS 11+ this can be called without the read/write permission as
|
|
112
|
+
* long as `Info.plist` carries `NSPhotoLibraryAddUsageDescription`.
|
|
113
|
+
*/
|
|
114
|
+
export async function saveToLibraryAsync(localUri) {
|
|
115
|
+
return expoMediaLibrary.saveToLibraryAsync(localUri);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* On Android, copies assets from their current album into the given one by default — pass
|
|
119
|
+
* `copy: false` to move them instead. Copied assets show up twice in `getAssetsAsync`.
|
|
120
|
+
*/
|
|
121
|
+
export async function addAssetsToAlbumAsync(assets, album, copy = true) {
|
|
122
|
+
const assetIds = arrayize(assets).map(getId);
|
|
123
|
+
const albumId = getId(album);
|
|
124
|
+
assertAssetIds(assetIds);
|
|
125
|
+
if (!albumId || typeof albumId !== 'string') {
|
|
126
|
+
throw new Error('Invalid album ID. It must be a string!');
|
|
127
|
+
}
|
|
128
|
+
if (Platform.OS === 'ios') {
|
|
129
|
+
return expoMediaLibrary.addAssetsToAlbumAsync(assetIds, albumId);
|
|
130
|
+
}
|
|
131
|
+
return expoMediaLibrary.addAssetsToAlbumAsync(assetIds, albumId, !!copy);
|
|
132
|
+
}
|
|
133
|
+
/** On Android, an empty album is deleted automatically once its last asset is removed. */
|
|
134
|
+
export async function removeAssetsFromAlbumAsync(assets, album) {
|
|
135
|
+
const assetIds = arrayize(assets).map(getId);
|
|
136
|
+
assertAssetIds(assetIds);
|
|
137
|
+
return expoMediaLibrary.removeAssetsFromAlbumAsync(assetIds, getId(album));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* iOS removes the asset from every album it belongs to (behind a system confirmation dialog);
|
|
141
|
+
* Android keeps other albums' copies (an asset is strictly tied to one album there).
|
|
142
|
+
*/
|
|
143
|
+
export async function deleteAssetsAsync(assets) {
|
|
144
|
+
const assetIds = arrayize(assets).map(getId);
|
|
145
|
+
assertAssetIds(assetIds);
|
|
146
|
+
return expoMediaLibrary.deleteAssetsAsync(assetIds);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* GPS location, local URI and EXIF metadata. Prefer this only when you need more than the
|
|
150
|
+
* fields already on `Asset` — it costs more than a plain list query.
|
|
151
|
+
*/
|
|
152
|
+
export async function getAssetInfoAsync(asset, options = {
|
|
153
|
+
shouldDownloadFromNetwork: true,
|
|
154
|
+
}) {
|
|
155
|
+
const assetId = getId(asset);
|
|
156
|
+
assertAssetIds([assetId]);
|
|
157
|
+
const info = await expoMediaLibrary.getAssetInfoAsync(assetId, options);
|
|
158
|
+
return Array.isArray(info) ? info[0] : info;
|
|
159
|
+
}
|
|
160
|
+
/** @platform android */
|
|
161
|
+
export async function getAssetContentUriAsync(asset) {
|
|
162
|
+
if (Platform.OS !== 'android') {
|
|
163
|
+
throw new Error('getAssetContentUriAsync is only available on Android.');
|
|
164
|
+
}
|
|
165
|
+
const assetId = getId(asset);
|
|
166
|
+
assertAssetIds([assetId]);
|
|
167
|
+
return expoMediaLibrary.getAssetContentUriAsync(assetId);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* @return Depending on Android version, the root storage directory may be listed as an album
|
|
171
|
+
* titled `"0"`, or not listed at all.
|
|
172
|
+
*/
|
|
173
|
+
export async function getAlbumsAsync({ includeSmartAlbums = false, } = {}) {
|
|
174
|
+
return expoMediaLibrary.getAlbumsAsync({ includeSmartAlbums });
|
|
175
|
+
}
|
|
176
|
+
export async function getAlbumAsync(title) {
|
|
177
|
+
if (typeof title !== 'string') {
|
|
178
|
+
throw new Error('Album title must be a string!');
|
|
179
|
+
}
|
|
180
|
+
return expoMediaLibrary.getAlbumAsync(title);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* `asset` (or `initialAssetLocalUri`) is required on Android — an empty album cannot be
|
|
184
|
+
* created on that platform.
|
|
185
|
+
*/
|
|
186
|
+
export async function createAlbumAsync(albumName, asset, copyAsset = true, initialAssetLocalUri) {
|
|
187
|
+
const assetId = getId(asset);
|
|
188
|
+
if (Platform.OS === 'android' &&
|
|
189
|
+
(typeof assetId !== 'string' || assetId.length === 0) &&
|
|
190
|
+
!initialAssetLocalUri) {
|
|
191
|
+
throw new Error('createAlbumAsync must be called with an asset or a localUri on Android.');
|
|
192
|
+
}
|
|
193
|
+
if (!albumName || typeof albumName !== 'string') {
|
|
194
|
+
throw new Error('Invalid argument "albumName". It must be a string!');
|
|
195
|
+
}
|
|
196
|
+
if (Platform.OS === 'ios') {
|
|
197
|
+
return expoMediaLibrary.createAlbumAsync(albumName, assetId, initialAssetLocalUri);
|
|
198
|
+
}
|
|
199
|
+
return expoMediaLibrary.createAlbumAsync(albumName, assetId, !!copyAsset, initialAssetLocalUri);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* iOS keeps the assets in the main library unless `deleteAssets` is set; Android always drops
|
|
203
|
+
* an album's assets along with it.
|
|
204
|
+
*/
|
|
205
|
+
export async function deleteAlbumsAsync(albums, deleteAssets = false) {
|
|
206
|
+
const albumIds = arrayize(albums).map(getId);
|
|
207
|
+
assertAlbumIds(albumIds);
|
|
208
|
+
if (Platform.OS === 'android') {
|
|
209
|
+
return expoMediaLibrary.deleteAlbumsAsync(albumIds);
|
|
210
|
+
}
|
|
211
|
+
return expoMediaLibrary.deleteAlbumsAsync(albumIds, !!deleteAssets);
|
|
212
|
+
}
|
|
213
|
+
// UPSTREAM-BUG(expo): legacy/MediaLibrary.ts's own `arrayize()` passes any array through
|
|
214
|
+
// unchanged, so a single `[key, ascending]` tuple in `sortBy` is misread as two independent
|
|
215
|
+
// sort keys instead of one pair — the caller must double-nest it (`[[key, ascending]]`) to
|
|
216
|
+
// sort by one key ascending. Ported verbatim for parity; see media-library.test.ts.
|
|
217
|
+
export async function getAssetsAsync(assetsOptions = {}) {
|
|
218
|
+
const { first, after, album, sortBy, mediaType, createdAfter, createdBefore, mediaSubtypes, resolveWithFullInfo, } = assetsOptions;
|
|
219
|
+
if (first != null && first < 0) {
|
|
220
|
+
throw new Error('Option "first" must be a positive integer!');
|
|
221
|
+
}
|
|
222
|
+
const afterId = getId(after);
|
|
223
|
+
if (after != null &&
|
|
224
|
+
Platform.OS === 'android' &&
|
|
225
|
+
isNaN(parseInt(afterId, 10))) {
|
|
226
|
+
throw new Error('Option "after" must be a valid ID!');
|
|
227
|
+
}
|
|
228
|
+
const mediaTypeList = arrayize(mediaType || [MediaType.photo]);
|
|
229
|
+
mediaTypeList.forEach(assertMediaType);
|
|
230
|
+
return expoMediaLibrary.getAssetsAsync({
|
|
231
|
+
first: first == null ? 20 : first,
|
|
232
|
+
after: afterId,
|
|
233
|
+
album: getId(album),
|
|
234
|
+
sortBy: arrayize(sortBy).map(sortByOptionToString),
|
|
235
|
+
mediaType: mediaTypeList,
|
|
236
|
+
mediaSubtypes: arrayize(mediaSubtypes),
|
|
237
|
+
createdAfter: dateToNumber(createdAfter),
|
|
238
|
+
createdBefore: dateToNumber(createdBefore),
|
|
239
|
+
resolveWithFullInfo: resolveWithFullInfo ?? false,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* @param listener Fired on Android with an empty object; on iOS with a real
|
|
244
|
+
* `MediaLibraryAssetsChangeEvent`, and also whenever the user updates per-asset access via
|
|
245
|
+
* `presentPermissionsPickerAsync()`.
|
|
246
|
+
*/
|
|
247
|
+
export function addListener(listener) {
|
|
248
|
+
return expoMediaLibrary.addListener(expoMediaLibrary.CHANGE_LISTENER_NAME, (event) => listener(event));
|
|
249
|
+
}
|
|
250
|
+
export function removeAllListeners() {
|
|
251
|
+
expoMediaLibrary.removeAllListeners(expoMediaLibrary.CHANGE_LISTENER_NAME);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A "moment" is a group of assets taken around the same place and time.
|
|
255
|
+
* @platform ios
|
|
256
|
+
*/
|
|
257
|
+
export async function getMomentsAsync() {
|
|
258
|
+
return expoMediaLibrary.getMomentsAsync();
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Moves an album's content into the scoped-storage media directories, only where needed
|
|
262
|
+
* (Android R+, and only if the app lacks write permission to the album folder). No-op on iOS,
|
|
263
|
+
* web, and Android below R, or if the app already has write access.
|
|
264
|
+
*/
|
|
265
|
+
export async function migrateAlbumIfNeededAsync(album) {
|
|
266
|
+
if (!expoMediaLibrary.migrateAlbumIfNeededAsync)
|
|
267
|
+
return;
|
|
268
|
+
return expoMediaLibrary.migrateAlbumIfNeededAsync(getId(album));
|
|
269
|
+
}
|
|
270
|
+
/** Always `false` on iOS, web, and Android below R. */
|
|
271
|
+
export async function albumNeedsMigrationAsync(album) {
|
|
272
|
+
if (!expoMediaLibrary.albumNeedsMigrationAsync)
|
|
273
|
+
return false;
|
|
274
|
+
return expoMediaLibrary.albumNeedsMigrationAsync(getId(album));
|
|
275
|
+
}
|
|
276
|
+
/** Adds or removes the asset from the system "Favorites" smart album. @platform ios */
|
|
277
|
+
export async function setAssetFavoriteAsync(asset, isFavorite) {
|
|
278
|
+
if (Platform.OS !== 'ios') {
|
|
279
|
+
throw new Error('setAssetFavoriteAsync is only available on iOS.');
|
|
280
|
+
}
|
|
281
|
+
const assetId = getId(asset);
|
|
282
|
+
assertAssetIds([assetId]);
|
|
283
|
+
return expoMediaLibrary.setAssetFavoriteAsync(assetId, isFavorite);
|
|
284
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import type { IGranularPermission, IMediaLibraryAlbum, IMediaLibraryAlbumsOptions, IMediaLibraryAsset, IMediaLibraryAssetInfo, IMediaLibraryAssetInfoQueryOptions, IMediaLibraryMediaTypeObject, IMediaLibraryPagedInfo, IMediaLibraryPermissionResponse, IMediaLibrarySortByObject } from './types';
|
|
3
|
+
export type INativeMediaLibraryModule = {
|
|
4
|
+
MediaType: IMediaLibraryMediaTypeObject;
|
|
5
|
+
SortBy: IMediaLibrarySortByObject;
|
|
6
|
+
CHANGE_LISTENER_NAME: string;
|
|
7
|
+
addListener(eventName: string, listener: (event: unknown) => void): EventSubscription;
|
|
8
|
+
removeAllListeners(eventName: string): void;
|
|
9
|
+
requestPermissionsAsync(writeOnly: boolean, granularPermissions?: IGranularPermission[]): Promise<IMediaLibraryPermissionResponse>;
|
|
10
|
+
getPermissionsAsync(writeOnly: boolean, granularPermissions?: IGranularPermission[]): Promise<IMediaLibraryPermissionResponse>;
|
|
11
|
+
presentPermissionsPickerAsync?(): Promise<void>;
|
|
12
|
+
createAssetAsync(localUri: string, albumId?: string): Promise<IMediaLibraryAsset | IMediaLibraryAsset[]>;
|
|
13
|
+
saveToLibraryAsync(localUri: string): Promise<void>;
|
|
14
|
+
addAssetsToAlbumAsync(assetIds: string[], albumId: string, copy?: boolean): Promise<boolean>;
|
|
15
|
+
removeAssetsFromAlbumAsync(assetIds: string[], albumId: string | undefined): Promise<boolean>;
|
|
16
|
+
deleteAssetsAsync(assetIds: string[]): Promise<boolean>;
|
|
17
|
+
getAssetInfoAsync(assetId: string, options: IMediaLibraryAssetInfoQueryOptions): Promise<IMediaLibraryAssetInfo | IMediaLibraryAssetInfo[]>;
|
|
18
|
+
getAssetContentUriAsync(assetId: string): Promise<string>;
|
|
19
|
+
getAlbumsAsync(options: IMediaLibraryAlbumsOptions): Promise<IMediaLibraryAlbum[]>;
|
|
20
|
+
getAlbumAsync(title: string): Promise<IMediaLibraryAlbum>;
|
|
21
|
+
createAlbumAsync(albumName: string, assetId: string | undefined, copyOrInitialUri?: boolean | string, initialAssetLocalUri?: string): Promise<IMediaLibraryAlbum>;
|
|
22
|
+
deleteAlbumsAsync(albumIds: string[], deleteAssets?: boolean): Promise<boolean>;
|
|
23
|
+
getAssetsAsync(options: Record<string, unknown>): Promise<IMediaLibraryPagedInfo<IMediaLibraryAsset>>;
|
|
24
|
+
getMomentsAsync(): Promise<IMediaLibraryAlbum[]>;
|
|
25
|
+
migrateAlbumIfNeededAsync?(albumId: string | undefined): Promise<void>;
|
|
26
|
+
albumNeedsMigrationAsync?(albumId: string | undefined): Promise<boolean>;
|
|
27
|
+
setAssetFavoriteAsync(assetId: string, isFavorite: boolean): Promise<boolean>;
|
|
28
|
+
};
|
|
29
|
+
export declare const expoMediaLibrary: INativeMediaLibraryModule;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { PermissionResponse } from 'expo-modules-core';
|
|
2
|
+
export type IGranularPermission = 'audio' | 'photo' | 'video';
|
|
3
|
+
export type IMediaLibraryMediaTypeValue = 'audio' | 'photo' | 'video' | 'unknown' | 'pairedVideo';
|
|
4
|
+
/** @platform android 14+ */
|
|
5
|
+
export type IMediaLibraryMediaTypeFilter = 'photo' | 'video';
|
|
6
|
+
export type IMediaLibrarySortByKey = 'default' | 'mediaType' | 'width' | 'height' | 'creationTime' | 'modificationTime' | 'duration';
|
|
7
|
+
export type IMediaLibrarySortByValue = [
|
|
8
|
+
IMediaLibrarySortByKey,
|
|
9
|
+
boolean
|
|
10
|
+
] | IMediaLibrarySortByKey;
|
|
11
|
+
export type IMediaLibraryMediaTypeObject = {
|
|
12
|
+
audio: 'audio';
|
|
13
|
+
photo: 'photo';
|
|
14
|
+
video: 'video';
|
|
15
|
+
unknown: 'unknown';
|
|
16
|
+
};
|
|
17
|
+
export type IMediaLibrarySortByObject = {
|
|
18
|
+
default: 'default';
|
|
19
|
+
mediaType: 'mediaType';
|
|
20
|
+
width: 'width';
|
|
21
|
+
height: 'height';
|
|
22
|
+
creationTime: 'creationTime';
|
|
23
|
+
modificationTime: 'modificationTime';
|
|
24
|
+
duration: 'duration';
|
|
25
|
+
};
|
|
26
|
+
/** @platform ios */
|
|
27
|
+
export type IMediaLibraryMediaSubtype = 'depthEffect' | 'hdr' | 'highFrameRate' | 'livePhoto' | 'panorama' | 'screenshot' | 'stream' | 'timelapse' | 'spatialMedia' | 'videoCinematic';
|
|
28
|
+
export type IMediaLibraryLocation = {
|
|
29
|
+
latitude: number;
|
|
30
|
+
longitude: number;
|
|
31
|
+
};
|
|
32
|
+
export type IMediaLibraryAsset = {
|
|
33
|
+
id: string;
|
|
34
|
+
filename: string;
|
|
35
|
+
/** `ph://*` (iOS), `file://*` (Android) */
|
|
36
|
+
uri: string;
|
|
37
|
+
mediaType: IMediaLibraryMediaTypeValue;
|
|
38
|
+
/** @platform ios */
|
|
39
|
+
mediaSubtypes?: IMediaLibraryMediaSubtype[];
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
creationTime: number;
|
|
43
|
+
modificationTime: number;
|
|
44
|
+
/** Seconds, for audio/video assets. */
|
|
45
|
+
duration: number;
|
|
46
|
+
/** @platform android */
|
|
47
|
+
albumId?: string;
|
|
48
|
+
};
|
|
49
|
+
export type IMediaLibraryAssetInfo = IMediaLibraryAsset & {
|
|
50
|
+
localUri?: string;
|
|
51
|
+
location?: IMediaLibraryLocation;
|
|
52
|
+
exif?: object;
|
|
53
|
+
/** @platform ios */
|
|
54
|
+
isFavorite?: boolean;
|
|
55
|
+
/** Only set when `shouldDownloadFromNetwork` is `false`. @platform ios */
|
|
56
|
+
isNetworkAsset?: boolean;
|
|
57
|
+
/** 1-8, see the EXIF orientation spec. Only for `mediaType: 'photo'`. @platform ios */
|
|
58
|
+
orientation?: number;
|
|
59
|
+
/** The video paired with a Live Photo. @platform ios */
|
|
60
|
+
pairedVideoAsset?: IMediaLibraryAsset | null;
|
|
61
|
+
};
|
|
62
|
+
export type IMediaLibraryAssetInfoQueryOptions = {
|
|
63
|
+
/** @default true — iOS iCloud assets only */
|
|
64
|
+
shouldDownloadFromNetwork?: boolean;
|
|
65
|
+
};
|
|
66
|
+
export type IMediaLibraryAssetsChangeEvent = {
|
|
67
|
+
/** `false` on Android — the platform never reports incremental detail. */
|
|
68
|
+
hasIncrementalChanges: boolean;
|
|
69
|
+
insertedAssets?: IMediaLibraryAsset[];
|
|
70
|
+
deletedAssets?: IMediaLibraryAsset[];
|
|
71
|
+
updatedAssets?: IMediaLibraryAsset[];
|
|
72
|
+
};
|
|
73
|
+
export type IMediaLibraryAlbumType = 'album' | 'moment' | 'smartAlbum';
|
|
74
|
+
export type IMediaLibraryAlbum = {
|
|
75
|
+
id: string;
|
|
76
|
+
title: string;
|
|
77
|
+
assetCount: number;
|
|
78
|
+
/** @platform ios */
|
|
79
|
+
type?: IMediaLibraryAlbumType;
|
|
80
|
+
/** Only for `type: 'moment'`. @platform ios */
|
|
81
|
+
startTime: number;
|
|
82
|
+
/** Only for `type: 'moment'`. @platform ios */
|
|
83
|
+
endTime: number;
|
|
84
|
+
/** Only for `type: 'moment'`. @platform ios */
|
|
85
|
+
approximateLocation?: IMediaLibraryLocation;
|
|
86
|
+
/** Only for `type: 'moment'`. @platform ios */
|
|
87
|
+
locationNames?: string[];
|
|
88
|
+
};
|
|
89
|
+
export type IMediaLibraryAlbumsOptions = {
|
|
90
|
+
includeSmartAlbums?: boolean;
|
|
91
|
+
};
|
|
92
|
+
export type IMediaLibraryAssetRef = IMediaLibraryAsset | string;
|
|
93
|
+
export type IMediaLibraryAlbumRef = IMediaLibraryAlbum | string;
|
|
94
|
+
export type IMediaLibraryAssetsOptions = {
|
|
95
|
+
/** @default 20 */
|
|
96
|
+
first?: number;
|
|
97
|
+
/** Pass the previous page's `endCursor` to fetch the next page. */
|
|
98
|
+
after?: IMediaLibraryAssetRef;
|
|
99
|
+
album?: IMediaLibraryAlbumRef;
|
|
100
|
+
/** All keys sort descending by default; pass `[key, true]` for ascending. */
|
|
101
|
+
sortBy?: IMediaLibrarySortByValue[] | IMediaLibrarySortByValue;
|
|
102
|
+
/** @default MediaType.photo */
|
|
103
|
+
mediaType?: IMediaLibraryMediaTypeValue[] | IMediaLibraryMediaTypeValue;
|
|
104
|
+
/** @platform ios */
|
|
105
|
+
mediaSubtypes?: IMediaLibraryMediaSubtype[] | IMediaLibraryMediaSubtype;
|
|
106
|
+
createdAfter?: Date | number;
|
|
107
|
+
createdBefore?: Date | number;
|
|
108
|
+
/** @default false @platform android */
|
|
109
|
+
resolveWithFullInfo?: boolean;
|
|
110
|
+
};
|
|
111
|
+
export type IMediaLibraryPagedInfo<T> = {
|
|
112
|
+
assets: T[];
|
|
113
|
+
/** iOS: the ID of the last fetched asset. Android: its index. */
|
|
114
|
+
endCursor: string;
|
|
115
|
+
hasNextPage: boolean;
|
|
116
|
+
totalCount: number;
|
|
117
|
+
};
|
|
118
|
+
export type IMediaLibraryPermissionResponse = PermissionResponse & {
|
|
119
|
+
/**
|
|
120
|
+
* - `'all'` — full library access
|
|
121
|
+
* - `'limited'` — only selected photos (Android 14+, iOS 14+)
|
|
122
|
+
* - `'none'` — denied or not yet granted
|
|
123
|
+
*/
|
|
124
|
+
accessPrivileges?: 'all' | 'limited' | 'none';
|
|
125
|
+
};
|
|
126
|
+
export type { PermissionResponse };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { expoMediaLibraryNext } from './native-module';
|
|
2
|
+
import { Asset } from './asset';
|
|
3
|
+
/**
|
|
4
|
+
* A media album (collection of assets) on the device, matching upstream's own trivial subclass
|
|
5
|
+
* for every method but `getAssets()` — same reason as Query.exe(), see query.ts.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Album extends expoMediaLibraryNext.Album {
|
|
8
|
+
getAssets(): Promise<Asset[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { expoMediaLibraryNext } from './native-module.js';
|
|
2
|
+
import { Asset } from './asset.js';
|
|
3
|
+
/**
|
|
4
|
+
* A media album (collection of assets) on the device, matching upstream's own trivial subclass
|
|
5
|
+
* for every method but `getAssets()` — same reason as Query.exe(), see query.ts.
|
|
6
|
+
*/
|
|
7
|
+
export class Album extends expoMediaLibraryNext.Album {
|
|
8
|
+
async getAssets() {
|
|
9
|
+
const results = await super.getAssets();
|
|
10
|
+
return results.map(result => new Asset(result.id));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expoMediaLibraryNext } from './native-module';
|
|
2
|
+
import type { MediaSubtype } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A single media asset on the device (image, video, or audio). Four getters are iOS-only on
|
|
5
|
+
* upstream's own native side; guarded here to fail with the same `UnavailabilityError` upstream
|
|
6
|
+
* throws rather than whatever the Android native method happens to do when called anyway.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Asset extends expoMediaLibraryNext.Asset {
|
|
9
|
+
getMediaSubtypes(): Promise<MediaSubtype[]>;
|
|
10
|
+
getLivePhotoVideoUri(): Promise<string | null>;
|
|
11
|
+
getIsInCloud(): Promise<boolean>;
|
|
12
|
+
getOrientation(): Promise<number | null>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
3
|
+
import { expoMediaLibraryNext } from './native-module.js';
|
|
4
|
+
/**
|
|
5
|
+
* A single media asset on the device (image, video, or audio). Four getters are iOS-only on
|
|
6
|
+
* upstream's own native side; guarded here to fail with the same `UnavailabilityError` upstream
|
|
7
|
+
* throws rather than whatever the Android native method happens to do when called anyway.
|
|
8
|
+
*/
|
|
9
|
+
export class Asset extends expoMediaLibraryNext.Asset {
|
|
10
|
+
getMediaSubtypes() {
|
|
11
|
+
if (Platform.OS !== 'ios') {
|
|
12
|
+
throw new UnavailabilityError('MediaLibrary', 'getMediaSubtypes is only available on iOS');
|
|
13
|
+
}
|
|
14
|
+
return super.getMediaSubtypes();
|
|
15
|
+
}
|
|
16
|
+
getLivePhotoVideoUri() {
|
|
17
|
+
if (Platform.OS !== 'ios') {
|
|
18
|
+
throw new UnavailabilityError('MediaLibrary', 'getLivePhotoVideoUri is only available on iOS');
|
|
19
|
+
}
|
|
20
|
+
return super.getLivePhotoVideoUri();
|
|
21
|
+
}
|
|
22
|
+
getIsInCloud() {
|
|
23
|
+
if (Platform.OS !== 'ios') {
|
|
24
|
+
throw new UnavailabilityError('MediaLibrary', 'getIsInCloud is only available on iOS');
|
|
25
|
+
}
|
|
26
|
+
return super.getIsInCloud();
|
|
27
|
+
}
|
|
28
|
+
getOrientation() {
|
|
29
|
+
if (Platform.OS !== 'ios') {
|
|
30
|
+
throw new UnavailabilityError('MediaLibrary', 'getOrientation is only available on iOS');
|
|
31
|
+
}
|
|
32
|
+
return super.getOrientation();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import type { IGranularPermission, IMediaLibraryAssetsChangeEvent, IMediaLibraryNextPermissionResponse, IMediaTypeFilter, IPermissionHookOptions } from './types';
|
|
3
|
+
export { Query } from './query';
|
|
4
|
+
export { Asset } from './asset';
|
|
5
|
+
export { Album } from './album';
|
|
6
|
+
export * from './types';
|
|
7
|
+
/**
|
|
8
|
+
* @param granularPermissions Only has an effect on Android 13+. Defaults to every possible
|
|
9
|
+
* permission — make sure your app manifest requests everything you pass here.
|
|
10
|
+
*/
|
|
11
|
+
export declare function requestPermissionsAsync(writeOnly?: boolean, granularPermissions?: IGranularPermission[]): Promise<IMediaLibraryNextPermissionResponse>;
|
|
12
|
+
export declare function getPermissionsAsync(writeOnly?: boolean, granularPermissions?: IGranularPermission[]): Promise<IMediaLibraryNextPermissionResponse>;
|
|
13
|
+
export declare const usePermissions: (options?: import("expo-modules-core").PermissionHookOptions<IPermissionHookOptions> | undefined) => [IMediaLibraryNextPermissionResponse | null, () => Promise<IMediaLibraryNextPermissionResponse>, () => Promise<IMediaLibraryNextPermissionResponse>];
|
|
14
|
+
/**
|
|
15
|
+
* Allows the user to update the assets your app has access to. Only a no-op unless the user
|
|
16
|
+
* previously granted `'limited'` access.
|
|
17
|
+
*/
|
|
18
|
+
export declare function presentPermissionsPicker(mediaTypes?: IMediaTypeFilter[]): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribes for updates in the user's media library. On Android it's invoked with an empty
|
|
21
|
+
* object — only iOS reports `insertedAssets`/`deletedAssets`/`updatedAssets`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function addListener(listener: (event: IMediaLibraryAssetsChangeEvent) => void): EventSubscription;
|
|
24
|
+
export declare function removeAllListeners(): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { createPermissionHook, } from 'expo-modules-core';
|
|
3
|
+
import { expoMediaLibraryNext } from './native-module.js';
|
|
4
|
+
export { Query } from './query.js';
|
|
5
|
+
export { Asset } from './asset.js';
|
|
6
|
+
export { Album } from './album.js';
|
|
7
|
+
export * from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* @param granularPermissions Only has an effect on Android 13+. Defaults to every possible
|
|
10
|
+
* permission — make sure your app manifest requests everything you pass here.
|
|
11
|
+
*/
|
|
12
|
+
export async function requestPermissionsAsync(writeOnly = false, granularPermissions) {
|
|
13
|
+
if (Platform.OS === 'android') {
|
|
14
|
+
return expoMediaLibraryNext.requestPermissionsAsync(writeOnly, granularPermissions);
|
|
15
|
+
}
|
|
16
|
+
return expoMediaLibraryNext.requestPermissionsAsync(writeOnly);
|
|
17
|
+
}
|
|
18
|
+
export async function getPermissionsAsync(writeOnly = false, granularPermissions) {
|
|
19
|
+
if (Platform.OS === 'android') {
|
|
20
|
+
return expoMediaLibraryNext.getPermissionsAsync(writeOnly, granularPermissions);
|
|
21
|
+
}
|
|
22
|
+
return expoMediaLibraryNext.getPermissionsAsync(writeOnly);
|
|
23
|
+
}
|
|
24
|
+
export const usePermissions = createPermissionHook({
|
|
25
|
+
getMethod: (options) => getPermissionsAsync(options?.writeOnly, options?.granularPermissions),
|
|
26
|
+
requestMethod: (options) => requestPermissionsAsync(options?.writeOnly, options?.granularPermissions),
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Allows the user to update the assets your app has access to. Only a no-op unless the user
|
|
30
|
+
* previously granted `'limited'` access.
|
|
31
|
+
*/
|
|
32
|
+
export async function presentPermissionsPicker(mediaTypes) {
|
|
33
|
+
return expoMediaLibraryNext.presentPermissionsPicker(mediaTypes);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Subscribes for updates in the user's media library. On Android it's invoked with an empty
|
|
37
|
+
* object — only iOS reports `insertedAssets`/`deletedAssets`/`updatedAssets`.
|
|
38
|
+
*/
|
|
39
|
+
export function addListener(listener) {
|
|
40
|
+
return expoMediaLibraryNext.addListener('mediaLibraryDidChange', listener);
|
|
41
|
+
}
|
|
42
|
+
export function removeAllListeners() {
|
|
43
|
+
expoMediaLibraryNext.removeAllListeners('mediaLibraryDidChange');
|
|
44
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import type { AssetField, IAssetFieldValueMap, IAssetInfo, IAssetMetadata, ISortDescriptor } from './types';
|
|
3
|
+
export declare class NativeMediaLibraryQuery {
|
|
4
|
+
constructor();
|
|
5
|
+
eq<T extends AssetField>(field: T, value: IAssetFieldValueMap[T]): this;
|
|
6
|
+
within<T extends AssetField>(field: T, value: IAssetFieldValueMap[T][]): this;
|
|
7
|
+
gt(field: AssetField, value: number): this;
|
|
8
|
+
gte(field: AssetField, value: number): this;
|
|
9
|
+
lt(field: AssetField, value: number): this;
|
|
10
|
+
lte(field: AssetField, value: number): this;
|
|
11
|
+
limit(limit: number): this;
|
|
12
|
+
offset(offset: number): this;
|
|
13
|
+
orderBy(sortDescriptors: ISortDescriptor | AssetField): this;
|
|
14
|
+
album(album: NativeMediaLibraryAlbum): this;
|
|
15
|
+
exe(): Promise<NativeMediaLibraryAsset[]>;
|
|
16
|
+
exeForMetadata(): Promise<IAssetMetadata[]>;
|
|
17
|
+
}
|
|
18
|
+
export declare class NativeMediaLibraryAsset {
|
|
19
|
+
constructor(id: string);
|
|
20
|
+
id: string;
|
|
21
|
+
getCreationTime(): Promise<number | null>;
|
|
22
|
+
getDuration(): Promise<number | null>;
|
|
23
|
+
getFilename(): Promise<string>;
|
|
24
|
+
getHeight(): Promise<number>;
|
|
25
|
+
getMediaType(): Promise<string>;
|
|
26
|
+
getMediaSubtypes(): Promise<string[]>;
|
|
27
|
+
getLivePhotoVideoUri(): Promise<string | null>;
|
|
28
|
+
getIsInCloud(): Promise<boolean>;
|
|
29
|
+
getOrientation(): Promise<number | null>;
|
|
30
|
+
getModificationTime(): Promise<number | null>;
|
|
31
|
+
getShape(): Promise<{
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
} | null>;
|
|
35
|
+
getUri(): Promise<string>;
|
|
36
|
+
getWidth(): Promise<number>;
|
|
37
|
+
getInfo(): Promise<IAssetInfo>;
|
|
38
|
+
getAlbums(): Promise<NativeMediaLibraryAlbum[]>;
|
|
39
|
+
getLocation(): Promise<{
|
|
40
|
+
latitude: number;
|
|
41
|
+
longitude: number;
|
|
42
|
+
} | null>;
|
|
43
|
+
getExif(): Promise<Record<string, unknown>>;
|
|
44
|
+
delete(): Promise<void>;
|
|
45
|
+
getFavorite(): Promise<boolean>;
|
|
46
|
+
setFavorite(isFavorite: boolean): Promise<void>;
|
|
47
|
+
static create(filePath: string, album?: NativeMediaLibraryAlbum): Promise<NativeMediaLibraryAsset>;
|
|
48
|
+
static delete(assets: NativeMediaLibraryAsset[]): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
export declare class NativeMediaLibraryAlbum {
|
|
51
|
+
constructor(id: string);
|
|
52
|
+
id: string;
|
|
53
|
+
getAssets(): Promise<NativeMediaLibraryAsset[]>;
|
|
54
|
+
getTitle(): Promise<string>;
|
|
55
|
+
delete(): Promise<void>;
|
|
56
|
+
add(assets: NativeMediaLibraryAsset | NativeMediaLibraryAsset[]): Promise<void>;
|
|
57
|
+
removeAssets(assets: NativeMediaLibraryAsset[]): Promise<void>;
|
|
58
|
+
static create(name: string, assetsRefs: string[] | NativeMediaLibraryAsset[], moveAssets?: boolean): Promise<NativeMediaLibraryAlbum>;
|
|
59
|
+
static delete(albums: NativeMediaLibraryAlbum[], deleteAssets?: boolean): Promise<void>;
|
|
60
|
+
static get(title: string): Promise<NativeMediaLibraryAlbum | null>;
|
|
61
|
+
static getAll(): Promise<NativeMediaLibraryAlbum[]>;
|
|
62
|
+
}
|
|
63
|
+
export type INativeMediaLibraryNextModule = {
|
|
64
|
+
Query: typeof NativeMediaLibraryQuery;
|
|
65
|
+
Asset: typeof NativeMediaLibraryAsset;
|
|
66
|
+
Album: typeof NativeMediaLibraryAlbum;
|
|
67
|
+
getPermissionsAsync(writeOnly?: boolean, granularPermissions?: string[]): Promise<import('expo-modules-core').PermissionResponse>;
|
|
68
|
+
requestPermissionsAsync(writeOnly?: boolean, granularPermissions?: string[]): Promise<import('expo-modules-core').PermissionResponse>;
|
|
69
|
+
presentPermissionsPicker(mediaTypes?: string[]): Promise<void>;
|
|
70
|
+
addListener(eventName: 'mediaLibraryDidChange', listener: (event: {
|
|
71
|
+
hasIncrementalChanges: boolean;
|
|
72
|
+
insertedAssets?: string[];
|
|
73
|
+
deletedAssets?: string[];
|
|
74
|
+
updatedAssets?: string[];
|
|
75
|
+
}) => void): EventSubscription;
|
|
76
|
+
removeAllListeners(eventName: 'mediaLibraryDidChange'): void;
|
|
77
|
+
};
|
|
78
|
+
export declare const expoMediaLibraryNext: INativeMediaLibraryNextModule;
|