Skip to content

Commit

Permalink
✨ Defines PMDarwinAVFileType (#1191)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexV525 authored Sep 26, 2024
2 parents 9c8b7f9 + 6082d72 commit b875673
Show file tree
Hide file tree
Showing 18 changed files with 1,047 additions and 399 deletions.
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@ To know more about breaking changes, see the [Migration Guide][].

## Unreleased

*None.*
### Features

- Provide `PMDarwinAVFileType` to help convert entities' files on iOS and macOS by making explicit exports.

### Improvements

- Improve cache output path equality on iOS and macOS.
- Get the current resource filename rather than the raw one on iOS and macOS.
Also the plugin expands the ability when getting titles.
- Use `PHCachingImageManager` to improve images memory caches on iOS and macOS.

### Fixes

- Fix incorrect download finished predication during iCloud file downloading.

## 3.4.0

Expand Down
76 changes: 69 additions & 7 deletions README-ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ that can be found in the LICENSE file. -->
* [通过原始数据获取](#通过原始数据获取)
* [通过 iCloud 获取](#通过-icloud-获取)
* [展示资源](#展示资源)
* [获取文件](#获取文件)
* [获取「实况照片」](#获取实况照片)
* [仅过滤「实况照片」](#仅过滤实况照片)
* [获取「实况照片」的视频](#获取实况照片的视频)
Expand Down Expand Up @@ -236,6 +237,9 @@ PhotoManager.setIgnorePermissionCheck(true);

对于一些后台操作(应用未启动等)而言,忽略检查是比较合适的做法。

同时你还可以调用 `PhotoManager.getPermissionState` 来获取权限状态。
请确保调用该方法使用的权限设置与调用其他方法的权限设置相同。

#### 受限的资源权限

##### iOS 受限的资源权限
Expand Down Expand Up @@ -397,6 +401,11 @@ final AssetEntity? entity = await PhotoManager.editor.darwin.saveLivePhoto(

请留意资源可能访问受限,或随时被删除,所以结果可能为空。

iOS 和 macOS 可能会对资源保存附加额外的限制,目前仅部分资源类型可以保存为图库资源。
该限制由 [`AVFileType`][] 定义,当你在保存时看到
`PHPhotosErrorDomain Code=3302` (或者 `330x`) 错误时,
确保你的文件类型受支持。

#### 通过 iCloud 获取

iOS 为了节省磁盘空间,可能将资源仅保存在 iCloud 上。
Expand All @@ -406,6 +415,17 @@ iOS 为了节省磁盘空间,可能将资源仅保存在 iCloud 上。
推荐参考的实践是 `wechat_asset_picker` 中的
[`LocallyAvailableBuilder`][],它会在下载文件时提供进度的展示。

有数个方法可以结合 `PMProgressHandler` 来提供进度反馈:
* AssetEntity.thumbnailDataWithSize
* AssetEntity.thumbnailDataWithOption
* AssetEntity.getMediaUrl
* AssetEntity.loadFile
* PhotoManager.plugin.getOriginBytes

iCloud 文件只能在设备上的 Apple ID 正常登录时获取。
当账号要求重新输入密码验证时,未缓存在本地的 iCloud 文件将无法访问,
此时相关方法会抛出 `CloudPhotoLibraryErrorDomain` 错误。

#### 展示资源

从 v3.0.0 开始,插件不再提供任何 UI 组件。
Expand Down Expand Up @@ -436,6 +456,19 @@ final Widget imageFromProvider = Image(
);
```

#### 获取文件

`AssetEntity` 包含数个获取文件的 getter 和方法:
* `.file`
* `.fileWithSubtype`
* `.originFile`
* `.originFileWithSubtype`
* `.loadFile`

这些方法会获取与该资源关联的不同类型的文件。阅读它们的文档以了解具体的用途。

另外,你可以使用 `PhotoManager.plugin.getFullFile` 来获取文件,该方法有完整的获取参数。

#### 获取「实况照片」

该插件支持获取和过滤 iOS 上的实况照片。
Expand All @@ -450,15 +483,41 @@ final List<AssetPathEntity> paths = await PhotoManager.getAssetPathList(
);
```

或者你也可以使用 `CustomSqlFilter` 来获取「实况照片」:

```dart
final List<AssetPathEntity> paths = await PhotoManager.getAssetPathList(
type: RequestType.image,
filterOption: CustomFilter.sql(
where: '${CustomColumns.base.mediaType} = 1'
' AND '
'${CustomColumns.darwin.mediaSubtypes} & (1 << 3) = (1 << 3)',
),
);
```

##### 获取「实况照片」的视频

```dart
final AssetEntity entity = livePhotoEntity;
// 播放实况照片的视频
final String? mediaUrl = await entity.getMediaUrl();
// 获取缩略图和视频
final File? imageFile = await entity.file;
final File? videoFile = await entity.fileWithSubtype;
// 获取原图和原视频
final File? originImageFile = await entity.originFile;
final File? originVideoFile = await entity.originFileWithSubtype;
// 将实况照片的视频从 mov 转换为 mp4
final File? convertedFile = await entity.loadFile(
isOriginal: true,
withSubtye: true,
darwinFileType: PMDarwinAVFileType.mp4,
);
```

#### 限制
Expand Down Expand Up @@ -934,14 +993,16 @@ PhotoManager.editor.darwin.deletePath();

#### 适用于 OpenHarmony 的功能

目前支持大部分的功能,除了跟缓存相关。目前鸿蒙只支持图片和视频 2 种资源类型。
> 鸿蒙官方处于安全考虑已禁止相关资源能力。
目前以支持除缓存相关的其他功能,且只支持图片和视频类型资源。

| Feature | OpenHarmony |
| :----------------------------- | :---------: |
| releaseCache ||
| clearFileCache ||
| requestCacheAssetsThumbnail ||
| getSubPathEntities ||
| Feature | OpenHarmony |
|:----------------------------|:-----------:|
| releaseCache ||
| clearFileCache ||
| requestCacheAssetsThumbnail ||
| getSubPathEntities ||


[pub package]: https://pub.flutter-io.cn/packages/photo_manager
Expand All @@ -963,6 +1024,7 @@ PhotoManager.editor.darwin.deletePath();
[`PhotoManager.getAssetListRange`]: https://pub.flutter-io.cn/documentation/photo_manager/latest/photo_manager/PhotoManager/getAssetListRange.html
[`AssetEntity.fromId`]: https://pub.flutter-io.cn/documentation/photo_manager/latest/photo_manager/AssetEntity/fromId.html

[`AVFileType`]: https://developer.apple.com/documentation/avfoundation/avfiletype
[`LocallyAvailableBuilder`]: https://github.com/fluttercandies/flutter_wechat_assets_picker/blob/2055adfa74370339d10e6f09adef72f2130d2380/lib/src/widget/builder/locally_available_builder.dart

[flutter/flutter#20522]: https://github.com/flutter/flutter/issues/20522
Expand Down
86 changes: 77 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ see the [migration guide](MIGRATION_GUIDE.md) for detailed info.
* [From raw data](#from-raw-data)
* [From iCloud](#from-icloud)
* [Display assets](#display-assets)
* [Get asset files](#get-asset-files)
* [Obtain "Live Photos"](#obtain-live-photos)
* [Filtering only "Live Photos"](#filtering-only-live-photos)
* [Obtain the video from "Live Photos"](#obtain-the-video-from-live-photos)
Expand Down Expand Up @@ -248,6 +249,10 @@ PhotoManager.setIgnorePermissionCheck(true);
For background processing (such as when the app is not in the foreground),
ignore permissions check would be proper solution.

You can also read the current permission state with
`PhotoManager.getPermissionState`. Make sure the same permission request option
is used between this request and other asset requests.

#### Limited entities access

##### Limited entities access on iOS
Expand Down Expand Up @@ -424,10 +429,14 @@ final AssetEntity? entity = await PhotoManager.editor.darwin.saveLivePhoto(
);
```

Be aware that the created asset might have
limited access or got deleted in anytime,
Be aware that the created asset might have limited access or got deleted in anytime,
so the result might be null.

iOS and macOS might set extra limitations when saving assets, it seems only
certain file types can be saved as a media assets. The limitation follows the definition of
[`AVFileType`][], when you saw `PHPhotosErrorDomain Code=3302` (or `330x`),
make sure the file type is supported.

#### From iCloud

Resources might be saved only on iCloud to save disk space.
Expand All @@ -440,6 +449,19 @@ The preferred implementation would be the [`LocallyAvailableBuilder`][]
in the `wechat_asset_picker` package, which provides a progress indicator
when the file is downloading.

There are several methods that can combine with `PMProgressHandler`
to provide responsive progress events, which are:
* AssetEntity.thumbnailDataWithSize
* AssetEntity.thumbnailDataWithOption
* AssetEntity.getMediaUrl
* AssetEntity.loadFile
* PhotoManager.plugin.getOriginBytes

iCloud files can only be obtained when the Apple ID on the device are correctly authorized.
When the account requires to re-enter the password to verify, iCloud files that are not
locally available are not allowed to be fetched. The photo library will throws
`CloudPhotoLibraryErrorDomain` in this circumstance.

#### Display assets

> Starts from v3.0.0, `AssetEntityImage` and `AssetEntityImageProvider`
Expand Down Expand Up @@ -468,6 +490,21 @@ final Widget imageFromProvider = Image(
);
```

#### Get asset files

There are several file getters and methods with an `AssetEntity`:
* `.file`
* `.fileWithSubtype`
* `.originFile`
* `.originFileWithSubtype`
* `.loadFile`

These getters and methods will fetch different types of file related to that asset.
Read their comment (documentation) to know their abilities.

Additionally, you can use the raw plugin method
`PhotoManager.plugin.getFullFile` with more parameters.

#### Obtain "Live Photos"

This plugin supports obtain live photos and filtering them:
Expand All @@ -483,15 +520,42 @@ final List<AssetPathEntity> paths = await PhotoManager.getAssetPathList(
);
```

Or you can use the `CustomSqlFilter` to obtain live photos:

```dart
final List<AssetPathEntity> paths = await PhotoManager.getAssetPathList(
type: RequestType.image,
filterOption: CustomFilter.sql(
where: '${CustomColumns.base.mediaType} = 1'
' AND '
'${CustomColumns.darwin.mediaSubtypes} & (1 << 3) = (1 << 3)',
),
);
```

##### Obtain the video from "Live Photos"

```dart
final AssetEntity entity = livePhotoEntity;
// To play Live Photo's video.
final String? mediaUrl = await entity.getMediaUrl();
// Get files for normal displays like thumbnails.
final File? imageFile = await entity.file;
final File? videoFile = await entity.fileWithSubtype;
// Get files for the raw displays like detail preview.
final File? originImageFile = await entity.originFile;
final File? originVideoFile = await entity.originFileWithSubtype;
// Additionally, you can convert Live Photo's (on iOS) video file
// from `mov` to `mp4` using:
final File? convertedFile = await entity.loadFile(
isOriginal: true,
withSubtye: true,
darwinFileType: PMDarwinAVFileType.mp4,
);
```

#### Limitations
Expand Down Expand Up @@ -1002,14 +1066,17 @@ PhotoManager.editor.darwin.deletePath();

#### Features for OpenHarmony

Currently, most functions are supported, except for those related to caching. and only support image and video types.
> The photo library feature is disabled in OpenHarmony officially because of the security concern.
Most functions are supported except caching,
and only images/videos are supported.

| Feature | OpenHarmony |
| :----------------------------- | :---------: |
| releaseCache ||
| clearFileCache ||
| requestCacheAssetsThumbnail ||
| getSubPathEntities ||
| Feature | OpenHarmony |
|:----------------------------|:-----------:|
| releaseCache ||
| clearFileCache ||
| requestCacheAssetsThumbnail ||
| getSubPathEntities ||


[pub package]: https://pub.dev/packages/photo_manager
Expand All @@ -1031,6 +1098,7 @@ Currently, most functions are supported, except for those related to caching. an
[`PhotoManager.getAssetListRange`]: https://pub.dev/documentation/photo_manager/latest/photo_manager/PhotoManager/getAssetListRange.html
[`AssetEntity.fromId`]: https://pub.dev/documentation/photo_manager/latest/photo_manager/AssetEntity/fromId.html

[`AVFileType`]: https://developer.apple.com/documentation/avfoundation/avfiletype
[`LocallyAvailableBuilder`]: https://github.com/fluttercandies/flutter_wechat_assets_picker/blob/2055adfa74370339d10e6f09adef72f2130d2380/lib/src/widget/builder/locally_available_builder.dart

[flutter/flutter#20522]: https://github.com/flutter/flutter/issues/20522
Expand Down
9 changes: 9 additions & 0 deletions example/lib/page/image_list_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ class _GalleryContentListPageState extends State<GalleryContentListPage> {
Log.d(watch.elapsed);
},
),
ElevatedButton(
child: const Text('Get file'),
onPressed: () => getFile(entity),
),
ElevatedButton(
child: const Text('Show detail page'),
onPressed: () => routeToDetailPage(entity),
Expand Down Expand Up @@ -243,6 +247,11 @@ class _GalleryContentListPageState extends State<GalleryContentListPage> {
return assets.indexWhere((AssetEntity e) => e.id == id);
}

Future<void> getFile(AssetEntity entity) async {
final file = await entity.file;
print(file);
}

Future<void> routeToDetailPage(AssetEntity entity) async {
Navigator.of(context).push<void>(
MaterialPageRoute<void>(builder: (_) => DetailPage(entity: entity)),
Expand Down
1 change: 0 additions & 1 deletion example/lib/widget/live_photos_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class _LivePhotosWidgetState extends State<LivePhotosWidget> {
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
)
..initialize()
..setVolume(0)
..addListener(() {
if (mounted) {
setState(() {});
Expand Down
20 changes: 17 additions & 3 deletions ios/Classes/PMPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -405,10 +405,12 @@ - (void)handleMethodResultHandler:(ResultHandler *)handler manager:(PMManager *)
NSString *assetId = call.arguments[@"id"];
BOOL isOrigin = [call.arguments[@"isOrigin"] boolValue];
int subtype = [call.arguments[@"subtype"] intValue];
AVFileType fileType = [PMConvertUtils convertNumberToAVFileType:[call.arguments[@"darwinFileType"] intValue]];
PMProgressHandler *progressHandler = [self getProgressHandlerFromDict:call.arguments];
[manager getFullSizeFileWithId:assetId
isOrigin:isOrigin
subtype:subtype
fileType:fileType
resultHandler:handler
progressHandler:progressHandler];
} else if ([call.method isEqualToString:@"fetchPathProperties"]) {
Expand Down Expand Up @@ -513,20 +515,32 @@ - (void)handleMethodResultHandler:(ResultHandler *)handler manager:(PMManager *)
NSString *assetId = call.arguments[@"id"];
BOOL isOrigin = [call.arguments[@"isOrigin"] boolValue];
int subtype = [call.arguments[@"subtype"] intValue];
BOOL exists = [manager entityIsLocallyAvailable:assetId resource:nil isOrigin:isOrigin subtype:subtype];
AVFileType fileType = [PMConvertUtils convertNumberToAVFileType:[call.arguments[@"darwinFileType"] intValue]];
BOOL exists = [manager entityIsLocallyAvailable:assetId
resource:nil
isOrigin:isOrigin
subtype:subtype
fileType:fileType];
[handler reply:@(exists)];
} else if ([call.method isEqualToString:@"getTitleAsync"]) {
NSString *assetId = call.arguments[@"id"];
int subtype = [call.arguments[@"subtype"] intValue];
NSString *title = [manager getTitleAsyncWithAssetId:assetId subtype:subtype];
BOOL isOrigin = [call.arguments[@"isOrigin"] boolValue];
AVFileType fileType = [PMConvertUtils convertNumberToAVFileType:[call.arguments[@"darwinFileType"] intValue]];
NSString *title = [manager getTitleAsyncWithAssetId:assetId
subtype:subtype
isOrigin:isOrigin
fileType:fileType];
[handler reply:title];
} else if ([call.method isEqualToString:@"getMimeTypeAsync"]) {
NSString *assetId = call.arguments[@"id"];
NSString *mimeType = [manager getMimeTypeAsyncWithAssetId:assetId];
[handler reply:mimeType];
} else if ([@"getMediaUrl" isEqualToString:call.method]) {
PMProgressHandler *progressHandler = [self getProgressHandlerFromDict:call.arguments];
[manager getMediaUrl:call.arguments[@"id"] resultHandler:handler progressHandler:progressHandler];
[manager getMediaUrl:call.arguments[@"id"]
resultHandler:handler
progressHandler:progressHandler];
} else if ([@"fetchEntityProperties" isEqualToString:call.method]) {
NSString *assetId = call.arguments[@"id"];
PMAssetEntity *entity = [manager getAssetEntity:assetId withCache:NO];
Expand Down
Loading

0 comments on commit b875673

Please sign in to comment.