Thomas
Thomas

Reputation: 4297

Memory Leak playing videos on Windows IoT | UWP

I've built an app that can read video files from an USB drive and switch between them using physical buttons. The app works well for a while, but after a while the device (DragonBoard 410c, latest Windows Insider Preview Build 15051) crashes due to the fact that all memory has been consumed by the app.

Looking at the processes in the device portal, I can see the "Working Set" memory jump each time I switch a video file while the "Private Working Set" roughly stays the same (around 30MB).

Here's how I load the video file:

C#

private IReadOnlyList<StorageFile> _videofiles

// list all available video files
public void Init(){
    var queryOptions = new QueryOptions();
    queryOptions.FolderDepth = depth;
    foreach (var fileType in fileTypes)
    {
        queryOptions.FileTypeFilter.Add(fileType);
    }

    var query = KnownFolders.RemovableDevices.CreateFileQueryWithOptions(queryOptions);
    _videofiles = await query.GetFilesAsync();
}

private async void SelectVideo(int videoId)
{         
    StorageFile videofile = _videofiles.Where(x => x.DisplayName == videoId.ToString()).FirstOrDefault();
    if (videofile != null)
    {
        Debug.WriteLine($"Video {videofile.DisplayName} was selected");
        var stream = await videofile.OpenAsync(FileAccessMode.Read);
        VideoPlayer.SetSource(stream, videofile.FileType);
    }
}

// since the button interrupt is not on the UI thread, SelectVideo() is called like this
private async void SelectVideoMarshalled(int videoId)
{
    await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
    () =>
    {
        SelectVideo(videoId);
    });
}

XAML

<ContentControl x:Name="VideoPlayer" Content="{x:Bind ViewModel.VideoPlayer, Mode=OneWay}"/>

I have tried running GC.Collect() manually in several places, but no luck yet. Any ideas?

Upvotes: 1

Views: 489

Answers (2)

Thomas
Thomas

Reputation: 4297

Turns out my code was fine after all. I had a Windows Update stuck / failing several times which I didn't notice. When the update finally completed successfully the memory leaks were gone.

Upvotes: 1

Lance McCarthy
Lance McCarthy

Reputation: 1917

Since you have a StorageFile object, I recommend using the Source property and the file's Path instead of SetSource and opening the Stream manually.

Additionally, you should always null out the MediaElement when you're done with it (best done in OnNavigatingFrom).

Here's your code, simplified:

private void SelectVideo(string videoId)
{
    var videofile = _videofiles.FirstOrDefault(x => x.DisplayName == videoId.ToString());
    if (videofile == null) return;

    Debug.WriteLine($"Video {videofile.DisplayName} was selected");

    VideoPlayer.Source = new Uri(videofile.Path);
}


protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    VideoPlayer.Stop();
    VideoPlayer.Source = null;

    base.OnNavigatedFrom(e);
}

I also have a side comment, you can x:Bind event handlers to the ViewModel.

For example, if your video file list is a ListView of string:

public void VideosListView_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e?.AddedItems?.Count > 0)
    {
        var fileDisplayName = e.AddedItems.FirstOrDefault() as string;
        if (!string.IsNullOrEmpty(fileDisplayName))
            SelectVideo(fileDisplayName);
    }
}

Notice I only need to change the method signature to public and then in the XAML you can do this:

<ListView ItemsSource="{x:Bind ViewModel.VideoFiles, Mode=OneTime}"
          SelectionChanged="{x:Bind ViewModel.VideosListView_OnSelectionChanged}"/>

No need to marshal back to the UI thread :)

Lastly, you can check out the demo here on GitHub where I've implemented something similar to this.

Upvotes: 1

Related Questions