Video Compression

    Skand Swami

A subtle process as to compress a video in Xamarin in order to reduce its size. This involves a tradeoff with the quality of the video, but that is easily compensated by the ease the process provides when you are buffering a video through the internet.

Step 1- Create an empty Xamarin shared Project, Let’s say “CompressedVideoDemo.






Step 2- Make a folder named DS in the shared project and create Dependency Services class.


namespace CompressedVideoDemo.DS
{
   public static class DependencyServices
    {
       public static IMobileFeature MobileFeature
        {
            get
            {
                return DependencyService.Get<IMobileFeature>(DependencyFetchTarget.GlobalInstance);
            }
        }

    }
}


Step 3- Create an Interface which will contain the prototypes of all the functions that we need to perform as to compress a video, let’s name it ”IMobileFeature”.Make 4 functions in, namely Record video, Select video, Compressed video, Play video.


namespace CompressedVideoDemo.Interface
{
    public interface IMobileFeature
    {        
        Task<bool> CompressVideo(string inputPath,string outputPath);
    }
}     


Step 4-  For Android - Create an IntentHelper class which provides functions for compression of video, recording, playing video and callbacks their response.


internal class IntentHelper
    {
        static readonly Dictionary<int, Action<Result, Intent>> _CallbackDictionary = new Dictionary<int, Action<Result, Intent>>();
        public struct RequestCodes
        {           
            public const int CompressVideo = 106;
            public const int SelectVideo = 105;

        }

        static Java.IO.File _destFile;

        static Action<string> _callback;

        static Activity CurrentActivity
        {
            get
            {
                return (Xamarin.Forms.Forms.Context as MainActivity);
            }
        }

        #region Public Methods

        public static void StartIntent(Intent intent, int requestCode, Action<Result, Intent> callback)
        {
            if (_CallbackDictionary.ContainsKey(requestCode))
                _CallbackDictionary.Remove(requestCode);
            _CallbackDictionary.Add(requestCode, callback);
            (Xamarin.Forms.Forms.Context as Activity).StartActivityForResult(intent, requestCode);
        }


        public static void ActivityResult(int requestCode, Result resultCode, Intent data)
        {
            if (_callback == null)
                return;
            if (resultCode == Result.Ok)
            {
               
                else if (requestCode == RequestCodes.CompressVideo)
                {
                } 
            }
        }
               
        public static void CompressVideo(string inputPath, string outputPath, Action<string> callback)
        {
            Activity activity = new Activity();
            _callback = callback;
            ProgressDialog progress = new ProgressDialog(Forms.Context);
            progress.Indeterminate = true;
            progress.SetProgressStyle(ProgressDialogStyle.Spinner);
            progress.SetMessage("Compressing Video. Please wait...");
            progress.SetCancelable(false);
            progress.Show();

            Task.Run(() =>
            {
                var _workingDirectory = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
                var sourceMp4 = inputPath;
                var destinationPath1 = outputPath;
                FFMpeg ffmpeg = new FFMpeg(Android.App.Application.Context, _workingDirectory);
                TransposeVideoFilter vfTranspose = new TransposeVideoFilter(TransposeVideoFilter.NINETY_CLOCKWISE);
                var filters = new List<VideoFilter>();
                filters.Add(vfTranspose);

                var sourceClip = new Clip(System.IO.Path.Combine(_workingDirectory, sourceMp4)) { videoFilter = VideoFilter.Build(filters) };
                var br = System.Environment.NewLine;
                var onComplete = new MyCommand((_) =>
                {
                    _callback(destinationPath1);
                    progress.Dismiss();
                });

                var onMessage = new MyCommand((message) =>
                {
                    System.Console.WriteLine(message);
                });

                var callbacks = new FFMpegCallbacks(onComplete, onMessage);
                string[] cmds = new string[] {
                "-y",
                "-i",
                sourceClip.path,
               "-strict", "experimental",
                        "-vcodec", "libx264",
                        "-preset", "ultrafast",
                        "-crf","30", "-acodec","aac", "-ar", "44100" ,
                        "-q:v", "20",
                  "-vf",sourceClip.videoFilter,                

                destinationPath1 ,
            };
                ffmpeg.Execute(cmds, callbacks);
            });
        }

Step 5-   For Android - Create Dependency class MobileFeature in Android which implements IMobileFeature class made in the shared project. Do not forget to include the dependency information while implementing the interface.


[assembly: Dependency(typeof(CompressedVideoDemo.Droid.DS.MobileFeature))]

    public class MobileFeature : IMobileFeature
    {
        static TaskCompletionSource<string> _tcsVideo;
        static bool isBusy = false;
        static int duration;
        static Activity CurrentActivity
        {
            get
            {
                return (Forms.Context as MainActivity);
            }
        }       

        public static void HandleActivity(int requestCode, Result resultCode, Intent data)
        {
            isBusy = false;
            if (requestCode == IntentHelper.RequestCodes.SelectVideo)
            {
                if (resultCode == Result.Ok)
                {
                    var path = ImageFilePath.GetPath(Forms.Context, data.Data);
                    if (!System.IO.File.Exists(path))
                    {
                        Toast.MakeText(Forms.Context, "Invalid file source", ToastLength.Long).Show();
                        _tcsVideo.SetResult(null);
                        return;
                    }
                    
                }
                else
                    _tcsVideo.SetResult(null);
            }
        }    
        public Task<bool> CompressVideo(string path,string path2)
        {
            var task = new TaskCompletionSource<bool>();
            try
            {
                IntentHelper.CompressVideo( path,path2, (u) =>
                {
                    task.SetResult(true);
                });
            }
            catch (Exception ex)
            {
                task.SetResult(false);
            }
            return task.Task;
        }
    }
}      

