使用 PlayReady 的自适应流式处理Adaptive streaming with PlayReady

本文介绍如何将使用 Microsoft PlayReady 内容保护的多媒体内容自适应流式处理添加到通用 Windows 平台 (UWP) 应用。This article describes how to add adaptive streaming of multimedia content with Microsoft PlayReady content protection to a Universal Windows Platform (UWP) app.

此功能当前支持 HTTP 动态流 (DASH) 内容的播放。This feature currently supports playback of Dynamic streaming over HTTP (DASH) content.

HLS(Apple 的 HTTP 实时流)不受 PlayReady 支持。HLS (Apple's HTTP Live Streaming) is not supported with PlayReady.

平滑流式处理当前也不受本地支持;但是,PlayReady 可扩展,并且通过使用其他代码或库,PlayReady 保护的平滑流式处理可以受到支持,从而利用软件甚至是硬件 DRM(数字版权管理)。Smooth streaming is also currently not supported natively; however, PlayReady is extensible and by using additional code or libraries, PlayReady-protected Smooth streaming can be supported, leveraging software or even hardware DRM (digital rights management).

本文仅介绍特定于 PlayReady 的自适应流式处理方面的内容。This article only deals with the aspects of adaptive streaming specific to PlayReady. 有关一般实现自适应流式处理的信息,请参阅自适应流式处理For information about implementing adaptive streaming in general, see Adaptive streaming.

本文使用的代码来自 GitHub 上 Microsoft 的 Windows-universal-samples 存储库中的自适应流式处理示例This article uses code from the Adaptive streaming sample in Microsoft's Windows-universal-samples repository on GitHub. 方案 4 介绍自适应流式处理与 PlayReady 的结合使用。Scenario 4 deals with using adaptive streaming with PlayReady. 你可以下载 ZIP 文件形式的存储库,方法是导航到存储库的根级别并选择“下载 ZIP”**** 按钮。You can download the repo in a ZIP file by navigating to the root level of the repository and selecting the Download ZIP button.

你将需要以下 using 语句:You will need the following using statements:

using LicenseRequest;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Windows.Foundation.Collections;
using Windows.Media.Protection;
using Windows.Media.Protection.PlayReady;
using Windows.Media.Streaming.Adaptive;
using Windows.UI.Xaml.Controls;

LicenseRequest 命名空间来自于 CommonLicenseRequest.cs,这是 Microsoft 提供给被许可方的 PlayReady 文件。The LicenseRequest namespace is from CommonLicenseRequest.cs, a PlayReady file provided by Microsoft to licensees.

你将需要声明几个全局变量:You will need to declare a few global variables:

private AdaptiveMediaSource ams = null;
private MediaProtectionManager protectionManager = null;
private string playReadyLicenseUrl = "";
private string playReadyChallengeCustomData = "";

你还需要声明以下常量:You will also want to declare the following constant:

private const uint MSPR_E_CONTENT_ENABLING_ACTION_REQUIRED = 0x8004B895;

设置 MediaProtectionManagerSetting up the MediaProtectionManager

若要向你的 UWP 应用添加 PlayReady 内容保护,需要设置 MediaProtectionManager 对象。To add PlayReady content protection to your UWP app, you will need to set up a MediaProtectionManager object. 在初始化 AdaptiveMediaSource 对象时执行此操作。You do this when initializing your AdaptiveMediaSource object.

以下代码可设置 MediaProtectionManagerThe following code sets up a MediaProtectionManager:

private void SetUpProtectionManager(ref MediaElement mediaElement)
{
    protectionManager = new MediaProtectionManager();

    protectionManager.ComponentLoadFailed += 
        new ComponentLoadFailedEventHandler(ProtectionManager_ComponentLoadFailed);

    protectionManager.ServiceRequested += 
        new ServiceRequestedEventHandler(ProtectionManager_ServiceRequested);

    PropertySet cpSystems = new PropertySet();

    cpSystems.Add(
        "{F4637010-03C3-42CD-B932-B48ADF3A6A54}", 
        "Windows.Media.Protection.PlayReady.PlayReadyWinRTTrustedInput");

    protectionManager.Properties.Add("Windows.Media.Protection.MediaProtectionSystemIdMapping", cpSystems);

    protectionManager.Properties.Add(
        "Windows.Media.Protection.MediaProtectionSystemId", 
        "{F4637010-03C3-42CD-B932-B48ADF3A6A54}");

    protectionManager.Properties.Add(
        "Windows.Media.Protection.MediaProtectionContainerGuid", 
        "{9A04F079-9840-4286-AB92-E65BE0885F95}");

    mediaElement.ProtectionManager = protectionManager;
}

只能将此代码复制到你的应用,因为它对设置内容保护是强制性的。This code can simply be copied to your app, since it is mandatory for setting up content protection.

在加载二进制数据失败时,引发 ComponentLoadFailed 事件。The ComponentLoadFailed event is fired when the load of binary data fails. 我们需要添加事件处理程序来处理这一暗示加载未完成的情况:We need to add an event handler to handle this, signaling that the load did not complete:

private void ProtectionManager_ComponentLoadFailed(
    MediaProtectionManager sender, 
    ComponentLoadFailedEventArgs e)
{
    e.Completion.Complete(false);
}

同样,我们需要为 ServiceRequested 事件添加事件处理程序,该事件将在请求服务时引发。Similarly, we need to add an event handler for the ServiceRequested event, which fires when a service is requested. 此代码会检查请求的类型,然后相应地做出响应:This code checks what kind of request it is, and responds appropriately:

private async void ProtectionManager_ServiceRequested(
    MediaProtectionManager sender, 
    ServiceRequestedEventArgs e)
{
    if (e.Request is PlayReadyIndividualizationServiceRequest)
    {
        PlayReadyIndividualizationServiceRequest IndivRequest = 
            e.Request as PlayReadyIndividualizationServiceRequest;

        bool bResultIndiv = await ReactiveIndivRequest(IndivRequest, e.Completion);
    }
    else if (e.Request is PlayReadyLicenseAcquisitionServiceRequest)
    {
        PlayReadyLicenseAcquisitionServiceRequest licenseRequest = 
            e.Request as PlayReadyLicenseAcquisitionServiceRequest;

        LicenseAcquisitionRequest(
            licenseRequest, 
            e.Completion, 
            playReadyLicenseUrl, 
            playReadyChallengeCustomData);
    }
}

个性化服务请求Individualization service requests

下面的代码将被动发出 PlayReady 个性化服务请求。The following code reactively makes a PlayReady individualization service request. 我们将以参数形式将请求传递给函数。We pass in the request as a parameter to the function. 我们将调用包含在 try/catch 块中,如果未出现任何异常,则说明请求已成功完成:We surround the call in a try/catch block, and if there are no exceptions, we say the request completed successfully:

async Task<bool> ReactiveIndivRequest(
    PlayReadyIndividualizationServiceRequest IndivRequest, 
    MediaProtectionServiceCompletion CompletionNotifier)
{
    bool bResult = false;
    Exception exception = null;

    try
    {
        await IndivRequest.BeginServiceRequest();
    }
    catch (Exception ex)
    {
        exception = ex;
    }
    finally
    {
        if (exception == null)
        {
            bResult = true;
        }
        else
        {
            COMException comException = exception as COMException;
            if (comException != null && comException.HResult == MSPR_E_CONTENT_ENABLING_ACTION_REQUIRED)
            {
                IndivRequest.NextServiceRequest();
            }
        }
    }

    if (CompletionNotifier != null) CompletionNotifier.Complete(bResult);
    return bResult;
}

或者,我们可能需要主动发出个性化服务请求,在此情况下,我们将通过调用以下函数来代替在 ProtectionManager_ServiceRequested 中调用 ReactiveIndivRequest 的代码:Alternatively, we may want to proactively make an individualization service request, in which case we call the following function in place of the code calling ReactiveIndivRequest in ProtectionManager_ServiceRequested:

async void ProActiveIndivRequest()
{
    PlayReadyIndividualizationServiceRequest indivRequest = new PlayReadyIndividualizationServiceRequest();
    bool bResultIndiv = await ReactiveIndivRequest(indivRequest, null);
}

许可证获取服务请求License acquisition service requests

如果请求改为 PlayReadyLicenseAcquisitionServiceRequest,我们将调用以下函数以进行请求并获取 PlayReady 许可证。If instead the request was a PlayReadyLicenseAcquisitionServiceRequest, we call the following function to request and acquire the PlayReady license. 我们告知我们传入的 MediaProtectionServiceCompletion 对象请求是否已成功,并完成以下请求:We tell the MediaProtectionServiceCompletion object that we passed in whether the request was successful or not, and we complete the request:

async void LicenseAcquisitionRequest(
    PlayReadyLicenseAcquisitionServiceRequest licenseRequest, 
    MediaProtectionServiceCompletion CompletionNotifier, 
    string Url, 
    string ChallengeCustomData)
{
    bool bResult = false;
    string ExceptionMessage = string.Empty;

    try
    {
        if (!string.IsNullOrEmpty(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();

                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
        {
            await licenseRequest.BeginServiceRequest();
            bResult = true;
        }
    }
    catch (Exception e)
    {
        ExceptionMessage = e.Message;
    }

    CompletionNotifier.Complete(bResult);
}

初始化 AdaptiveMediaSourceInitializing the AdaptiveMediaSource

最后,你将需要一个函数来初始化从给定 UriMediaElement 创建的 AdaptiveMediaSourceFinally, you will need a function to initialize the AdaptiveMediaSource, created from a given Uri and MediaElement. Uri 应为指向媒体文件(HLS 或 DASH)的链接;MediaElement 应在 XAML 中进行定义。The Uri should be the link to the media file (HLS or DASH); the MediaElement should be defined in your XAML.

async private void InitializeAdaptiveMediaSource(System.Uri uri, MediaElement m)
{
    AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(uri);
    if (result.Status == AdaptiveMediaSourceCreationStatus.Success)
    {
        ams = result.MediaSource;
        SetUpProtectionManager(ref m);
        m.SetMediaStreamSource(ams);
    }
    else
    {
        // Error handling
    }
}

你可以在处理自适应流式处理开始的任何事件中调用此函数;例如在按钮单击事件中。You can call this function in whichever event handles the start of adaptive streaming; for example, in a button click event.

另请参阅See also