Accessing the file system efficiently (HTML)

The file system and media files are an important part of most apps, and also one of the most common sources of performance issues. Accessing media files can be expensive, because it takes memory and CPU cycles to store and decode or display the media. Learn how to improve the performance of your app when working with media files.

Use thumbnails for quick rendering

Instead of scaling down a large version of an image to display as a thumbnail, use the Windows Runtime thumbnail APIs. Scaling down a full-size image is inefficient because the app must read and decode the full-size image and then spend additional time scaling it. The Windows Runtime provides a set of APIs backed by an efficient cache to allow the app to quickly get a smaller version of an image to use for a thumbnail. Using these APIs can improve code execution times by a few seconds and improve the visual quality of the thumbnail. Because these APIs cache the thumbnails, they can speed up subsequent launches of your app.

This example prompts the user for an image and then displays the image.

// Pick an image file
picker.pickSingleFileAsync()
.then(function (file) {
  var imgTag = document.getElementById("imageTag");
  imgTag.src = URL.createObjectURL(file, false);
});

The previous example works well when you want to render the image at, or near, its full size, but it's inefficient for displaying a thumbnail view of the image. The thumbnail APIs return a thumbnail version of the image that the app can decode and display much faster than the full-sized image. The next example uses the getThumbnailAsync method to retrieve the image and create a thumbnail based on it.

// Pick an image file
picker.pickSingleFileAsync()
.then(function (file) {
  var properties = Windows.Storage.FileProperties.ThumbnailMode;
  return file.getThumbnailAsync(properties.singleItem, 1024);
})
.then(function (thumb) {
  var imgTag = document.getElementById("imageTag");
  imgTag.src = URL.createObjectURL(thumb, false);
});

Use the right thumbnail size

The system caches thumbnails at several different sizes. To improve performance and image quality, display thumbnails at one of their cached sizes so the app doesn't have to resize it. Here are the thumbnail sizes (in pixels) for the SingleItem and PicturesView thumbnail modes:

Batch requests for multiple file property reads

When you want to access a large collection of files and you want to access property values other than the typical name, fileType, and path properties, access them through the Windows.Storage.BulkAccess APIs. Using the Windows.Storage.BulkAccess can reduce read times by a few hundred milliseconds when processing a lot of files.

The Windows.Storage.BulkAccess APIs combine multiple file operations into a single operation, making it faster to access multiple files and file properties. The Windows.Storage.BulkAccess APIs can dramatically improve the performance of apps that display a collection of items obtained from the file system, such as a collection of images.

The next set of examples shows a few ways to access multiple files.

The first example uses Windows.Storage.StorageFolder.getFilesAsync to retrieve the name info for a set of files. This approach provides good performance, because the example accesses only the name property.

var library = Windows.Storage.KnownFolders.picturesLibrary;
library.getFilesAsync(Windows.Storage.Search.CommonFileQuery.orderByDate, start, page)
.then(function (files) {
    var count = files.length;
    for (var i = 0; i < count; i++) {
        // The name property comes with every file
        console.log(files[i].name);
    }
});

The second example uses Windows.Storage.StorageFolder.getFilesAsync and then retrieves the image properties for each file. This approach provides poor performance.

// Do not use: inefficient code. 
var library = Windows.Storage.KnownFolders.picturesLibrary;
library.getFilesAsync(Windows.Storage.Search.CommonFileQuery.orderByDate, start, page)
.then(function (files) {
    var count = files.length;
    for (var i = 0; i < count; i++) {
        // The dateTaken property is image specific and needs to be specially requested
        files[i].properties.getImagePropertiesAsync().then(function (props) {
            console.log(file.name + " - " + props.dateTaken);
        });
    });
});

The third example uses Windows.Storage.BulkAccess.FileInformationFactory.getFilesAsync to obtain image property info about a set of files. This approach provides much better performance than the previous example.

