Audio/Video Player for Windows 8 and Windows Phone 8 using HttpClient to acquire a PlayReady License

Introduction

This article describes how to create Windows 8 and Windows Phone 8 applications playing Smooth Streaming assets protected with PlayReady.  With the sample c# applications provided with this article, the user can define:
- the URL of the Smooth Streaming asset you want to play,
- the URL used for the PlayReady license acquisition,
- the Challenge Custom Data transmitted to the PlayReady server to acquire the license.
If the URL used for the license acquisition is empty, the player will use the license acquisition URL included in the PlayReady protection header (if available).

                                      image   
  image

Steps to acquire a PlayReady license for Windows Store Applications

Before describing how the PlayReady client acquires the PlayReady license, some words about the Individualization. Every PlayReady client has a unique digital certificate and key, the installation of the certificate and the key is called Individualization.

Moreover, before the PlayReady client requests the license to decrypt the content, it must first determine whether the user’s computer has the appropriate PlayReady software installed. This software is called the Individualized Black boX (IBX) and is the client component of PlayReady that is required before any protected content can be played. The individualization component software enables the client computer to request and use a PlayReady license.

The PlayReady client for Windows Store applications supports dynamic individualization: the IBX is downloaded from the Individualization Server. In your application you can implement a Pro-Active Individualization Request or a Reactive Individualization Request.

  • A Pro-Active Individualization Request can be transmitted just after the installation of the application or when the user launches the application for the first time.
  • A Reactive Individualization Request is automatically prepared by the PlayReady client when the application is about to play an asset protected with PlayReady and if the IBX is not installed on the computer.

The schema below describes the acquisition of a PlayReady license to play an asset protected with PlayReady with a Reactive Individualization Request: 

image

The schema below describes the acquisition of a PlayReady license to play an asset protected with PlayReady without any Individualization Request as the Individualized Black Box (IBX) is already installed: 

image

The schema below describes the exchanges between the client and the server when the application plays an asset protected with PlayReady when the Individualized Black Box (IBX) is already installed and the PlayReady license has already been acquired and the license is persistent: 

image

Http Client Libraries:

These PlayReady sample applications are based on Player Framework V1.2 and the PlayReady license acquisition is based on HttpClient class for both platforms Windows and Windows Phone.  
HttpClient is a part of .NET Framework 4.5 and Windows Store applications that provides developers an extremely easy way to connect with services across the Internet including REST-based services. HttpClient is now available for Windows Phone developers provided you install the Microsoft HttpClient Libraries Nuget package.

image

Building the sample applications

In order to build the Player sample applications for Windows Store and Windows Phone, make sure you have the basics installed:
1. a computer running Windows 8,
2. Visual Studio 2012,
3. Windows Phone 8 SDK.

For the Windows Store player application you need to install:
1. The Smooth Streaming Client SDK for Windows 8
2. The PlayReady Client SDK for Windows Store Apps
3. The Player Framework for Windows 8 and Windows Phone 8 (v1.2).

For the Windows Phone 8 player application you need to install:
1. The Microsoft Smooth Streaming Client 2.0 RTW
2. The Player Framework for Windows 8 and Windows Phone 8 (v1.2).
The Windows Phone 8 player application is delivered with the following Nuget packages:
1. Microsoft HTTP Client Libraries: Microsof.Net.Http 
2. Microsoft BCL Portability Pack: Microsoft.BCL
3. Microsoft BCL Build Components: Microsoft.BCL.Build

Installing the Microsoft Smooth Streaming Client SDK for Windows 8

1. Download the Smooth Streaming Client SDK from this link: https://visualstudiogallery.msdn.microsoft.com/04423d13-3b3e-4741-a01c-1ae29e84fea6
2. Double click on the VSIX file to launch the installation
      image
3. On the VSIX Installer dialog box click on the Install button
      image
4. After few seconds, the SDK is installed. Click on the Close button to close the VSIX Installer dialog box.
       image

Installing the Microsoft PlayReady Client SDK for Windows Store Apps

