private string GetDescription(string lastPathComponent)
        {
            string description = string.Empty;

            if (!Empty)
            {
                CMTime startTime = TimeRange.Start;
                CMTime endTime   = CMTime.Add(TimeRange.Start, TimeRange.Duration);

                string url = lastPathComponent;

                description = String.Format("{0:0.0} - {1:0.0}: {2}", startTime.Seconds, endTime.Seconds, url);

                if (MediaType == AVMediaType.Video)
                {
                    description += "(v)";
                }
                else if (MediaType == AVMediaType.Audio)
                {
                    description += "(a)";
                }
                else
                {
                    description += String.Format("({0})", MediaType);
                }
            }

            return(description);
        }
Example #2
0
        public void MethodsTest()
        {
            CMTime v, w, x, y;

            v = new CMTime(1, 2);
            w = new CMTime(1, 2);
            x = new CMTime(2, 1);
            y = new CMTime(2, 2);

            // equality operators
            Assert.That(v == w, "Equality #1");
            Assert.That(!(v == x), "Equality #2");
            Assert.That(v != x, "Inequality #1");
            Assert.That(!(v != w), "Inequality #2");
            Assert.That(CMTime.Compare(v, w), Is.EqualTo(0), "Compare #1");
            Assert.That(CMTime.Compare(v, x) != 0, "Compare #2");
            Assert.That(v.Equals(w), "Equals #1");
            Assert.That(!x.Equals(v), "Equals #2");

            // addition operator
            Assert.That(v + w == new CMTime(2, 2), "Addition #1");
            Assert.That(CMTime.Add(v, w) == new CMTime(2, 2), "Addition #2");

            // subtraction operator
            Assert.That(v - w == new CMTime(0, 2), "Subtraction #1");
            Assert.That(CMTime.Subtract(v, w) == new CMTime(0, 2), "Subtraction #2");

            // multiplication operators
            Assert.That(v * 2 == new CMTime(2, 2), "Multiplication * int, #1");
            Assert.That(CMTime.Multiply(v, 3) == new CMTime(3, 2), "Multiplication * int, #2");
            Assert.That(v * 4.0 == new CMTime(4, 2), "Multiplication * double, #1");
            Assert.That(CMTime.Multiply(v, 5.0) == new CMTime(5, 2), "Multiplication * double, #2");

            // ConvertScale
            Assert.That(new CMTime(10, 2).ConvertScale(1, CMTimeRoundingMethod.Default) == new CMTime(5, 1), "ConvertScale #1");

            // FromSeconds
            Assert.That(CMTime.FromSeconds(20, 1) == new CMTime(20, 1), "FromSeconds #1");

            // GetMaximum
            Assert.That(CMTime.GetMaximum(v, y) == y, "GetMaximum #1");

            // GetMinimum
            Assert.That(CMTime.GetMinimum(v, y) == v, "GetMinimum #1");

#if XAMCORE_2_0
            using (var d = x.ToDictionary()) {
                Assert.That(d.RetainCount, Is.EqualTo((nint)1), "RetainCount");
                Assert.That(d.Count, Is.EqualTo((nuint)4), "Count");

                var time = CMTime.FromDictionary(d);
                Assert.That(time, Is.EqualTo(x), "FromDictionary");
            }
#endif
        }
        private void ProcessVideoComposition(AVMutableVideoComposition videoComposition)
        {
            var stages = new List <APLVideoCompositionStageInfo> ();

            foreach (AVVideoCompositionInstruction instruction in videoComposition.Instructions)
            {
                var stage = new APLVideoCompositionStageInfo();
                stage.TimeRange = instruction.TimeRange;

                var rampsDictionary = new Dictionary <string, List <CGPoint> > ();
                var layerNames      = new List <string> ();
                foreach (AVVideoCompositionLayerInstruction layerInstruction in instruction.LayerInstructions)
                {
                    var ramp = new List <CGPoint> ();

                    CMTime      startTime    = CMTime.Zero;
                    float       startOpacity = 1f;
                    float       endOpacity   = 1f;
                    CMTimeRange timeRange    = new CMTimeRange();

                    while (layerInstruction.GetOpacityRamp(startTime, ref startOpacity, ref endOpacity, ref timeRange))
                    {
                        if (CMTime.Compare(startTime, CMTime.Zero) == 0 &&
                            CMTime.Compare(timeRange.Start, CMTime.Zero) == 1)
                        {
                            ramp.Add(new CGPoint((float)timeRange.Start.Seconds, startOpacity));
                        }

                        CMTime endTime = CMTime.Add(timeRange.Start, timeRange.Duration);
                        ramp.Add(new CGPoint((float)endTime.Seconds, endOpacity));
                        startTime = CMTime.Add(timeRange.Start, timeRange.Duration);
                    }

                    NSString name = new NSString(layerInstruction.TrackID.ToString());
                    layerNames.Add(name);
                    rampsDictionary [name] = ramp;
                }

                if (layerNames.Count > 1)
                {
                    stage.OpacityRamps = rampsDictionary;
                }

                stage.LayerNames = layerNames;
                stages.Add(stage);
            }

            videoCompositionStages = stages;
        }
        private void ProcessAudioMix(AVMutableAudioMix audioMix)
        {
            var mixTracks = new List <List <CGPoint> > ();

            foreach (AVAudioMixInputParameters input in audioMix.InputParameters)
            {
                List <CGPoint> ramp = new List <CGPoint> ();

                CMTime      startTime   = CMTime.Zero;
                float       startVolume = 1f;
                float       endVolume   = 1f;
                CMTimeRange timeRange   = new CMTimeRange();

                while (input.GetVolumeRamp(startTime, ref startVolume, ref endVolume, ref timeRange))
                {
                    if (CMTime.Compare(startTime, CMTime.Zero) == 0 &&
                        CMTime.Compare(timeRange.Start, CMTime.Zero) == 1)
                    {
                        ramp.Add(new CGPoint(0f, 1f));
                        ramp.Add(new CGPoint((float)timeRange.Start.Seconds, startVolume));
                    }

                    ramp.Add(new CGPoint((float)timeRange.Start.Seconds, startVolume));

                    CMTime endTime = CMTime.Add(timeRange.Start, timeRange.Duration);
                    ramp.Add(new CGPoint((float)endTime.Seconds, endVolume));
                    startTime = CMTime.Add(timeRange.Start, timeRange.Duration);
                }

                if (CMTime.Compare(startTime, duration) == -1)
                {
                    ramp.Add(new CGPoint((float)duration.Seconds, endVolume));
                }

                mixTracks.Add(ramp);
            }

            audioMixTracks = mixTracks;
        }