Step 6- Implement the IMobileFeature interface in iOS as well and include the following code in it with the same method "CompressVideo".

public Task<string> CompressVideo(int duration)
        {
            var task = new TaskCompletionSource<string>();
            try
            {
                Services.Media.RecordVideo(GetController(),//this method provides recorded video
 duration, (data) =>
                {
                    if (data == null)
                    {
                        task.SetResult(null);
                        return;
                    }
//mediaUrl here is the Url for your video to be compressed.
//here, i m using a video from the recorder

                    var mediaUrl = data.ValueForKey(new NSString("UIImagePickerControllerMediaURL")) as NSUrl;

                    var export = new AVAssetExportSession(AVAsset.FromUrl(mediaUrl), AVAssetExportSession.PresetMediumQuality);
                    string videoFilename = System.IO.Path.Combine(GetVideoDirectoryPath(), GetUniqueFileName(".mp4"));
                    export.OutputUrl = NSUrl.FromFilename(videoFilename);
                    export.OutputFileType = AVFileType.Mpeg4;
                    export.ShouldOptimizeForNetworkUse = true;

                    export.ExportAsynchronously(() =>
                      {
                          if (export.Status == AVAssetExportSessionStatus.Completed)
                          {
                            var videoData = NSData.FromUrl(NSUrl.FromString(export.OutputUrl.ToString()));
                            NSError err = null;
                              if (videoData.Save(videoFilename, false, out err))
                              {
                                  task.SetResult(videoFilename);
                              }
                              else
                              {
                                  task.SetResult(null);
                              }
                          }
                          else
                              tcs.SetResult(null);
                      });
                });
            }
            catch (Exception ex)
            {
                task.SetException(ex);
            }
            return task.Task;
        }
Step 7- Here is how to call the Dependency device in the shared project.


_destFile = Helpers.FileHelper.CreateNewVideoPath();

        private async void ChooseVideoAndCompressBtn_Clicked(object Sender, EventArgs e)
        {
            var actions = new string[] { "Open Camera", "Open Gallery" };
            var action = await App.Current.MainPage.DisplayActionSheet("Choose Video", "Cancel", null, actions);
            if (actions[0].Equals(action))
            {
                viewModel.VideoPath = await DependencyServices.mobileFeature.RecordVideo();
                await App.Current.MainPage.DisplayAlert("Success", "Video is saved to gallery", "ok");
                CompressVideo();
            }
            else if (actions[1].Equals(action))
            {
                viewModel.VideoPath = await DependencyServices.MobileFeature.SelectVideo();
                CompressVideo();
            }
        }

        private async void CompressVideo()
        {
            if (!File.Exists(viewModel.VideoPath))
                return;

            var isCompressCmd = await App.Current.MainPage.DisplayAlert("Action", "Do You want to compress this video?", "Ok", "Cancel");
            if (isCompressCmd)
            {
                              var isCompressed = await DependencyServices.MobileFeature.CompressVideo(viewModel.VideoPath, _destFile);
                
                if (isCompressed)
                {
                    viewModel.CompressVideoPath = _destFile;
                    await App.Current.MainPage.DisplayAlert("", "Video compressed successfully", "Ok");
                }
                else
                    await App.Current.MainPage.DisplayAlert("", "Video not compressed", "Ok");
            }
        }   

Find the sample for the same here.