Skip to content

Commit

Permalink
[media-library] add options for getAssetInfoAsync (#9405)
Browse files Browse the repository at this point in the history
# Why

This is an enhanced PR from #8800

# How

I added `options` as the optional second parameter to `MediaLibrary.getAssetInfoAsync`.

# Test Plan

Currently I modified `MediaDetailsScreen` in bare-expo app with payload of `shouldDownloadFromNetwork` to `false` so that the response payload also contains the field `isNetworkAsset`.

# Open question

Since this changes the public API of `MediaLibrary`, I wonder how should I update the bare-expo app to reflect this option?
  • Loading branch information
Jarvis Luong committed Jul 29, 2020
1 parent 9cc1201 commit b326bbe
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 13 deletions.
10 changes: 8 additions & 2 deletions docs/pages/versions/unversioned/sdk/media-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ In managed apps, `MediaLibrary` requires `Permissions.CAMERA_ROLL`.
import * as MediaLibrary from 'expo-media-library';
```

<TableOfContentSection title='Methods' contents={['MediaLibrary.requestPermissionsAsync()', 'MediaLibrary.getPermissionsAsync()', 'MediaLibrary.createAssetAsync(localUri)', 'MediaLibrary.saveToLibraryAsync(localUri)', 'MediaLibrary.getAssetsAsync(options)', 'MediaLibrary.getAssetInfoAsync(asset)', 'MediaLibrary.deleteAssetsAsync(assets)', 'MediaLibrary.getAlbumsAsync()', 'MediaLibrary.getAlbumAsync(albumName)', 'MediaLibrary.createAlbumAsync(albumName, asset, copyAsset)', 'MediaLibrary.deleteAlbumsAsync(albums, deleteAssets)', 'MediaLibrary.addAssetsToAlbumAsync(assets, album, copyAssets)', 'MediaLibrary.removeAssetsFromAlbumAsync(assets, album)', 'MediaLibrary.getMomentsAsync()', 'MediaLibrary.addListener(listener)', 'MediaLibrary.removeAllListeners()']} />
<TableOfContentSection title='Methods' contents={['MediaLibrary.requestPermissionsAsync()', 'MediaLibrary.getPermissionsAsync()', 'MediaLibrary.createAssetAsync(localUri)', 'MediaLibrary.saveToLibraryAsync(localUri)', 'MediaLibrary.getAssetsAsync(options)', 'MediaLibrary.getAssetInfoAsync(asset, options)', 'MediaLibrary.deleteAssetsAsync(assets)', 'MediaLibrary.getAlbumsAsync()', 'MediaLibrary.getAlbumAsync(albumName)', 'MediaLibrary.createAlbumAsync(albumName, asset, copyAsset)', 'MediaLibrary.deleteAlbumsAsync(albums, deleteAssets)', 'MediaLibrary.addAssetsToAlbumAsync(assets, album, copyAssets)', 'MediaLibrary.removeAssetsFromAlbumAsync(assets, album)', 'MediaLibrary.getMomentsAsync()', 'MediaLibrary.addListener(listener)', 'MediaLibrary.removeAllListeners()']} />

<TableOfContentSection title='Types' contents={['Asset', 'Album']} />

Expand Down Expand Up @@ -103,13 +103,15 @@ A promise that resolves to an object that contains following keys:
- **hasNextPage (_boolean_)** -- Whether there are more assets to fetch.
- **totalCount (_number_)** -- Estimated total number of assets that match the query.

### `MediaLibrary.getAssetInfoAsync(asset)`
### `MediaLibrary.getAssetInfoAsync(asset, options)`

Provides more informations about an asset, including GPS location, local URI and EXIF metadata.

#### Arguments

- **asset (_string_ | _Asset_)** -- [Asset](#asset) or its ID.
- **options (_object_)**
- **shouldDownloadFromNetwork (_boolean_)** -- Whether allow the asset to be downloaded from network. Only available in iOS with iCloud assets. Defaults to `true`.

#### Returns

Expand Down Expand Up @@ -230,6 +232,7 @@ Subscribes for updates in user's media library.
- **listener (_function_)** -- A callback that is called when any assets have been inserted or deleted from the library. **On Android** it's invoked with an empty object. **On iOS** it's invoked with an object that contains following keys:
- **insertedAssets (_array_)** -- Array of [assets](#assets) that have been inserted to the library.
- **deletedAssets (_array_)** -- Array of [assets](#assets) that have been deleted from the library.
- **updatedAssets (_array_)** -- Array of [assets](#assets) that have been updated or completed downloading from network storage (iCloud in iOS).

#### Returns

Expand Down Expand Up @@ -270,9 +273,12 @@ Removes all listeners.
| exif \* | _object_ | both | EXIF metadata associated with the image | |
| orientation \* | _number_ | iOS | Display orientation of the image. Orientation is available only for assets whose mediaType is MediaType.photo | Numbers 1-8, see [EXIF orientation specification](http://sylvana.net/jpegcrop/exif_orientation.html) |
| isFavorite \* | _boolean_ | iOS | Whether the asset is marked as favorite | `true`, `false` |
| isNetworkAsset \*\*| _boolean_ | iOS | Whether the asset is stored on the network (iCloud on iOS)| `true`, `false`

> \* These fields can be obtained only by calling `getAssetInfoAsync` method
> \*\* This field is available only if flag `shouldDownloadFromNetwork` is set to `false`
### `Album`

| Field name | Type | Platforms | Description | Possible values |
Expand Down
1 change: 1 addition & 0 deletions packages/expo-media-library/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

### 🎉 New features

- Added `options` to `getAssetInfoAsync()`, which allows specifying whether to download the asset from network in iOS. ([#9405](https://github.com/expo/expo/pull/9405) by [@jarvisluong](https://github.com/jarvisluong))
- Added support for the limited `CAMERA_ROLL` permission on iOS 14. ([#9423](https://github.com/expo/expo/pull/9423) by [@lukmccall](https://github.com/lukmccall))

### 🐛 Bug fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public void deleteAssetsAsync(List<String> assetsId, Promise promise) {
}

@ExpoMethod
public void getAssetInfoAsync(String assetId, Promise promise) {
public void getAssetInfoAsync(String assetId, Map<String, Object> options /* unused on android atm */, Promise promise) {
if (isMissingPermissions()) {
promise.reject(ERROR_NO_PERMISSIONS, ERROR_NO_PERMISSIONS_MESSAGE);
return;
Expand Down
13 changes: 11 additions & 2 deletions packages/expo-media-library/build/MediaLibrary.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/expo-media-library/build/MediaLibrary.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-media-library/build/MediaLibrary.js.map

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions packages/expo-media-library/ios/EXMediaLibrary/EXMediaLibrary.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

NSString *const EXMediaLibraryCachesDirectory = @"MediaLibrary";

NSString *const EXMediaLibraryShouldDownloadFromNetworkKey = @"shouldDownloadFromNetwork";

@interface EXMediaLibrary ()

@property (nonatomic, strong) PHFetchResult *allAssetsFetchResult;
Expand Down Expand Up @@ -393,6 +395,7 @@ - (NSDictionary *)constantsToExport

UM_EXPORT_METHOD_AS(getAssetInfoAsync,
getAssetInfo:(nonnull NSString *)assetId
withOptions:(nonnull NSDictionary *)options
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject)
{
Expand All @@ -401,25 +404,28 @@ - (NSDictionary *)constantsToExport
}

PHAsset *asset = [EXMediaLibrary _getAssetById:assetId];

BOOL shouldDownloadFromNetwork = [[options objectForKey:EXMediaLibraryShouldDownloadFromNetworkKey] boolValue] ?: YES;

if (asset) {
NSMutableDictionary *result = [EXMediaLibrary _exportAssetInfo:asset];
if (asset.mediaType == PHAssetMediaTypeImage) {
PHContentEditingInputRequestOptions *options = [PHContentEditingInputRequestOptions new];
options.networkAccessAllowed = YES;
options.networkAccessAllowed = shouldDownloadFromNetwork;

[asset requestContentEditingInputWithOptions:options
completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
result[@"localUri"] = [contentEditingInput.fullSizeImageURL absoluteString];
result[@"orientation"] = @(contentEditingInput.fullSizeImageOrientation);
result[@"isNetworkAsset"] = [info objectForKey:PHContentEditingInputResultIsInCloudKey];

CIImage *ciImage = [CIImage imageWithContentsOfURL:contentEditingInput.fullSizeImageURL];
result[@"exif"] = ciImage.properties;
resolve(result);
}];
} else {
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.networkAccessAllowed = YES;
options.networkAccessAllowed = shouldDownloadFromNetwork;

[[PHImageManager defaultManager] requestAVAssetForVideo:asset
options:options
Expand All @@ -440,6 +446,7 @@ - (NSDictionary *)constantsToExport
[exporter exportAsynchronouslyWithCompletionHandler:^{
if (exporter.status == AVAssetExportSessionStatusCompleted) {
result[@"localUri"] = videoFileOutputURL.absoluteString;
result[@"isNetworkAsset"] = [info objectForKey:PHImageResultIsInCloudKey];
resolve(result);
} else if (exporter.status == AVAssetExportSessionStatusFailed) {
reject(@"E_EXPORT_FAILED", @"Could not export the requested video.", nil);
Expand All @@ -451,6 +458,7 @@ - (NSDictionary *)constantsToExport
} else {
AVURLAsset *urlAsset = (AVURLAsset *)asset;
result[@"localUri"] = [[urlAsset URL] absoluteString];
result[@"isNetworkAsset"] = [info objectForKey:PHImageResultIsInCloudKey];
resolve(result);
}
}];
Expand Down Expand Up @@ -605,9 +613,11 @@ - (void)photoLibraryDidChange:(PHChange *)changeInstance
if (changeDetails.hasIncrementalChanges && (changeDetails.insertedObjects.count > 0 || changeDetails.removedObjects.count > 0)) {
NSMutableArray *insertedAssets = [NSMutableArray new];
NSMutableArray *deletedAssets = [NSMutableArray new];
NSMutableArray *updatedAssets = [NSMutableArray new];
NSDictionary *body = @{
@"insertedAssets": insertedAssets,
@"deletedAssets": deletedAssets,
@"updatedAssets": updatedAssets
};

for (PHAsset *asset in changeDetails.insertedObjects) {
Expand All @@ -616,6 +626,9 @@ - (void)photoLibraryDidChange:(PHChange *)changeInstance
for (PHAsset *asset in changeDetails.removedObjects) {
[deletedAssets addObject:[EXMediaLibrary _exportAsset:asset]];
}
for (PHAsset *asset in changeDetails.changedObjects) {
[updatedAssets addObject:[EXMediaLibrary _exportAsset:asset]];
}

[_eventEmitter sendEventWithName:EXMediaLibraryDidChangeEvent body:body];
}
Expand Down
20 changes: 17 additions & 3 deletions packages/expo-media-library/src/MediaLibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ export type AssetInfo = Asset & {
location?: Location;
exif?: object;
isFavorite?: boolean; //iOS only
isNetworkAsset?: boolean; //iOS only
};

export type MediaLibraryAssetInfoQueryOptions = {
shouldDownloadFromNetwork?: boolean;
};

export type MediaLibraryAssetChangeEvent = {
insertedAssets: Asset[];
deletedAssets: Asset[];
updatedAssets: Asset[];
};

export type Location = {
Expand Down Expand Up @@ -252,7 +263,10 @@ export async function deleteAssetsAsync(assets: AssetRef[] | AssetRef) {
return await MediaLibrary.deleteAssetsAsync(assetIds);
}

export async function getAssetInfoAsync(asset: AssetRef): Promise<AssetInfo> {
export async function getAssetInfoAsync(
asset: AssetRef,
options: MediaLibraryAssetInfoQueryOptions = { shouldDownloadFromNetwork: true }
): Promise<AssetInfo> {
if (!MediaLibrary.getAssetInfoAsync) {
throw new UnavailabilityError('MediaLibrary', 'getAssetInfoAsync');
}
Expand All @@ -261,7 +275,7 @@ export async function getAssetInfoAsync(asset: AssetRef): Promise<AssetInfo> {

checkAssetIds([assetId]);

const assetInfo = await MediaLibrary.getAssetInfoAsync(assetId);
const assetInfo = await MediaLibrary.getAssetInfoAsync(assetId, options);

if (Array.isArray(assetInfo)) {
// Android returns an array with asset info, we need to pick the first item
Expand Down Expand Up @@ -367,7 +381,7 @@ export async function getAssetsAsync(assetsOptions: AssetsOptions = {}): Promise
return await MediaLibrary.getAssetsAsync(options);
}

export function addListener(listener: () => void): Subscription {
export function addListener(listener: (event: MediaLibraryAssetChangeEvent) => void): Subscription {
const subscription = eventEmitter.addListener(MediaLibrary.CHANGE_LISTENER_NAME, listener);
return subscription;
}
Expand Down

0 comments on commit b326bbe

Please sign in to comment.