New Year, New Dev: Video Capture and Media Editing — Part 2

Windows Developer
Windows Developer
Published in
7 min readMar 15, 2017

This is the second post, in a two-part series, that shows you how to record video and edit it in your own UWP application. In the last post, we showed you how to use the MediaCapture API to record video from a camera connected to the user’s device. In this post, we’ll show you how to edit that video using the MediaComposition APIs.

As in the last post, we need to think about the operational steps before we start coding. At this point in the application, we know we’ve saved a video file to the user’s LocalStorage folder. This will be our starting point today. You can follow along with full example code by going to the demo app’s EditingPage source here.

Okay, let’s get started!

The UI

Before digging into the MediaComposition API, let’s set up our XAML. We’ll primarily need a MediaElement to play the video, a Slider to trim the video, a trim AppBarButton and a save AppBarButton.

<Grid Background=”{ThemeResource ApplicationPageBackgroundThemeBrush}”>

<Grid.RowDefinitions><RowDefinition Height=”Auto” /><RowDefinition Height=”Auto” /><RowDefinition /><RowDefinition Height=”Auto” /></Grid.RowDefinitions><Grid Background=”{ThemeResource ApplicationForegroundThemeBrush}”><TextBlock x:Name=”StatusTextBlock”Foreground=”{ThemeResource ApplicationPageBackgroundThemeBrush}”Text=”Status”HorizontalAlignment=”Center” /></Grid><Grid Grid.Row=”1"><Slider x:Name=”EndTrimSlider”Header=”Trim the end of the video (in milliseconds)”IsDirectionReversed=”True”Margin=”20,0" /></Grid><MediaElement x:Name=”EditorMediaElement”AutoPlay=”False”Margin=”5"HorizontalAlignment=”Stretch”AreTransportControlsEnabled=”True”Grid.Row=”2" /><CommandBar Grid.Row=”3"><AppBarButton x:Name=”TrimClipButton”Icon=”Trim”Click=”TrimClip_Click”IsEnabled=”False” /><AppBarButton x:Name=”SaveButton”Icon=”Save”Click=”Save_Click”IsEnabled=”False” /></CommandBar></Grid>

With the XAML done, now we can turn our attention to the code-behind.

MediaComposition and MediaClip

The MediaComposition API is a powerful set of tools you can use to do a lot of things to media with very little code. You can trim video, add background audio, stitch together video clips, add video overlays and much more. In today’s example, we’ll use the Trim operation.

A MediaComposition object has a Clips collection, from which you add or remove MediaClips. You can create a MediaClip from a few different sources; today we’ll be using a StorageFile.

MediaComposition and MediaClip

The MediaComposition API is a powerful set of tools you can use to do a lot of things to media with very little code. You can trim video, add background audio, stitch together video clips, add video overlays and much more. In today’s example, we’ll use the Trim operation.

A MediaComposition object has a Clips collection, from which you add or remove MediaClips. You can create a MediaClip from a few different sources; today we’ll be using a StorageFile.

In the demo, I passed the StorageFile as a parameter when navigating to EditingPage. With this we can cast e.Parameter to StorageFile and use that to create the MediaClip (at this point, you can load a storage file any way you want, like from a FileOpenPicker).

