How to do Slow Motion video in IOS How to do Slow Motion video in IOS ios ios

How to do Slow Motion video in IOS


You could scale video using AVFoundation and CoreMedia frameworks.Take a look at the AVMutableCompositionTrack method:

- (void)scaleTimeRange:(CMTimeRange)timeRange toDuration:(CMTime)duration;

Sample:

AVURLAsset* videoAsset = nil; //self.inputAsset;//create mutable compositionAVMutableComposition *mixComposition = [AVMutableComposition composition];AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo                                                                               preferredTrackID:kCMPersistentTrackID_Invalid];NSError *videoInsertError = nil;BOOL videoInsertResult = [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)                                                        ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]                                                         atTime:kCMTimeZero                                                          error:&videoInsertError];if (!videoInsertResult || nil != videoInsertError) {    //handle error    return;}//slow down whole video by 2.0double videoScaleFactor = 2.0;CMTime videoDuration = videoAsset.duration;[compositionVideoTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)                           toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];//exportAVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition                                                                     presetName:AVAssetExportPresetLowQuality];

(Probably audio track from videoAsset should also be added to mixComposition)


Slower + Faster with or without audio track

I have tried and able to Slower the asset.

compositionVideoTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration) did the trick.

I made a class which will help you to generate a slower video from AVAsset. + point is you can also make it faster and another + point is it will handle the audio too.

Here is my custom class sample:

