Flusso adattivo con PlayReady

Questo articolo descrive come aggiungere lo streaming adattivo di contenuti multimediali con la protezione del contenuto PlayReady di Microsoft a un'app UWP (piattaforma UWP (Universal Windows Platform)).

Questa funzionalità supporta attualmente la riproduzione di contenuti in streaming dinamico su HTTP (DASH).

HLS (Http Live Streaming di Apple) non è supportato con PlayReady.

Smooth Streaming non è attualmente supportato in modo nativo; Tuttavia, PlayReady è estendibile e usando codice o librerie aggiuntive, Lo streaming Smooth protetto da PlayReady può essere supportato, sfruttando software o anche DRM hardware (Digital Rights Management).

Questo articolo riguarda solo gli aspetti dello streaming adattivo specifico di PlayReady. Per informazioni sull'implementazione dello streaming adattivo in generale, vedere Streaming adattivo.

Questo articolo usa il codice dell'esempio di streaming adattivo nel repository Windows-universal-samples di Microsoft su GitHub. Lo scenario 4 riguarda l'uso dello streaming adattivo con PlayReady. È possibile scaricare il repository in un file ZIP passando al livello radice del repository e selezionando il pulsante Scarica ZIP .

Saranno necessarie le istruzioni using seguenti:

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;

Lo spazio dei nomi LicenseRequest proviene da CommonLicenseRequest.cs, un file PlayReady fornito da Microsoft alle licenze.

Sarà necessario dichiarare alcune variabili globali:

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

Si vuole anche dichiarare la costante seguente:

private const uint MSPR_E_CONTENT_ENABLING_ACTION_REQUIRED = 0x8004B895;

Configurazione di MediaProtectionManager

Per aggiungere la protezione del contenuto PlayReady alla tua app UWP, dovrai configurare un oggetto MediaProtectionManager . Questa operazione viene eseguita durante l'inizializzazione dell'oggetto AdaptiveMediaSource .

Il codice seguente configura un Oggetto 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;
}

Questo codice può essere semplicemente copiato nell'app, perché è obbligatorio per configurare la protezione del contenuto.

L'evento ComponentLoadFailed viene generato quando il caricamento dei dati binari non riesce. È necessario aggiungere un gestore eventi per gestirlo, segnalando che il caricamento non è stato completato:

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

Analogamente, è necessario aggiungere un gestore eventi per l'evento ServiceRequested , che viene generato quando viene richiesto un servizio. Questo codice controlla il tipo di richiesta e risponde in modo appropriato:

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);
    }
}

Richieste di servizio di individualizzazione

Il codice seguente effettua in modo reattivo una richiesta del servizio di individualizzazione PlayReady. La richiesta viene passata come parametro alla funzione . La chiamata viene racchiusa in un blocco try/catch e, se non sono presenti eccezioni, si afferma che la richiesta è stata completata correttamente:

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;
}

In alternativa, è possibile eseguire in modo proattivo una richiesta di servizio di individualizzazione, nel qual caso viene chiamata la funzione seguente al posto del codice che chiama ReactiveIndivRequest in ProtectionManager_ServiceRequested:

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

Richieste di servizio di acquisizione delle licenze

Se invece la richiesta è un PlayReadyLicenseAcquisitionServiceRequest, chiamiamo la funzione seguente per richiedere e acquisire la licenza PlayReady. Viene indicato all'oggetto MediaProtectionServiceCompletion che è stato passato se la richiesta ha avuto esito positivo o negativo e la richiesta è stata completata:

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);
}

Inizializzazione di AdaptiveMediaSource

Infine, sarà necessaria una funzione per inizializzare AdaptiveMediaSource, creato da un determinato Uri e MediaElement. L'URI deve essere il collegamento al file multimediale (HLS o DASH); MediaElement deve essere definito nel codice 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
    }
}

È possibile chiamare questa funzione in qualsiasi evento gestisca l'inizio del flusso adattivo; ad esempio, in un evento click del pulsante.

Vedi anche