Streaming adaptável

Este artigo é sobre como adicionar playback de conteúdo de mídia para streaming adaptável a um aplicativo da Plataforma Universal do Windows (UWP). Esse recurso oferece suporte à reprodução de conteúdo Http Live Streaming (HLS) e Dynamic Streaming over HTTP (DASH).

A partir do Windows 10, versão 1803, o Smooth Streaming é compatível com AdaptiveMediaSource. Para o Smooth Streaming, apenas codecs H264 e WVC1 são suportados. Outros tipos de manifesto não têm essa limitação.

Para ver uma lista de tags de protocolo HLS suportadas, consulte Suporte a tags HLS.

Para ver uma lista de perfis DASH suportados, consulte Suporte a perfis DASH.

Observação

O código neste artigo foi adaptado do exemplo de streaming adaptável da UWP.

Streaming adaptável simples com MediaPlayer e MediaPlayerElement

Para reproduzir mídia de streaming adaptável em um aplicativo UWP, crie um objeto Uri apontando para um arquivo de manifesto DASH ou HLS. Criar uma instância da classe MediaPlayer. Chame MediaSource.CreateFromUri para criar um novo objeto MediaSource e, em seguida, defina-o para a propriedade Source do MediaPlayer. Chame Play para iniciar a reprodução do conteúdo de mídia.

MediaPlayer _mediaPlayer;
System.Uri manifestUri = new Uri("http://amssamples.streaming.mediaservices.windows.net/49b57c87-f5f3-48b3-ba22-c55cfdffa9cb/Sintel.ism/manifest(format=m3u8-aapl)");
_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = MediaSource.CreateFromUri(manifestUri);
_mediaPlayer.Play();

O exemplo acima reproduzirá o áudio do conteúdo de mídia, mas não renderizará automaticamente o conteúdo em sua interface do usuário. A maioria dos aplicativos que reproduz conteúdo de vídeo desejará renderizar o conteúdo em uma página XAML. Para fazer isso, adicione um controle MediaPlayerElement à sua página XAML.

<MediaPlayerElement x:Name="mediaPlayerElement" HorizontalAlignment="Stretch" AreTransportControlsEnabled="True"/>

Chame MediaSource.CreateFromUri para criar um MediaSource a partir do URI de um arquivo de manifesto DASH ou HLS. Em seguida, defina a propriedade Source do MediaPlayerElement. O MediaPlayerElement criará automaticamente um novo objeto MediaPlayer para o conteúdo. Você pode chamar Play no MediaPlayer para iniciar a reprodução do conteúdo.

System.Uri manifestUri = new Uri("http://amssamples.streaming.mediaservices.windows.net/49b57c87-f5f3-48b3-ba22-c55cfdffa9cb/Sintel.ism/manifest(format=m3u8-aapl)");
mediaPlayerElement.Source = MediaSource.CreateFromUri(manifestUri);
mediaPlayerElement.MediaPlayer.Play();

Observação

A partir do Windows 10, versão 1607, é recomendável que você use a classe MediaPlayer para reproduzir itens de mídia. O MediaPlayerElement é um controle XAML leve que é usado para renderizar o conteúdo de um MediaPlayer em uma página XAML. O controle MediaElement continua a ser suportado para compatibilidade com versões anteriores. Para mais informações sobre como usar MediaPlayer e MediaPlayerElement para reproduzir conteúdo de mídia, consulte Reproduzir áudio e vídeo com o MediaPlayer. Para mais informações sobre como usar o MediaSource e APIs relacionadas para trabalhar com conteúdo de mídia, consulte Itens de mídia, listas de reprodução e faixas.

Streaming adaptável com AdaptiveMediaSource

Se o aplicativo precisar recursos de streaming adaptáveis mais avançados, como fornecer cabeçalhos HTTP personalizados, monitorar as taxas de bits de download e reprodução atuais ou ajustar as proporções que determinam quando o sistema alterna as taxas de bits do fluxo adaptável, use o objeto AdaptiveMediaSource.

As APIs de streaming adaptável estão no namespace Windows.Media.Streaming.Adaptive. Os exemplos neste artigo usam APIs dos namespaces a seguir.

using Windows.Media.Streaming.Adaptive;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.Media.Playback;
using Windows.Media.Core;

Inicialize um AdaptiveMediaSource a partir de um URI.

Inicialize o AdaptiveMediaSource com o URI de um arquivo de manifesto de streaming adaptável chamando CreateFromUriAsync. O valor AdaptiveMediaSourceCreationStatus retornado desse método permite que você saiba se a fonte de mídia foi criada com êxito. Nesse caso, você pode definir o objeto como a fonte de fluxo para o MediaPlayer criando um objeto MediaSource chamando MediaSource.CreateFromAdaptiveMediaSource e, em seguida, atribuindo-o à propriedade Source do player de mídia. Neste exemplo, a propriedade AvailableBitrates é consultada para determinar a taxa de bits máxima suportada para esse fluxo e, em seguida, esse valor é definido como a taxa de bits inicial. Este exemplo também registra manipuladores para os vários eventos AdaptiveMediaSource discutidos posteriormente neste artigo.