Example #5
0
        private void BuildTransitionComposition(AVMutableComposition composition, AVMutableVideoComposition videoComposition, AVMutableAudioMix audioMix)
        {
            CMTime nextClipStartTime = CMTime.Zero;
            int    clipsCount        = Clips.Count;

            // Make transitionDuration no greater than half the shortest clip duration.
            CMTime transitionDuration = TransitionDuration;

            Console.WriteLine("Clips Count:" + clipsCount);
            Console.WriteLine("Clips Range Count:" + ClipTimeRanges.Count);

            for (int i = 0; i < clipsCount; i++)
            {
                NSValue clipTimeRange = ClipTimeRanges [i];
                if (clipTimeRange != null)
                {
                    CMTime halfClipDuration = clipTimeRange.CMTimeRangeValue.Duration;
                    halfClipDuration.TimeScale *= 2;
                    transitionDuration          = CMTime.GetMinimum(transitionDuration, halfClipDuration);
                }
            }

            // Add two video tracks and two audio tracks.
            var compositionVideoTracks = new AVMutableCompositionTrack [] {
                composition.AddMutableTrack(AVMediaType.Video, 0),
                composition.AddMutableTrack(AVMediaType.Video, 0)
            };
            var compositionAudioTracks = new AVMutableCompositionTrack [] {
                composition.AddMutableTrack(AVMediaType.Audio, 0),
                composition.AddMutableTrack(AVMediaType.Audio, 0)
            };

            var passThroughTimeRanges = new CMTimeRange[clipsCount];
            var transitionTimeRanges  = new CMTimeRange[clipsCount];

            // Place clips into alternating video & audio tracks in composition, overlapped by transitionDuration.
            for (int i = 0; i < clipsCount; i++)
            {
                int         alternatingIndex = i % 2;
                AVAsset     asset            = Clips [i];
                NSValue     clipTimeRange    = ClipTimeRanges [i];
                CMTimeRange timeRangeInAsset;
                if (clipTimeRange != null)
                {
                    timeRangeInAsset = clipTimeRange.CMTimeRangeValue;
                }
                else
                {
                    timeRangeInAsset          = new CMTimeRange();
                    timeRangeInAsset.Start    = CMTime.Zero;
                    timeRangeInAsset.Duration = asset.Duration;
                }
                NSError      error;
                AVAssetTrack clipVideoTrack = asset.TracksWithMediaType(AVMediaType.Video) [0];
                compositionVideoTracks [alternatingIndex].InsertTimeRange(timeRangeInAsset, clipVideoTrack, nextClipStartTime, out error);

                AVAssetTrack clipAudioTrack = asset.TracksWithMediaType(AVMediaType.Audio) [0];
                compositionAudioTracks [alternatingIndex].InsertTimeRange(timeRangeInAsset, clipAudioTrack, nextClipStartTime, out error);

                // Remember the time range in which this clip should pass through.
                // First clip ends with a transition.
                // Second clip begins with a transition.
                // Exclude that transition from the pass through time ranges
                CMTimeRange timeRange = new CMTimeRange();
                timeRange.Start           = nextClipStartTime;
                timeRange.Duration        = timeRangeInAsset.Duration;
                passThroughTimeRanges [i] = timeRange;

                if (i > 0)
                {
                    passThroughTimeRanges[i].Start    = CMTime.Add(passThroughTimeRanges[i].Start, transitionDuration);
                    passThroughTimeRanges[i].Duration = CMTime.Subtract(passThroughTimeRanges[i].Duration, transitionDuration);
                }

                if (i + 1 < clipsCount)
                {
                    passThroughTimeRanges[i].Duration = CMTime.Subtract(passThroughTimeRanges[i].Duration, transitionDuration);
                }

                // The end of this clip will overlap the start of the next by transitionDuration.
                // (Note: this arithmetic falls apart if timeRangeInAsset.duration < 2 * transitionDuration.)
                nextClipStartTime = CMTime.Add(nextClipStartTime, timeRangeInAsset.Duration);
                nextClipStartTime = CMTime.Subtract(nextClipStartTime, transitionDuration);

                // Remember the time range for the transition to the next item
                if (i + 1 < clipsCount)
                {
                    transitionTimeRanges [i] = new CMTimeRange()
                    {
                        Start    = nextClipStartTime,
                        Duration = transitionDuration
                    };
                }
            }

            List <AVVideoCompositionInstruction>    instructions  = new List <AVVideoCompositionInstruction> ();
            List <AVMutableAudioMixInputParameters> trackMixArray = new List <AVMutableAudioMixInputParameters> ();

            // Set up the video composition if we are to perform crossfade transitions between clips.
            for (int i = 0; i < clipsCount; i++)
            {
                int alternatingIndex = i % 2;
                AVMutableVideoCompositionInstruction passThroughInstructions = AVMutableVideoCompositionInstruction.Create() as AVMutableVideoCompositionInstruction;
                passThroughInstructions.TimeRange = passThroughTimeRanges [i];

                AVMutableVideoCompositionLayerInstruction passThroughLayerInstructions = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks [alternatingIndex]);

                passThroughInstructions.LayerInstructions = new AVVideoCompositionLayerInstruction[] { passThroughLayerInstructions };
                instructions.Add(passThroughInstructions);

                if (i + 1 < clipsCount)
                {
                    var transitionInstruction = AVMutableVideoCompositionInstruction.Create() as AVMutableVideoCompositionInstruction;
                    transitionInstruction.TimeRange = transitionTimeRanges [i];
                    var fromLayer = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks [alternatingIndex]);
                    var toLayer   = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks [1 - alternatingIndex]);


                    // Fade in the toLayer by setting a ramp from 0.0 to 1.0.
                    toLayer.SetOpacityRamp(0.0f, 1.0f, transitionTimeRanges [i]);
                    transitionInstruction.LayerInstructions = new AVVideoCompositionLayerInstruction[]
                    {
                        toLayer,
                        fromLayer,
                    };
                    instructions.Add(transitionInstruction);

                    // Add AudioMix to fade in the volume ramps
                    var trackMix = AVMutableAudioMixInputParameters.FromTrack(compositionAudioTracks[0]);
                    trackMix.SetVolumeRamp(1f, 0f, transitionTimeRanges[0]);
                    trackMixArray.Add(trackMix);

                    trackMix = AVMutableAudioMixInputParameters.FromTrack(compositionAudioTracks[1]);
                    trackMix.SetVolumeRamp(0f, 1f, transitionTimeRanges[0]);
                    trackMix.SetVolumeRamp(1f, 1f, passThroughTimeRanges[1]);
                    trackMixArray.Add(trackMix);
                }
            }

            videoComposition.Instructions = instructions.ToArray();
            audioMix.InputParameters      = trackMixArray.ToArray();
        }
