Issue playing slow-mo AVAsset in AVPlayer
Asked Answered
B

5

6

I'm trying to play an slow motion video (filmed by the user's iPhone) in an AVPlayer.

I am retrieving the AVAsset with a request on a PHAsset from a picker:

   [manager requestAVAssetForVideo:PHAsset
                           options:videoRequestOptions
                     resultHandler:^(AVAsset * avasset, AVAudioMix * audioMix, NSDictionary * info) {}];

The problem is once it plays, I get this error:

 -[AVComposition URL]: unrecognized selector sent to instance 0x138d17f40

However, if I set this option on the manager request, it will play as normal speed video at 120/240fps and no crashes:

  videoRequestOptions.version = PHVideoRequestOptionsVersionOriginal;

Whats going on? The default version property is PHVideoRequestOptionsVersionCurrent which incorporates slow motion, user edits and trims, etc.

I would like to play that video version. Thanks

Bespoke answered 1/5, 2016 at 0:58 Comment(1)
Were you able to fix this? I am recording a slow mo video instead of picking it up from the library. If I save it in the library, its ok and slow mo works. If I view it inside an AVPLayer in my app slow mo does not work.Postilion
B
12

So it turns out that slow motion videos are passed as AVComposition.

You can export that into a video file / URL, and then handle it like any other video.

Solution here: https://overflow.buffer.com/2016/02/29/slow-motion-video-ios/

//Output URL
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths.firstObject;
NSString *myPathDocs =  [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"mergeSlowMoVideo-%d.mov",arc4random() % 1000]];
NSURL *url = [NSURL fileURLWithPath:myPathDocs];

//Begin slow mo video export
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
exporter.outputURL = url;
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;

[exporter exportAsynchronouslyWithCompletionHandler:^{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (exporter.status == AVAssetExportSessionStatusCompleted) {
            NSURL *URL = exporter.outputURL;
            NSData *videoData = [NSData dataWithContentsOfURL:URL];

             // Upload
             [self uploadSelectedVideo:video data:videoData];
         }
    });
}];
Bespoke answered 1/5, 2016 at 4:56 Comment(2)
Thank you for this solution, however I have a few extra questions. I use your code above to play the video in AVPlayerViewController. However, when I call "player.currentItem.currentTime.value" it behaves strangely. I've spent enough time to know that if I go into the video from the Photos app, and make the entire video Slow Motion, or Normal Motion I'm able to get steady behavior. It's only the standard iPhone default (making a short segment at the beginning and end where the video does not show in Slow Motion), where the strange behavior starts. Any idea how to save this as ONLY slow motion?Alegar
This is for the case when you pick from photos library. What if I record a slow mo video and then want to upload it? #47514782Postilion
A
1

Looking answer in Swift? here's how I did it

Creating "Slow motion" video in iOS swift is not easy, that I came across many "slow motion" that came to know not working or some of the codes in them are depreciated. And so I finally figured a way to make slow motion in Swift. This code can be used for 120fps are greater than that too.

Here is the "code snippet I created for achieving slow motion"

        func slowMotion(pathUrl: URL) {

    let videoAsset = AVURLAsset.init(url: pathUrl, options: nil)
    let currentAsset = AVAsset.init(url: pathUrl)

    let vdoTrack = currentAsset.tracks(withMediaType: .video)[0]
    let mixComposition = AVMutableComposition()

    let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)

    let videoInsertError: Error? = nil
    var videoInsertResult = false
    do {
        try compositionVideoTrack?.insertTimeRange(
            CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
            of: videoAsset.tracks(withMediaType: .video)[0],
            at: .zero)
        videoInsertResult = true
    } catch let videoInsertError {
    }

    if !videoInsertResult || videoInsertError != nil {
        //handle error
        return
    }


    var duration: CMTime = .zero
    duration = CMTimeAdd(duration, currentAsset.duration)
    
    
    //MARK: You see this constant (videoScaleFactor) this helps in achieving the slow motion that you wanted. This increases the time scale of the video that makes slow motion
    // just increase the videoScaleFactor value in order to play video in higher frames rates(more slowly)
    let videoScaleFactor = 2.0
    let videoDuration = videoAsset.duration
    
    compositionVideoTrack?.scaleTimeRange(
        CMTimeRangeMake(start: .zero, duration: videoDuration),
        toDuration: CMTimeMake(value: videoDuration.value * Int64(videoScaleFactor), timescale: videoDuration.timescale))
    compositionVideoTrack?.preferredTransform = vdoTrack.preferredTransform
    
    let dirPaths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).map(\.path)
    let docsDir = dirPaths[0]
    let outputFilePath = URL(fileURLWithPath: docsDir).appendingPathComponent("slowMotion\(UUID().uuidString).mp4").path
    
    if FileManager.default.fileExists(atPath: outputFilePath) {
        do {
            try FileManager.default.removeItem(atPath: outputFilePath)
        } catch {
        }
    }
    let filePath = URL(fileURLWithPath: outputFilePath)
    
    let assetExport = AVAssetExportSession(
        asset: mixComposition,
        presetName: AVAssetExportPresetHighestQuality)
    assetExport?.outputURL = filePath
    assetExport?.outputFileType = .mp4
    
    assetExport?.exportAsynchronously(completionHandler: {
        switch assetExport?.status {
        case .failed:
            print("asset output media url = \(String(describing: assetExport?.outputURL))")
            print("Export session faiied with error: \(String(describing: assetExport?.error))")
            DispatchQueue.main.async(execute: {
                // completion(nil);
            })
        case .completed:
            print("Successful")
            let outputURL = assetExport!.outputURL
            print("url path = \(String(describing: outputURL))")
            
            PHPhotoLibrary.shared().performChanges({
                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL!)
            }) { saved, error in
                if saved {
                    print("video successfully saved in photos gallery view video in photos gallery")
                }
                if (error != nil) {
                    print("error in saing video \(String(describing: error?.localizedDescription))")
                }
            }
            DispatchQueue.main.async(execute: {
                //      completion(_filePath);
            })
        case .none:
            break
        case .unknown:
            break
        case .waiting:
            break
        case .exporting:
            break
        case .cancelled:
            break
        case .some(_):
            break
        }
    })
}
Agha answered 10/5, 2021 at 4:18 Comment(0)
S
0