async private void InitializeAdaptiveMediaSource(System.Uri uri)
{
    AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(uri);

    if (result.Status == AdaptiveMediaSourceCreationStatus.Success)
    {
        ams = result.MediaSource;
        mediaPlayerElement.SetMediaPlayer(new MediaPlayer());
        mediaPlayerElement.MediaPlayer.Source = MediaSource.CreateFromAdaptiveMediaSource(ams);
        mediaPlayerElement.MediaPlayer.Play();


        ams.InitialBitrate = ams.AvailableBitrates.Max<uint>();

        //Register for download requests
        ams.DownloadRequested += DownloadRequested;

        //Register for download failure and completion events
        ams.DownloadCompleted += DownloadCompleted;
        ams.DownloadFailed += DownloadFailed;

        //Register for bitrate change events
        ams.DownloadBitrateChanged += DownloadBitrateChanged;
        ams.PlaybackBitrateChanged += PlaybackBitrateChanged;

        //Register for diagnostic event
        ams.Diagnostics.DiagnosticAvailable += DiagnosticAvailable;
    }
    else
    {
        // Handle failure to create the adaptive media source
        MyLogMessageFunction($"Adaptive source creation failed: {uri} - {result.ExtendedError}");
    }
}

Inicializar um AdaptiveMediaSource usando HttpClient

Se você precisar definir cabeçalhos HTTP personalizados para obter o arquivo de manifesto, poderá criar um objeto HttpClient, definir os cabeçalhos desejados e passar o objeto para a sobrecarga de CreateFromUriAsync.

httpClient = new Windows.Web.Http.HttpClient();
httpClient.DefaultRequestHeaders.TryAppendWithoutValidation("X-CustomHeader", "This is a custom header");
AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(manifestUri, httpClient);

O evento DownloadRequested é gerado quando o sistema está prestes a recuperar um recurso do servidor. O AdaptiveMediaSourceDownloadRequestedEventArgs passado para o manipulador de eventos expõe propriedades que fornecem informações sobre o recurso que está sendo solicitado, como o tipo e o URI do recurso.

Modificar propriedades de solicitação de recurso usando o evento DownloadRequested

Você pode usar o manipulador de eventos DownloadRequested para modificar a solicitação de recurso atualizando as propriedades do objeto AdaptiveMediaSourceDownloadResult fornecido pelo evento args. No exemplo abaixo, o URI do qual o recurso será recuperado é modificado atualizando as propriedades ResourceUri do objeto de resultado. Você também pode reescrever o deslocamento e o comprimento do intervalo de bytes para segmentos de mídia ou, como mostrado no exemplo abaixo, alterar o URI do recurso para baixar o recurso completo e definir o deslocamento e o comprimento do intervalo de bytes como nulo.

Você pode substituir o conteúdo do recurso solicitado definindo as propriedades Buffer ou InputStream do objeto de resultado. No exemplo abaixo, o conteúdo do recurso de manifesto é substituído pela configuração da propriedade Buffer. Se você estiver atualizando a solicitação de recurso com dados obtidos de forma assíncrona, como recuperar dados de um servidor remoto ou fazer uma autenticação de usuário assíncrona, deverá chamar AdaptiveMediaSourceDownloadRequestedEventArgs.GetDeferral para obter um adiamento e, em seguida, chamar Complete quando a operação for concluída para sinalizar ao sistema que a operação de solicitação de download pode continuar.

    private async void DownloadRequested(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadRequestedEventArgs args)
    {

        // rewrite key URIs to replace http:// with https://
        if (args.ResourceType == AdaptiveMediaSourceResourceType.Key)
        {
            string originalUri = args.ResourceUri.ToString();
            string secureUri = originalUri.Replace("http:", "https:");

            // override the URI by setting property on the result sub object
            args.Result.ResourceUri = new Uri(secureUri);
        }

        if (args.ResourceType == AdaptiveMediaSourceResourceType.Manifest)
        {
            AdaptiveMediaSourceDownloadRequestedDeferral deferral = args.GetDeferral();
            args.Result.Buffer = await CreateMyCustomManifest(args.ResourceUri);
            deferral.Complete();
        }

        if (args.ResourceType == AdaptiveMediaSourceResourceType.MediaSegment)
        {
            var resourceUri = args.ResourceUri.ToString() + "?range=" + 
                args.ResourceByteRangeOffset + "-" + (args.ResourceByteRangeLength - 1);

            // override the URI by setting a property on the result sub object
            args.Result.ResourceUri = new Uri(resourceUri);

            // clear the byte range properties on the result sub object
            args.Result.ResourceByteRangeOffset = null;
            args.Result.ResourceByteRangeLength = null;
        }
    }