Example #6
0
        void buildTransitionComposition(AVMutableComposition composition, AVMutableVideoComposition videoComposition)
        {
            CMTime nextClipStartTime = CMTime.Zero;
            int    clipsCount        = Clips.Count;

            // Make transitionDuration no greater than half the shortest clip duration.
            CMTime transitionDuration = TransitionDuration;

            foreach (var clipTimeRange in ClipTimeRanges)
            {
                if (clipTimeRange == null)
                {
                    continue;
                }

                CMTime halfClipDuration = clipTimeRange.CMTimeRangeValue.Duration;
                halfClipDuration.TimeScale *= 2;
                transitionDuration          = CMTime.GetMinimum(transitionDuration, halfClipDuration);
            }

            // Add two video tracks and two audio tracks.
            var compositionVideoTracks = new AVMutableCompositionTrack [2];
            var compositionAudioTracks = new AVMutableCompositionTrack [2];

            compositionVideoTracks [0] = composition.AddMutableTrack(AVMediaType.Video, 0);
            compositionVideoTracks [1] = composition.AddMutableTrack(AVMediaType.Video, 0);
            compositionAudioTracks [0] = composition.AddMutableTrack(AVMediaType.Audio, 0);
            compositionAudioTracks [1] = composition.AddMutableTrack(AVMediaType.Audio, 0);

            var passThroughTimeRanges = new CMTimeRange[clipsCount];
            var transitionTimeRanges  = new CMTimeRange[clipsCount];

            // Place clips into alternating video & audio tracks in composition, overlapped by transitionDuration.
            for (int i = 0; i < clipsCount; i++)
            {
                int         alternatingIndex = i % 2;
                AVAsset     asset            = Clips [i];
                NSValue     clipTimeRange    = ClipTimeRanges [i];
                CMTimeRange timeRangeInAsset;
                if (clipTimeRange != null)
                {
                    timeRangeInAsset = clipTimeRange.CMTimeRangeValue;
                }
                else
                {
                    timeRangeInAsset = new CMTimeRange {
                        Start    = CMTime.Zero,
                        Duration = asset.Duration
                    };
                }
                NSError      error          = new NSError();
                AVAssetTrack clipVideoTrack = asset.TracksWithMediaType(AVMediaType.Video) [0];
                compositionVideoTracks [alternatingIndex].InsertTimeRange(timeRangeInAsset, clipVideoTrack, nextClipStartTime, out error);

                AVAssetTrack clipAudioTrack = asset.TracksWithMediaType(AVMediaType.Audio) [0];
                compositionAudioTracks [alternatingIndex].InsertTimeRange(timeRangeInAsset, clipAudioTrack, nextClipStartTime, out error);

                // Remember the time range in which this clip should pass through.
                // First clip ends with a transition.
                // Second clip begins with a transition.
                // Exclude that transition from the pass through time ranges
                passThroughTimeRanges [i] = new CMTimeRange {
                    Start    = nextClipStartTime,
                    Duration = timeRangeInAsset.Duration
                };

                if (i > 0)
                {
                    passThroughTimeRanges[i].Start    = CMTime.Add(passThroughTimeRanges[i].Start, transitionDuration);
                    passThroughTimeRanges[i].Duration = CMTime.Subtract(passThroughTimeRanges[i].Duration, transitionDuration);
                }
                if (i + 1 < clipsCount)
                {
                    passThroughTimeRanges[i].Duration = CMTime.Subtract(passThroughTimeRanges[i].Duration, transitionDuration);
                }

                // The end of this clip will overlap the start of the next by transitionDuration.
                // (Note: this arithmetic falls apart if timeRangeInAsset.duration < 2 * transitionDuration.)
                nextClipStartTime = CMTime.Add(nextClipStartTime, timeRangeInAsset.Duration);
                nextClipStartTime = CMTime.Subtract(nextClipStartTime, transitionDuration);

                // Remember the time range for the transition to the next item.

                if (i + 1 < clipsCount)
                {
                    transitionTimeRanges [i] = new CMTimeRange()
                    {
                        Start    = nextClipStartTime,
                        Duration = transitionDuration
                    };
                }
            }

            // Set up the video composition to perform cross dissolve or diagonal wipe transitions between clips.
            var instructions = new List <AVVideoCompositionInstruction> ();

            // Cycle between "pass through A", "transition from A to B", "pass through B"
            for (int i = 0; i < clipsCount; i++)
            {
                int alternatingIndex = i % 2;

//				if (videoComposition.CustomVideoCompositorClass != null) {
//					var videoInstruction = new CustomVideoCompositionInstruction (compositionVideoTracks [alternatingIndex].TrackID, passThroughTimeRanges [i]);
//					instructions.Add (videoInstruction);
//				} else {
//					// Pass through clip i.
//					var passThroughInstruction = AVMutableVideoCompositionInstruction.Create () as AVMutableVideoCompositionInstruction;
//					passThroughInstruction.TimeRange = passThroughTimeRanges [i];
//					var passThroughLayer = AVMutableVideoCompositionLayerInstruction.FromAssetTrack (compositionVideoTracks [alternatingIndex]);
//					passThroughInstruction.LayerInstructions = new [] { passThroughLayer };
//					instructions.Add (passThroughInstruction);
//
//				}
                //TODO: Remove following call if previous works
                if (videoComposition.CustomVideoCompositorClass.Name != "nil")
                {
                    var videoInstruction = new CustomVideoCompositionInstruction(compositionVideoTracks [alternatingIndex].TrackID, passThroughTimeRanges [i]);
                    instructions.Add(videoInstruction);
                }
                else
                {
                    // Pass through clip i.
                    var passThroughInstruction = AVMutableVideoCompositionInstruction.Create() as AVMutableVideoCompositionInstruction;
                    passThroughInstruction.TimeRange = passThroughTimeRanges [i];
                    var passThroughLayer = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks [alternatingIndex]);
                    passThroughInstruction.LayerInstructions = new [] { passThroughLayer };
                    instructions.Add(passThroughInstruction);
                }

                if (i + 1 < clipsCount)
                {
                    // Add transition from clip i to clip i+1.
//					if (videoComposition.CustomVideoCompositorClass != null) {
//						var videoInstruction = new CustomVideoCompositionInstruction (new NSNumber [] {
//							compositionVideoTracks [0].TrackID,
//							compositionVideoTracks [1].TrackID
//						}, transitionTimeRanges [1]);
//
//						if (alternatingIndex == 0) {
//							videoInstruction.ForegroundTrackID = compositionVideoTracks [alternatingIndex].TrackID;
//							videoInstruction.BackgroundTrackID = compositionVideoTracks [1 - alternatingIndex].TrackID;
//						}
//
//						instructions.Add (videoInstruction);
//					} else {
//						var transitionInstruction = AVMutableVideoCompositionInstruction.Create () as AVMutableVideoCompositionInstruction;
//						transitionInstruction.TimeRange = transitionTimeRanges [i];
//						var fromLayer = AVMutableVideoCompositionLayerInstruction.FromAssetTrack (compositionVideoTracks [alternatingIndex]);
//						var toLayer = AVMutableVideoCompositionLayerInstruction.FromAssetTrack (compositionVideoTracks [1 - alternatingIndex]);
//						transitionInstruction.LayerInstructions = new [] { toLayer, fromLayer };
//						instructions.Add (transitionInstruction);
//					}
                    // TODO: remove following call if previous works
                    if (videoComposition.CustomVideoCompositorClass.Name != "nil")
                    {
                        NSNumber[] sources = new NSNumber[] {
                            new NSNumber(compositionVideoTracks [0].TrackID),
                            new NSNumber(compositionVideoTracks [1].TrackID)
                        };
                        var videoInstructions = new CustomVideoCompositionInstruction(sources, transitionTimeRanges [i]);
                        if (alternatingIndex == 0)
                        {
                            videoInstructions.ForegroundTrackID = compositionVideoTracks [alternatingIndex].TrackID;
                            videoInstructions.BackgroundTrackID = compositionVideoTracks [1 - alternatingIndex].TrackID;
                        }

                        instructions.Add(videoInstructions);
                        Console.WriteLine("Add transition from clip i to clip i+1");
                    }
                    else
                    {
                        AVMutableVideoCompositionInstruction transitionInstruction = AVMutableVideoCompositionInstruction.Create() as AVMutableVideoCompositionInstruction;
                        transitionInstruction.TimeRange = transitionTimeRanges [i];
                        AVMutableVideoCompositionLayerInstruction fromLayer = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks [alternatingIndex]);
                        AVMutableVideoCompositionLayerInstruction toLayer   = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks [1 - alternatingIndex]);
                        transitionInstruction.LayerInstructions = new AVVideoCompositionLayerInstruction[] {
                            fromLayer,
                            toLayer,
                        };
                        instructions.Add(transitionInstruction);
                    }
                }
            }

            videoComposition.Instructions = instructions.ToArray();
        }