1. Download the PlayReady Client SDK from this link:
https://visualstudiogallery.msdn.microsoft.com/e02ccac7-f3eb-4b53-b11a-c657d5631483
2. Double click on the VSIX file to launch the installation
      image
3. On the VSIX Installer dialog box click on the Install button
      image
4. After few seconds, the SDK is installed. Click on the Close button to close the VSIX Installer dialog box.
       image

Installing the Microsoft Smooth Streaming Client 2.0 RTW

1. Download the Smooth Streaming Client SDK from this link:
https://www.microsoft.com/en-us/download/details.aspx?id=29940
2. Double click on the MSI file to launch the installation
      image
3. On the Microsoft Smooth Streaming Client 2.0 setup dialog box click on the Next button
      image
4. On the Microsoft Smooth Streaming Client 2.0 setup dialog box check the check box and click on the Next button
      image
5. On the Microsoft Smooth Streaming Client 2.0 setup dialog box click on the Next button
      image
6. On the Microsoft Smooth Streaming Client 2.0 setup dialog box click on the Install button
      image
7. After few seconds, the SDK is installed. Click on the Finish button to close the dialog box.
      image     

Installing the Player Framework for Windows 8 and Windows Phone 8 (v1.2)

1. Download the Player Framework from this link:
https://playerframework.codeplex.com/releases/view/105214  
2. Double click on the VSIX file to launch the installation
      image
3. On the VSIX Installer dialog box click on the Install button
     image
4. After few seconds, the SDK is installed. Click on the Close button to close the VSIX Installer dialog box.
     image

Opening and building the Solution

Once the SDKs are installed you can open the Solution file associated with the sample applications.

  1. Start Visual Studio 2012 and select File > Open > Project/Solution.
  2. Go to the directory in which you unzipped the sample. Go to the directory named for the sample, and double-click the Microsoft Visual Studio Solution (.sln) file.
    image
  3. Check there is no warning for the Windows 8 references
    image
  4. Check there is no warning for the Windows Phone 8 references
    image
  5. Both projects use a common C# source code file for PlayReady license acquisition CommonLicenseRequest.cs
    image
  6. The file CommonLicenseRequest.cs has been shared between both projects using the Visual Studio option Add As Link for the Add Existing Item menu .
    image
  7. Press F7 or use Build > Build Solution to build the samples.

Exploring the Windows Store Player Application project

The Windows Store Player Application project is a XAML based project. 

                image      

  • MainPage.cs contains the main page source code.
  • CommonLicenseRequest.cs contains the source code to acquire the PlayReady license using HttpClient class.
  • ApplicationConfiguration.cs contains the source code used to store and retrieve the application settings in the local app store.

      
     
