使用 PlayReady 進行自適性串流
本文說明如何使用 Microsoft PlayReady 內容保護將多媒體內容的調適性串流新增至通用 Windows 平台 (UWP) 應用程式。
這項功能目前支援透過 HTTP (DASH) 內容播放動態串流。
PlayReady 不支援 HLS (Apple 的 HTTP 即時串流)。
目前原生也不支援 Smooth streaming;然而,PlayReady 是可擴展的,透過使用額外的程式碼或程式庫,可以利用軟體甚至硬體 DRM (數位版權管理) 來支援受 PlayReady 保護的 Smooth streaming。
本文僅討論 PlayReady 特有的自適應串流層面。 如需一般實作自適性串流的相關資訊,請參閱自適性串流。
本文使用 GitHub 上 Microsoft Windows-universal-samples 儲存庫中的自適應串流範例中的程式碼。 案例 4 處理搭配 PlayReady 使用自適性串流。 您可以導覽至儲存庫的根層級並選取下載 ZIP 按鈕,以 ZIP 檔案形式下載儲存庫。
您將需要下列 using 語句:
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 檔案。
您必須宣告幾個全域變數:
private AdaptiveMediaSource ams = null;
private MediaProtectionManager protectionManager = null;
private string playReadyLicenseUrl = "";
private string playReadyChallengeCustomData = "";
您也會想要宣告下列常數:
private const uint MSPR_E_CONTENT_ENABLING_ACTION_REQUIRED = 0x8004B895;
設定 MediaProtectionManager
若要將 PlayReady 內容保護新增至 UWP 應用程式,您必須設定 MediaProtectionManager 物件。 初始化 AdaptiveMediaSource 物件時,您可以執行此動作。
下列程式碼會設定 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;
}
此程式碼可以直接複製到您的應用程式,因為設定內容保護是必要的。
當二進位資料載入失敗時,將觸發 ComponentLoadFailed 事件。 我們需要新增事件處理常式來處理此事件,表示負載未完成:
private void ProtectionManager_ComponentLoadFailed(
MediaProtectionManager sender,
ComponentLoadFailedEventArgs e)
{
e.Completion.Complete(false);
}
同樣地,我們需要為 ServiceRequested 事件新增事件處理常式,這會在要求服務時引發。 此程式碼會檢查其要求類型,並適當地回應:
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);
}
}
個別化服務要求
下列程式碼會以回應方式提出 PlayReady 個人化服務要求。 我們會將要求當做參數傳入函式。 我們會在 try/catch 區塊中圍繞呼叫,如果沒有任何例外狀況,我們表示要求已順利完成:
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
的程式碼:
async void ProActiveIndivRequest()
{
PlayReadyIndividualizationServiceRequest indivRequest = new PlayReadyIndividualizationServiceRequest();
bool bResultIndiv = await ReactiveIndivRequest(indivRequest, null);
}
授權取得服務要求
如果要求是 PlayReadyLicenseAcquisitionServiceRequest,我們將呼叫下列函數來要求並取得 PlayReady 授權。 我們會告訴 MediaProtectionServiceCompletion 物件,我們傳入的要求是否成功,而且我們完成要求:
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);
}
初始化 AdaptiveMediaSource
最後,您將需要一個函式來初始化從特定的 Uri 和 MediaElement 建立的 AdaptiveMediaSource。 Uri 應該是媒體檔案 (HLS 或 DASH) 的連結;MediaElement 應在 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
}
}
您可以在處理自適應串流啟動的任何事件中呼叫此函式;例如,在按鈕按一下事件中。