Example #7
0
        private void BuildTransitionComposition(AVMutableComposition mutableComposition, AVMutableVideoComposition mutableVideoComposition)
        {
            var nextClipStartTime = CMTime.Zero;
            var clipsCount        = this.Clips.Count;

            // Make transitionDuration no greater than half the shortest clip duration.
            var transitionDuration = this.TransitionDuration;

            foreach (var clipTimeRange in this.ClipTimeRanges)
            {
                var halfClipDuration = clipTimeRange.Duration;
                halfClipDuration.TimeScale *= 2; // You can halve a rational by doubling its denominator.
                transitionDuration          = CMTime.GetMinimum(transitionDuration, halfClipDuration);
            }

            // Add two video tracks and two audio tracks.
            var compositionVideoTracks = new AVMutableCompositionTrack[2];
            var compositionAudioTracks = new AVMutableCompositionTrack[2];

            compositionVideoTracks[0] = mutableComposition.AddMutableTrack(AVMediaType.Video, 0);
            compositionVideoTracks[1] = mutableComposition.AddMutableTrack(AVMediaType.Video, 0);
            compositionAudioTracks[0] = mutableComposition.AddMutableTrack(AVMediaType.Audio, 0);
            compositionAudioTracks[1] = mutableComposition.AddMutableTrack(AVMediaType.Audio, 0);

            var passThroughTimeRanges = new CMTimeRange[clipsCount];
            var transitionTimeRanges  = new CMTimeRange[clipsCount];

            // Place clips into alternating video & audio tracks in composition, overlapped by transitionDuration.
            for (int i = 0; i < clipsCount; i++)
            {
                int alternatingIndex = i % 2; // alternating targets: 0, 1, 0, 1, ...
                var asset            = this.Clips[i];
                var timeRangeInAsset = this.ClipTimeRanges[i];

                var clipVideoTrack = asset.TracksWithMediaType(AVMediaType.Video)[0];
                compositionVideoTracks[alternatingIndex].InsertTimeRange(timeRangeInAsset, clipVideoTrack, nextClipStartTime, out _);

                var clipAudioTrack = asset.TracksWithMediaType(AVMediaType.Audio)[0];
                compositionAudioTracks[alternatingIndex].InsertTimeRange(timeRangeInAsset, clipAudioTrack, nextClipStartTime, out _);

                // Remember the time range in which this clip should pass through.
                // First clip ends with a transition.
                // Second clip begins with a transition.
                // Exclude that transition from the pass through time ranges
                passThroughTimeRanges[i] = new CMTimeRange {
                    Start = nextClipStartTime, Duration = timeRangeInAsset.Duration
                };

                if (i > 0)
                {
                    passThroughTimeRanges[i].Start    = CMTime.Add(passThroughTimeRanges[i].Start, transitionDuration);
                    passThroughTimeRanges[i].Duration = CMTime.Subtract(passThroughTimeRanges[i].Duration, transitionDuration);
                }

                if (i + 1 < clipsCount)
                {
                    passThroughTimeRanges[i].Duration = CMTime.Subtract(passThroughTimeRanges[i].Duration, transitionDuration);
                }

                // The end of this clip will overlap the start of the next by transitionDuration.
                // (Note: this arithmetic falls apart if timeRangeInAsset.duration < 2 * transitionDuration.)
                nextClipStartTime = CMTime.Add(nextClipStartTime, timeRangeInAsset.Duration);
                nextClipStartTime = CMTime.Subtract(nextClipStartTime, transitionDuration);

                // Remember the time range for the transition to the next item.
                if (i + 1 < clipsCount)
                {
                    transitionTimeRanges[i] = new CMTimeRange {
                        Start = nextClipStartTime, Duration = transitionDuration
                    };
                }
            }

            // Set up the video composition to perform cross dissolve or diagonal wipe transitions between clips.
            var instructions = new List <AVVideoCompositionInstruction>();

            // Cycle between "pass through A", "transition from A to B", "pass through B"
            for (int i = 0; i < clipsCount; i++)
            {
                int alternatingIndex = i % 2; // alternating targets

                if (mutableVideoComposition.CustomVideoCompositorClass != null)
                {
                    var videoInstruction = new CustomVideoCompositionInstruction(compositionVideoTracks[alternatingIndex].TrackID, passThroughTimeRanges[i]);
                    instructions.Add(videoInstruction);
                }
                else
                {
                    // Pass through clip i.
                    var passThroughInstruction = AVMutableVideoCompositionInstruction.Create() as AVMutableVideoCompositionInstruction;
                    passThroughInstruction.TimeRange = passThroughTimeRanges[i];

                    var passThroughLayer = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks[alternatingIndex]);
                    passThroughInstruction.LayerInstructions = new[] { passThroughLayer };

                    instructions.Add(passThroughInstruction);
                }

                if (i + 1 < clipsCount)
                {
                    // Add transition from clip i to clip i+1.
                    if (mutableVideoComposition.CustomVideoCompositorClass != null)
                    {
                        var videoInstruction = new CustomVideoCompositionInstruction(new NSNumber[]
                        {
                            compositionVideoTracks[0].TrackID,
                            compositionVideoTracks[1].TrackID
                        }, transitionTimeRanges[i]);

                        if (alternatingIndex == 0)
                        {
                            // First track -> Foreground track while compositing
                            videoInstruction.ForegroundTrackId = compositionVideoTracks[alternatingIndex].TrackID;
                            // Second track -> Background track while compositing
                            videoInstruction.BackgroundTrackId = compositionVideoTracks[1 - alternatingIndex].TrackID;
                        }

                        instructions.Add(videoInstruction);
                    }
                    else
                    {
                        var transitionInstruction = AVMutableVideoCompositionInstruction.Create() as AVMutableVideoCompositionInstruction;
                        transitionInstruction.TimeRange = transitionTimeRanges[i];

                        var fromLayer = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks[alternatingIndex]);
                        var toLayer   = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(compositionVideoTracks[1 - alternatingIndex]);

                        transitionInstruction.LayerInstructions = new[] { toLayer, fromLayer };
                        instructions.Add(transitionInstruction);
                    }
                }
            }

            mutableVideoComposition.Instructions = instructions.ToArray();
        }