88144


UIKitのPHPickerViewControllerをUIViewControllerRepresentableでSwiftUIで使えるようにして、写真アプリから動画を取ってきて、１フレームを画像(サムネイル)として`AVAssetImageGenerator`で取得して表示しようとしているのですが、一部の動画がうまくいきません。

画面を録画したような動画は全てうまくいきません。

### エラー内容

[code]

    Error Domain=AVFoundationErrorDomain Code=-11829 "Cannot Open" UserInfo={NSLocalizedFailureReason=This media may be damaged., NSLocalizedDescription=Cannot Open, NSUnderlyingError=0x28233e970 {Error Domain=NSOSStatusErrorDomain Code=-12848 "(null)"}}
    
[/code]

### ファイル先

[code]

    /private/var/mobile/Containers/Data/Application/E28C8686-3039-426C-9EB0-97237DC4D785/tmp/version=1&uuid=4231FFD7-0969-417C-8A9A-07062D463E40&mode=compatible.mp4
    
[/code]

[code]

    struct ContentView: View {
      @State var results: [PHPickerResult] = []
      @State var isPresentedPhotoPicker = false
      @State var images: [NSItemProviderReading] = []
      @State var tappedVideoURL: URL?
      
      private func loadPhoto(from provider: NSItemProvider) async throws -> NSItemProviderReading? {
        if provider.canLoadObject(ofClass: PHLivePhoto.self) {
          return try await provider.loadObject(ofClass: PHLivePhoto.self)
        }
        
        else if provider.canLoadObject(ofClass: UIImage.self) {
          return try await provider.loadObject(ofClass: UIImage.self)
        }
        
        else if provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
          let secureCoding = try await provider.loadItem(forTypeIdentifier: UTType.movie.identifier, options: nil)
          
          if let url = secureCoding as? URL {
            let localURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
            
            try? FileManager.default.removeItem(at: localURL)
            try FileManager.default.copyItem(at: url, to: localURL)
            
            return localURL as NSItemProviderReading
          }
        }
        
        return nil
      }
      
      private func loadPhotos(results: [PHPickerResult]) async throws -> [NSItemProviderReading] {
        var images: [NSItemProviderReading?] = []
        
        try await withThrowingTaskGroup(of: Optional<NSItemProviderReading>.self) { group in
          for provider in results.map(\.itemProvider) {
            group.addTask {
              return try await loadPhoto(from: provider)
            }
          }
          
          for try await data in group {
            images.append(data)
          }
        }
        
        return images.compactMap { $0 }
      }
      
      func getThumbnailImage(url: URL) -> UIImage? {
        let asset: AVAsset = .init(url: url)
        let generator = AVAssetImageGenerator(asset: asset)
        do {
          let cgImage = try generator.copyCGImage(at: asset.duration, actualTime: nil)
          let uiImage: UIImage = .init(cgImage: cgImage)
          return uiImage
        } catch {
          print(error)
          return nil
        }
      }
      
      var body: some View {
        VStack {
          Button(action: {
            isPresentedPhotoPicker.toggle()
          }, label: {
            Image(systemName: "photo")
          })
          .sheet(isPresented: $isPresentedPhotoPicker) {
            PhotoPicker(results: $results)
          }
          
          ScrollView(.horizontal) {
            HStack {
              ForEach(0..<images.count, id: \.self) { index in
                if let livePhoto = images[index] as? PHLivePhoto {
                  LivePhoto(livePhoto: livePhoto)
                    .aspectRatio(contentMode: .fit)
                    .frame(maxWidth: 240)
                }
                else if let uiimage = images[index] as? UIImage {
                  Image(uiImage: uiimage)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(height: 240)
                } else if let url = images[index] as? URL {
                  ZStack {
                    if let uiImage = getThumbnailImage(url: url) {
                      Image(uiImage: uiImage)
                        .resizable()
                        .frame(maxWidth: 200)
                    } else {
                      Image(systemName: "heart")
                        .resizable()
                        .frame(width: 200)
                    }
                    Image(systemName: "play")
                  }
                  .onTapGesture {
                    tappedVideoURL = url
                  }
                  .sheet(item: $tappedVideoURL) { newURL in
                    let _ = print(newURL.path)
                    let player: AVPlayer = .init(url: newURL)
                    VideoPlayer(player: player)
                      .onAppear {
                        player.play()
                      }
                  }
                }
              }
            }
          }
        }
        .onChange(of: results) { newResults in
          Task {
            do {
              let newImages = try await loadPhotos(results: newResults)
              print(newImages.count)
              self.images = newImages
            } catch {
              print(error)
            }
          }
        }
      }
    }
    
[/code]

[code]

    struct PhotoPicker: UIViewControllerRepresentable {
      @Binding public var results: [PHPickerResult]
        
      init(results: Binding<[PHPickerResult]>) {
        self._results = results
      }
      
      func makeUIViewController(context: Context) -> PHPickerViewController {
        var configuration = PHPickerConfiguration(photoLibrary: .shared())
        configuration.preselectedAssetIdentifiers = results.compactMap(\.assetIdentifier)
        configuration.selectionLimit = 0
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator
        return picker
      }
      
      func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
      }
      
      func makeCoordinator() -> Coordinator {
        return Coordinator(self)
      }
      
      class Coordinator: NSObject, PHPickerViewControllerDelegate, UINavigationControllerDelegate {
        var parent: PhotoPicker
        
        init(_ parent: PhotoPicker) {
          self.parent = parent
        }
        
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
          picker.dismiss(animated: true)
          parent.results = results
        }
      }
    }
    
[/code]

[code]

    extension NSItemProvider {
      public func loadFileRepresentation(forTypeIdentifier typeIdentifier: String) async throws -> URL {
        try await withCheckedThrowingContinuation { continuation in
          self.loadFileRepresentation(forTypeIdentifier: typeIdentifier) { url, error in
            if let error = error {
              return continuation.resume(throwing: error)
            }
            
            guard let url = url else {
              return continuation.resume(throwing: NSError())
            }
            
            continuation.resume(returning: url)
          }.resume()
        }
      }
      
      public func loadObject(ofClass aClass : NSItemProviderReading.Type) async throws -> NSItemProviderReading {
        try await withCheckedThrowingContinuation { continuation in
          self.loadObject(ofClass: aClass) { data, error in
            if let error = error {
              return continuation.resume(throwing: error)
            }
            
            guard let data = data else {
              return continuation.resume(throwing: NSError())
            }
            
            continuation.resume(returning: data)
          }.resume()
        }
      }
    }
    
[/code]

[code]

    struct LivePhoto: UIViewRepresentable {
        let livePhoto: PHLivePhoto
        
        func makeUIView(context: Context) -> PHLivePhotoView {
            let livePhotoView = PHLivePhotoView()
            livePhotoView.livePhoto = livePhoto
            return livePhotoView
        }
        
        func updateUIView(_ livePhotoView: PHLivePhotoView, context: Context) {
        }
    }
    
[/code]

[code]

    extension URL: Identifiable {
      public var id: URL {
        return self
      }
    }
    
[/code]


CC BY-SA 4.0
2022-04-01T16:53:09.397
88137
2022-04-02T15:42:08.543
40856
question
swift swiftui uikit
SwiftUIでPHPickerViewControllerから取ってきた(特定の)動画のサムネイルが取得できない


`PHPickerConfiguration`の`preferredAssetRepresentationMode`を`current`にしたら取得できるようになりました。


CC BY-SA 4.0
2022-04-02T15:42:08.543
88144
2022-04-02T15:42:08.543
40856
88137
answer