var library = Windows.Storage.KnownFolders.picturesLibrary;
var query = library.createFileQuery(Windows.Storage.Search.CommonFileQuery.orderByDate);
var delayLoad = true; // Depends on if/when/how fast you want your thumbnails
var access = new Windows.Storage.BulkAccess.FileInformationFactory(
    query, Windows.Storage.FileProperties.ThumbnailMode.picturesView,
    Math.max(app.settings.itemHeight, app.settings.itemWidth),
    Windows.Storage.FileProperties.ThumbnailOptions.returnOnlyIfCached,
    delayLoad);
access.getFilesAsync(start, page).then(function (files) {
    var count = files.length;
    for (var i = 0; i < count; i++) {
        console.log(files[i].name + " - " + files[i].imageProperties.dateTaken);
        // The more 'extra' properties you're accessing, the better the perf gains
    }
});

If you're performing multiple operations on Windows.Storage objects such as Windows.Storage.ApplicationData.Current.LocalFolder, create a local variable to reference that storage source so that you don't recreate intermediate objects each time you access it.

Given the expensive cost of reading files, avoid reading files while the app is launching. Instead, enumerate the library after the app launches and consider caching the results if they are needed for each launch.

Release media and streams when you're done

Media files are some of the most common and expensive resources apps typically use. Because media file resources can greatly increase the size of your app's memory footprint, it's important to release the handle to media as soon as the app is finished using it. Releasing media streams that are unused can significantly reduce the memory footprint of your app and help it avoid getting terminated when it's suspended.

For example, if your app is using a Blob, be sure to call URL.revokeObjectURL to revoke the URL for the Blob which releases the reference to the underlying stream.

If your app is working with a RandomAccessStream or an IInputStream object, be sure to call the close method on the object when your app has finished using it, to release the underlying object.

Revoke all URLs created with URL.createObjectURL to avoid memory leaks

A common way to load media for an audio, video, or img element is to use the URL.createObjectURL method to create a URL it can use. When you use this method, it tells the system to keep an internal reference to your media (which might be a Blob, a File, or a StorageFile). The system uses this internal reference to stream the object to the appropriate element. But the system doesn't know when the data is need, so it keeps the internal reference until you tell it to release. It's easy to accidently retain unnecessary internal references, which can consume large amounts of memory.

There are two ways to release your objects:

  • You can revoke the URL explicitly by calling the URL.revokeObjectURL method and passing it the URL.

  • You can tell the system to automatically revoke the URL after it's used once.

    If you are certain the URL will be read (by using it to set the src attribute of a media element), you can set the URL.createObjectURL method's second parameter, a property bag, with the oneTimeOnly property set to true to tell the system to automatically revoke the URL after the system finishes reading the object. For example:

    var url = URL.createObjectURL(blob, {oneTimeOnly: true});
    

    Note  When using a non-reusable URL, make sure to use the URL to set the src attribute of an audio, video, or img element. Otherwise, the system won't release the internal reference. If you're not sure how or when the URL will be used, create the URL when it is needed or use URL.revokeObjectURL to explicitly revoke the URL instead.

     

    Note  Blob contents can only be printed in full fidelity when you use a reusable URL. So, if you want to print the contents of a Blob, don't set oneTimeOnly property to true when you call URL.createObjectURL.

     

Create media elements only when needed, and use a poster image

Don't create media elements, such as the video element, until you're about to use them. Creating media elements requires considerable CPU and memory overhead, so create them as they're needed rather than creating them in bulk.

When creating a video element that doesn't play automatically, set the video element's poster attribute. If you don't set the poster attribute, the media engine has to start decoding the video to render the first frame to obtain a poster image.

Here's an example of how to set the poster attribute.

<video src = "video.h264" poster = "poster.png"></video>

Use the ms-appdata:// scheme to load media

There are two common ways to load media stored in your app's data location: using the storage APIs and using the ms-appdata:// scheme. The storage APIs go through a broker process to access the media in the app's data location, so they take longer to execute. Instead of using the storage APIs, use the ms-appdata:// scheme, which loads the media in the same process and is much faster.