Açıklama Yok
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NativeGallery.mm 55KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589
  1. #import <Foundation/Foundation.h>
  2. #import <Photos/Photos.h>
  3. #import <MobileCoreServices/UTCoreTypes.h>
  4. #import <MobileCoreServices/MobileCoreServices.h>
  5. #import <ImageIO/ImageIO.h>
  6. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  7. #import <AssetsLibrary/AssetsLibrary.h>
  8. #endif
  9. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  10. #import <PhotosUI/PhotosUI.h>
  11. #endif
  12. #ifdef UNITY_4_0 || UNITY_5_0
  13. #import "iPhone_View.h"
  14. #else
  15. extern UIViewController* UnityGetGLViewController();
  16. #endif
  17. #define CHECK_IOS_VERSION( version ) ([[[UIDevice currentDevice] systemVersion] compare:version options:NSNumericSearch] != NSOrderedAscending)
  18. @interface UNativeGallery:NSObject
  19. + (int)checkPermission:(BOOL)readPermission permissionFreeMode:(BOOL)permissionFreeMode;
  20. + (int)requestPermission:(BOOL)readPermission permissionFreeMode:(BOOL)permissionFreeMode asyncMode:(BOOL)asyncMode;
  21. + (void)showLimitedLibraryPicker;
  22. + (int)canOpenSettings;
  23. + (void)openSettings;
  24. + (int)canPickMultipleMedia;
  25. + (void)saveMedia:(NSString *)path albumName:(NSString *)album isImg:(BOOL)isImg permissionFreeMode:(BOOL)permissionFreeMode;
  26. + (void)pickMedia:(int)mediaType savePath:(NSString *)mediaSavePath permissionFreeMode:(BOOL)permissionFreeMode selectionLimit:(int)selectionLimit;
  27. + (int)isMediaPickerBusy;
  28. + (int)getMediaTypeFromExtension:(NSString *)extension;
  29. + (char *)getImageProperties:(NSString *)path;
  30. + (char *)getVideoProperties:(NSString *)path;
  31. + (char *)getVideoThumbnail:(NSString *)path savePath:(NSString *)savePath maximumSize:(int)maximumSize captureTime:(double)captureTime;
  32. + (char *)loadImageAtPath:(NSString *)path tempFilePath:(NSString *)tempFilePath maximumSize:(int)maximumSize;
  33. @end
  34. @implementation UNativeGallery
  35. static NSString *pickedMediaSavePath;
  36. static UIPopoverController *popup;
  37. static UIImagePickerController *imagePicker;
  38. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  39. static PHPickerViewController *imagePickerNew;
  40. #endif
  41. static int imagePickerState = 0; // 0 -> none, 1 -> showing (always in this state on iPad), 2 -> finished
  42. static BOOL simpleMediaPickMode;
  43. static BOOL pickingMultipleFiles = NO;
  44. #pragma clang diagnostic push
  45. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  46. + (int)checkPermission:(BOOL)readPermission permissionFreeMode:(BOOL)permissionFreeMode
  47. {
  48. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  49. if( CHECK_IOS_VERSION( @"8.0" ) )
  50. {
  51. #endif
  52. // version >= iOS 8: check permission using Photos framework
  53. // On iOS 11 and later, permission isn't mandatory to fetch media from Photos
  54. if( readPermission && permissionFreeMode && CHECK_IOS_VERSION( @"11.0" ) )
  55. return 1;
  56. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  57. // Photos permissions has changed on iOS 14
  58. if( CHECK_IOS_VERSION( @"14.0" ) )
  59. {
  60. // Request ReadWrite permission in 2 cases:
  61. // 1) When attempting to pick media from Photos with PHPhotoLibrary (readPermission=true and permissionFreeMode=false)
  62. // 2) When attempting to write media to a specific album in Photos using PHPhotoLibrary (readPermission=false and permissionFreeMode=false)
  63. PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatusForAccessLevel:( ( readPermission || !permissionFreeMode ) ? PHAccessLevelReadWrite : PHAccessLevelAddOnly )];
  64. if( status == PHAuthorizationStatusAuthorized )
  65. return 1;
  66. else if( status == PHAuthorizationStatusRestricted )
  67. return 3;
  68. else if( status == PHAuthorizationStatusNotDetermined )
  69. return 2;
  70. else
  71. return 0;
  72. }
  73. else
  74. #endif
  75. {
  76. PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
  77. if( status == PHAuthorizationStatusAuthorized )
  78. return 1;
  79. else if( status == PHAuthorizationStatusNotDetermined )
  80. return 2;
  81. else
  82. return 0;
  83. }
  84. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  85. }
  86. else
  87. {
  88. // version < iOS 8: check permission using AssetsLibrary framework (Photos framework not available)
  89. ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
  90. if( status == ALAuthorizationStatusAuthorized )
  91. return 1;
  92. else if( status == ALAuthorizationStatusNotDetermined )
  93. return 2;
  94. else
  95. return 0;
  96. }
  97. #endif
  98. }
  99. #pragma clang diagnostic pop
  100. + (int)requestPermission:(BOOL)readPermission permissionFreeMode:(BOOL)permissionFreeMode asyncMode:(BOOL)asyncMode
  101. {
  102. int result;
  103. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  104. if( CHECK_IOS_VERSION( @"8.0" ) )
  105. {
  106. #endif
  107. // version >= iOS 8: request permission using Photos framework
  108. // On iOS 11 and later, permission isn't mandatory to fetch media from Photos
  109. if( readPermission && permissionFreeMode && CHECK_IOS_VERSION( @"11.0" ) )
  110. result = 1;
  111. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  112. else if( CHECK_IOS_VERSION( @"14.0" ) )
  113. {
  114. // Photos permissions has changed on iOS 14. There are 2 permission dialogs now:
  115. // - AddOnly permission dialog: has 2 options: "Allow" and "Don't Allow". This dialog grants permission for save operations only. Unfortunately,
  116. // saving media to a custom album isn't possible with this dialog, media can only be saved to the default Photos album
  117. // - ReadWrite permission dialog: has 3 options: "Allow Access to All Photos" (i.e. full permission), "Select Photos" (i.e. limited access) and
  118. // "Don't Allow". To be able to save media to a custom album, user must grant Full Photos permission. Thus, even when readPermission is false,
  119. // this dialog will be used if PermissionFreeMode is set to false. So, PermissionFreeMode determines whether or not saving to a custom album is
  120. // be supported
  121. result = [self requestPermissionNewest:( readPermission || !permissionFreeMode ) asyncMode:asyncMode];
  122. }
  123. #endif
  124. else
  125. result = [self requestPermissionNew:asyncMode];
  126. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  127. }
  128. else
  129. {
  130. // version < iOS 8: request permission using AssetsLibrary framework (Photos framework not available)
  131. result = [self requestPermissionOld:asyncMode];
  132. }
  133. #endif
  134. if( asyncMode && result >= 0 ) // Result returned immediately, forward it
  135. UnitySendMessage( "NGPermissionCallbackiOS", "OnPermissionRequested", [self getCString:[NSString stringWithFormat:@"%d", result]] );
  136. return result;
  137. }
  138. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  139. // Credit: https://stackoverflow.com/a/26933380/2373034
  140. #pragma clang diagnostic push
  141. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  142. + (int)requestPermissionOld:(BOOL)asyncMode
  143. {
  144. ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
  145. if( status == ALAuthorizationStatusAuthorized )
  146. return 1;
  147. else if( status == ALAuthorizationStatusNotDetermined )
  148. {
  149. if( asyncMode )
  150. {
  151. [[[ALAssetsLibrary alloc] init] enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^( ALAssetsGroup *group, BOOL *stop )
  152. {
  153. *stop = YES;
  154. UnitySendMessage( "NGPermissionCallbackiOS", "OnPermissionRequested", "1" );
  155. }
  156. failureBlock:^( NSError *error )
  157. {
  158. UnitySendMessage( "NGPermissionCallbackiOS", "OnPermissionRequested", "0" );
  159. }];
  160. return -1;
  161. }
  162. else
  163. {
  164. __block BOOL authorized = NO;
  165. dispatch_semaphore_t sema = dispatch_semaphore_create( 0 );
  166. [[[ALAssetsLibrary alloc] init] enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^( ALAssetsGroup *group, BOOL *stop )
  167. {
  168. *stop = YES;
  169. authorized = YES;
  170. dispatch_semaphore_signal( sema );
  171. }
  172. failureBlock:^( NSError *error )
  173. {
  174. dispatch_semaphore_signal( sema );
  175. }];
  176. dispatch_semaphore_wait( sema, DISPATCH_TIME_FOREVER );
  177. return authorized ? 1 : 0;
  178. }
  179. }
  180. return 0;
  181. }
  182. #pragma clang diagnostic pop
  183. #endif
  184. // Credit: https://stackoverflow.com/a/32989022/2373034
  185. + (int)requestPermissionNew:(BOOL)asyncMode
  186. {
  187. PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
  188. if( status == PHAuthorizationStatusAuthorized )
  189. return 1;
  190. else if( status == PHAuthorizationStatusNotDetermined )
  191. {
  192. if( asyncMode )
  193. {
  194. [PHPhotoLibrary requestAuthorization:^( PHAuthorizationStatus status )
  195. {
  196. UnitySendMessage( "NGPermissionCallbackiOS", "OnPermissionRequested", ( status == PHAuthorizationStatusAuthorized ) ? "1" : "0" );
  197. }];
  198. return -1;
  199. }
  200. else
  201. {
  202. __block BOOL authorized = NO;
  203. dispatch_semaphore_t sema = dispatch_semaphore_create( 0 );
  204. [PHPhotoLibrary requestAuthorization:^( PHAuthorizationStatus status )
  205. {
  206. authorized = ( status == PHAuthorizationStatusAuthorized );
  207. dispatch_semaphore_signal( sema );
  208. }];
  209. dispatch_semaphore_wait( sema, DISPATCH_TIME_FOREVER );
  210. return authorized ? 1 : 0;
  211. }
  212. }
  213. return 0;
  214. }
  215. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  216. + (int)requestPermissionNewest:(BOOL)readPermission asyncMode:(BOOL)asyncMode
  217. {
  218. PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatusForAccessLevel:( readPermission ? PHAccessLevelReadWrite : PHAccessLevelAddOnly )];
  219. if( status == PHAuthorizationStatusAuthorized )
  220. return 1;
  221. else if( status == PHAuthorizationStatusRestricted )
  222. return 3;
  223. else if( status == PHAuthorizationStatusNotDetermined )
  224. {
  225. if( asyncMode )
  226. {
  227. [PHPhotoLibrary requestAuthorizationForAccessLevel:( readPermission ? PHAccessLevelReadWrite : PHAccessLevelAddOnly ) handler:^( PHAuthorizationStatus status )
  228. {
  229. if( status == PHAuthorizationStatusAuthorized )
  230. UnitySendMessage( "NGPermissionCallbackiOS", "OnPermissionRequested", "1" );
  231. else if( status == PHAuthorizationStatusRestricted )
  232. UnitySendMessage( "NGPermissionCallbackiOS", "OnPermissionRequested", "3" );
  233. else
  234. UnitySendMessage( "NGPermissionCallbackiOS", "OnPermissionRequested", "0" );
  235. }];
  236. return -1;
  237. }
  238. else
  239. {
  240. __block int authorized = 0;
  241. dispatch_semaphore_t sema = dispatch_semaphore_create( 0 );
  242. [PHPhotoLibrary requestAuthorizationForAccessLevel:( readPermission ? PHAccessLevelReadWrite : PHAccessLevelAddOnly ) handler:^( PHAuthorizationStatus status )
  243. {
  244. if( status == PHAuthorizationStatusAuthorized )
  245. authorized = 1;
  246. else if( status == PHAuthorizationStatusRestricted )
  247. authorized = 3;
  248. dispatch_semaphore_signal( sema );
  249. }];
  250. dispatch_semaphore_wait( sema, DISPATCH_TIME_FOREVER );
  251. return authorized;
  252. }
  253. }
  254. return 0;
  255. }
  256. #endif
  257. // When Photos permission is set to restricted, allows user to change the permission or change the list of restricted images
  258. // It doesn't support a deterministic callback; for example there is a photoLibraryDidChange event but it won't be invoked if
  259. // user doesn't change the list of restricted images
  260. + (void)showLimitedLibraryPicker
  261. {
  262. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  263. PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite];
  264. if( status == PHAuthorizationStatusNotDetermined )
  265. [self requestPermissionNewest:YES asyncMode:YES];
  266. else if( status == PHAuthorizationStatusRestricted )
  267. [[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:UnityGetGLViewController()];
  268. #endif
  269. }
  270. // Credit: https://stackoverflow.com/a/25453667/2373034
  271. + (int)canOpenSettings
  272. {
  273. return ( &UIApplicationOpenSettingsURLString != NULL ) ? 1 : 0;
  274. }
  275. // Credit: https://stackoverflow.com/a/25453667/2373034
  276. #pragma clang diagnostic push
  277. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  278. + (void)openSettings
  279. {
  280. if( &UIApplicationOpenSettingsURLString != NULL )
  281. {
  282. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  283. if( CHECK_IOS_VERSION( @"10.0" ) )
  284. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
  285. else
  286. #endif
  287. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
  288. }
  289. }
  290. #pragma clang diagnostic pop
  291. + (int)canPickMultipleMedia
  292. {
  293. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  294. if( CHECK_IOS_VERSION( @"14.0" ) )
  295. return 1;
  296. else
  297. #endif
  298. return 0;
  299. }
  300. + (void)saveMedia:(NSString *)path albumName:(NSString *)album isImg:(BOOL)isImg permissionFreeMode:(BOOL)permissionFreeMode
  301. {
  302. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  303. if( CHECK_IOS_VERSION( @"8.0" ) )
  304. {
  305. #endif
  306. // version >= iOS 8: save to specified album using Photos framework
  307. // On iOS 14+, permission workflow has changed significantly with the addition of PHAuthorizationStatusRestricted permission. On those versions,
  308. // user must grant Full Photos permission to be able to save to a custom album. Hence, there are 2 workflows:
  309. // - If PermissionFreeMode is enabled, save the media directly to the default album (i.e. ignore 'album' parameter). This will present a simple
  310. // permission dialog stating "The app requires access to Photos to save media to it." and the "Selected Photos" permission won't be listed in the options
  311. // - Otherwise, the more complex "The app requires access to Photos to interact with it." permission dialog will be shown and if the user grants
  312. // Full Photos permission, only then the image will be saved to the specified album. If user selects "Selected Photos" permission, default album will be
  313. // used as fallback
  314. [self saveMediaNew:path albumName:album isImage:isImg saveToDefaultAlbum:( permissionFreeMode && CHECK_IOS_VERSION( @"14.0" ) )];
  315. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  316. }
  317. else
  318. {
  319. // version < iOS 8: save using AssetsLibrary framework (Photos framework not available)
  320. [self saveMediaOld:path albumName:album isImage:isImg];
  321. }
  322. #endif
  323. }
  324. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  325. // Credit: https://stackoverflow.com/a/22056664/2373034
  326. #pragma clang diagnostic push
  327. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  328. + (void)saveMediaOld:(NSString *)path albumName:(NSString *)album isImage:(BOOL)isImage
  329. {
  330. ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
  331. if( !isImage && ![library videoAtPathIsCompatibleWithSavedPhotosAlbum:[NSURL fileURLWithPath:path]])
  332. {
  333. NSLog( @"Error saving video: Video format is not compatible with Photos" );
  334. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  335. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  336. return;
  337. }
  338. void (^saveBlock)(ALAssetsGroup *assetCollection) = ^void( ALAssetsGroup *assetCollection )
  339. {
  340. void (^saveResultBlock)(NSURL *assetURL, NSError *error) = ^void( NSURL *assetURL, NSError *error )
  341. {
  342. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  343. if( error.code == 0 )
  344. {
  345. [library assetForURL:assetURL resultBlock:^( ALAsset *asset )
  346. {
  347. [assetCollection addAsset:asset];
  348. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveCompleted", "" );
  349. }
  350. failureBlock:^( NSError* error )
  351. {
  352. NSLog( @"Error moving asset to album: %@", error );
  353. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  354. }];
  355. }
  356. else
  357. {
  358. NSLog( @"Error creating asset: %@", error );
  359. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  360. }
  361. };
  362. if( !isImage )
  363. [library writeImageDataToSavedPhotosAlbum:[NSData dataWithContentsOfFile:path] metadata:nil completionBlock:saveResultBlock];
  364. else
  365. [library writeVideoAtPathToSavedPhotosAlbum:[NSURL fileURLWithPath:path] completionBlock:saveResultBlock];
  366. };
  367. __block BOOL albumFound = NO;
  368. [library enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^( ALAssetsGroup *group, BOOL *stop )
  369. {
  370. if( [[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:album] )
  371. {
  372. *stop = YES;
  373. albumFound = YES;
  374. saveBlock( group );
  375. }
  376. else if( group == nil && albumFound==NO )
  377. {
  378. // Album doesn't exist
  379. [library addAssetsGroupAlbumWithName:album resultBlock:^( ALAssetsGroup *group )
  380. {
  381. saveBlock( group );
  382. }
  383. failureBlock:^( NSError *error )
  384. {
  385. NSLog( @"Error creating album: %@", error );
  386. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  387. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  388. }];
  389. }
  390. }
  391. failureBlock:^( NSError* error )
  392. {
  393. NSLog( @"Error listing albums: %@", error );
  394. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  395. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  396. }];
  397. }
  398. #pragma clang diagnostic pop
  399. #endif
  400. // Credit: https://stackoverflow.com/a/39909129/2373034
  401. + (void)saveMediaNew:(NSString *)path albumName:(NSString *)album isImage:(BOOL)isImage saveToDefaultAlbum:(BOOL)saveToDefaultAlbum
  402. {
  403. void (^saveToPhotosAlbum)() = ^void()
  404. {
  405. if( isImage )
  406. {
  407. // Try preserving image metadata (essential for animated gif images)
  408. [[PHPhotoLibrary sharedPhotoLibrary] performChanges:
  409. ^{
  410. [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:[NSURL fileURLWithPath:path]];
  411. }
  412. completionHandler:^( BOOL success, NSError *error )
  413. {
  414. if( success )
  415. {
  416. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  417. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveCompleted", "" );
  418. }
  419. else
  420. {
  421. NSLog( @"Error creating asset in default Photos album: %@", error );
  422. UIImage *image = [UIImage imageWithContentsOfFile:path];
  423. if( image != nil )
  424. UIImageWriteToSavedPhotosAlbum( image, self, @selector(image:didFinishSavingWithError:contextInfo:), (__bridge_retained void *) path );
  425. else
  426. {
  427. NSLog( @"Couldn't create UIImage from file at path: %@", path );
  428. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  429. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  430. }
  431. }
  432. }];
  433. }
  434. else
  435. {
  436. if( UIVideoAtPathIsCompatibleWithSavedPhotosAlbum( path ) )
  437. UISaveVideoAtPathToSavedPhotosAlbum( path, self, @selector(video:didFinishSavingWithError:contextInfo:), (__bridge_retained void *) path );
  438. else
  439. {
  440. NSLog( @"Video at path isn't compatible with saved photos album: %@", path );
  441. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  442. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  443. }
  444. }
  445. };
  446. void (^saveBlock)(PHAssetCollection *assetCollection) = ^void( PHAssetCollection *assetCollection )
  447. {
  448. [[PHPhotoLibrary sharedPhotoLibrary] performChanges:
  449. ^{
  450. PHAssetChangeRequest *assetChangeRequest;
  451. if( isImage )
  452. assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:[NSURL fileURLWithPath:path]];
  453. else
  454. assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:path]];
  455. PHAssetCollectionChangeRequest *assetCollectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:assetCollection];
  456. [assetCollectionChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]];
  457. }
  458. completionHandler:^( BOOL success, NSError *error )
  459. {
  460. if( success )
  461. {
  462. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  463. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveCompleted", "" );
  464. }
  465. else
  466. {
  467. NSLog( @"Error creating asset: %@", error );
  468. saveToPhotosAlbum();
  469. }
  470. }];
  471. };
  472. if( saveToDefaultAlbum )
  473. saveToPhotosAlbum();
  474. else
  475. {
  476. PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
  477. fetchOptions.predicate = [NSPredicate predicateWithFormat:@"localizedTitle = %@", album];
  478. PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:fetchOptions];
  479. if( fetchResult.count > 0 )
  480. saveBlock( fetchResult.firstObject);
  481. else
  482. {
  483. __block PHObjectPlaceholder *albumPlaceholder;
  484. [[PHPhotoLibrary sharedPhotoLibrary] performChanges:
  485. ^{
  486. PHAssetCollectionChangeRequest *changeRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:album];
  487. albumPlaceholder = changeRequest.placeholderForCreatedAssetCollection;
  488. }
  489. completionHandler:^( BOOL success, NSError *error )
  490. {
  491. if( success )
  492. {
  493. PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[albumPlaceholder.localIdentifier] options:nil];
  494. if( fetchResult.count > 0 )
  495. saveBlock( fetchResult.firstObject);
  496. else
  497. {
  498. NSLog( @"Error creating album: Album placeholder not found" );
  499. saveToPhotosAlbum();
  500. }
  501. }
  502. else
  503. {
  504. NSLog( @"Error creating album: %@", error );
  505. saveToPhotosAlbum();
  506. }
  507. }];
  508. }
  509. }
  510. }
  511. + (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
  512. {
  513. NSString* path = (__bridge_transfer NSString *)(contextInfo);
  514. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  515. if( error == nil )
  516. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveCompleted", "" );
  517. else
  518. {
  519. NSLog( @"Error saving image with UIImageWriteToSavedPhotosAlbum: %@", error );
  520. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  521. }
  522. }
  523. + (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
  524. {
  525. NSString* path = (__bridge_transfer NSString *)(contextInfo);
  526. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  527. if( error == nil )
  528. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveCompleted", "" );
  529. else
  530. {
  531. NSLog( @"Error saving video with UISaveVideoAtPathToSavedPhotosAlbum: %@", error );
  532. UnitySendMessage( "NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "" );
  533. }
  534. }
  535. // Credit: https://stackoverflow.com/a/10531752/2373034
  536. + (void)pickMedia:(int)mediaType savePath:(NSString *)mediaSavePath permissionFreeMode:(BOOL)permissionFreeMode selectionLimit:(int)selectionLimit
  537. {
  538. pickedMediaSavePath = mediaSavePath;
  539. imagePickerState = 1;
  540. simpleMediaPickMode = permissionFreeMode && CHECK_IOS_VERSION( @"11.0" );
  541. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  542. if( CHECK_IOS_VERSION( @"14.0" ) )
  543. {
  544. // PHPickerViewController is used on iOS 14
  545. PHPickerConfiguration *config = simpleMediaPickMode ? [[PHPickerConfiguration alloc] init] : [[PHPickerConfiguration alloc] initWithPhotoLibrary:[PHPhotoLibrary sharedPhotoLibrary]];
  546. config.preferredAssetRepresentationMode = PHPickerConfigurationAssetRepresentationModeCurrent;
  547. config.selectionLimit = selectionLimit;
  548. pickingMultipleFiles = selectionLimit != 1;
  549. // mediaType is a bitmask:
  550. // 1: image
  551. // 2: video
  552. // 4: audio (not supported)
  553. if( mediaType == 1 )
  554. config.filter = [PHPickerFilter anyFilterMatchingSubfilters:[NSArray arrayWithObjects:[PHPickerFilter imagesFilter], [PHPickerFilter livePhotosFilter], nil]];
  555. else if( mediaType == 2 )
  556. config.filter = [PHPickerFilter videosFilter];
  557. else
  558. config.filter = [PHPickerFilter anyFilterMatchingSubfilters:[NSArray arrayWithObjects:[PHPickerFilter imagesFilter], [PHPickerFilter livePhotosFilter], [PHPickerFilter videosFilter], nil]];
  559. imagePickerNew = [[PHPickerViewController alloc] initWithConfiguration:config];
  560. imagePickerNew.delegate = (id) self;
  561. [UnityGetGLViewController() presentViewController:imagePickerNew animated:YES completion:^{ imagePickerState = 0; }];
  562. }
  563. else
  564. #endif
  565. {
  566. // UIImagePickerController is used on previous versions
  567. imagePicker = [[UIImagePickerController alloc] init];
  568. imagePicker.delegate = (id) self;
  569. imagePicker.allowsEditing = NO;
  570. imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  571. // mediaType is a bitmask:
  572. // 1: image
  573. // 2: video
  574. // 4: audio (not supported)
  575. if( mediaType == 1 )
  576. {
  577. if( CHECK_IOS_VERSION( @"9.1" ) )
  578. imagePicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeImage, (NSString *)kUTTypeLivePhoto, nil];
  579. else
  580. imagePicker.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeImage];
  581. }
  582. else if( mediaType == 2 )
  583. imagePicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeMovie, (NSString *)kUTTypeVideo, nil];
  584. else
  585. {
  586. if( CHECK_IOS_VERSION( @"9.1" ) )
  587. imagePicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeImage, (NSString *)kUTTypeLivePhoto, (NSString *)kUTTypeMovie, (NSString *)kUTTypeVideo, nil];
  588. else
  589. imagePicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeImage, (NSString *)kUTTypeMovie, (NSString *)kUTTypeVideo, nil];
  590. }
  591. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
  592. if( mediaType != 1 )
  593. {
  594. // Don't compress picked videos if possible
  595. if( CHECK_IOS_VERSION( @"11.0" ) )
  596. imagePicker.videoExportPreset = AVAssetExportPresetPassthrough;
  597. }
  598. #endif
  599. UIViewController *rootViewController = UnityGetGLViewController();
  600. if( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ) // iPhone
  601. [rootViewController presentViewController:imagePicker animated:YES completion:^{ imagePickerState = 0; }];
  602. else
  603. {
  604. // iPad
  605. popup = [[UIPopoverController alloc] initWithContentViewController:imagePicker];
  606. popup.delegate = (id) self;
  607. [popup presentPopoverFromRect:CGRectMake( rootViewController.view.frame.size.width / 2, rootViewController.view.frame.size.height / 2, 1, 1 ) inView:rootViewController.view permittedArrowDirections:0 animated:YES];
  608. }
  609. }
  610. }
  611. + (int)isMediaPickerBusy
  612. {
  613. if( imagePickerState == 2 )
  614. return 1;
  615. if( imagePicker != nil )
  616. {
  617. if( imagePickerState == 1 || [imagePicker presentingViewController] == UnityGetGLViewController() )
  618. return 1;
  619. else
  620. {
  621. imagePicker = nil;
  622. return 0;
  623. }
  624. }
  625. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  626. else if( CHECK_IOS_VERSION( @"14.0" ) && imagePickerNew != nil )
  627. {
  628. if( imagePickerState == 1 || [imagePickerNew presentingViewController] == UnityGetGLViewController() )
  629. return 1;
  630. else
  631. {
  632. imagePickerNew = nil;
  633. return 0;
  634. }
  635. }
  636. #endif
  637. else
  638. return 0;
  639. }
  640. #pragma clang diagnostic push
  641. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  642. + (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
  643. {
  644. NSString *resultPath = nil;
  645. if( [info[UIImagePickerControllerMediaType] isEqualToString:(NSString *)kUTTypeImage] )
  646. {
  647. NSLog( @"Picked an image" );
  648. // On iOS 8.0 or later, try to obtain the raw data of the image (which allows picking gifs properly or preserving metadata)
  649. if( CHECK_IOS_VERSION( @"8.0" ) )
  650. {
  651. PHAsset *asset = nil;
  652. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
  653. if( CHECK_IOS_VERSION( @"11.0" ) )
  654. {
  655. // Try fetching the source image via UIImagePickerControllerImageURL
  656. NSURL *mediaUrl = info[UIImagePickerControllerImageURL];
  657. if( mediaUrl != nil )
  658. {
  659. NSString *imagePath = [mediaUrl path];
  660. if( imagePath != nil && [[NSFileManager defaultManager] fileExistsAtPath:imagePath] )
  661. {
  662. NSError *error;
  663. NSString *newPath = [pickedMediaSavePath stringByAppendingPathExtension:[imagePath pathExtension]];
  664. if( ![[NSFileManager defaultManager] fileExistsAtPath:newPath] || [[NSFileManager defaultManager] removeItemAtPath:newPath error:&error] )
  665. {
  666. if( [[NSFileManager defaultManager] copyItemAtPath:imagePath toPath:newPath error:&error] )
  667. {
  668. resultPath = newPath;
  669. NSLog( @"Copied source image from UIImagePickerControllerImageURL" );
  670. }
  671. else
  672. NSLog( @"Error copying image: %@", error );
  673. }
  674. else
  675. NSLog( @"Error deleting existing image: %@", error );
  676. }
  677. }
  678. if( resultPath == nil )
  679. asset = info[UIImagePickerControllerPHAsset];
  680. }
  681. #endif
  682. if( resultPath == nil && !simpleMediaPickMode )
  683. {
  684. if( asset == nil )
  685. {
  686. NSURL *mediaUrl = info[UIImagePickerControllerReferenceURL] ?: info[UIImagePickerControllerMediaURL];
  687. if( mediaUrl != nil )
  688. asset = [[PHAsset fetchAssetsWithALAssetURLs:[NSArray arrayWithObject:mediaUrl] options:nil] firstObject];
  689. }
  690. resultPath = [self trySavePHAsset:asset atIndex:1];
  691. }
  692. }
  693. if( resultPath == nil )
  694. {
  695. // Save image as PNG
  696. UIImage *image = info[UIImagePickerControllerOriginalImage];
  697. if( image != nil )
  698. {
  699. resultPath = [pickedMediaSavePath stringByAppendingPathExtension:@"png"];
  700. if( ![self saveImageAsPNG:image toPath:resultPath] )
  701. {
  702. NSLog( @"Error creating PNG image" );
  703. resultPath = nil;
  704. }
  705. }
  706. else
  707. NSLog( @"Error fetching original image from picker" );
  708. }
  709. }
  710. else if( CHECK_IOS_VERSION( @"9.1" ) && [info[UIImagePickerControllerMediaType] isEqualToString:(NSString *)kUTTypeLivePhoto] )
  711. {
  712. NSLog( @"Picked a live photo" );
  713. // Save live photo as PNG
  714. UIImage *image = info[UIImagePickerControllerOriginalImage];
  715. if( image != nil )
  716. {
  717. resultPath = [pickedMediaSavePath stringByAppendingPathExtension:@"png"];
  718. if( ![self saveImageAsPNG:image toPath:resultPath] )
  719. {
  720. NSLog( @"Error creating PNG image" );
  721. resultPath = nil;
  722. }
  723. }
  724. else
  725. NSLog( @"Error fetching live photo's still image from picker" );
  726. }
  727. else
  728. {
  729. NSLog( @"Picked a video" );
  730. NSURL *mediaUrl = info[UIImagePickerControllerMediaURL] ?: info[UIImagePickerControllerReferenceURL];
  731. if( mediaUrl != nil )
  732. {
  733. resultPath = [mediaUrl path];
  734. // On iOS 13, picked file becomes unreachable as soon as the UIImagePickerController disappears,
  735. // in that case, copy the video to a temporary location
  736. if( CHECK_IOS_VERSION( @"13.0" ) )
  737. {
  738. NSError *error;
  739. NSString *newPath = [pickedMediaSavePath stringByAppendingPathExtension:[resultPath pathExtension]];
  740. if( ![[NSFileManager defaultManager] fileExistsAtPath:newPath] || [[NSFileManager defaultManager] removeItemAtPath:newPath error:&error] )
  741. {
  742. if( [[NSFileManager defaultManager] copyItemAtPath:resultPath toPath:newPath error:&error] )
  743. resultPath = newPath;
  744. else
  745. {
  746. NSLog( @"Error copying video: %@", error );
  747. resultPath = nil;
  748. }
  749. }
  750. else
  751. {
  752. NSLog( @"Error deleting existing video: %@", error );
  753. resultPath = nil;
  754. }
  755. }
  756. }
  757. }
  758. popup = nil;
  759. imagePicker = nil;
  760. imagePickerState = 2;
  761. UnitySendMessage( "NGMediaReceiveCallbackiOS", "OnMediaReceived", [self getCString:resultPath] );
  762. [picker dismissViewControllerAnimated:NO completion:nil];
  763. }
  764. #pragma clang diagnostic pop
  765. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
  766. // Credit: https://ikyle.me/blog/2020/phpickerviewcontroller
  767. +(void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results
  768. {
  769. imagePickerNew = nil;
  770. imagePickerState = 2;
  771. [picker dismissViewControllerAnimated:NO completion:nil];
  772. if( results != nil && [results count] > 0 )
  773. {
  774. NSMutableArray<NSString *> *resultPaths = [NSMutableArray arrayWithCapacity:[results count]];
  775. NSLock *arrayLock = [[NSLock alloc] init];
  776. dispatch_group_t group = dispatch_group_create();
  777. for( int i = 0; i < [results count]; i++ )
  778. {
  779. PHPickerResult *result = results[i];
  780. NSItemProvider *itemProvider = result.itemProvider;
  781. NSString *assetIdentifier = result.assetIdentifier;
  782. __block NSString *resultPath = nil;
  783. int j = i + 1;
  784. //NSLog( @"result: %@", result );
  785. //NSLog( @"%@", result.assetIdentifier);
  786. //NSLog( @"%@", result.itemProvider);
  787. if( [itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage] )
  788. {
  789. NSLog( @"Picked an image" );
  790. if( !simpleMediaPickMode && assetIdentifier != nil )
  791. {
  792. PHAsset *asset = [[PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:assetIdentifier] options:nil] firstObject];
  793. resultPath = [self trySavePHAsset:asset atIndex:j];
  794. }
  795. if( resultPath != nil )
  796. {
  797. [arrayLock lock];
  798. [resultPaths addObject:resultPath];
  799. [arrayLock unlock];
  800. }
  801. else
  802. {
  803. dispatch_group_enter( group );
  804. [itemProvider loadFileRepresentationForTypeIdentifier:(NSString *)kUTTypeImage completionHandler:^( NSURL *url, NSError *error )
  805. {
  806. if( url != nil )
  807. {
  808. // Copy the image to a temporary location because the returned image will be deleted by the OS after this callback is completed
  809. resultPath = [url path];
  810. NSString *newPath = [[NSString stringWithFormat:@"%@%d", pickedMediaSavePath, j] stringByAppendingPathExtension:[resultPath pathExtension]];
  811. if( ![[NSFileManager defaultManager] fileExistsAtPath:newPath] || [[NSFileManager defaultManager] removeItemAtPath:newPath error:&error] )
  812. {
  813. if( [[NSFileManager defaultManager] copyItemAtPath:resultPath toPath:newPath error:&error])
  814. resultPath = newPath;
  815. else
  816. {
  817. NSLog( @"Error copying image: %@", error );
  818. resultPath = nil;
  819. }
  820. }
  821. else
  822. {
  823. NSLog( @"Error deleting existing image: %@", error );
  824. resultPath = nil;
  825. }
  826. }
  827. else
  828. NSLog( @"Error getting the picked image's path: %@", error );
  829. if( resultPath != nil )
  830. {
  831. [arrayLock lock];
  832. [resultPaths addObject:resultPath];
  833. [arrayLock unlock];
  834. }
  835. else
  836. {
  837. if( [itemProvider canLoadObjectOfClass:[UIImage class]] )
  838. {
  839. dispatch_group_enter( group );
  840. [itemProvider loadObjectOfClass:[UIImage class] completionHandler:^( __kindof id<NSItemProviderReading> object, NSError *error )
  841. {
  842. if( object != nil && [object isKindOfClass:[UIImage class]] )
  843. {
  844. resultPath = [[NSString stringWithFormat:@"%@%d", pickedMediaSavePath, j] stringByAppendingPathExtension:@"png"];
  845. if( ![self saveImageAsPNG:(UIImage *)object toPath:resultPath] )
  846. {
  847. NSLog( @"Error creating PNG image" );
  848. resultPath = nil;
  849. }
  850. }
  851. else
  852. NSLog( @"Error generating UIImage from picked image: %@", error );
  853. [arrayLock lock];
  854. [resultPaths addObject:( resultPath != nil ? resultPath : @"" )];
  855. [arrayLock unlock];
  856. dispatch_group_leave( group );
  857. }];
  858. }
  859. else
  860. {
  861. NSLog( @"Can't generate UIImage from picked image" );
  862. [arrayLock lock];
  863. [resultPaths addObject:@""];
  864. [arrayLock unlock];
  865. }
  866. }
  867. dispatch_group_leave( group );
  868. }];
  869. }
  870. }
  871. else if( CHECK_IOS_VERSION( @"9.1" ) && [itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeLivePhoto] )
  872. {
  873. NSLog( @"Picked a live photo" );
  874. if( [itemProvider canLoadObjectOfClass:[UIImage class]] )
  875. {
  876. dispatch_group_enter( group );
  877. [itemProvider loadObjectOfClass:[UIImage class] completionHandler:^( __kindof id<NSItemProviderReading> object, NSError *error )
  878. {
  879. if( object != nil && [object isKindOfClass:[UIImage class]] )
  880. {
  881. resultPath = [[NSString stringWithFormat:@"%@%d", pickedMediaSavePath, j] stringByAppendingPathExtension:@"png"];
  882. if( ![self saveImageAsPNG:(UIImage *)object toPath:resultPath] )
  883. {
  884. NSLog( @"Error creating PNG image" );
  885. resultPath = nil;
  886. }
  887. }
  888. else
  889. NSLog( @"Error generating UIImage from picked live photo: %@", error );
  890. [arrayLock lock];
  891. [resultPaths addObject:( resultPath != nil ? resultPath : @"" )];
  892. [arrayLock unlock];
  893. dispatch_group_leave( group );
  894. }];
  895. }
  896. else if( [itemProvider canLoadObjectOfClass:[PHLivePhoto class]] )
  897. {
  898. dispatch_group_enter( group );
  899. [itemProvider loadObjectOfClass:[PHLivePhoto class] completionHandler:^( __kindof id<NSItemProviderReading> object, NSError *error )
  900. {
  901. if( object != nil && [object isKindOfClass:[PHLivePhoto class]] )
  902. {
  903. // Extract image data from live photo
  904. // Credit: https://stackoverflow.com/a/41341675/2373034
  905. NSArray<PHAssetResource*>* livePhotoResources = [PHAssetResource assetResourcesForLivePhoto:(PHLivePhoto *)object];
  906. PHAssetResource *livePhotoImage = nil;
  907. for( int k = 0; k < [livePhotoResources count]; k++ )
  908. {
  909. if( livePhotoResources[k].type == PHAssetResourceTypePhoto )
  910. {
  911. livePhotoImage = livePhotoResources[k];
  912. break;
  913. }
  914. }
  915. if( livePhotoImage == nil )
  916. {
  917. NSLog( @"Error extracting image data from live photo" );
  918. [arrayLock lock];
  919. [resultPaths addObject:@""];
  920. [arrayLock unlock];
  921. }
  922. else
  923. {
  924. dispatch_group_enter( group );
  925. NSString *originalFilename = livePhotoImage.originalFilename;
  926. if( originalFilename == nil || [originalFilename length] == 0 )
  927. resultPath = [NSString stringWithFormat:@"%@%d", pickedMediaSavePath, j];
  928. else
  929. resultPath = [[NSString stringWithFormat:@"%@%d", pickedMediaSavePath, j] stringByAppendingPathExtension:[originalFilename pathExtension]];
  930. [[PHAssetResourceManager defaultManager] writeDataForAssetResource:livePhotoImage toFile:[NSURL fileURLWithPath:resultPath] options:nil completionHandler:^( NSError * _Nullable error2 )
  931. {
  932. if( error2 != nil )
  933. {
  934. NSLog( @"Error saving image data from live photo: %@", error2 );
  935. resultPath = nil;
  936. }
  937. [arrayLock lock];
  938. [resultPaths addObject:( resultPath != nil ? resultPath : @"" )];
  939. [arrayLock unlock];
  940. dispatch_group_leave( group );
  941. }];
  942. }
  943. }
  944. else
  945. {
  946. NSLog( @"Error generating PHLivePhoto from picked live photo: %@", error );
  947. [arrayLock lock];
  948. [resultPaths addObject:@""];
  949. [arrayLock unlock];
  950. }
  951. dispatch_group_leave( group );
  952. }];
  953. }
  954. else
  955. {
  956. NSLog( @"Can't convert picked live photo to still image" );
  957. [arrayLock lock];
  958. [resultPaths addObject:@""];
  959. [arrayLock unlock];
  960. }
  961. }
  962. else if( [itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeMovie] || [itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeVideo] )
  963. {
  964. NSLog( @"Picked a video" );
  965. // Get the video file's path
  966. dispatch_group_enter( group );
  967. [itemProvider loadFileRepresentationForTypeIdentifier:([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeMovie] ? (NSString *)kUTTypeMovie : (NSString *)kUTTypeVideo) completionHandler:^( NSURL *url, NSError *error )
  968. {
  969. if( url != nil )
  970. {
  971. // Copy the video to a temporary location because the returned video will be deleted by the OS after this callback is completed
  972. resultPath = [url path];
  973. NSString *newPath = [[NSString stringWithFormat:@"%@%d", pickedMediaSavePath, j] stringByAppendingPathExtension:[resultPath pathExtension]];
  974. if( ![[NSFileManager defaultManager] fileExistsAtPath:newPath] || [[NSFileManager defaultManager] removeItemAtPath:newPath error:&error] )
  975. {
  976. if( [[NSFileManager defaultManager] copyItemAtPath:resultPath toPath:newPath error:&error])
  977. resultPath = newPath;
  978. else
  979. {
  980. NSLog( @"Error copying video: %@", error );
  981. resultPath = nil;
  982. }
  983. }
  984. else
  985. {
  986. NSLog( @"Error deleting existing video: %@", error );
  987. resultPath = nil;
  988. }
  989. }
  990. else
  991. NSLog( @"Error getting the picked video's path: %@", error );
  992. [arrayLock lock];
  993. [resultPaths addObject:( resultPath != nil ? resultPath : @"" )];
  994. [arrayLock unlock];
  995. dispatch_group_leave( group );
  996. }];
  997. }
  998. else
  999. {
  1000. // Unknown media type picked?
  1001. NSLog( @"Couldn't determine type of picked media: %@", itemProvider );
  1002. [arrayLock lock];
  1003. [resultPaths addObject:@""];
  1004. [arrayLock unlock];
  1005. }
  1006. }
  1007. dispatch_group_notify( group, dispatch_get_main_queue(),
  1008. ^{
  1009. if( !pickingMultipleFiles )
  1010. UnitySendMessage( "NGMediaReceiveCallbackiOS", "OnMediaReceived", [self getCString:resultPaths[0]] );
  1011. else
  1012. UnitySendMessage( "NGMediaReceiveCallbackiOS", "OnMultipleMediaReceived", [self getCString:[resultPaths componentsJoinedByString:@">"]] );
  1013. });
  1014. }
  1015. else
  1016. {
  1017. NSLog( @"No media picked" );
  1018. if( !pickingMultipleFiles )
  1019. UnitySendMessage( "NGMediaReceiveCallbackiOS", "OnMediaReceived", "" );
  1020. else
  1021. UnitySendMessage( "NGMediaReceiveCallbackiOS", "OnMultipleMediaReceived", "" );
  1022. }
  1023. }
  1024. #endif
  1025. + (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
  1026. {
  1027. NSLog( @"UIImagePickerController cancelled" );
  1028. popup = nil;
  1029. imagePicker = nil;
  1030. UnitySendMessage( "NGMediaReceiveCallbackiOS", "OnMediaReceived", "" );
  1031. [picker dismissViewControllerAnimated:NO completion:nil];
  1032. }
  1033. + (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
  1034. {
  1035. NSLog( @"UIPopoverController dismissed" );
  1036. popup = nil;
  1037. imagePicker = nil;
  1038. UnitySendMessage( "NGMediaReceiveCallbackiOS", "OnMediaReceived", "" );
  1039. }
  1040. + (NSString *)trySavePHAsset:(PHAsset *)asset atIndex:(int)filenameIndex
  1041. {
  1042. if( asset == nil )
  1043. return nil;
  1044. __block NSString *resultPath = nil;
  1045. PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
  1046. options.synchronous = YES;
  1047. options.version = PHImageRequestOptionsVersionCurrent;
  1048. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  1049. if( CHECK_IOS_VERSION( @"13.0" ) )
  1050. {
  1051. [[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset options:options resultHandler:^( NSData *imageData, NSString *dataUTI, CGImagePropertyOrientation orientation, NSDictionary *imageInfo )
  1052. {
  1053. if( imageData != nil )
  1054. resultPath = [self trySaveSourceImage:imageData withInfo:imageInfo atIndex:filenameIndex];
  1055. else
  1056. NSLog( @"Couldn't fetch raw image data" );
  1057. }];
  1058. }
  1059. else
  1060. #endif
  1061. {
  1062. [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^( NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *imageInfo )
  1063. {
  1064. if( imageData != nil )
  1065. resultPath = [self trySaveSourceImage:imageData withInfo:imageInfo atIndex:filenameIndex];
  1066. else
  1067. NSLog( @"Couldn't fetch raw image data" );
  1068. }];
  1069. }
  1070. return resultPath;
  1071. }
  1072. + (NSString *)trySaveSourceImage:(NSData *)imageData withInfo:(NSDictionary *)info atIndex:(int)filenameIndex
  1073. {
  1074. NSString *filePath = info[@"PHImageFileURLKey"];
  1075. if( filePath != nil ) // filePath can actually be an NSURL, convert it to NSString
  1076. filePath = [NSString stringWithFormat:@"%@", filePath];
  1077. if( filePath == nil || [filePath length] == 0 )
  1078. {
  1079. filePath = info[@"PHImageFileUTIKey"];
  1080. if( filePath != nil )
  1081. filePath = [NSString stringWithFormat:@"%@", filePath];
  1082. }
  1083. NSString *resultPath;
  1084. if( filePath == nil || [filePath length] == 0 )
  1085. resultPath = [NSString stringWithFormat:@"%@%d", pickedMediaSavePath, filenameIndex];
  1086. else
  1087. resultPath = [[NSString stringWithFormat:@"%@%d", pickedMediaSavePath, filenameIndex] stringByAppendingPathExtension:[filePath pathExtension]];
  1088. NSError *error;
  1089. if( ![[NSFileManager defaultManager] fileExistsAtPath:resultPath] || [[NSFileManager defaultManager] removeItemAtPath:resultPath error:&error] )
  1090. {
  1091. if( ![imageData writeToFile:resultPath atomically:YES] )
  1092. {
  1093. NSLog( @"Error copying source image to file" );
  1094. resultPath = nil;
  1095. }
  1096. }
  1097. else
  1098. {
  1099. NSLog( @"Error deleting existing image: %@", error );
  1100. resultPath = nil;
  1101. }
  1102. return resultPath;
  1103. }
  1104. // Credit: https://lists.apple.com/archives/cocoa-dev/2012/Jan/msg00052.html
  1105. + (int)getMediaTypeFromExtension:(NSString *)extension
  1106. {
  1107. CFStringRef fileUTI = UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension, (__bridge CFStringRef) extension, NULL );
  1108. // mediaType is a bitmask:
  1109. // 1: image
  1110. // 2: video
  1111. // 4: audio (not supported)
  1112. int result = 0;
  1113. if( UTTypeConformsTo( fileUTI, kUTTypeImage ) )
  1114. result = 1;
  1115. else if( CHECK_IOS_VERSION( @"9.1" ) && UTTypeConformsTo( fileUTI, kUTTypeLivePhoto ) )
  1116. result = 1;
  1117. else if( UTTypeConformsTo( fileUTI, kUTTypeMovie ) || UTTypeConformsTo( fileUTI, kUTTypeVideo ) )
  1118. result = 2;
  1119. else if( UTTypeConformsTo( fileUTI, kUTTypeAudio ) )
  1120. result = 4;
  1121. CFRelease( fileUTI );
  1122. return result;
  1123. }
  1124. // Credit: https://stackoverflow.com/a/4170099/2373034
  1125. + (NSArray *)getImageMetadata:(NSString *)path
  1126. {
  1127. int width = 0;
  1128. int height = 0;
  1129. int orientation = -1;
  1130. CGImageSourceRef imageSource = CGImageSourceCreateWithURL( (__bridge CFURLRef) [NSURL fileURLWithPath:path], nil );
  1131. if( imageSource != nil )
  1132. {
  1133. NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:(__bridge NSString *)kCGImageSourceShouldCache];
  1134. CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex( imageSource, 0, (__bridge CFDictionaryRef) options );
  1135. CFRelease( imageSource );
  1136. CGFloat widthF = 0.0f, heightF = 0.0f;
  1137. if( imageProperties != nil )
  1138. {
  1139. if( CFDictionaryContainsKey( imageProperties, kCGImagePropertyPixelWidth ) )
  1140. CFNumberGetValue( (CFNumberRef) CFDictionaryGetValue( imageProperties, kCGImagePropertyPixelWidth ), kCFNumberCGFloatType, &widthF );
  1141. if( CFDictionaryContainsKey( imageProperties, kCGImagePropertyPixelHeight ) )
  1142. CFNumberGetValue( (CFNumberRef) CFDictionaryGetValue( imageProperties, kCGImagePropertyPixelHeight ), kCFNumberCGFloatType, &heightF );
  1143. if( CFDictionaryContainsKey( imageProperties, kCGImagePropertyOrientation ) )
  1144. {
  1145. CFNumberGetValue( (CFNumberRef) CFDictionaryGetValue( imageProperties, kCGImagePropertyOrientation ), kCFNumberIntType, &orientation );
  1146. if( orientation > 4 )
  1147. {
  1148. // Landscape image
  1149. CGFloat temp = widthF;
  1150. widthF = heightF;
  1151. heightF = temp;
  1152. }
  1153. }
  1154. CFRelease( imageProperties );
  1155. }
  1156. width = (int) roundf( widthF );
  1157. height = (int) roundf( heightF );
  1158. }
  1159. return [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:width], [NSNumber numberWithInt:height], [NSNumber numberWithInt:orientation], nil];
  1160. }
  1161. + (char *)getImageProperties:(NSString *)path
  1162. {
  1163. NSArray *metadata = [self getImageMetadata:path];
  1164. int orientationUnity;
  1165. int orientation = [metadata[2] intValue];
  1166. // To understand the magic numbers, see ImageOrientation enum in NativeGallery.cs
  1167. // and http://sylvana.net/jpegcrop/exif_orientation.html
  1168. if( orientation == 1 )
  1169. orientationUnity = 0;
  1170. else if( orientation == 2 )
  1171. orientationUnity = 4;
  1172. else if( orientation == 3 )
  1173. orientationUnity = 2;
  1174. else if( orientation == 4 )
  1175. orientationUnity = 6;
  1176. else if( orientation == 5 )
  1177. orientationUnity = 5;
  1178. else if( orientation == 6 )
  1179. orientationUnity = 1;
  1180. else if( orientation == 7 )
  1181. orientationUnity = 7;
  1182. else if( orientation == 8 )
  1183. orientationUnity = 3;
  1184. else
  1185. orientationUnity = -1;
  1186. return [self getCString:[NSString stringWithFormat:@"%d>%d> >%d", [metadata[0] intValue], [metadata[1] intValue], orientationUnity]];
  1187. }
  1188. + (char *)getVideoProperties:(NSString *)path
  1189. {
  1190. CGSize size = CGSizeZero;
  1191. float rotation = 0;
  1192. long long duration = 0;
  1193. AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:nil];
  1194. if( asset != nil )
  1195. {
  1196. duration = (long long) round( CMTimeGetSeconds( [asset duration] ) * 1000 );
  1197. CGAffineTransform transform = [asset preferredTransform];
  1198. NSArray<AVAssetTrack *>* videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
  1199. if( videoTracks != nil && [videoTracks count] > 0 )
  1200. {
  1201. size = [[videoTracks objectAtIndex:0] naturalSize];
  1202. transform = [[videoTracks objectAtIndex:0] preferredTransform];
  1203. }
  1204. rotation = atan2( transform.b, transform.a ) * ( 180.0 / M_PI );
  1205. }
  1206. return [self getCString:[NSString stringWithFormat:@"%d>%d>%lld>%f", (int) roundf( size.width ), (int) roundf( size.height ), duration, rotation]];
  1207. }
  1208. + (char *)getVideoThumbnail:(NSString *)path savePath:(NSString *)savePath maximumSize:(int)maximumSize captureTime:(double)captureTime
  1209. {
  1210. AVAssetImageGenerator *thumbnailGenerator = [[AVAssetImageGenerator alloc] initWithAsset:[[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:path] options:nil]];
  1211. thumbnailGenerator.appliesPreferredTrackTransform = YES;
  1212. thumbnailGenerator.maximumSize = CGSizeMake( (CGFloat) maximumSize, (CGFloat) maximumSize );
  1213. thumbnailGenerator.requestedTimeToleranceBefore = kCMTimeZero;
  1214. thumbnailGenerator.requestedTimeToleranceAfter = kCMTimeZero;
  1215. if( captureTime < 0.0 )
  1216. captureTime = 0.0;
  1217. else
  1218. {
  1219. AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:nil];
  1220. if( asset != nil )
  1221. {
  1222. double videoDuration = CMTimeGetSeconds( [asset duration] );
  1223. if( videoDuration > 0.0 && captureTime >= videoDuration - 0.1 )
  1224. {
  1225. if( captureTime > videoDuration )
  1226. captureTime = videoDuration;
  1227. thumbnailGenerator.requestedTimeToleranceBefore = CMTimeMakeWithSeconds( 1.0, 600 );
  1228. }
  1229. }
  1230. }
  1231. NSError *error = nil;
  1232. CGImageRef image = [thumbnailGenerator copyCGImageAtTime:CMTimeMakeWithSeconds( captureTime, 600 ) actualTime:nil error:&error];
  1233. if( image == nil )
  1234. {
  1235. if( error != nil )
  1236. NSLog( @"Error generating video thumbnail: %@", error );
  1237. else
  1238. NSLog( @"Error generating video thumbnail..." );
  1239. return [self getCString:@""];
  1240. }
  1241. UIImage *thumbnail = [[UIImage alloc] initWithCGImage:image];
  1242. CGImageRelease( image );
  1243. if( ![UIImagePNGRepresentation( thumbnail ) writeToFile:savePath atomically:YES] )
  1244. {
  1245. NSLog( @"Error saving thumbnail image" );
  1246. return [self getCString:@""];
  1247. }
  1248. return [self getCString:savePath];
  1249. }
  1250. + (BOOL)saveImageAsPNG:(UIImage *)image toPath:(NSString *)resultPath
  1251. {
  1252. return [UIImagePNGRepresentation( [self scaleImage:image maxSize:16384] ) writeToFile:resultPath atomically:YES];
  1253. }
  1254. + (UIImage *)scaleImage:(UIImage *)image maxSize:(int)maxSize
  1255. {
  1256. CGFloat width = image.size.width;
  1257. CGFloat height = image.size.height;
  1258. UIImageOrientation orientation = image.imageOrientation;
  1259. if( width <= maxSize && height <= maxSize && orientation != UIImageOrientationDown &&
  1260. orientation != UIImageOrientationLeft && orientation != UIImageOrientationRight &&
  1261. orientation != UIImageOrientationLeftMirrored && orientation != UIImageOrientationRightMirrored &&
  1262. orientation != UIImageOrientationUpMirrored && orientation != UIImageOrientationDownMirrored )
  1263. return image;
  1264. CGFloat scaleX = 1.0f;
  1265. CGFloat scaleY = 1.0f;
  1266. if( width > maxSize )
  1267. scaleX = maxSize / width;
  1268. if( height > maxSize )
  1269. scaleY = maxSize / height;
  1270. // Credit: https://github.com/mbcharbonneau/UIImage-Categories/blob/master/UIImage%2BAlpha.m
  1271. CGImageAlphaInfo alpha = CGImageGetAlphaInfo( image.CGImage );
  1272. BOOL hasAlpha = alpha == kCGImageAlphaFirst || alpha == kCGImageAlphaLast || alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedLast;
  1273. CGFloat scaleRatio = scaleX < scaleY ? scaleX : scaleY;
  1274. CGRect imageRect = CGRectMake( 0, 0, width * scaleRatio, height * scaleRatio );
  1275. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  1276. // Resize image with UIGraphicsImageRenderer (Apple's recommended API) if possible
  1277. if( CHECK_IOS_VERSION( @"10.0" ) )
  1278. {
  1279. UIGraphicsImageRendererFormat *format = [image imageRendererFormat];
  1280. format.opaque = !hasAlpha;
  1281. format.scale = image.scale;
  1282. UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:imageRect.size format:format];
  1283. image = [renderer imageWithActions:^( UIGraphicsImageRendererContext* _Nonnull myContext )
  1284. {
  1285. [image drawInRect:imageRect];
  1286. }];
  1287. }
  1288. else
  1289. #endif
  1290. {
  1291. UIGraphicsBeginImageContextWithOptions( imageRect.size, !hasAlpha, image.scale );
  1292. [image drawInRect:imageRect];
  1293. image = UIGraphicsGetImageFromCurrentImageContext();
  1294. UIGraphicsEndImageContext();
  1295. }
  1296. return image;
  1297. }
  1298. + (char *)loadImageAtPath:(NSString *)path tempFilePath:(NSString *)tempFilePath maximumSize:(int)maximumSize
  1299. {
  1300. // Check if the image can be loaded by Unity without requiring a conversion to PNG
  1301. // Credit: https://stackoverflow.com/a/12048937/2373034
  1302. NSString *extension = [path pathExtension];
  1303. BOOL conversionNeeded = [extension caseInsensitiveCompare:@"jpg"] != NSOrderedSame && [extension caseInsensitiveCompare:@"jpeg"] != NSOrderedSame && [extension caseInsensitiveCompare:@"png"] != NSOrderedSame;
  1304. if( !conversionNeeded )
  1305. {
  1306. // Check if the image needs to be processed at all
  1307. NSArray *metadata = [self getImageMetadata:path];
  1308. int orientationInt = [metadata[2] intValue]; // 1: correct orientation, [1,8]: valid orientation range
  1309. if( orientationInt == 1 && [metadata[0] intValue] <= maximumSize && [metadata[1] intValue] <= maximumSize )
  1310. return [self getCString:path];
  1311. }
  1312. UIImage *image = [UIImage imageWithContentsOfFile:path];
  1313. if( image == nil )
  1314. return [self getCString:path];
  1315. UIImage *scaledImage = [self scaleImage:image maxSize:maximumSize];
  1316. if( conversionNeeded || scaledImage != image )
  1317. {
  1318. if( ![UIImagePNGRepresentation( scaledImage ) writeToFile:tempFilePath atomically:YES] )
  1319. {
  1320. NSLog( @"Error creating scaled image" );
  1321. return [self getCString:path];
  1322. }
  1323. return [self getCString:tempFilePath];
  1324. }
  1325. else
  1326. return [self getCString:path];
  1327. }
  1328. // Credit: https://stackoverflow.com/a/37052118/2373034
  1329. + (char *)getCString:(NSString *)source
  1330. {
  1331. if( source == nil )
  1332. source = @"";
  1333. const char *sourceUTF8 = [source UTF8String];
  1334. char *result = (char*) malloc( strlen( sourceUTF8 ) + 1 );
  1335. strcpy( result, sourceUTF8 );
  1336. return result;
  1337. }
  1338. @end
  1339. extern "C" int _NativeGallery_CheckPermission( int readPermission, int permissionFreeMode )
  1340. {
  1341. return [UNativeGallery checkPermission:( readPermission == 1 ) permissionFreeMode:( permissionFreeMode == 1 )];
  1342. }
  1343. extern "C" int _NativeGallery_RequestPermission( int readPermission, int permissionFreeMode, int asyncMode )
  1344. {
  1345. return [UNativeGallery requestPermission:( readPermission == 1 ) permissionFreeMode:( permissionFreeMode == 1 ) asyncMode:( asyncMode == 1 )];
  1346. }
  1347. extern "C" void _NativeGallery_ShowLimitedLibraryPicker()
  1348. {
  1349. return [UNativeGallery showLimitedLibraryPicker];
  1350. }
  1351. extern "C" int _NativeGallery_CanOpenSettings()
  1352. {
  1353. return [UNativeGallery canOpenSettings];
  1354. }
  1355. extern "C" void _NativeGallery_OpenSettings()
  1356. {
  1357. [UNativeGallery openSettings];
  1358. }
  1359. extern "C" int _NativeGallery_CanPickMultipleMedia()
  1360. {
  1361. return [UNativeGallery canPickMultipleMedia];
  1362. }
  1363. extern "C" void _NativeGallery_ImageWriteToAlbum( const char* path, const char* album, int permissionFreeMode )
  1364. {
  1365. [UNativeGallery saveMedia:[NSString stringWithUTF8String:path] albumName:[NSString stringWithUTF8String:album] isImg:YES permissionFreeMode:( permissionFreeMode == 1 )];
  1366. }
  1367. extern "C" void _NativeGallery_VideoWriteToAlbum( const char* path, const char* album, int permissionFreeMode )
  1368. {
  1369. [UNativeGallery saveMedia:[NSString stringWithUTF8String:path] albumName:[NSString stringWithUTF8String:album] isImg:NO permissionFreeMode:( permissionFreeMode == 1 )];
  1370. }
  1371. extern "C" void _NativeGallery_PickMedia( const char* mediaSavePath, int mediaType, int permissionFreeMode, int selectionLimit )
  1372. {
  1373. [UNativeGallery pickMedia:mediaType savePath:[NSString stringWithUTF8String:mediaSavePath] permissionFreeMode:( permissionFreeMode == 1 ) selectionLimit:selectionLimit];
  1374. }
  1375. extern "C" int _NativeGallery_IsMediaPickerBusy()
  1376. {
  1377. return [UNativeGallery isMediaPickerBusy];
  1378. }
  1379. extern "C" int _NativeGallery_GetMediaTypeFromExtension( const char* extension )
  1380. {
  1381. return [UNativeGallery getMediaTypeFromExtension:[NSString stringWithUTF8String:extension]];
  1382. }
  1383. extern "C" char* _NativeGallery_GetImageProperties( const char* path )
  1384. {
  1385. return [UNativeGallery getImageProperties:[NSString stringWithUTF8String:path]];
  1386. }
  1387. extern "C" char* _NativeGallery_GetVideoProperties( const char* path )
  1388. {
  1389. return [UNativeGallery getVideoProperties:[NSString stringWithUTF8String:path]];
  1390. }
  1391. extern "C" char* _NativeGallery_GetVideoThumbnail( const char* path, const char* thumbnailSavePath, int maxSize, double captureTimeInSeconds )
  1392. {
  1393. return [UNativeGallery getVideoThumbnail:[NSString stringWithUTF8String:path] savePath:[NSString stringWithUTF8String:thumbnailSavePath] maximumSize:maxSize captureTime:captureTimeInSeconds];
  1394. }
  1395. extern "C" char* _NativeGallery_LoadImageAtPath( const char* path, const char* temporaryFilePath, int maxSize )
  1396. {
  1397. return [UNativeGallery loadImageAtPath:[NSString stringWithUTF8String:path] tempFilePath:[NSString stringWithUTF8String:temporaryFilePath] maximumSize:maxSize];
  1398. }