SwiftUI - How to load a video from Photos

Hello,

I'm currently stuck trying to load a Video - previously picked by an PHPicker. In the photos you can see the current Views. The Videoplayer View stays unresponsive but in the first frames when the picker disappears you can see the thumbnail and a play button. What am i doing wrong? Should i load the file differently?

This is my Picker:

struct VideoPicker: UIViewControllerRepresentable{
    @Binding var videoURL:String?

func makeUIViewController(context: Context) -> PHPickerViewController {

        var config = PHPickerConfiguration()
        config.filter = .videos
        let picker = PHPickerViewController(configuration: config)
        picker.delegate = context.coordinator
        return picker
    }

func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}

func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

class Coordinator:NSObject, PHPickerViewControllerDelegate{

let parent:VideoPicker
init(_ parent: VideoPicker){
     self.parent = parent
}

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {

picker.dismiss(animated: true) {
    // do something on dismiss
}

            

guard let provider = results.first?.itemProvider else {return}
provider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in

guard let url = url else {return}

self.parent.videoURL = url.absoluteString
print(url)
print(FileManager.default.fileExists(atPath: url.path))

}
}
}
}

I'm totally able to get the URL (local URL - e.g.:

file:///private/var/mobile/Containers/Data/Application/22126131-CBF4-4CAF-B943-22540F1096E1/tmp/.com.apple.Foundation.NSItemProvider.

)

But for the life of me - the VideoPlayer won't play it:

struct VideoView:View{
    @Binding var videoURL:String?
    @Binding var showVideoPicker:Bool

    var body: some View{
        if let videoURL = videoURL {
            VideoPlayer(player: AVPlayer(url: URL(fileURLWithPath:videoURL)))
 .frame(width: 100, height: 100, alignment: .center)
 .clipShape(RoundedRectangle(cornerRadius: 16))
.onLongPressGesture{
    generator.feedback.notificationOccurred(.success)
    showVideoPicker.toggle()
}
}
else{
    Text("...")
  }
}
}

Maybe somebody can point me in the right direction because in every Tutorial everybody uses stuff that's bundled to play a video. I want to use Videos from the Photos APP (apple). The videoURL is a @State in my ContentView. It gets updated through the VideoPicker. Sorry for the formatting this is my first Post.

Accepted Reply

Okay i just fixed it by myself.

If anybody comes across this and has the same problem: You first have to copy the video to your APP storage. Well at least that's what it made it work for me. In your Picker do something like this after receiving the URL from the picker. I always thought the videoplayer could just play the Video from the given URL. It can't. In my picker delegate method:

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            picker.dismiss(animated: true) {
                // do something on dismiss
            }
           
            guard let provider = results.first?.itemProvider else {return}
            provider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
                   guard error == nil else{
                   print(error)
                   return
                }
                // receiving the video-local-URL / filepath
                guard let url = url else {return}
                // create a new filename
                let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)"
                // create new URL
                let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
                // copy item to APP Storage
                try? FileManager.default.copyItem(at: url, to: newUrl)
                self.parent.videoURL = newUrl.absoluteString
            }
        }

be sure to remove any gestures on VideoView (my longpressgesture messed with the standard interactions given by the AVPlayer)

  • Thank you for sharing your solution! I have been trying to figure this out for quite some time. But I still wonder about one thing: how do you save the url, or some key to it, permanently so the video can be watched and deleted after closing the app? I tried saving the url to core data, but with every reboot it changes. Then I read that book marks are the solution, but I am a beginner and stuck implementing them. Any ideas on how to do this? Help would be much appreciated

Add a Comment

Replies

Okay i just fixed it by myself.

If anybody comes across this and has the same problem: You first have to copy the video to your APP storage. Well at least that's what it made it work for me. In your Picker do something like this after receiving the URL from the picker. I always thought the videoplayer could just play the Video from the given URL. It can't. In my picker delegate method:

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            picker.dismiss(animated: true) {
                // do something on dismiss
            }
           
            guard let provider = results.first?.itemProvider else {return}
            provider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
                   guard error == nil else{
                   print(error)
                   return
                }
                // receiving the video-local-URL / filepath
                guard let url = url else {return}
                // create a new filename
                let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)"
                // create new URL
                let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
                // copy item to APP Storage
                try? FileManager.default.copyItem(at: url, to: newUrl)
                self.parent.videoURL = newUrl.absoluteString
            }
        }

be sure to remove any gestures on VideoView (my longpressgesture messed with the standard interactions given by the AVPlayer)

  • Thank you for sharing your solution! I have been trying to figure this out for quite some time. But I still wonder about one thing: how do you save the url, or some key to it, permanently so the video can be watched and deleted after closing the app? I tried saving the url to core data, but with every reboot it changes. Then I read that book marks are the solution, but I am a beginner and stuck implementing them. Any ideas on how to do this? Help would be much appreciated

Add a Comment

Watch this example:

https://gist.github.com/FireRayo/508952f39b5539bba08c8eb5e18ebfbc

This is how I implemented video picker:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    if let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL {
      DispatchQueue.main.async {
        self.parent.videoPlayer = AVPlayer(url: url)
      }
    }
    parent.isShowPhotoLibrary = false
  }

Hi I just wanted to document an issue I was having with the accepted answer and my solution to fix it just in case anyone is running into the same issue as me. When I used the

let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName) for videos my AVPlayer was not able to play the video from this local file url. I was getting the error where the AVPlayer's play button had a line through it and then black screen. My solution to fix this bug was to use the userDomainMask to make sure the file was accessible. Here it is:

let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let newURL = documentsURL.appendingPathComponent("temp.mov")
do {
    try FileManager.default.copyItem(at: url, to: newURL)
} catch {
    print("File manager copy failed")
}