import UIKitimport AVFoundationenum SpeedoMode {    case Slower    case Faster}class VSVideoSpeeder: NSObject {    /// Singleton instance of `VSVideoSpeeder`    static var shared: VSVideoSpeeder = {       return VSVideoSpeeder()    }()    /// Range is b/w 1x, 2x and 3x. Will not happen anything if scale is out of range. Exporter will be nil in case url is invalid or unable to make asset instance.    func scaleAsset(fromURL url: URL,  by scale: Int64, withMode mode: SpeedoMode, completion: @escaping (_ exporter: AVAssetExportSession?) -> Void) {        /// Check the valid scale        if scale < 1 || scale > 3 {            /// Can not proceed, Invalid range            completion(nil)            return        }        /// Asset        let asset = AVAsset(url: url)        /// Video Tracks        let videoTracks = asset.tracks(withMediaType: AVMediaType.video)        if videoTracks.count == 0 {            /// Can not find any video track            completion(nil)            return        }        /// Get the scaled video duration        let scaledVideoDuration = (mode == .Faster) ? CMTimeMake(asset.duration.value / scale, asset.duration.timescale) : CMTimeMake(asset.duration.value * scale, asset.duration.timescale)        let timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration)        /// Video track        let videoTrack = videoTracks.first!        let mixComposition = AVMutableComposition()        let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)        /// Audio Tracks        let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)        if audioTracks.count > 0 {            /// Use audio if video contains the audio track            let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)            /// Audio track            let audioTrack = audioTracks.first!            do {                try compositionAudioTrack?.insertTimeRange(timeRange, of: audioTrack, at: kCMTimeZero)                compositionAudioTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)            } catch _ {                /// Ignore audio error            }        }        do {            try compositionVideoTrack?.insertTimeRange(timeRange, of: videoTrack, at: kCMTimeZero)            compositionVideoTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)            /// Keep original transformation            compositionVideoTrack?.preferredTransform = videoTrack.preferredTransform            /// Initialize Exporter now            let outputFileURL = URL(fileURLWithPath: "/Users/thetiger/Desktop/scaledVideo.mov")           /// Note:- Please use directory path if you are testing with device.            if FileManager.default.fileExists(atPath: outputFileURL.absoluteString) {                try FileManager.default.removeItem(at: outputFileURL)            }            let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)            exporter?.outputURL = outputFileURL            exporter?.outputFileType = AVFileType.mov            exporter?.shouldOptimizeForNetworkUse = true            exporter?.exportAsynchronously(completionHandler: {                completion(exporter)            })        } catch let error {            print(error.localizedDescription)            completion(nil)            return        }    }}

I took 1x, 2x and 3x as a valid scale. Class contains the proper validation and handling. Below is the sample of how to use this function.

let url = Bundle.main.url(forResource: "1", withExtension: "mp4")!VSVideoSpeeder.shared.scaleAsset(fromURL: url, by: 3, withMode: SpeedoMode.Slower) { (exporter) in     if let exporter = exporter {         switch exporter.status {                case .failed: do {                      print(exporter.error?.localizedDescription ?? "Error in exporting..")                }                case .completed: do {                      print("Scaled video has been generated successfully!")                }                case .unknown: break                case .waiting: break                case .exporting: break                case .cancelled: break           }      }      else {           /// Error           print("Exporter is not initialized.")      }}

This line will handle the audio scaling

compositionAudioTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)


I have achieved on adding slow motion to video including audio as well with proper output orientation.

 - (void)SlowMotion:(NSURL *)URl {   AVURLAsset* videoAsset = [AVURLAsset URLAssetWithURL:URl options:nil]; //self.inputAsset;AVAsset *currentAsset = [AVAsset assetWithURL:URl];AVAssetTrack *vdoTrack = [[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];//create mutable compositionAVMutableComposition *mixComposition = [AVMutableComposition composition];AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];AVMutableCompositionTrack *compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];NSError *videoInsertError = nil;BOOL videoInsertResult = [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)                                                        ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]                                                         atTime:kCMTimeZero                                                          error:&videoInsertError];if (!videoInsertResult || nil != videoInsertError) {    //handle error    return;}NSError *audioInsertError =nil;BOOL audioInsertResult =[compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)                                                       ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]                                                        atTime:kCMTimeZero                                                         error:&audioInsertError];if (!audioInsertResult || nil != audioInsertError) {    //handle error    return;}CMTime duration =kCMTimeZero;duration=CMTimeAdd(duration, currentAsset.duration);//slow down whole video by 2.0double videoScaleFactor = 2.0;CMTime videoDuration = videoAsset.duration;[compositionVideoTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)                           toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];[compositionAudioTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)                           toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];[compositionVideoTrack setPreferredTransform:vdoTrack.preferredTransform];        NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);        NSString *docsDir = [dirPaths objectAtIndex:0];        NSString *outputFilePath = [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"slowMotion.mov"]];        if ([[NSFileManager defaultManager] fileExistsAtPath:outputFilePath])        [[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];        NSURL *_filePath = [NSURL fileURLWithPath:outputFilePath];//exportAVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition                                                                        presetName:AVAssetExportPresetLowQuality];assetExport.outputURL=_filePath;                          assetExport.outputFileType =           AVFileTypeQuickTimeMovie;  exporter.shouldOptimizeForNetworkUse = YES;                           [assetExport exportAsynchronouslyWithCompletionHandler:^                            {                                switch ([assetExport status]) {                                    case AVAssetExportSessionStatusFailed:                                    {                                        NSLog(@"Export session faiied with error: %@", [assetExport error]);                                        dispatch_async(dispatch_get_main_queue(), ^{                                            // completion(nil);                                        });                                    }                                        break;                                    case AVAssetExportSessionStatusCompleted:                                    {                                        NSLog(@"Successful");                                        NSURL *outputURL = assetExport.outputURL;                                        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];                                        if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputURL]) {                                            [self writeExportedVideoToAssetsLibrary:outputURL];                                        }                                        dispatch_async(dispatch_get_main_queue(), ^{                                            //                                            completion(_filePath);                                        });                                    }                                        break;                                    default:                                        break;                                }                            }]; }  - (void)writeExportedVideoToAssetsLibrary :(NSURL *)url {NSURL *exportURL = url;ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:exportURL]) {    [library writeVideoAtPathToSavedPhotosAlbum:exportURL completionBlock:^(NSURL *assetURL, NSError *error){        dispatch_async(dispatch_get_main_queue(), ^{            if (error) {                UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[error localizedDescription]                                                                    message:[error localizedRecoverySuggestion]                                                                   delegate:nil                                                          cancelButtonTitle:@"OK"                                                          otherButtonTitles:nil];                [alertView show];            }            if(!error)            {               // [activityView setHidden:YES];                UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Sucess"                                                                    message:@"video added to gallery successfully"                                                                   delegate:nil                                                          cancelButtonTitle:@"OK"                                                          otherButtonTitles:nil];                [alertView show];            } #if !TARGET_IPHONE_SIMULATOR            [[NSFileManager defaultManager] removeItemAtURL:exportURL error:nil];#endif        });    }];} else {    NSLog(@"Video could not be exported to assets library.");}}