Usar eventos de taxa de bits para gerenciar e responder a alterações de taxa de bits

O objeto AdaptiveMediaSource fornece eventos que permitem que você reaja quando as taxas de bits de download ou reprodução são alteradas. Neste exemplo, as taxas de bits atuais são simplesmente atualizadas na interface do usuário. Você pode modificar as proporções que determinam quando o sistema alterna as taxas de bits do fluxo adaptável. Para mais informações, consulte a propriedade AdvancedSettings.

private async void DownloadBitrateChanged(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadBitrateChangedEventArgs args)
{
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
    {
        txtDownloadBitrate.Text = args.NewValue.ToString();
    }));
}

private async void PlaybackBitrateChanged(AdaptiveMediaSource sender, AdaptiveMediaSourcePlaybackBitrateChangedEventArgs args)
{
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
    {
        txtPlaybackBitrate.Text = args.NewValue.ToString();
    }));
}

Manipular eventos de conclusão e falha de download

O objeto AdaptiveMediaSource gera o evento DownloadFailed quando o download de um recurso solicitado falha. Você pode usar esse evento para atualizar sua interface do usuário em resposta à falha. Você também pode usar o evento para registrar informações estatísticas sobre a operação de download e a falha.

O objeto AdaptiveMediaSourceDownloadFailedEventArgs passado para o manipulador de eventos contém metadados sobre o download do recurso com falha, como o tipo de recurso, o URI do recurso e a posição dentro do fluxo onde a falha ocorreu. O RequestId obtém um identificador exclusivo gerado pelo sistema para a solicitação que pode ser usado para correlacionar informações de status sobre uma solicitação individual em vários eventos.

A propriedade Statistics retorna um objeto AdaptiveMediaSourceDownloadStatistics, que fornece informações detalhadas sobre o número de bytes recebidos no momento do evento e o tempo de várias etapas na operação de download. Você pode registrar essas informações para identificar problemas de desempenho em sua implementação de streaming adaptável.

private void DownloadFailed(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadFailedEventArgs args)
{
    var statistics = args.Statistics;

    MyLogMessageFunction("download failed for: " + args.ResourceType + 
     " - " + args.ResourceUri +
     " – Error:" + args.ExtendedError.HResult +
     " - RequestId" + args.RequestId + 
     " – Position:" + args.Position +
     " - Duration:" + args.ResourceDuration +
     " - ContentType:" + args.ResourceContentType +
     " - TimeToHeadersReceived:" + statistics.TimeToHeadersReceived + 
     " - TimeToFirstByteReceived:" + statistics.TimeToFirstByteReceived + 
     " - TimeToLastByteReceived:" + statistics.TimeToLastByteReceived +
     " - ContentBytesReceivedCount:" + statistics.ContentBytesReceivedCount);

}

O evento DownloadCompleted ocorre quando um download de recurso é concluído e fornece dados semelhantes ao evento DownloadFailed. Mais uma vez, um RequestId é fornecido para correlacionar eventos para uma única solicitação. Além disso, um objeto AdaptiveMediaSourceDownloadStatistics é fornecido para habilitar o log de estatísticas de download.

private void DownloadCompleted(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadCompletedEventArgs args)
{
    var statistics = args.Statistics;

    MyLogMessageFunction("download completed for: " + args.ResourceType + " - " +
     args.ResourceUri +
     " – RequestId:" + args.RequestId +
     " – Position:" + args.Position +
     " - Duration:" + args.ResourceDuration +
     " - ContentType:" + args.ResourceContentType +
     " - TimeToHeadersReceived:" + statistics.TimeToHeadersReceived + 
     " - TimeToFirstByteReceived:" + statistics.TimeToFirstByteReceived + 
     " - TimeToLastByteReceived:" + statistics.TimeToLastByteReceived +
     " - ContentBytesReceivedCount:" + statistics.ContentBytesReceivedCount);

}

Coletar dados de telemetria de streaming adaptável com AdaptiveMediaSourceDiagnostics

O AdaptiveMediaSource expõe uma propriedade Diagnostics, que retorna um objeto AdaptiveMediaSourceDiagnostics. Use esse objeto para se registrar no evento DiagnosticAvailable. Esse evento é usado para coleta de telemetria, e não deve ser usado para modificar o comportamento do aplicativo em runtime. Este evento diagnostic é gerado por muitas razões diferentes. Verifique a propriedade DiagnosticType do objeto AdaptiveMediaSourceDiagnosticAvailableEventArgs passado para o evento a fim de determinar o motivo pelo qual o evento foi gerado. Os possíveis motivos incluem erros ao acessar o recurso solicitado e ao analisar o arquivo de manifesto de streaming. Para uma lista completa de situações que podem disparar um evento de diagnóstico, consulte AdaptiveMediaSourceDiagnosticType. Como os argumentos para outros eventos de streaming adaptável, o AdaptiveMediaSourceDiagnosticAvailableEventArgs fornece uma propriedade RequestId para correlacionar informações de solicitação entre eventos diferentes.