In the main page constructor, the PlayReady and Smooth Streaming events are registered.  

 public MainPage()
{
    this.InitializeComponent();

    // Add Plugins
    Player.Plugins.Add(new Microsoft.PlayerFramework.Adaptive.AdaptivePlugin());

    // Init PlayReady Protection Manager
    var protectionManager = new MediaProtectionManager();
    Windows.Foundation.Collections.PropertySet cpSystems = new Windows.Foundation.Collections.PropertySet();
    cpSystems.Add("{F4637010-03C3-42CD-B932-B48ADF3A6A54}", "Microsoft.Media.PlayReadyClient.PlayReadyWinRTTrustedInput"); //Playready
    protectionManager.Properties.Add("Windows.Media.Protection.MediaProtectionSystemIdMapping", cpSystems);
    protectionManager.Properties.Add("Windows.Media.Protection.MediaProtectionSystemId", "{F4637010-03C3-42CD-B932-B48ADF3A6A54}");
    this.Player.ProtectionManager = protectionManager;
    // PlayReady Events registration
    Player.ProtectionManager.ComponentLoadFailed += ProtectionManager_ComponentLoadFailed;
    Player.ProtectionManager.ServiceRequested += ProtectionManager_ServiceRequested;


    // Init Smooth Streaming Handler
    Windows.Media.MediaExtensionManager extensions = new Windows.Media.MediaExtensionManager();
    extensions.RegisterByteStreamHandler("Microsoft.Media.AdaptiveStreaming.SmoothByteStreamHandler", ".ism", "text/xml");
    extensions.RegisterByteStreamHandler("Microsoft.Media.AdaptiveStreaming.SmoothByteStreamHandler", ".isml", "text/xml");
    extensions.RegisterByteStreamHandler("Microsoft.Media.AdaptiveStreaming.SmoothByteStreamHandler", ".ism", "application/vnd.ms-ss");
    extensions.RegisterByteStreamHandler("Microsoft.Media.AdaptiveStreaming.SmoothByteStreamHandler", ".isml", "application/vnd.ms-ss");
    extensions.RegisterSchemeHandler("Microsoft.Media.AdaptiveStreaming.SmoothSchemeHandler", "ms-sstr:");
    Player.MediaExtensionManager = extensions;
  

The method ProtectionManager_ServiceRequested is called each time a PlayReady license or a Individualization are requested. If it’s an Individualisation request the method ReactiveIndivRequest is called. If it’s a PlayReady License request the method LicenseAcquisitionRequest is called.    

 /// <summary>
/// Invoked to send a PlayReady request (Individualization or License request)
/// </summary>
async void ProtectionManager_ServiceRequested(MediaProtectionManager sender, ServiceRequestedEventArgs srEvent)
{
    LogMessage("ProtectionManager ServiceRequested");
    if (srEvent.Request is PlayReadyIndividualizationServiceRequest)
    {
        PlayReadyIndividualizationServiceRequest IndivRequest = srEvent.Request as PlayReadyIndividualizationServiceRequest;
        bool bResultIndiv = await ReactiveIndivRequest(IndivRequest, srEvent.Completion);
    }
    else if (srEvent.Request is PlayReadyLicenseAcquisitionServiceRequest)
    {
        PlayReadyLicenseAcquisitionServiceRequest licenseRequest = srEvent.Request as PlayReadyLicenseAcquisitionServiceRequest;
        LicenseAcquisitionRequest(licenseRequest, srEvent.Completion, PlayReadyLicenseUrl, PlayReadyChallengeCustomData);
    }
}

If the URL used to acquire the PlayReady License is not forced and is the one defined in the PlayReady protection header, the method LicenseAcquisitionRequest calls licenseRequest.BeginServiceRequest. If the URL to acquire the PlayReady License is forced by the application, the method LicenseAcquisitionRequest creates a PlayReady challenge using the ChallengeCustomData and then call licenseAcquisition.AcquireLicense using the URL defined by the user.

 /// <summary>
/// Invoked to acquire the PlayReady License
/// </summary>
async void LicenseAcquisitionRequest(PlayReadyLicenseAcquisitionServiceRequest licenseRequest, MediaProtectionServiceCompletion CompletionNotifier, string Url, string ChallengeCustomData)
{
    bool bResult = false;
    string ExceptionMessage = string.Empty;

    try
    {
        if (!string.IsNullOrEmpty(Url))
        {
            LogMessage("ProtectionManager PlayReady Manual License Acquisition Service Request in progress - URL: " + Url);

            if (!string.IsNullOrEmpty(ChallengeCustomData))
            {
                System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
                byte[] b = encoding.GetBytes(ChallengeCustomData);
                licenseRequest.ChallengeCustomData = Convert.ToBase64String(b, 0, b.Length);
            }

            PlayReadySoapMessage soapMessage = licenseRequest.GenerateManualEnablingChallenge(); 
            
            byte[] messageBytes = soapMessage.GetMessageBody();
            HttpContent httpContent = new ByteArrayContent(messageBytes);

            IPropertySet propertySetHeaders = soapMessage.MessageHeaders;
            foreach (string strHeaderName in propertySetHeaders.Keys)
            {
                string strHeaderValue = propertySetHeaders[strHeaderName].ToString();

                // The Add method throws an ArgumentException try to set protected headers like "Content-Type"
                // so set it via "ContentType" property
                if (strHeaderName.Equals("Content-Type", StringComparison.OrdinalIgnoreCase))
                    httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse(strHeaderValue);
                else
                    httpContent.Headers.Add(strHeaderName.ToString(), strHeaderValue);
            }
            CommonLicenseRequest licenseAcquision = new CommonLicenseRequest();
            HttpContent responseHttpContent = await licenseAcquision.AcquireLicense(new Uri(Url), httpContent);
            if (responseHttpContent != null)
            {
                Exception exResult = licenseRequest.ProcessManualEnablingResponse(await responseHttpContent.ReadAsByteArrayAsync());
                if (exResult != null)
                {
                    throw exResult;
                }
                bResult = true;
            }
            else
                ExceptionMessage = licenseAcquision.GetLastErrorMessage();
        }
        else
        {
            LogMessage("ProtectionManager PlayReady License Acquisition Service Request in progress - URL: " + licenseRequest.Uri.ToString());
            await licenseRequest.BeginServiceRequest();
            bResult = true;
        }
    }
    catch (Exception e)
    {
        ExceptionMessage = e.Message;
    }
    
    if (bResult == true)
            LogMessage(!string.IsNullOrEmpty(Url) ? "ProtectionManager Manual PlayReady License Acquisition Service Request successful":
                "ProtectionManager PlayReady License Acquisition Service Request successful");
    else
        LogMessage(!string.IsNullOrEmpty(Url) ? "ProtectionManager Manual PlayReady License Acquisition Service Request failed: "+ExceptionMessage :
            "ProtectionManager PlayReady License Acquisition Service Request failed: " + ExceptionMessage);
    CompletionNotifier.Complete(bResult);
}

The method AcquireLicense sends the PlayReady license Http Request to the PlayReady server. The request contains the PlayReady challenge and add http headers. The method PostAsync of class HttpClient is used to transmit this request.

 /// <summary>
/// Invoked to acquire the PlayReady license.
/// </summary>
/// <param name="licenseServerUri">License Server URI to retrieve the PlayReady license.</param>
/// <param name="httpRequestContent">HttpContent including the Challenge transmitted to the PlayReady server.</param>
public async virtual Task<HttpContent> AcquireLicense(Uri licenseServerUri, HttpContent httpRequestContent)
{
    try
    {
        HttpClient httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Add("msprdrm_server_redirect_compat", "false");
        httpClient.DefaultRequestHeaders.Add("msprdrm_server_exception_compat", "false");

        HttpResponseMessage response = await httpClient.PostAsync(licenseServerUri, httpRequestContent);
        response.EnsureSuccessStatusCode();

        if (response.StatusCode == HttpStatusCode.OK)
        {
            _lastErrorMessage = string.Empty;
            return response.Content;
        }
        else
        {
            _lastErrorMessage = "AcquireLicense - Http Response Status Code: " + response.StatusCode.ToString();
        }
    }
    catch (Exception exception)
    {
        _lastErrorMessage = exception.Message;
        return null;
    }
    return null;
}

        

Exploring the Windows Phone 8 Player Application project

The Windows Phone 8 Player Application project is a XAML based project. 

                image    

  • MainPage.cs contains the main page source code.    
  • CommonLicenseRequest.cs contains the source code to acquire the PlayReady license using HttpClient class.  
  • ApplicationConfiguration.cs contains the source code used to store and retrieve the application settings in the local app store.    
  • WindowsPhoneLicenseAcquisition.cs contains the source code of the class WindowsPhoneLicenseAcquisition derived from PlayReady class LicenseAcquirer used to acquire the PlayReady license.

In the main page constructor, the PlayReady events are registered using the class WindowsPhoneLicenseAcquisition.  

 //Add Smooth Streaming and Caption plugins 
Microsoft.PlayerFramework.Adaptive.AdaptivePlugin AP = new Microsoft.PlayerFramework.Adaptive.AdaptivePlugin();
Player.Plugins.Add(AP);
Player.Plugins.Add(new Microsoft.PlayerFramework.TimedText.CaptionsPlugin());
Microsoft.PlayerFramework.Adaptive.AdaptivePlugin ap = Player.Plugins.OfType<Microsoft.PlayerFramework.Adaptive.AdaptivePlugin>().FirstOrDefault();
if (ap != null)
{
    // Register PlayReady LicenseAcquirer
    ap.MediaElement.LicenseAcquirer = new WindowsPhoneLicenseAcquisition(LogMessage);
    ap.MediaElement.MediaFailed += Player_MediaFailed;
}

        
The method OnAcquireLicense is called to acquire a PlayReady license. If the LicenseServerUriOverride is defined it’s a manual license acquisition. The method convert the Stream which contains the PlayReady Challenge into a HttpContent object before calling _licenseRequest.AcquireLicense which is the common method to acquire the PlayReady license for Windows Phone and Windows Store application.

 protected async override void OnAcquireLicense(System.IO.Stream licenseChallenge, Uri licenseServerUri)
{

    // Need to resolve the URI for the License Server -- make sure it is correct
    // and store that correct URI as resolvedLicenseServerUri.
    Uri resolvedLicenseServerUri;
    if (LicenseServerUriOverride == null)
    {
        if (_LogMessage != null) _LogMessage("PlayReady License Acquisition Service Request in progress - URL: " + licenseServerUri.ToString());
        resolvedLicenseServerUri = licenseServerUri;
    }
    else
    {
        if (_LogMessage != null) _LogMessage("PlayReady Manual License Acquisition Service Request in progress - URL: " + LicenseServerUriOverride.ToString());
        resolvedLicenseServerUri = LicenseServerUriOverride;
    }

    try
    {
        StreamReader sr = new StreamReader(licenseChallenge);
        string challengeString = sr.ReadToEnd();
        Byte[] Data = System.Text.Encoding.UTF8.GetBytes(challengeString);
        HttpContent httpRequestContent = new ByteArrayContent(Data);

        httpRequestContent.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
        httpRequestContent.Headers.ContentLength = Data.Length;


        HttpContent httpResponseContent = await _licenseRequest.AcquireLicense(resolvedLicenseServerUri, httpRequestContent);
        if (httpResponseContent != null)
        {
            Stream srep = await httpResponseContent.ReadAsStreamAsync();
            if (srep != null)
            {
                if (_LogMessage != null) _LogMessage("PlayReady License Acquisition successful.");
                SetLicenseResponse(srep);
                return;
            }
        }
        else
            if (_LogMessage != null) _LogMessage("PlayReady License Acquisition failed. Exception: " + _licenseRequest.GetLastErrorMessage());
    }
    catch(Exception e)
    {
        if (_LogMessage != null) _LogMessage("PlayReady License Acquisition failed. Exception: " + e.Message);
    }
    
    if (_LogMessage != null) _LogMessage("PlayReady License Acquisition failed.");
    // Call CancelAsync 
    // In that case the MediaElement transitions from the AcquiringLicense state to the error state and
    // calling this method causes playback to fail with a "license acquisition failure" error.
    try
    {
        CancelAsync();
    }
    catch
    {
    }
}

 

Testing the Windows Store and Windows Phone PlayReady Player applications

By default the applications will try to play the following Smooth Streaming asset:
https://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism/Manifest
using the following URL for the license acquisition:
https://playready.directtaps.net/pr/svc/rightsmanager.asmx?PlayRight=1&UseSimpleNonPersistentLicense=1 
This URL will return a non-persistent license. Each time the application will try to play the smooth streaming asset, a request to the PlayReady server will be transmitted.
By default, the challenge custom data is empty, you can change this field if the PlayReady server expect a specific challenge custom data.
Persistent license:
If you use the following URL, the license returned will be persistent:
https://playready.directtaps.net/pr/svc/rightsmanager.asmx

Downloading the projects

Download:        C# (3.3 MB)
Requires:           Windows 8, Visual Studio 2012, Windows Phone 8 SDK
Last updated:    10/18/2013
License:              MS-LPL
Technologies:    PlayReady, Smooth Streaming, PlayerFramework, C#, XAML
Topics:               PlayReady Smooth Streaming Player for Windows Phone and Windows Store