RCTAssetsLibraryRequestHandler.mm 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTAssetsLibraryRequestHandler.h"
  8. #import <atomic>
  9. #import <dlfcn.h>
  10. #import <memory>
  11. #import <objc/runtime.h>
  12. #import <Photos/Photos.h>
  13. #import <MobileCoreServices/MobileCoreServices.h>
  14. #import <React/RCTBridge.h>
  15. #import <React/RCTNetworking.h>
  16. #import <React/RCTUtils.h>
  17. #import <ReactCommon/RCTTurboModule.h>
  18. #import "RCTCameraRollPlugins.h"
  19. @interface RCTAssetsLibraryRequestHandler() <RCTTurboModule>
  20. @end
  21. @implementation RCTAssetsLibraryRequestHandler
  22. RCT_EXPORT_MODULE()
  23. #pragma mark - RCTURLRequestHandler
  24. - (BOOL)canHandleRequest:(NSURLRequest *)request
  25. {
  26. if (![PHAsset class]) {
  27. return NO;
  28. }
  29. return [request.URL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame
  30. || [request.URL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame
  31. || [request.URL.scheme caseInsensitiveCompare:RCTNetworkingPHUploadHackScheme] == NSOrderedSame;
  32. }
  33. - (id)sendRequest:(NSURLRequest *)request
  34. withDelegate:(id<RCTURLRequestDelegate>)delegate
  35. {
  36. auto cancelled = std::make_shared<std::atomic<bool>>(false);
  37. void (^cancellationBlock)(void) = ^{
  38. cancelled->store(true);
  39. };
  40. NSURL *requestURL = request.URL;
  41. BOOL isPHUpload = [requestURL.scheme caseInsensitiveCompare:RCTNetworkingPHUploadHackScheme] == NSOrderedSame;
  42. if (isPHUpload) {
  43. requestURL = [NSURL URLWithString:[@"ph" stringByAppendingString:[requestURL.absoluteString substringFromIndex:RCTNetworkingPHUploadHackScheme.length]]];
  44. }
  45. if (!requestURL) {
  46. NSString *const msg = [NSString stringWithFormat:@"Cannot send request without URL"];
  47. [delegate URLRequest:cancellationBlock didCompleteWithError:RCTErrorWithMessage(msg)];
  48. return cancellationBlock;
  49. }
  50. PHFetchResult<PHAsset *> *fetchResult;
  51. if ([requestURL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame) {
  52. // Fetch assets using PHAsset localIdentifier (recommended)
  53. NSString *const localIdentifier = [requestURL.absoluteString substringFromIndex:@"ph://".length];
  54. fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil];
  55. } else if ([requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame) {
  56. // This is the older, deprecated way of fetching assets from assets-library
  57. // using the "assets-library://" protocol
  58. fetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[requestURL] options:nil];
  59. } else {
  60. NSString *const msg = [NSString stringWithFormat:@"Cannot send request with unknown protocol: %@", requestURL];
  61. [delegate URLRequest:cancellationBlock didCompleteWithError:RCTErrorWithMessage(msg)];
  62. return cancellationBlock;
  63. }
  64. if (![fetchResult firstObject]) {
  65. NSString *errorMessage = [NSString stringWithFormat:@"Failed to load asset"
  66. " at URL %@ with no error message.", requestURL];
  67. NSError *error = RCTErrorWithMessage(errorMessage);
  68. [delegate URLRequest:cancellationBlock didCompleteWithError:error];
  69. return cancellationBlock;
  70. }
  71. if (cancelled->load()) {
  72. return cancellationBlock;
  73. }
  74. PHAsset *const _Nonnull asset = [fetchResult firstObject];
  75. // When we're uploading a video, provide the full data but in any other case,
  76. // provide only the thumbnail of the video.
  77. if (asset.mediaType == PHAssetMediaTypeVideo && isPHUpload) {
  78. PHVideoRequestOptions *const requestOptions = [PHVideoRequestOptions new];
  79. requestOptions.networkAccessAllowed = YES;
  80. [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:requestOptions resultHandler:^(AVAsset * _Nullable avAsset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
  81. NSError *error = [info objectForKey:PHImageErrorKey];
  82. if (error) {
  83. [delegate URLRequest:cancellationBlock didCompleteWithError:error];
  84. return;
  85. }
  86. if (![avAsset isKindOfClass:[AVURLAsset class]]) {
  87. error = [NSError errorWithDomain:RCTErrorDomain code:0 userInfo:
  88. @{
  89. NSLocalizedDescriptionKey: @"Unable to load AVURLAsset",
  90. }];
  91. [delegate URLRequest:cancellationBlock didCompleteWithError:error];
  92. return;
  93. }
  94. NSData *data = [NSData dataWithContentsOfURL:((AVURLAsset *)avAsset).URL
  95. options:(NSDataReadingOptions)0
  96. error:&error];
  97. if (data) {
  98. NSURLResponse *const response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:nil expectedContentLength:data.length textEncodingName:nil];
  99. [delegate URLRequest:cancellationBlock didReceiveResponse:response];
  100. [delegate URLRequest:cancellationBlock didReceiveData:data];
  101. }
  102. [delegate URLRequest:cancellationBlock didCompleteWithError:error];
  103. }];
  104. } else {
  105. // By default, allow downloading images from iCloud
  106. PHImageRequestOptions *const requestOptions = [PHImageRequestOptions new];
  107. requestOptions.networkAccessAllowed = YES;
  108. [[PHImageManager defaultManager] requestImageDataForAsset:asset
  109. options:requestOptions
  110. resultHandler:^(NSData * _Nullable imageData,
  111. NSString * _Nullable dataUTI,
  112. UIImageOrientation orientation,
  113. NSDictionary * _Nullable info) {
  114. NSError *const error = [info objectForKey:PHImageErrorKey];
  115. if (error) {
  116. [delegate URLRequest:cancellationBlock didCompleteWithError:error];
  117. return;
  118. }
  119. NSInteger const length = [imageData length];
  120. CFStringRef const dataUTIStringRef = (__bridge CFStringRef _Nonnull)(dataUTI);
  121. CFStringRef const mimeType = UTTypeCopyPreferredTagWithClass(dataUTIStringRef, kUTTagClassMIMEType);
  122. NSURLResponse *const response = [[NSURLResponse alloc] initWithURL:request.URL
  123. MIMEType:(__bridge NSString *)(mimeType)
  124. expectedContentLength:length
  125. textEncodingName:nil];
  126. CFRelease(mimeType);
  127. [delegate URLRequest:cancellationBlock didReceiveResponse:response];
  128. [delegate URLRequest:cancellationBlock didReceiveData:imageData];
  129. [delegate URLRequest:cancellationBlock didCompleteWithError:nil];
  130. }];
  131. }
  132. return cancellationBlock;
  133. }
  134. - (void)cancelRequest:(id)requestToken
  135. {
  136. ((void (^)(void))requestToken)();
  137. }
  138. @end
  139. Class RCTAssetsLibraryRequestHandlerCls(void) {
  140. return RCTAssetsLibraryRequestHandler.class;
  141. }