搭配使用彈性資料流與 PlayReadyAdaptive streaming with PlayReady

本文章說明如何將包含 Microsoft PlayReady 內容保護的多媒體內容彈性資料流新增到通用 Windows 平台 (UWP) app。This article describes how to add adaptive streaming of multimedia content with Microsoft PlayReady content protection to a Universal Windows Platform (UWP) app.

本功能目前支援播放 DASH (Dynamic Streaming over HTTP) 內容。This feature currently supports playback of Dynamic streaming over HTTP (DASH) content.

PlayReady 不支援 HLS (Apple 的 HTTP 即時資料流)。HLS (Apple's HTTP Live Streaming) is not supported with PlayReady.

目前也不支援原生的 Smooth Streaming,不過,PlayReady 可以使用額外的程式碼或程式庫來延伸,因此可支援 PlayReady 保護的 Smooth Streaming,以利用軟體或甚至硬體 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.

本文使用的程式碼來自 Microsoft 在 GitHub 上的 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

若要將 PlayReady 內容保護新增到您的 UWP app,您將需要設定 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

最後,您將需要一個函式來初始化 AdaptiveMediaSource,可從已知的 UriMediaElement 來建立。Finally, 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