For those coming here looking for a swift answer, this is the swift translation, which I use in my project, where I need the url of the slow-motion Video to play it with the AVPlayerViewController:

else if asset is AVComposition {
//Slow-Motion Assets are passed as AVComposition
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory: NSString? = paths.first as NSString?
if documentsDirectory != nil {
    let random = Int(arc4random() % 1000)
    let pathToAppend = String(format: "mergeSlowMoVideo-%d.mov", random)
    let myPathDocs = documentsDirectory!.strings(byAppendingPaths: [pathToAppend])
    let myPath = myPathDocs.first
    if myPath != nil {
        let url = URL(fileURLWithPath: myPath!)
        let exporter = AVAssetExportSession(asset: asset!, presetName: AVAssetExportPresetHighestQuality)
        if exporter != nil {
        exporter!.outputURL = url
        exporter!.outputFileType = AVFileTypeQuickTimeMovie
        exporter!.shouldOptimizeForNetworkUse = true
        exporter!.exportAsynchronously(completionHandler: {
            AsyncUtil.asyncMain {
                let url = exporter!.outputURL
                if url != nil {
                    let player = AVPlayer(url: url!)
                    let playerViewController = AVPlayerViewController()
                    playerViewController.player = player
                    playerViewController.modalTransitionStyle = .crossDissolve
                    view.present(playerViewController, animated: true) {
                        playerViewController.player!.play()
                    }
                }
            }
        })
    }
}
Sankhya answered 4/7, 2017 at 7:42 Comment(0)
A
0

Playing Slo-mo & Library Video in Swift 4 n above with Custom View

var vcPlayer = AVPlayerViewController()
var player = AVPlayer()

func playallVideo(_ customView: UIView, asset: PHAsset) {
        guard asset.mediaType == .video
            else {
                print("Not a valid video media type")
                return
        }
        let options = PHVideoRequestOptions()
        options.isNetworkAccessAllowed = true
        PHCachingImageManager().requestPlayerItem(forVideo: asset, options: options) { (playerItem, info) in
            DispatchQueue.main.async {
                self.player = AVPlayer(playerItem: playerItem)
                self.vcPlayer.player = self.player
                self.vcPlayer.view.frame = customView.bounds
                self.vcPlayer.videoGravity = .resizeAspectFill
                self.vcPlayer.showsPlaybackControls = true
                //self.vcPlayer.allowsPictureInPicturePlayback = true
                self.playerView.addSubview(self.vcPlayer.view)
                self.player.play()
            }
        }
    }

/**********Function Call ********/

self.playallVideo(self.playerView/*YourCustomView*/, asset: currentAssetArr[currentIndex]/*Current PHAsset Fetched from Library*/)

:) enjoy

Akan answered 28/7, 2020 at 14:52 Comment(0)
C
0

Swift version of voted answer:

  // Output URL
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    guard let documentsDirectory = paths.first else {
        return
    }
    let fileName = String(format: "mergeSlowMoVideo-%d.mov", Int(arc4random_uniform(1000)))
    let url = documentsDirectory.appendingPathComponent(fileName)
    
    // Begin slow mo video export
    guard let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
        return
    }
    exporter.outputURL = url
    exporter.outputFileType = .mov
    exporter.shouldOptimizeForNetworkUse = true
    
    exporter.exportAsynchronously {
        DispatchQueue.main.async {
            if exporter.status == .completed {
                guard let url = exporter.outputURL, let videoData = try? Data(contentsOf: url) else {
                    return
                }
                // Upload
                self.uploadSelectedVideo(video: url, data: videoData)
            }
        }
    }
Candelabra answered 28/7, 2023 at 18:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.