HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

UWP scan for music files

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
scanfilesuwpformusic

Problem

I am implementing a music player for Windows 10, and I am in a bit of a pickle at the library phase.

I am scanning for all the files in a music library (plus optional folders the user selects) and I need to get their tags (only need the title and artist; don't need complex details).

The problem is performance. Getting the music properties cost a LOT if you have a large media library. At the moment I am doing the following:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    Task.Factory.StartNew(async () =>
    {
        try
        {
            var files = await StorageHelper.GetFiles(KnownFolders.MusicLibrary);
            List tempList = new List();
            foreach (var file in files)
            {
                MusicProperties properties = await file.Properties.GetMusicPropertiesAsync();

                var title = string.IsNullOrWhiteSpace(properties.Title) ? file.Name : properties.Title;
                var artist = string.IsNullOrWhiteSpace(properties.Artist) ? file.Path : properties.Artist;
                tempList.Add(new LocalAudio() { Title = title, FilePath = file.Path, Artist = artist, Duration = properties.Duration });
                if (tempList.Count > 50)
                {
                    await AddToLibrary(tempList);
                    tempList.Clear();
                }
            }
            await AddToLibrary(tempList);
            tempList.Clear();
        }
        catch (Exception ex)
        {
            //log exception here
        }
    });
}

private async Task AddToLibrary(List tempList)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        foreach (var item in tempList)
        {
            this.LocalFiles.Add(item);
        }
    });
}


Where LocalAudio is my audio model and this.LocalFiles is an ObservableCollection. Also, the StorageHelper.GetFiles(StorageFolder) method returns an IEnumerable of StorageFiles (music files) (it's

Solution

Task.Factory.StartNew(async () =>


I don't see any expensive non-async operations here, so I would consider directly running this code on the UI thread. It would also mean that you don't have to use Dispatcher.RunAsync in AddToLibrary.

Getting the properties will stay asynchronous, so it shouldn't block the UI.

List tempList = new List();


You can use var here, since the type of the variable is clear.

All async methods should be named ending with Async, in your case, that means AddToLibraryAsync.

I don't know if this is going to help, but you might consider getting the music properties in parallel. For example (using Batch from MoreLINQ or your own implementation):

var files = await StorageHelper.GetFiles(KnownFolders.MusicLibrary);

foreach (var batch in files.Batch(50))
{
    IEnumerable> propertiesTasks = batch.Select(async file => 
    {
        MusicProperties properties = await file.Properties.GetMusicPropertiesAsync();

        var title = string.IsNullOrWhiteSpace(properties.Title) ? file.Name : properties.Title;
        var artist = string.IsNullOrWhiteSpace(properties.Artist) ? file.Path : properties.Artist;
        return new LocalAudio { Title = title, FilePath = file.Path, Artist = artist, Duration = properties.Duration };
    });

    LocalAudio[] properties = await Task.WhenAll(propertiesTasks);

    AddToLibrary(properties); // I'm assuming AddToLibrary is no longer async
}


You seem to be worried that adding the data to your UI will take too long. The solution to that is to not show the user so much data. Some solutions to that are only showing one "page" of data at a time or using virtualization to not create more UI elements than necessary (I have no idea if or how well does virtualization work on UPW).

Another possibility is to use paging, virtualization or just showing only a limited number of items, and combine it with some form of filtering. That way, when a user is looking for a song, they for example can type its name, instead of scrolling though a long list.

Code Snippets

Task.Factory.StartNew(async () =>
List<LocalAudio> tempList = new List<LocalAudio>();
var files = await StorageHelper.GetFiles(KnownFolders.MusicLibrary);

foreach (var batch in files.Batch(50))
{
    IEnumerable<Task<LocalAudio>> propertiesTasks = batch.Select(async file => 
    {
        MusicProperties properties = await file.Properties.GetMusicPropertiesAsync();

        var title = string.IsNullOrWhiteSpace(properties.Title) ? file.Name : properties.Title;
        var artist = string.IsNullOrWhiteSpace(properties.Artist) ? file.Path : properties.Artist;
        return new LocalAudio { Title = title, FilePath = file.Path, Artist = artist, Duration = properties.Duration };
    });

    LocalAudio[] properties = await Task.WhenAll(propertiesTasks);

    AddToLibrary(properties); // I'm assuming AddToLibrary is no longer async
}

Context

StackExchange Code Review Q#129767, answer score: 2

Revisions (0)

No revisions yet.