@react-native-oh-tpl/camera-roll
Version:
React Native Camera Roll for iOS & Android
412 lines (392 loc) • 16.7 kB
text/typescript
/*
* MIT License
*
* Copyright (C) 2023 Huawei Device Co., Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { TurboModule, TurboModuleContext } from '@rnoh/react-native-openharmony/ts';
import { TM } from '@rnoh/react-native-openharmony/generated/ts';
import {
Album,
PhotoThumbnail,
GetPhotosParams,
PhotoIdentifier,
GetAlbumsParams,
PhotoIdentifiersPage,
PhotoConvertionOptions,
PhotoThumbnailOptions,
SaveToCameraRollOptions,
SaveToCameraRollOptionsTypeMenu
} from './CameraRollParamTypes';
import photoAccessHelper from '@ohos.file.photoAccessHelper';
import dataSharePredicates from '@ohos.data.dataSharePredicates';
import util from '@ohos.util';
import image from '@ohos.multimedia.image';
import { request } from '@kit.BasicServicesKit';
import fs from '@ohos.file.fs';
import { Context } from '@kit.AbilityKit';
import { fileUri } from '@kit.CoreFileKit';
import Logger from './Logger';
const ASSET_TYPE_PHOTOS = 'Photos';
const ASSET_TYPE_VIDEOS = 'Videos';
const ASSET_TYPE_ALL = 'All';
const ResourceType = 'internet';
export class CameraRollTurboModule extends TurboModule implements TM.RNCCameraRoll.Spec {
private phAccessHelper: photoAccessHelper.PhotoAccessHelper;
private context: Context = getContext(this);
constructor(ctx: TurboModuleContext) {
super(ctx);
this.phAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.ctx.uiAbilityContext);
}
async saveToDevice(saveUri: string, options: SaveToCameraRollOptions,
resourceType: string): Promise<PhotoIdentifier> {
let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
{
title: saveUri.substring(saveUri.lastIndexOf('/') + 1, saveUri.lastIndexOf('.')),
fileNameExtension: saveUri.substring(saveUri.lastIndexOf('.') + 1),
photoType: options.type === SaveToCameraRollOptionsTypeMenu.photo ? photoAccessHelper.PhotoType.IMAGE :
photoAccessHelper.PhotoType.VIDEO,
subtype: photoAccessHelper.PhotoSubtype.DEFAULT
}
];
let _saveUri = saveUri;
if (!saveUri.startsWith('file://')) {
_saveUri = fileUri.getUriFromPath(saveUri)
}
let saveUris: string[] =
await this.phAccessHelper.showAssetsCreationDialog([_saveUri], photoCreationConfigs);
if (saveUris.length) {
try{
let file = fs.openSync(_saveUri, fs.OpenMode.READ_ONLY);
let stat = fs.statSync(file.fd);
let buffer = new ArrayBuffer(stat.size);
fs.readSync(file.fd, buffer);
let media_file = fs.openSync(saveUris[0], fs.OpenMode.WRITE_ONLY);
fs.writeSync(media_file.fd, buffer);
fs.closeSync(file);
if (resourceType) {
fs.unlinkSync(saveUri);
}
fs.closeSync(media_file);
let result: PhotoIdentifier = {
node: {
id: '',
type: options.type ?? '',
subTypes: 'PhotoPanorama',
sourceType: 'UserLibrary',
group_name: [],
image: {
filename: photoCreationConfigs[0].title ?? null,
filepath: null,
extension: photoCreationConfigs[0].fileNameExtension ?? null,
uri: saveUris[0],
height: 0,
width: 0,
fileSize: stat.size,
playableDuration: 0,
orientation: null
},
timestamp: 0,
modificationTimestamp: 0,
location: null
}
};
return result;
}catch(e){
Logger.error(`saveToDevice error: ${e}`);
}
} else {
if (resourceType) {
fs.unlinkSync(saveUri);
}
}
}
saveToCameraRoll(uri: string, options: SaveToCameraRollOptions): Promise<PhotoIdentifier> {
return new Promise<PhotoIdentifier>(async (resolve, reject) => {
if (!uri) {
Logger.error(`Incorrect path.${uri}`);
reject('Incorrect path.');
}
if (uri.startsWith('http') || uri.startsWith('https')) {
try {
let sandBoxPath: string = await this.DownloadResources(uri);
if (sandBoxPath) {
let saveData: PhotoIdentifier = await this.saveToDevice(sandBoxPath, options, ResourceType);
resolve(saveData);
}
} catch (err) {
reject(err);
}
} else {
try {
let saveData: PhotoIdentifier = await this.saveToDevice(uri, options, '');
resolve(saveData);
} catch (err) {
Logger.error(`Failed to save, errorCode: ${err.code}`);
reject(err);
}
}
})
}
getPhotos(params: GetPhotosParams): Promise<PhotoIdentifiersPage> {
return new Promise<PhotoIdentifiersPage>((resolve, reject) => {
let first = params.first;
let after = params.after;
let groupTypes = params.groupTypes;
let groupName = params.groupName;
let includeSharedAlbums = params.includeSharedAlbums;
let assetType = params.assetType;
let fromTime = params.fromTime;
let toTime = params.toTime;
let mimeTypes = params.mimeTypes;
let include = params.include;
let queryBegin = parseInt(this.isEmpty(after) ? '0' : after);
let predicates = new dataSharePredicates.DataSharePredicates();
predicates.in('media_type', mimeTypes);
predicates.limit(first + 1, queryBegin);
predicates.orderByDesc('date_added').orderByDesc('date_modified');
let fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.PHOTO_TYPE,
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
photoAccessHelper.PhotoKeys.SIZE,
photoAccessHelper.PhotoKeys.DATE_ADDED,
photoAccessHelper.PhotoKeys.DATE_MODIFIED,
photoAccessHelper.PhotoKeys.DURATION,
photoAccessHelper.PhotoKeys.WIDTH,
photoAccessHelper.PhotoKeys.HEIGHT,
photoAccessHelper.PhotoKeys.ORIENTATION,
photoAccessHelper.PhotoKeys.DATE_TAKEN,
photoAccessHelper.PhotoKeys.FAVORITE,
photoAccessHelper.PhotoKeys.TITLE],
predicates: predicates
};
this.phAccessHelper.getAssets(fetchOptions).then((fetchResult) => {
if (fetchResult !== undefined) {
fetchResult.getAllObjects().then((photoAssets) => {
if (photoAssets !== undefined && photoAssets.length != 0) {
let has_next_page: boolean = first < photoAssets.length;
let pageInfo: {
has_next_page: boolean;
start_cursor?: string;
end_cursor?: string;
} = {
has_next_page: has_next_page,
end_cursor: has_next_page ? (queryBegin + first).toString() : ''
}
let photos: Array<PhotoIdentifier> = new Array<PhotoIdentifier>();
photoAssets.forEach(photoAsset => {
let type: string = photoAsset.photoType == 1 ? 'IMAGE' : 'VIDEO'
let photo: PhotoIdentifier = {
node: {
id: '',
type: type,
subTypes: 'PhotoPanorama',
sourceType: 'UserLibrary',
group_name: [],
image: {
filename: photoAsset.displayName,
filepath: null,
extension: null,
uri: photoAsset.uri,
height: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.HEIGHT).toString()),
width: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.WIDTH).toString()),
fileSize: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.SIZE).toString()),
playableDuration: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.DURATION).toString()),
orientation: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.ORIENTATION).toString())
},
timestamp: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.DATE_ADDED).toString()),
modificationTimestamp: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.DATE_MODIFIED)
.toString()),
location: null
}
}
photos.push(photo);
})
let resulPage: PhotoIdentifiersPage = {
edges: photos,
page_info: pageInfo,
}
resolve(resulPage);
}
})
}
}).catch(err => {
Logger.error('getPhotos failed with err: ' + err);
})
})
}
getAlbums(params: GetAlbumsParams): Promise<Album[]> {
return new Promise<Album[]>((resolve, reject) => {
let albumSubtype;
if (ASSET_TYPE_PHOTOS == params.assetType) {
albumSubtype = photoAccessHelper.AlbumSubtype.USER_GENERIC;
} else if (ASSET_TYPE_VIDEOS == params.assetType) {
albumSubtype = photoAccessHelper.AlbumSubtype.VIDEO;
} else if (ASSET_TYPE_ALL == params.assetType) {
albumSubtype = photoAccessHelper.AlbumSubtype.ANY;
}
this.phAccessHelper.getAlbums(photoAccessHelper.AlbumType.USER, albumSubtype).then(result => {
let resultAlbums: Album[] = new Array();
result.getAllObjects().then(albums => {
albums.forEach(album => {
resultAlbums.push({
title: album.albumName,
count: album.count,
id: '',
type: 'All'
})
})
resolve(resultAlbums);
})
}).catch((err: Error) => {
Logger.error('getAlbums failed with err: ' + err);
reject(`Could not get media, ${JSON.stringify(err)}`)
})
})
}
deletePhotos(photoUris: Array<string>): Promise<void> {
// this.phAccessHelper.deleteAssets 为系统接口
return new Promise<void>((resolve, reject) => {
})
}
getPhotoByInternalID(internalID: string, options: PhotoConvertionOptions): Promise<PhotoIdentifier> {
// internalID IOS特有
return new Promise<PhotoIdentifier>((resolve, reject) => {
this.getPhotoByUri(internalID).then(photoAsset => {
let type: string = photoAsset.photoType == 1 ? 'IMAGE' : 'VIDEO'
let photo: PhotoIdentifier = {
node: {
id: '',
type: type,
subTypes: 'PhotoPanorama',
sourceType: 'UserLibrary',
group_name: [],
image: {
filename: photoAsset.displayName,
filepath: null,
extension: null,
uri: photoAsset.uri,
height: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.HEIGHT).toString()),
width: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.WIDTH).toString()),
fileSize: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.SIZE).toString()),
playableDuration: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.DURATION).toString()),
orientation: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.ORIENTATION).toString()),
},
timestamp: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.DATE_ADDED).toString()),
modificationTimestamp: parseInt(photoAsset.get(photoAccessHelper.PhotoKeys.DATE_MODIFIED).toString()),
location: null
}
}
resolve(photo);
})
})
}
getPhotoThumbnail(internalID: string, options: PhotoThumbnailOptions): Promise<PhotoThumbnail> {
// internalID IOS特有
return new Promise<PhotoThumbnail>((resolve, reject) => {
this.getPhotoByUri(internalID).then(photoAsset => {
photoAsset.getThumbnail({ height: options.targetSize.height, width: options.targetSize.width }).then(pixMap => {
var base64 = new util.Base64Helper();
let packOpts: image.PackingOption = {
format: 'image/jpeg', quality: options.quality * 100
}
let imagePackerApi = image.createImagePacker();
imagePackerApi.packing(pixMap, packOpts).then(arrayBuffer => {
base64.encodeToString(new Uint8Array(arrayBuffer)).then(base64String => {
resolve({ thumbnailBase64: base64String });
})
})
})
})
})
}
isEmpty(str: string): boolean {
if (str === null || str === undefined || str.trim().length === 0) {
return true;
}
return false;
}
getPhotoByUri(uri: string): Promise<photoAccessHelper.PhotoAsset> {
return new Promise<photoAccessHelper.PhotoAsset>((resolve, reject) => {
let predicates = new dataSharePredicates.DataSharePredicates();
predicates.equalTo('uri', uri);
let fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.PHOTO_TYPE,
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
photoAccessHelper.PhotoKeys.SIZE,
photoAccessHelper.PhotoKeys.DATE_ADDED,
photoAccessHelper.PhotoKeys.DATE_MODIFIED,
photoAccessHelper.PhotoKeys.DURATION,
photoAccessHelper.PhotoKeys.WIDTH,
photoAccessHelper.PhotoKeys.HEIGHT,
photoAccessHelper.PhotoKeys.ORIENTATION,
photoAccessHelper.PhotoKeys.DATE_TAKEN,
photoAccessHelper.PhotoKeys.FAVORITE,
photoAccessHelper.PhotoKeys.TITLE],
predicates: predicates
};
this.phAccessHelper.getAssets(fetchOptions).then((fetchResult) => {
fetchResult.getFirstObject().then(photoAsset => {
resolve(photoAsset);
})
})
})
}
DownloadResources(saveUri): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
let downloadTask: request.DownloadTask = await request.downloadFile(this.context, { url: saveUri });
downloadTask.on('complete', async () => {
try {
let downloadInfo: request.DownloadInfo = await downloadTask.getTaskInfo();
resolve(downloadInfo.filePath);
} catch (err) {
Logger.error(`Failed to get downloadInfo. Code: ${err.code}, message: ${err.message}`);
} finally {
downloadTask.off('complete');
}
})
} catch (err) {
Logger.error(`Failed to request the download. Code: ${err.code}, message: ${err.message}`);
reject(err);
}
})
}
addListener(eventName: string): void {
}
removeListeners(count: number): void {
}
getNowTime(): string {
let date = new Date();
let year = date.getFullYear().toString();
let month = date.getMonth() + 1;
let day = date.getDay();
let hours = date.getHours().toString();
let minutes = date.getMinutes().toString();
let seconds = date.getSeconds().toString();
let milliseconds = date.getMilliseconds().toString();
return year + (month > 10 ? month : '0' + month) + (day > 10 ? day : '0' + day) + hours + minutes + seconds +
milliseconds;
}
}