Video Streaming with a custom IRandomAccessStream on Windows and Windows Phone (Universal App)

 

Overview

 

We encountered a challenge on our project where we wanted to play videos in MediaElement but the MP4 file was stored in Sharepoint which required user credentials for authentication. If you pass a URL to the MediaElement on Windows Phone 8.1 then it just tried to access the file without passing any authentication information and there is no easy way to intercept this. We escalated this to the Product Group and got the response that this was by design.

Solution

We came up with the idea of writing a custom IRandomAccessStream and setting it on top of WebClient so we could manipulate the http requests going out, and we wanted to use range headers so we could request just a sequence of bytes from the server and would not need to download the whole MP4 file in order to stream it. Although simple in concept, this proved quite challenging and took many complete rewrites of our solution and a team effort where we were all contributing ideas and code, but we finally got it working, so I’m posting it here.

There were three really tough problems –

  1. Get the IRandomAccessStream implemented properly so that MediaElement would stream and play it
  2. Get the http RangeHeaders working so that we didn’t need to download the whole file before it started
    playing
  3. Figuring out the mix of IAsyncOperationWithProgress and the async/Task feature so we could do async
    operations in the middle of data requests

We actually got each of the three pieces working separately before combining them into the working solution. The web proved very little help as there seemed to be a lot of people struggling with this but nobody seemed to get it working so hence this blog post. We also saw people trying custom MediaStreamSource implementations but that looked much more complex than the custom IRandomAccessStream if we could get it to work.

The solution posted here uses Universal apps and has a Phone project and a Windows 8.1 project so works on both platforms. Most of the files in the solution are pretty much just the standard template files with the exception of StreamingRandomAccessStream.cs and the following lines in MainPage.xaml.cs:

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    var uri = new Uri("https://archive.org/download/RescuefromGilligansIsland/RescuefromGilligansIsland_512kb.mp4");
    var headers = await StreamingRandomAccessStream.GetHeaders(uri);
    ulong length = (ulong)headers.ContentLength;
    string mimeType = headers.ContentType.MediaType;
    var stream = new StreamingRandomAccessStream(null, uri, length);
    mediaElement.SetSource(stream, mimeType);
}

Here you see us fetching the headers from the MP4 file and creating a new instance of StreamingRandomAccessStream which is where the magic happens. Note that the sample Gilligan’s Island video does not actually need authentication but it was useful to test with as it is quite large. You’ll need to replace the dummy usernames and passwords in the sample to use the authentication piece.

The StreamingRandomAccessStream has some static methods for managing the http traffic but where it all happens is really this method:

public IAsyncOperationWithProgress<IBuffer, uint> ReadAsync(IBuffer buffer, uint count, InputStreamOptions options)
{
    return AsyncInfo.Run<IBuffer, uint>(async (cancellationToken, progress) =>
    {
        progress.Report(0);
        if (_stream == null)
        {
            var netStream = await GetStreamWithRange(_requestedUri, _requestedPosition, _requestedPosition + count);
            _stream = netStream.AsInputStream();
        }

        return await _stream.ReadAsync(buffer, count, options).AsTask(cancellationToken, progress);
    });
}

This method is called through the IRandomAccessStream interface by the MediaElement to request the bytes of the video. The really tough bit that took a lot of wrestling with was getting the delegate signatures right and figuring out how to do an await in the middle of an IAsyncOperation.

Note when using range headers on Windows Phone there's a slight difference to Windows 8 in that the end position cannot be null on phone otherwise it throws an error, despite being a nullable type. This works fine on Windows 8.

Anyhow, hoping this blog post helps you and thanks to the team – Iestyn Jones, Simon Middlemiss and Paul Tallett.

Enjoy!

 

VideoTest.zip