var videoFile = e.Parameter as StorageFile;if (videoFile != null){// Create a MediaClip from a StorageFilevar clip = await MediaClip.CreateFromFileAsync(videoFile);}

Because we’ll be trimming this video, we want to know the total duration of the video. This is because we have a Slider control in the XAML that we’ll use to trim the video. Knowing the length of the video lets us set the Slider’s Maximum value so we don’t try to trim more than the whole video.

We can actually get this video duration value right from the MediaClip!

EndTrimSlider.Maximum = clip.OriginalDuration.Milliseconds;

Now the work for the MediaClip is done, we can add the MediaClip to a MediaComposition. First, let’s add a MediaComposition and MediaStreamSource fields to the page class:

public sealed partial class EditingPage : Page{private MediaComposition composition;private MediaStreamSource mediaStreamSource;}

We want those at the class scope because we’ll be using them in other methods on the page. Now, back in the OnNavigatedTo handler, we new-up the MediaComposition and add the media clip.

composition = new MediaComposition();composition.Clips.Add(clip);

Now, we need to create a MediaStreamSource from the MediaComposition so that the MediaPlayer in the XAML (EditorMediaElement) can play what’s in the composition.

First, we want to make sure the MediaPlayer is at the beginning, then create the MediaStreamSource (passing the MediaElement’s dimensions) and set it as the MediaElement’s source.

EditorMediaElement.Position = TimeSpan.Zero;mediaStreamSource = composition.GeneratePreviewMediaStreamSource((int)EditorMediaElement.ActualWidth, (int)EditorMediaElement.ActualHeight);EditorMediaElement.SetMediaStreamSource(mediaStreamSource);

Here’s the complete OnNavigatedTo handler:

protected override async void OnNavigatedTo(NavigationEventArgs e){var videoFile = e.Parameter as StorageFile;if (videoFile != null){StatusTextBlock.Text = videoFile.DisplayName;// Create a MediaClip from the filevar clip = await MediaClip.CreateFromFileAsync(videoFile);// Set the End Trim slider’s maximum value so that the user can trim from the end// You can also do this from the startEndTrimSlider.Maximum = clip.OriginalDuration.Milliseconds;// Create a MediaComposition containing the clip and set it on the MediaElement.composition = new MediaComposition();composition.Clips.Add(clip);// start the MediaElement at the beginningEditorMediaElement.Position = TimeSpan.Zero;// Create the media source and assign it to the media playermediaStreamSource = composition.GeneratePreviewMediaStreamSource((int)EditorMediaElement.ActualWidth, (int)EditorMediaElement.ActualHeight);// Set the MediaElement’s sourceEditorMediaElement.SetMediaStreamSource(mediaStreamSource);TrimClipButton.IsEnabled = true;}}

At this point, you can actually play the video in the MediaElement, but we’re here to do some editing!

Trimming Video

Let’s think about the Slider, you can slide it back as far as you want to trim the end of the video. Here’s a screenshot:

With a length selected, the user would click the “Trim” button in the CommandBar at the bottom. In that button’s click handler, we can get the video clip that’s in the composition by doing the following:

MediaClip clip = composition.Clips.FirstOrDefault();

Now we can perform the Trim operation. There are two available: TrimTimeFromFromStart and TrimTimeFromEnd. Since we’re only trimming from the end, we can use the value of the Slider:

clip.TrimTimeFromEnd = TimeSpan.FromMilliseconds((long)EndTrimSlider.Value);

With the clip trimmed, let’s repeat what we did when first loading the clip: reset the MediaElement’s position to the beginning, generate the MediaStreamSource and set it to the MediaElement again.

EditorMediaElement.Position = TimeSpan.Zero;mediaStreamSource = composition.GeneratePreviewMediaStreamSource((int)EditorMediaElement.ActualWidth, (int)EditorMediaElement.ActualHeight);EditorMediaElement.SetMediaStreamSource(mediaStreamSource);

The user will see the new composition and can trim again if they’d like. Each time the composition is changed, the source is regenerated and the user will see instant results.

Here’s the complete click event handler:

private void TrimClip_Click(object sender, RoutedEventArgs e){// Get the first clip in the MediaComposition// We know this beforehand because it’s the only clip in the composition// that we created from the passed video fileMediaClip clip = composition.Clips.FirstOrDefault();// Trim the end of the clip (you can use TrimTimeFromStart to trim from the beginning)clip.TrimTimeFromEnd = TimeSpan.FromMilliseconds((long)EndTrimSlider.Value);// Rewind the MediaElementEditorMediaElement.Position = TimeSpan.Zero;// Update the video source with the trimmed clipmediaStreamSource = composition.GeneratePreviewMediaStreamSource((int)EditorMediaElement.ActualWidth, (int)EditorMediaElement.ActualHeight);// Set the MediaElement’s sourceEditorMediaElement.SetMediaStreamSource(mediaStreamSource);// Update the UIEndTrimSlider.Value = 0;StatusTextBlock.Text = “Clip trimmed! Trim again or click Save.”;StatusTextBlock.Foreground = new SolidColorBrush(Colors.LawnGreen);SaveButton.IsEnabled = true;}

Saving the MediaCompositon

What the user has been viewing is not the final video; we need to tell the MediaComposition that we’re done editing and want to render a video file of the result. We have a “Save” button in the XAML — in that button’s click handler, we can do this work.

First, we need a StorageFile to save the video. This time we’ll use “Edited Video.mp4” with a timestamp as a file name:

var file = await ApplicationData.Current.LocalFolder.CreateFileAsync($”Edited Video {DateTime.Now:D}.mp4");

Now, we can start the render operation. First we define the saveOperation Task:

var saveOperation = composition.RenderToFileAsync(file, MediaTrimmingPreference.Precise);

Notice the second parameter of the RenderToFileAsync method, MediaTrimmingPreference. This determines which trimming algorithm to use during transcoding. Fast is quicker but Precise gives better results.

Next, we hook into the Progress and Competed events of the transcoding operation. This not only lets you show the user progress and wrap things up when the file is done, but also if there were any problems.

// This will show progress as video is rendered and savedsaveOperation.Progress = async (info, progress) =>{await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>{StatusTextBlock.Text = string.Format(“Saving file… Progress: {0:F0}%”, progress);});};// This fires when the operation is completesaveOperation.Completed = async (info, status) =>{await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>{var results = info.GetResults();if (results != TranscodeFailureReason.None || status != AsyncStatus.Completed){StatusTextBlock.Text = “Saving was unsuccessful”;}else{// Successful save}});};

Note that we need to use the Dispatcher to update the UI because this work is not being done on the UI thread.

Here’s the complete click event handler:

private async void Save_Click(object sender, RoutedEventArgs e){EnableButtons(false);StatusTextBlock.Text = “Creating new file…”;var file = await ApplicationData.Current.LocalFolder.CreateFileAsync($”Edited Video {DateTime.Now:D}.mp4");if (file != null){var saveOperation = composition.RenderToFileAsync(file, MediaTrimmingPreference.Precise);// This will show progress as video is rendered and savedsaveOperation.Progress = async (info, progress) =>{await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>{StatusTextBlock.Text = string.Format(“Saving file… Progress: {0:F0}%”, progress);});};// This fires when the operation is completesaveOperation.Completed = async (info, status) =>{await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>{try{var results = info.GetResults();if (results != TranscodeFailureReason.None || status != AsyncStatus.Completed){StatusTextBlock.Foreground = new SolidColorBrush(Colors.Red);StatusTextBlock.Text = “Saving was unsuccessful”;}else{// Successful save, go back to main page.if (Frame.CanGoBack)Frame.GoBack();}}finally{// Remember to re-enable controls on both success and failureEnableButtons(true);}});};}else{EnableButtons(true);}}

Wrapping Up

That’s all there really is to trimming a video. The key takeaway is that the MediaComposition is an easy to use and powerful API: you can add and remove media clips, add background audio (or music) and video overlays as simply as adding an item to a collection. You can build a simple app, like we have here, or even build out a full media editing application with this UWP API.

Be sure to check out the full demo’s source code here on GitHub to see the extra polish, like enabling and disabling buttons at the right times. We look forward to seeing what you can create with MediaCapture and MediaComposition!

Resources

--

--

Windows Developer
Windows Developer

Everything you need to know to develop great apps, games and other experiences for Windows.