How to get video duration for an AVAsset in Swift

910 Views Asked by At

when trying to use .duration to get the length of the video i get the warning "'duration' was deprecated in iOS 16.0: Use load(.duration) instead", but when i try to use load(.duration) i get the errorr " Type of expression is ambiguous without more context" on line FileStorage.uploadVideo(videoData, directory: fileDirectory).

using duration

   func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        picker.dismiss(animated: true, completion: nil)
        let currentUser = FUser.currentUser()!

        let message = Message()

        message.id = UUID().uuidString
        message.chatRoomId = chatId
        message.senderId = currentUser.objectId
        message.senderName = currentUser.username

        message.sentDate = Date()
        message.senderInitials = String(currentUser.username.first!)
        message.status = kSENT
        message.message = "Video Message"

        if let videoUrl = info[.mediaURL] as? URL {
            let fileName = Date().stringDate()
            let fileDirectory = "MediaMessages/Video/" + "\(chatId)/" + "_\(fileName)" + ".mov"

            do {
                let videoData = try NSData(contentsOf: videoUrl, options: .mappedIfSafe)
                FileStorage.saveVideoLocally(videoData: videoData , fileName: fileName)
                FileStorage.uploadVideo(videoData, directory: fileDirectory) { (videoURL) in

                    if let videoURL = videoURL, let url = URL(string: videoURL) {
                        let video = AVURLAsset(url: url)
                        let duration = video.duration.seconds

                        if duration > 30 {
                            let alert = UIAlertController(title: "Error", message: "The selected video must be less than 30 seconds.", preferredStyle: .alert)
                            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                            self.present(alert, animated: true, completion: nil)
                        } else {
                            let outgoingMessage = OutgoingMessage(message: message, videoURL: videoURL, memberId: [FUser.currentId(), self.recipientId])
                            outgoingMessage.sendMessage(chatRoomId: self.chatId, messageId: message.id, memberIds: [FUser.currentId(), self.recipientId])
                            PushNotificationService.shared.sendPushNotificationTo(userIds: [self.recipientId], body: message.message)
                        }
                    } else {
                        print("Error: invalid video URL")
                        return
                    }
                }

            } catch {
                print(error)
                return
            }

            FirebaseListener.shared.updateRecents(chatRoomId: chatId, lastMessage: message.message)
        }

using load.duration

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        picker.dismiss(animated: true, completion: nil)
        let currentUser = FUser.currentUser()!

        let message = Message()

        message.id = UUID().uuidString
        message.chatRoomId = chatId
        message.senderId = currentUser.objectId
        message.senderName = currentUser.username

        message.sentDate = Date()
        message.senderInitials = String(currentUser.username.first!)
        message.status = kSENT
        message.message = "Video Message"

        if let videoUrl = info[.mediaURL] as? URL {
            let fileName = Date().stringDate()
            let fileDirectory = "MediaMessages/Video/" + "\(chatId)/" + "_\(fileName)" + ".mov"

            do {
                let videoData = try NSData(contentsOf: videoUrl, options: .mappedIfSafe)
                FileStorage.saveVideoLocally(videoData: videoData , fileName: fileName)
                FileStorage.uploadVideo(videoData, directory: fileDirectory) { (videoURL: String?) in

                    if let videoURL = videoURL, let url = URL(string: videoURL) {
                        let video = AVURLAsset(url: url)
                        video.load(.duration) { result in
                            switch result {
                            case .success(let durationValue):
                                let duration = CMTimeGetSeconds(durationValue as! CMTime)

                                if duration > 30 {
                                    let alert = UIAlertController(title: "Error", message: "The selected video must be less than 30 seconds.", preferredStyle: .alert)
                                    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                                    self.present(alert, animated: true, completion: nil)
                                } else {
                                    let outgoingMessage = OutgoingMessage(message: message, videoURL: videoURL,  memberId: [FUser.currentId(), self.recipientId])
                                    outgoingMessage.sendMessage(chatRoomId: self.chatId, messageId: message.id, memberIds: [FUser.currentId(), self.recipientId])
                                    PushNotificationService.shared.sendPushNotificationTo(userIds: [self.recipientId], body: message.message)
                                }
                            case .failure(let error):
                                print("Error: \(error)")
                                return
                            }
                        }
                    } else {
                        print("Error: invalid video URL")
                        return
                    }
                }

            } catch {
`                print(error)
                return
            }

            FirebaseListener.shared.updateRecents(chatRoomId: chatId, lastMessage: message.message)
        }
    }

    
    class func uploadVideo(_ video: NSData, directory: String, completion: @escaping (_ documentLink: String?) -> Void) {
            let storageRef = storage.reference(forURL: kFILEREFERENCE).child(directory)
            
           //let imageData = image.jpegData(compressionQuality: 0.6)
            
            var task: StorageUploadTask!
            
            task = storageRef.putData(video as Data, metadata: nil, completion: { (metaData, error) in
                
                task.removeAllObservers()
                ProgressHUD.dismiss()
                
                if error != nil {
                    print("error uploading video", error!.localizedDescription)
                    return
                }
                
                storageRef.downloadURL { (url, error) in
                    
                    guard let downloadUrl = url else {
                        completion(nil)
                        return
                    }
                    print("we have uploaded video to ", downloadUrl.absoluteString)
                    completion(downloadUrl.absoluteString)
                }
            })
            
            
            task.observe(StorageTaskStatus.progress) { (snapshot) in
                
                let progress = snapshot.progress!.completedUnitCount / snapshot.progress!.totalUnitCount
                ProgressHUD.showProgress(CGFloat(progress))
            }
            
            
        }
1

There are 1 best solutions below

0
Rob Napier On

.load() is an async method. I'm not certain where you're getting the closure syntax here. You need to move this work to an async context, and await the result:

    if let videoURL = videoURL, let url = URL(string: videoURL) {
        let video = AVURLAsset(url: url)
        Task { @MainActor in
            let duration = try await video.load(.duration).seconds
            // ...
        }
    } else {
       // ...
    }

I've moved the Task to the MainActor, since you perform UIKit-related operation in the block. (This may be implicit in a UIViewController, so I'm not 100% certain that @MainActor in is required in your situation.)

Note that you're doing a lot of possibly expensive I/O operations on the main queue here. For example, NSData(contentsOf:options:) is not appropriate for loading large files on the main queue (it's barely appropriate for reading small files). This can cause hangs on the UI thread, and even terminate the app if it takes too long (requesting memory mapping does not promise this won't happen). Most of this code should probably be built around AVAsset and its related Reader and Writer directly, rather than trying to manage things by hand with NSData.