PlayReady를 사용한 적응 스트리밍

이 문서에서는 Microsoft PlayReady 콘텐츠 보호를 사용하여 멀티미디어 콘텐츠의 적응 스트리밍을 UWP(유니버설 Windows 플랫폼) 앱에 추가하는 방법을 설명합니다.

이 기능은 현재 DASH(Dynamic Streaming over HTTP) 콘텐츠 재생을 지원합니다.

HLS(Apple의 HTTP 라이브 스트리밍)는 PlayReady에서 지원되지 않습니다.

부드러운 스트리밍은 현재 기본적으로 지원되지 않습니다. 그러나 PlayReady는 확장 가능하며 추가 코드 또는 라이브러리를 사용하여 PlayReady로 보호되는 부드러운 스트리밍을 지원하여 소프트웨어 또는 하드웨어 DRM(디지털 권한 관리)을 활용할 수 있습니다.

이 문서에서는 PlayReady와 관련된 적응 스트리밍의 측면만 다룹니다. 일반적으로 적응 스트리밍을 구현하는 방법에 대한 자세한 내용은 적응 스트리밍을 참조하세요.

이 문서에서는 GitHub의 Microsoft Windows 유니버설 샘플 리포지토리에 있는 적응 스트리밍 샘플의 코드를 사용합니다. 시나리오 4는 PlayReady에서 적응 스트리밍을 사용하는 것을 다룹니다. 리포지토리의 루트 수준으로 이동하고 ZIP 다운로드 단추를 선택하여 ZIP 파일에서 리포지토리를 다운로드할 수 있습니다.

문을 사용하는 다음과 같은 이 필요합니다.

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 네임스페이스는 Microsoft에서 라이선스 사용자에게 제공하는 PlayReady 파일인 CommonLicenseRequest.cs.에서 가져온 것이다.

몇 가지 전역 변수를 선언해야 합니다.

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 설정

UWP 앱에 PlayReady 콘텐츠 보호를 추가하려면 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 초기화

마지막으로 지정된 UriMediaElement에서 만든 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
    }
}

이 함수는 적응형 스트리밍 시작을 처리하는 모든 이벤트(예: 버튼 클릭 이벤트)에서 호출할 수 있습니다.

참고 항목