private void DiagnosticAvailable(AdaptiveMediaSourceDiagnostics sender, AdaptiveMediaSourceDiagnosticAvailableEventArgs args)
{
    MySendTelemetryFunction(args.RequestId, args.Position,
                            args.DiagnosticType, args.SegmentId,
                            args.ResourceType, args.ResourceUri,
                            args.ResourceDuration, args.ResourceContentType,
                            args.ResourceByteRangeOffset,
                            args.ResourceByteRangeLength, 
                            args.Bitrate,
                            args.ExtendedError);

}

Adiar a vinculação de conteúdo de streaming adaptável para itens em uma lista de reprodução usando o MediaBinder

A classe MediaBinder permite adiar a vinculação de conteúdo de mídia em uma MediaPlaybackList. A partir do Windows 10, versão 1703, você pode fornecer um AdaptiveMediaSource como conteúdo vinculado. O processo de vinculação adiada de uma fonte de mídia adaptável é basicamente o mesmo que vincular outros tipos de mídia, que é descrito em Itens de mídia, listas de reprodução e faixas.

Crie uma instância do MediaBinder, defina uma cadeia de caracteres de Token definida pelo aplicativo para identificar o conteúdo a ser vinculado e registre no evento Binding. Crie um MediaSource a partir do Binder chamando MediaSource.CreateFromMediaBinder. Em seguida, crie um MediaPlaybackItem a partir do MediaSource e adicione à lista de reprodução.

_mediaPlaybackList = new MediaPlaybackList();

var binder = new MediaBinder();
binder.Token = "MyBindingToken1";
binder.Binding += Binder_Binding;
_mediaPlaybackList.Items.Add(new MediaPlaybackItem(MediaSource.CreateFromMediaBinder(binder)));

binder = new MediaBinder();
binder.Token = "MyBindingToken2";
binder.Binding += Binder_Binding;
_mediaPlaybackList.Items.Add(new MediaPlaybackItem(MediaSource.CreateFromMediaBinder(binder)));

_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = _mediaPlaybackList;
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);

No manipulador de eventos Binding, use a cadeia de caracteres de token para identificar o conteúdo a ser vinculado e, em seguida, crie a fonte de mídia adaptável chamando uma das sobrecargas de CreateFromStreamAsync ou CreateFromUriAsync. Como esses são métodos assíncronos, você deve primeiro chamar o método MediaBindingEventArgs.GetDeferral para instruir o sistema a aguardar a conclusão da operação antes de continuar. Defina a fonte de mídia adaptável como o conteúdo vinculado chamando SetAdaptiveMediaSource. Finalmente, chame Deferral.Complete após a conclusão da operação para instruir o sistema a continuar.

private async void Binder_Binding_AdaptiveMediaSource(MediaBinder sender, MediaBindingEventArgs args)
{
    var deferral = args.GetDeferral();

    var contentUri = new Uri($"http://contoso.com/media/{args.MediaBinder.Token}");
    AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(contentUri);

    if (result.MediaSource != null)
    {
        args.SetAdaptiveMediaSource(result.MediaSource);
    }
    args.SetUri(contentUri);

    deferral.Complete();
}

Se desejar registrar manipuladores de eventos para a fonte de mídia adaptável vinculada, você poderá fazer isso no manipulador do evento CurrentItemChanged de MediaPlaybackList. A propriedade CurrentMediaPlaybackItemChangedEventArgs.NewItem contém o novo MediaPlaybackItem atualmente em reprodução na lista. Obtenha uma instância do AdaptiveMediaSource que representa o novo item acessando a propriedade Source do MediaPlaybackItem e, em seguida, a propriedade AdaptiveMediaSource da fonte de mídia. Essa propriedade será null se o novo item de reprodução não for um AdaptiveMediaSource, portanto, você deve testar para null antes de tentar registrar manipuladores para qualquer um dos eventos do objeto.

private void AMSMediaPlaybackList_CurrentItemChanged(MediaPlaybackList sender, CurrentMediaPlaybackItemChangedEventArgs args)
{
    if (!(args.NewItem is null))
    {
        var ams = args.NewItem.Source.AdaptiveMediaSource;
        if (!(ams is null))
        {
            ams.PlaybackBitrateChanged += Ams_PlaybackBitrateChanged;
        }
    }
}