Reproduzir áudio e vídeo com o MediaPlayer

Este artigo mostra como reproduzir mídia em seu aplicativo Universal do Windows usando a classe MediaPlayer . Com o Windows 10, versão 1607, foram feitas melhorias significativas nas APIs de reprodução de mídia, incluindo um design simplificado de processo único para áudio em segundo plano, integração automática com os Controles de Transporte de Mídia do Sistema (SMTC), a capacidade de sincronizar vários media players, a capacidade de renderizar quadros de vídeo em uma superfície Windows.UI.Composition e uma interface fácil para criar e agendar interrupções de mídia em seu conteúdo. Para tirar proveito dessas melhorias, a prática recomendada para a reprodução de mídia é usar a classe MediaPlayer em vez de MediaElement. O MediaPlayerElement, um controle XAML leve, foi introduzido para permitir que você renderize conteúdo de mídia em uma página XAML. Muitas das APIs de status e controle de reprodução fornecidas pelo MediaElement agora estão disponíveis por meio do novo objeto MediaPlaybackSession. O MediaElement continua a funcionar a fim de dar suporte à compatibilidade com versões anteriores, mas nenhum outro recurso será adicionado a essa classe.

Este artigo fornecerá orientações sobre os recursos do MediaPlayer que serão usados por um aplicativo típico de reprodução de mídia. Observe que o MediaPlayer usa a classe MediaSource como um contêiner para todos os itens de mídia. Essa classe permite carregar e reproduzir mídia de várias origens diferentes, incluindo arquivos locais, fluxos de memória e origens de rede, todos usando a mesma interface. Também há classes de nível superior que funcionam com o MediaSource, como MediaPlaybackItem e MediaPlaybackList, que oferecem recursos mais avançados, como playlists e a capacidade de gerenciar origens de mídia com várias faixas de áudio, vídeo e metadados. Para obter mais informações sobre MediaSource e APIs relacionadas, consulte Itens de mídia, playlists e faixas.

Observação

As edições do Windows 10 N e Windows 10 KN não incluem os recursos de mídia necessários para usar o MediaPlayer para reprodução. Esses recursos podem ser instalados manualmente. Para obter mais informações, consulte Pacote de recursos de mídia para Windows 10 N e Windows 10 KN.

Reproduzir um arquivo de mídia com o MediaPlayer

A reprodução básica de mídia com o MediaPlayer é bastante simples de implementar. Primeiro, crie uma nova instância da classe MediaPlayer. Seu aplicativo pode ter várias instâncias ativas do MediaPlayer simultaneamente. Em seguida, defina a propriedade Source do player como um objeto que implemente a IMediaPlaybackSource, como uma MediaSource, um MediaPlaybackItem ou um MediaPlaybackList. Neste exemplo, um MediaSource é criado a partir de um arquivo contido no armazenamento local do aplicativo e, em seguida, um MediaPlaybackItem é criado a partir da origem e é atribuído à propriedade Source do player.

Diferentemente do MediaElement, o MediaPlayer não inicia a reprodução automaticamente por padrão. Para iniciar a reprodução, você pode chamar Play, definir a propriedade AutoPlay como true ou aguardar que o usuário inicie a reprodução com os controles de mídia integrados.

mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer.Play();

Quando seu aplicativo terminar de usar o MediaPlayer, você deverá chamar o método Close (projetado como Dispose em C#) para limpar os recursos usados pelo player.

mediaPlayer.Dispose();

Usar o MediaPlayerElement para renderizar vídeo em XAML

É possível reproduzir mídia em um MediaPlayer sem exibi-la em XAML, mas em muitos aplicativos de reprodução de mídia, você precisará renderizar a mídia em uma página XAML. Para fazer isso, use o controle leve MediaPlayerElement. Como o MediaElement, o MediaPlayerElement permite especificar se os controles de transporte integrados deverão ou não ser mostrados.

<MediaPlayerElement x:Name="_mediaPlayerElement" AreTransportControlsEnabled="False" HorizontalAlignment="Stretch"  Grid.Row="0"/>

Você pode definir a instância doMediaPlayer à qual o elemento está vinculado chamando SetMediaPlayer.

_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

Você também pode definir a origem de reprodução no MediaPlayerElement, e o elemento criará automaticamente uma nova instância do MediaPlayer que poderá ser acessada com a propriedade MediaPlayer.

_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer = _mediaPlayerElement.MediaPlayer;
mediaPlayer.Play();

Observação

Se você desabilitar o MediaPlaybackCommandManager do MediaPlayer definindo IsEnabled como false, isso romperá o vínculo entre o MediaPlayer e o TransportControls fornecido pelo MediaPlayerElement, portanto os controles de transporte internos não vão mais controlar automaticamente a reprodução do player. Em vez disso, você deve implementar seus próprios controles para regular o MediaPlayer.

Tarefas comuns do MediaPlayer

Esta seção mostra como usar alguns recursos do MediaPlayer.

Definir a categoria de áudio

Defina a propriedade AudioCategory de um MediaPlayer como um dos valores da enumeração MediaPlayerAudioCategory para informar o sistema qual tipo de mídia você está executando. Jogos devem categorizar seus fluxos de música como GameMedia para que o mudo do jogo seja ativado automaticamente se outro aplicativo reproduzir música em segundo plano. Os aplicativos de música ou vídeo devem categorizar seus fluxos como Media ou Movie para que eles tenham prioridade sobre fluxos GameMedia.

mediaPlayer.AudioCategory = MediaPlayerAudioCategory.Media;

Saída para um ponto de extremidade de áudio específico

Por padrão, a saída de áudio de um MediaPlayer é roteada para o ponto de extremidade de áudio padrão do sistema, mas você pode especificar um ponto de extremidade de áudio específico que o MediaPlayer deverá usar para a saída. No exemplo a seguir, MediaDevice.GetAudioRenderSelector retorna uma cadeia de caracteres que identifica, de forma exclusiva, a categoria de renderização de áudio dos dispositivos. Em seguida, o método DeviceInformationFindAllAsync é chamado para obter uma lista de todos os dispositivos disponíveis do tipo selecionado. Você pode determinar programaticamente qual dispositivo deseja usar ou adicionar os dispositivos retornados a uma ComboBox para permitir que o usuário selecione um dispositivo.

string audioSelector = MediaDevice.GetAudioRenderSelector();
var outputDevices = await DeviceInformation.FindAllAsync(audioSelector);
foreach (var device in outputDevices)
{
    var deviceItem = new ComboBoxItem();
    deviceItem.Content = device.Name;
    deviceItem.Tag = device;
    _audioDeviceComboBox.Items.Add(deviceItem);
}

No evento SelectionChanged para a caixa de combinação de dispositivos, a propriedade AudioDevice do MediaPlayer está definida como o dispositivo selecionado, que foi armazenado na propriedade Tag do ComboBoxItem.

private void _audioDeviceComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    DeviceInformation selectedDevice = (DeviceInformation)((ComboBoxItem)_audioDeviceComboBox.SelectedItem).Tag;
    if (selectedDevice != null)
    {
        mediaPlayer.AudioDevice = selectedDevice;
    }
}

Sessão de reprodução

Conforme descrito anteriormente neste artigo, muitas das funções que são expostas pela classe MediaElement foram transferidas para a classe MediaPlaybackSession. Isso inclui informações sobre o estado de reprodução do player, como a posição de reprodução atual, se o player está pausado ou em reprodução e a velocidade de reprodução atual. O MediaPlaybackSession também fornece vários eventos para avisá-lo quando o estado é alterado, inclusive o status de transferência e o buffer atual do conteúdo que está sendo reproduzido, bem como o tamanho natural e a taxa de proporção do conteúdo de vídeo em reprodução no momento.

O exemplo a seguir mostra como implementar um manipulador de clique de botão que avança 10 segundos no conteúdo. Primeiro, o objetoMediaPlaybackSession do player é recuperado com a propriedade PlaybackSession. Em seguida, a propriedade Position é definida como a posição de reprodução atual mais 10 segundos.

private void _skipForwardButton_Click(object sender, RoutedEventArgs e)
{
    var session = mediaPlayer.PlaybackSession;
    session.Position = session.Position + TimeSpan.FromSeconds(10);
}

O próximo exemplo ilustra o uso de um botão de alternância para alternar entre a velocidade de reprodução normal e 2 X a velocidade definindo a propriedade PlaybackRate da sessão.

private void _speedToggleButton_Checked(object sender, RoutedEventArgs e)
{
    mediaPlayer.PlaybackSession.PlaybackRate = 2.0;
}
private void _speedToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
    mediaPlayer.PlaybackSession.PlaybackRate = 1.0;
}

A partir do Windows 10, versão 1803, você pode definir a rotação de apresentação do vídeo no MediaPlayer em incrementos de 90 graus.

mediaPlayer.PlaybackSession.PlaybackRotation = MediaRotation.Clockwise90Degrees;

Detectar buffer esperado e inesperado

O objeto MediaPlaybackSession descrito na seção anterior fornece dois eventos de detecção quando o arquivo de mídia em reprodução começa e encerra o buffer, BufferingStarted e BufferingEnded. Isso permite que você atualize a interface do usuário para mostrar ao usuário que o buffer está ocorrendo. O buffer inicial é esperado quando um arquivo de mídia é aberto pela primeira vez ou quando o usuário alterna para um novo item de uma playlist. O buffer inesperado pode ocorrer quando a velocidade de rede degrada ou se o sistema de gerenciamento de conteúdo que fornece o conteúdo tiver problemas técnicos. A partir do RS3, você pode usar o evento BufferingStarted para determinar se o evento de buffer é esperado ou se é inesperado e interrompe a reprodução. Você pode usar essas informações como dados de telemetria para o serviço de entrega do aplicativo ou mídia.

Registre manipuladores para os eventos BufferingStarted e BufferingEnded a fim de receber notificações de estado de buffer.

mediaPlayer.PlaybackSession.BufferingStarted += MediaPlaybackSession_BufferingStarted;
mediaPlayer.PlaybackSession.BufferingEnded += MediaPlaybackSession_BufferingEnded;

No manipulador de evento BufferingStarted, transmita os argumentos de evento passados para o evento em um objeto MediaPlaybackSessionBufferingStartedEventArgs e verifique a propriedade IsPlaybackInterruption. Se esse valor for true, o buffer que disparou o evento é inesperado e interrompe a reprodução. Caso contrário, o buffer inicial é esperado.

private void MediaPlaybackSession_BufferingStarted(MediaPlaybackSession sender, object args)
{
    MediaPlaybackSessionBufferingStartedEventArgs bufferingStartedEventArgs = args as MediaPlaybackSessionBufferingStartedEventArgs;
    if (bufferingStartedEventArgs != null && bufferingStartedEventArgs.IsPlaybackInterruption)
    {
        // update the playback quality telemetry report to indicate that
        // playback was interrupted
    }

    // update the UI to indicate that playback is buffering
}
private void MediaPlaybackSession_BufferingEnded(MediaPlaybackSession sender, object args)
{
    // update the UI to indicate that playback is no longer buffering
}

Pinçar e aplicar zoom em vídeo

O MediaPlayer permite especificar o retângulo de origem no conteúdo de vídeo que deve ser renderizado, permitindo aplicar zoom ao vídeo de forma efetiva. O retângulo especificado é relativo a um retângulo normalizado (0,0,1,1), onde 0,0 é a parte superior esquerda do quadro, e 1,1 especifica a largura e a altura totais do quadro. Assim, por exemplo, para definir o retângulo de zoom para que o quadrante superior direito do vídeo seja renderizado, você especificaria o retângulo (.5,0,.5,.5). É importante verificar seus valores para garantir que o retângulo de origem esteja dentro do retângulo normalizado (0,0,1,1). A tentativa de definir um valor fora desse intervalo fará com que uma exceção seja lançada.

Para implementar o recurso de pinçar e aplicar zoom usando gestos multitoque, primeiro é necessário especificar os gestos aos quais deseja dar suporte. Neste exemplo, é necessário dimensionar e fazer a translação dos gestos. O evento ManipulationDelta é acionado quando é feito um dos gestos inscritos. O evento DoubleTapped será usado para redefinir o zoom para o quadro completo.

_mediaPlayerElement.ManipulationMode = ManipulationModes.Scale | ManipulationModes.TranslateX | ManipulationModes.TranslateY;
_mediaPlayerElement.ManipulationDelta += _mediaPlayerElement_ManipulationDelta;
_mediaPlayerElement.DoubleTapped += _mediaPlayerElement_DoubleTapped;

Em seguida, declare um objeto Rect que armazenará o retângulo de origem de zoom atual.

Rect _sourceRect = new Rect(0, 0, 1, 1);

O manipulador ManipulationDelta ajusta a escala ou a translação do retângulo de zoom. Se o valor de escala delta não for 1, isso significa que o usuário realizou um gesto de pinçar. Se o valor for maior que 1, o retângulo de origem deverá ser menor para ampliar o conteúdo. Se o valor for menor que 1, o retângulo de origem deve ser maior para reduzir. Antes de configurar os novos valores de escala, o retângulo resultante é verificado para garantir que esteja inteiramente dentro dos limites (0,0,1,1).

Se o valor de escala for 1, o gesto de translação será manipulado. O retângulo é simplesmente convertido pelo número de pixels contidos no gesto, dividido pela largura e a altura do controle. Novamente, o retângulo resultante é verificado para garantir que ele fique dentro dos limites (0,0,1,1).

Por fim, o NormalizedSourceRect do MediaPlaybackSession é definido como o retângulo recém-ajustado, especificando a área dentro do quadro de vídeo que deve ser renderizado.

private void _mediaPlayerElement_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{

    if (e.Delta.Scale != 1)
    {
        var halfWidth = _sourceRect.Width / 2;
        var halfHeight = _sourceRect.Height / 2;

        var centerX = _sourceRect.X + halfWidth;
        var centerY = _sourceRect.Y + halfHeight;

        var scale = e.Delta.Scale;
        var newHalfWidth = (_sourceRect.Width * e.Delta.Scale) / 2;
        var newHalfHeight = (_sourceRect.Height * e.Delta.Scale) / 2;

        if (centerX - newHalfWidth > 0 && centerX + newHalfWidth <= 1.0 &&
            centerY - newHalfHeight > 0 && centerY + newHalfHeight <= 1.0)
        {
            _sourceRect.X = centerX - newHalfWidth;
            _sourceRect.Y = centerY - newHalfHeight;
            _sourceRect.Width *= e.Delta.Scale;
            _sourceRect.Height *= e.Delta.Scale;
        }
    }
    else
    {
        var translateX = -1 * e.Delta.Translation.X / _mediaPlayerElement.ActualWidth;
        var translateY = -1 * e.Delta.Translation.Y / _mediaPlayerElement.ActualHeight;

        if (_sourceRect.X + translateX >= 0 && _sourceRect.X + _sourceRect.Width + translateX <= 1.0 &&
            _sourceRect.Y + translateY >= 0 && _sourceRect.Y + _sourceRect.Height + translateY <= 1.0)
        {
            _sourceRect.X += translateX;
            _sourceRect.Y += translateY;
        }
    }

    mediaPlayer.PlaybackSession.NormalizedSourceRect = _sourceRect;
}

No manipulador de eventos DoubleTapped, o retângulo de origem é definido de volta como (0,0,1,1) para fazer com que o quadro de vídeo inteiro seja renderizado.

private void _mediaPlayerElement_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
    _sourceRect = new Rect(0, 0, 1, 1);
    mediaPlayer.PlaybackSession.NormalizedSourceRect = _sourceRect;
}

NOTA Esta seção descreve a entrada por toque. O Touchpad envia eventos de ponteiro e não enviará eventos de manipulação.

Processamento da degradação da reprodução com base em política

Em algumas circunstâncias, o sistema pode degradar a reprodução de um item de mídia, por exemplo, ao reduzir a resolução (restrição), com base em uma política em vez de um problema de desempenho. Por exemplo, o vídeo pode ser degradado pelo sistema se estiver sendo reproduzido usando um driver de vídeo não assinado. Você pode chamar MediaPlaybackSession.GetOutputDegradationPolicyState para determinar se e porque essa degradação com base em política ocorre, assim como alertar o usuário ou registrar o motivo para fins de telemetria.

O exemplo a seguir mostra uma implementação de um manipulador do evento MediaPlayer.MediaOpened é acionado quando o jogador abre um novo item de mídia. GetOutputDegradationPolicyState é chamado no MediaPlayer passado para o manipulador. O valor de VideoConstrictionReason indica que o motivo da política usada para restringir o vídeo. Se o valor não for None, esse exemplo registra um motivo de degradação para fins de telemetria. Esse exemplo também mostra a configuração da taxa de bits da AdaptiveMediaSource reproduzida no momento com a menor largura de banda para economizar o uso de dados, pois o vídeo está restrito e não será exibido com alta resolução. Para saber mais sobre como usar o AdaptiveMediaSource, consulte Streaming adaptável.

private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
    MediaPlaybackSessionOutputDegradationPolicyState info = sender.PlaybackSession.GetOutputDegradationPolicyState();

    if (info.VideoConstrictionReason != MediaPlaybackSessionVideoConstrictionReason.None)
    {
        // Switch to lowest bitrate to save bandwidth
        adaptiveMediaSource.DesiredMaxBitrate = adaptiveMediaSource.AvailableBitrates[0];

        // Log the degradation reason or show a message to the user
        System.Diagnostics.Debug.WriteLine("Logging constriction reason: " + info.VideoConstrictionReason);
    }
}

Usar MediaPlayerSurface para renderizar vídeo em uma superfície Windows.UI.Composition

A partir do Windows 10, versão 1607, é possível usar o MediaPlayer para renderizar vídeo em um ICompositionSurface, o que permite que o player interopere com as APIs no namespace Windows.UI.Composition. A estrutura de composição permite trabalhar com elementos gráficos na camada visual entre as APIs gráficas DirectX de nível inferior e XAML. Isso permite cenários como a renderização de vídeo em qualquer controle XAML. Para obter mais informações sobre como usar APIs de composição, consulte Camada Visual.

O exemplo a seguir ilustra como renderizar o conteúdo do player de vídeo em um controle Canvas. As chamadas específicas do media player neste exemplo são SetSurfaceSize e GetSurface. SetSurfaceSize informa ao sistema o tamanho do buffer que deve ser alocado para a renderização do conteúdo. GetSurface considera o Compositor como um argumento e recupera uma instância da classe MediaPlayerSurface. Essa classe fornece acesso ao MediaPlayer e ao Compositor usados para criar a superfície e a expõe por meio da propriedade CompositionSurface.

O restante do código neste exemplo cria um SpriteVisual, para que o vídeo seja renderizado, e define o tamanho como o tamanho do elemento canvas que exibirá o elemento visual. Em seguida, um CompositionBrush é criado a partir do MediaPlayerSurface e é atribuído à propriedade Brush do elemento visual. Em seguida, um ContainerVisual é criado, e o SpriteVisual é inserido na parte superior da árvore visual. Por fim, SetElementChildVisual é chamado para atribuir o elemento de contêiner visual ao Canvas.

mediaPlayer.SetSurfaceSize(new Size(_compositionCanvas.ActualWidth, _compositionCanvas.ActualHeight));

var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
MediaPlayerSurface surface = mediaPlayer.GetSurface(compositor);

SpriteVisual spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size =
    new System.Numerics.Vector2((float)_compositionCanvas.ActualWidth, (float)_compositionCanvas.ActualHeight);

CompositionBrush brush = compositor.CreateSurfaceBrush(surface.CompositionSurface);
spriteVisual.Brush = brush;

ContainerVisual container = compositor.CreateContainerVisual();
container.Children.InsertAtTop(spriteVisual);

ElementCompositionPreview.SetElementChildVisual(_compositionCanvas, container);

Use MediaTimelineController para sincronizar o conteúdo entre vários players.

Conforme discutido anteriormente neste artigo, seu aplicativo pode ter vários objetos MediaPlayer ativos por vez. Por padrão, cada MediaPlayer criado funciona de forma independente. Para alguns cenários, como a sincronização de uma faixa de comentário com um vídeo, é possível sincronizar o estado do player, bem como a posição e a velocidade de reprodução de vários players. A partir do Windows 10, versão 1607, é possível implementar esse comportamento usando a classe MediaTimelineController.

Implementar controles de reprodução

O exemplo a seguir mostra como usar um MediaTimelineController para controlar duas instâncias do MediaPlayer. Primeiro, cada instância do MediaPlayer é instanciada e Source é definido como um arquivo de mídia. Em seguida, um novo MediaTimelineController é criado. Para cada MediaPlayer, o MediaPlaybackCommandManager associado a cada player é desativado definindo a propriedade IsEnabled como false. Em seguida, a propriedade TimelineController é definida como o objeto do controlador de linha do tempo.

MediaTimelineController _mediaTimelineController;
mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);


_mediaPlayer2 = new MediaPlayer();
_mediaPlayer2.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video_2.mkv"));
_mediaPlayerElement2.SetMediaPlayer(_mediaPlayer2);

_mediaTimelineController = new MediaTimelineController();

mediaPlayer.CommandManager.IsEnabled = false;
mediaPlayer.TimelineController = _mediaTimelineController;

_mediaPlayer2.CommandManager.IsEnabled = false;
_mediaPlayer2.TimelineController = _mediaTimelineController;

Cuidado O MediaPlaybackCommandManager proporciona a integração automática entre o MediaPlayer e os Controles de Transporte de Mídia do Sistema (SMTC), mas essa integração automática não pode ser usada com media players que sejam controlados com um MediaTimelineController. Dessa forma, você deverá desabilitar o gerenciador de comandos do media player antes de configurar o controlador de linha do tempo do player. A falha ao fazer isso resultará na geração de uma exceção com a seguinte mensagem: "A anexação do Controlador da Linha do Tempo de Mídia está bloqueada devido ao estado atual do objeto". Para obter mais informações sobre a integração do player de mídia com o SMTC, consulte Integrar-se aos Controles de Transporte de Mídia do Sistema. Se você estiver usando um MediaTimelineController, ainda poderá controlar o SMTC manualmente. Para obter mais informações, consulte Controle Manual dos Controles de Transporte de Mídia do Sistema.

Depois de associar um MediaTimelineController a um ou mais media players, você poderá controlar o estado de reprodução usando os métodos expostos pelo controlador. A exemplo a seguir chama Start para iniciar a reprodução de todos os media players associados no início da mídia.

private void PlayButton_Click(object sender, RoutedEventArgs e)
{
    _mediaTimelineController.Start();
}

Este exemplo ilustra as ações de pausar e retomar todos os media players conectados.

private void PauseButton_Click(object sender, RoutedEventArgs e)
{
    if(_mediaTimelineController.State == MediaTimelineControllerState.Running)
    {
        _mediaTimelineController.Pause();
        _pauseButton.Content = "Resume";
    }
    else
    {
        _mediaTimelineController.Resume();
        _pauseButton.Content = "Pause";
    }
}

Para avançar todos os media players conectados, defina a velocidade de reprodução como um valor maior que 1.

private void FastForwardButton_Click(object sender, RoutedEventArgs e)
{
    _mediaTimelineController.ClockRate = 2.0;
}

O próximo exemplo mostra como usar um controle Slider para mostrar a posição de reprodução atual do controlador de linha do tempo em relação à duração do conteúdo de um dos media players conectados. Primeiro, um novo MediaSource é criado e um manipulador do OpenOperationCompleted da origem de mídia é registrado.

var mediaSource = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaSource.OpenOperationCompleted += MediaSource_OpenOperationCompleted;
mediaPlayer.Source = mediaSource;
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

O manipulador OpenOperationCompleted é usado como uma oportunidade de descobrir a duração do conteúdo da origem da mídia. Depois de determinar a duração, o valor máximo do controle Slider é definido como o número total de segundos do item de mídia. O valor é definido em uma chamada para RunAsync para garantir que ele seja executado no thread da interface do usuário.

TimeSpan _duration;
private async void MediaSource_OpenOperationCompleted(MediaSource sender, MediaSourceOpenOperationCompletedEventArgs args)
{
    _duration = sender.Duration.GetValueOrDefault();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        _positionSlider.Minimum = 0;
        _positionSlider.Maximum = _duration.TotalSeconds;
        _positionSlider.StepFrequency = 1;
    }); 
}

Em seguida, um manipulador para o evento PositionChanged do controlador de linha do tempo é registrado. Isso é chamado periodicamente pelo sistema, aproximadamente 4 vezes por segundo.

_mediaTimelineController.PositionChanged += _mediaTimelineController_PositionChanged;

No manipulador de PositionChanged, o valor do controle deslizante é atualizado para refletir a posição atual do controlador de linha do tempo.

private async void _mediaTimelineController_PositionChanged(MediaTimelineController sender, object args)
{
    if (_duration != TimeSpan.Zero)
    {
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            _positionSlider.Value = sender.Position.TotalSeconds / (float)_duration.TotalSeconds;
        });
    }
}

Deslocar a posição de reprodução da posição da linha do tempo

Em alguns casos, pode ser conveniente que a posição de reprodução de um ou mais media players associados a um controlador de linha do tempo seja deslocada dos outros players. Para fazer isso, defina a propriedade TimelineControllerPositionOffset do objeto MediaPlayer que você deseja deslocar. O exemplo a seguir usa as durações do conteúdo de dois media players para definir os valores mínimo e máximo dos dois controles deslizantes como mais e menos o comprimento do item.

_timelineOffsetSlider1.Minimum = -1 * _duration.TotalSeconds;
_timelineOffsetSlider1.Maximum = _duration.TotalSeconds;
_timelineOffsetSlider1.StepFrequency = 1;

_timelineOffsetSlider2.Minimum = -1 * _duration2.TotalSeconds;
_timelineOffsetSlider2.Maximum = _duration2.TotalSeconds;
_timelineOffsetSlider2.StepFrequency = 1;

No evento ValueChanged para cada controle deslizante, o TimelineControllerPositionOffset para cada player está definido como o valor correspondente.

private void _timelineOffsetSlider1_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    mediaPlayer.TimelineControllerPositionOffset = TimeSpan.FromSeconds(_timelineOffsetSlider1.Value);
}

private void _timelineOffsetSlider2_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    _mediaPlayer2.TimelineControllerPositionOffset = TimeSpan.FromSeconds(_timelineOffsetSlider2.Value);
}

Observe que se o valor de deslocamento de um player for mapeado para uma posição de reprodução negativa, o clipe permanecerá pausado até que o deslocamento atinja zero e, em seguida, a reprodução será iniciada. Da mesma forma, se o valor de deslocamento for mapeado para uma posição de reprodução maior que a duração do item de mídia, o quadro final será mostrado, assim como acontece quando um único media player atinge o fim do conteúdo.

Reproduzir áudio esférico com o MediaPlayer

A partir do Windows 10, versão 1703, o MediaPlayer dá suporte à projeção equirretangular para reprodução de vídeo esférico. O conteúdo de vídeo esférico não é diferente do vídeo regular e simples em que o MediaPlayer renderizará o vídeo, desde que a codificação de vídeo seja compatível. Para o vídeo esférico que contém uma marca de metadados que especifica que o vídeo usa a projeção equirretangular, o MediaPlayer pode renderizar o vídeo usando um campo de visão e uma orientação de exibição especificados. Isso possibilita cenários como reprodução de vídeo de realidade virtual com um capacete de realidade virtual ou simplesmente permite que o usuário faça uma panorâmica em torno do conteúdo de vídeo esférico usando o mouse ou o teclado.

Para reproduzir vídeo esférico, use as etapas para reproduzir o conteúdo de vídeo descrito anteriormente neste artigo. A única etapa adicional é registrar um manipulador para o evento MediaPlayer.MediaOpened . Esse evento oferece uma oportunidade para habilitar e controlar os parâmetros de reprodução de vídeo esférico.

mediaPlayer = new MediaPlayer();
mediaPlayer.MediaOpened += _mediaPlayer_MediaOpened;
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video_spherical.mp4"));
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);
mediaPlayer.Play();

No manipulador MediaOpened, primeiro verifique o formato do quadro do item de mídia recém-aberto ao verificar a propriedade PlaybackSession.SphericalVideoProjection.FrameFormat. Se esse valor for SphericaVideoFrameFormat.Equirectangular, então o sistema poderá projetar automaticamente o conteúdo de vídeo. Primeiro, defina a propriedade PlaybackSession.SphericalVideoProjection.IsEnabled como true. Você também pode ajustar propriedades, como a orientação de exibição e o campo de visão que o media player usará para projetar o conteúdo de vídeo. Neste exemplo, o campo de visão é definido como um valor amplo de 120 graus por meio da definição da propriedade HorizontalFieldOfViewInDegrees.

Se o conteúdo de vídeo for esférico, mas se estiver em um formato diferente do equirretangular, você poderá implementar seu próprio algoritmo de projeção usando o modo de servidor de quadros do player de mídia para receber e processar quadros individuais.

private void _mediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
    if (sender.PlaybackSession.SphericalVideoProjection.FrameFormat == SphericalVideoFrameFormat.Equirectangular)
    {
        sender.PlaybackSession.SphericalVideoProjection.IsEnabled = true;
        sender.PlaybackSession.SphericalVideoProjection.HorizontalFieldOfViewInDegrees = 120;

    }
    else if (sender.PlaybackSession.SphericalVideoProjection.FrameFormat == SphericalVideoFrameFormat.Unsupported)
    {
        // If the spherical format is unsupported, you can use frame server mode to implement a custom projection
    }
}

O código de exemplo a seguir ilustra como ajustar a orientação de exibição de vídeo esférico usando as teclas de seta para a esquerda e direita.

protected override void OnKeyDown(KeyRoutedEventArgs e)
{
    if (mediaPlayer.PlaybackSession.SphericalVideoProjection.FrameFormat != SphericalVideoFrameFormat.Equirectangular)
    {
        return;
    }

    switch (e.Key)
    {
        case Windows.System.VirtualKey.Right:
            mediaPlayer.PlaybackSession.SphericalVideoProjection.ViewOrientation *= Quaternion.CreateFromYawPitchRoll(.1f, 0, 0);
            break;
        case Windows.System.VirtualKey.Left:
            mediaPlayer.PlaybackSession.SphericalVideoProjection.ViewOrientation *= Quaternion.CreateFromYawPitchRoll(-.1f, 0, 0);
            break;
    }
}

Se seu aplicativo der suporte às playlists de vídeo, convém identificar itens de reprodução que contenham vídeo esférico em sua interface do usuário. As playlists de mídia são abordadas em detalhes no artigo Itens de mídia, playlists e faixas. O exemplo a seguir mostra a criação de uma nova playlist, adicionando um item e registrando um manipulador para o evento MediaPlaybackItem.VideoTracksChanged, que ocorre quando as faixas de vídeo de um item de mídia são resolvidas.

var playbackList = new MediaPlaybackList();
var item = new MediaPlaybackItem(MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/RIFTCOASTER HD_injected.mp4")));
item.VideoTracksChanged += Item_VideoTracksChanged;
playbackList.Items.Add(item);
mediaPlayer.Source = playbackList;

No manipulador de eventos VideoTracksChanged, obtenha as propriedades de codificação para quaisquer faixas de vídeo adicionadas ao chamar VideoTrack.GetEncodingProperties. Se a propriedade SphericalVideoFrameFormat das propriedades de codificação for um valor diferente de SphericaVideoFrameFormat.None, então a faixa de vídeo conterá vídeo esférico e será possível atualizar sua interface do usuário adequadamente, caso você queira.

private void Item_VideoTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if (args.CollectionChange != CollectionChange.ItemInserted)
    {
        return;
    }
    foreach (var videoTrack in sender.VideoTracks)
    {
        if (videoTrack.GetEncodingProperties().SphericalVideoFrameFormat != SphericalVideoFrameFormat.None)
        {
            // Optionally indicate in the UI that this item contains spherical video
        }
    }
}

Usar o MediaPlayer no modo de servidor de quadros

A partir do Windows 10, versão 1703, você poderá usar o MediaPlayer no modo de servidor de quadros. Nesse modo, o MediaPlayer não renderiza automaticamente os quadros para um MediaPlayerElement associado. Em vez disso, seu app copia o quadro atual do MediaPlayer para um objeto que implementa IDirect3DSurface. O principal cenário que esse recurso habilita é o uso de sombreadores de pixel para processar os quadros de vídeo fornecidos pelo MediaPlayer. Seu aplicativo é responsável pela exibição de cada quadro após o processamento, como ao mostrar o quadro em um controle Image XAML.

No exemplo a seguir, um novo MediaPlayer é inicializado e o conteúdo de vídeo é carregado. Em seguida, um manipulador para VideoFrameAvailable é registrado. O modo de servidor de quadros é habilitado por meio da configuração da propriedade IsVideoFrameServerEnabled do objeto MediaPlayer como true. Por fim, a reprodução de mídia é iniciada com uma chamada a Play.

mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer.VideoFrameAvailable += mediaPlayer_VideoFrameAvailable;
mediaPlayer.IsVideoFrameServerEnabled = true;
mediaPlayer.Play();

O próximo exemplo mostra um manipulador para VideoFrameAvailable que usa Win2D para adicionar um efeito simples de desfoque a cada quadro de um vídeo e então exibe os quadros processados em um controleImage XAML.

Sempre que o manipulador VideoFrameAvailable for chamado, o método CopyFrameToVideoSurface será usado para copiar o conteúdo do quadro para uma IDirect3DSurface. Você também pode usar CopyFrameToStereoscopicVideoSurfaces para copiar o conteúdo 3D para duas superfícies, para processamento do conteúdo do olho esquerdo e do olho direito separadamente. Para obter um objeto que implementa IDirect3DSurface este exemplo cria um SoftwareBitmap e, em seguida, usa esse objeto para criar um Win2D CanvasBitmap, que implementa a interface necessária. A CanvasImageSource é um objeto Win2D que pode ser usado como a origem para um controle Imagem e, portanto, um novo é criado e definido como a origem para a Imagem na qual o conteúdo será exibido. Em seguida, uma CanvasDrawingSession é criada. Ela é usada pelo Win2D para renderizar o efeito de desfoque.

Depois que todos os objetos necessários tenham sido criados, CopyFrameToVideoSurface será chamado, o que copia o quadro atual do MediaPlayer para o CanvasBitmap. Em seguida, um GaussianBlurEffect Win2D é criado, com o CanvasBitmap definido como a origem da operação. Por fim, CanvasDrawingSession.DrawImage é chamada para desenhar a imagem de origem, com o efeito de desfoque aplicado, para a CanvasImageSource que foi associada ao controle Image, fazendo com que ela seja desenhada na interface do usuário.

private async void mediaPlayer_VideoFrameAvailable(MediaPlayer sender, object args)
{
    CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        if(frameServerDest == null)
        {
            // FrameServerImage in this example is a XAML image control
            frameServerDest = new SoftwareBitmap(BitmapPixelFormat.Rgba8, (int)FrameServerImage.Width, (int)FrameServerImage.Height, BitmapAlphaMode.Ignore);
        }
        if(canvasImageSource == null)
        {
            canvasImageSource = new CanvasImageSource(canvasDevice, (int)FrameServerImage.Width, (int)FrameServerImage.Height, DisplayInformation.GetForCurrentView().LogicalDpi);//96); 
            FrameServerImage.Source = canvasImageSource;
        }

        using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromSoftwareBitmap(canvasDevice, frameServerDest))
        using (CanvasDrawingSession ds = canvasImageSource.CreateDrawingSession(Windows.UI.Colors.Black))
        {

            mediaPlayer.CopyFrameToVideoSurface(inputBitmap);

            var gaussianBlurEffect = new GaussianBlurEffect
            {
                Source = inputBitmap,
                BlurAmount = 5f,
                Optimization = EffectOptimization.Speed
            };

            ds.DrawImage(gaussianBlurEffect);

        }
    });
}

private void FrameServerSubtitlesButton_Click(object sender, RoutedEventArgs e)
{

    mediaPlayer = new MediaPlayer();
    var source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
    var item = new MediaPlaybackItem(source);

    item.TimedMetadataTracksChanged += Item_TimedMetadataTracksChanged;


    mediaPlayer.Source = item;
    mediaPlayer.VideoFrameAvailable += mediaPlayer_VideoFrameAvailable_Subtitle;
    mediaPlayer.IsVideoFrameServerEnabled = true;
    mediaPlayer.Play();

    mediaPlayer.IsMuted = true;

}

private void Item_TimedMetadataTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if(sender.TimedMetadataTracks.Count > 0)
    {
        sender.TimedMetadataTracks.SetPresentationMode(0, TimedMetadataTrackPresentationMode.PlatformPresented);
    }
}

private async void mediaPlayer_VideoFrameAvailable_Subtitle(MediaPlayer sender, object args)
{
    CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        if (frameServerDest == null)
        {
            // FrameServerImage in this example is a XAML image control
            frameServerDest = new SoftwareBitmap(BitmapPixelFormat.Rgba8, (int)FrameServerImage.Width, (int)FrameServerImage.Height, BitmapAlphaMode.Ignore);
        }
        if (canvasImageSource == null)
        {
            canvasImageSource = new CanvasImageSource(canvasDevice, (int)FrameServerImage.Width, (int)FrameServerImage.Height, DisplayInformation.GetForCurrentView().LogicalDpi);//96); 
            FrameServerImage.Source = canvasImageSource;
        }

        using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromSoftwareBitmap(canvasDevice, frameServerDest))
        {
            using (CanvasDrawingSession ds = canvasImageSource.CreateDrawingSession(Windows.UI.Colors.Black))
            {

                mediaPlayer.CopyFrameToVideoSurface(inputBitmap);

                //Rect subtitleTargetRect = new Rect(0, 0, inputBitmap.Bounds.Width, inputBitmap.Bounds.Bottom * .1);
                Rect subtitleTargetRect = new Rect(0, 0, 100, 100);

                mediaPlayer.RenderSubtitlesToSurface(inputBitmap);//, subtitleTargetRect);

                //var gaussianBlurEffect = new GaussianBlurEffect
                //{
                //    Source = inputBitmap,
                //    BlurAmount = 5f,
                //    Optimization = EffectOptimization.Speed
                //};

                //ds.DrawImage(gaussianBlurEffect);

                ds.DrawImage(inputBitmap);
            }
        }
    });
}

Para saber mais sobre como usar o Win2D, veja o Repositório do Win2D no GitHub. Para experimentar o código de exemplo mostrado acima, você precisará adicionar o pacote NuGet Win2D ao seu projeto com as instruções a seguir.

Para adicionar o pacote NuGet Win2D ao seu projeto de efeito

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Gerenciar Pacotes NuGet.
  2. Na parte superior da janela, selecione a guia Procurar.
  3. Na caixa de pesquisa, digite Win2D.
  4. Selecione Win2D.uwp e, em seguida, selecione Instalar no painel à direita.
  5. A caixa de diálogo Revisar Alterações mostra o pacote a ser instalado. Clique em OK.
  6. Aceite a licença do pacote.

Detectar e responder às alterações no nível de áudio pelo sistema

A partir do Windows 10, versão 1803, seu aplicativo pode detectar quando o sistema reduz o nível ou ativa o mudo de um MediaPlayer reproduzido no momento. Por exemplo, o sistema pode reduzir ou "ignorar", o nível de reprodução de áudio quando um alarme está tocando. O sistema ativa o mudo do aplicativo quando entra em segundo plano caso não tenha declarado a funcionalidade backgroundMediaPlayback no manifesto. A classe AudioStateMonitor permite que você registre-se para receber um evento quando o sistema modifica o volume de um fluxo de áudio. Acesse a propriedade AudioStateMonitor de um MediaPlayer e registre um manipulador do evento SoundLevelChanged para ser notificado quando o nível de áudio do MediaPlayer é alterado pelo sistema.

mediaPlayer.AudioStateMonitor.SoundLevelChanged += AudioStateMonitor_SoundLevelChanged;

Ao manipular o evento SoundLevelChanged, você pode ter ações diferentes dependendo do tipo de conteúdo reproduzido. Se você está reproduzindo músicas, convém permitir que a música continue a ser reproduzida enquanto o volume é abaixado. Entretanto, se estiver reproduzindo um podcast, você provavelmente desejará pausar a reprodução enquanto o áudio é abaixado para que o usuário não perca qualquer conteúdo.

Esse exemplo declara uma variável para controlar se o conteúdo em execução é um podcast; presume-se que você define isso com o valor apropriado ao selecionar o conteúdo do MediaPlayer. Também criamos uma variável de classe para controlar quando pausamos a reprodução programaticamente e o nível de áudio é alterado.

bool isPodcast;
bool isPausedDueToAudioStateMonitor;

No manipulador de eventos SoundLevelChanged, verifique a propriedade SoundLevel do remetente de AudioStateMonitor para determinar o novo nível de som. Esse exemplo verifica se o nível do novo som está com volume total, ou seja, se o sistema deixou de ativar o mudo ou diminuir o volume, ou se o nível de som foi reduzido, mas está reproduzindo o conteúdo diferente de um podcast. Se qualquer um dos seguintes procedimentos for verdadeiro e o conteúdo foi pausado anteriormente de modo programático, a reprodução é retomada. Se o nível do som novo for silenciado ou se o conteúdo atual for um podcast e o nível de som for baixo, a reprodução é pausada e a variável é definida para verificar se a pausa foi iniciada de forma programática.

private void AudioStateMonitor_SoundLevelChanged(Windows.Media.Audio.AudioStateMonitor sender, object args)
{
    if ((sender.SoundLevel == SoundLevel.Full) || (sender.SoundLevel == SoundLevel.Low && !isPodcast))
    {
        if (isPausedDueToAudioStateMonitor)
        {
            mediaPlayer.Play();
            isPausedDueToAudioStateMonitor = false;
        }
    }
    else if ((sender.SoundLevel == SoundLevel.Muted) ||
         (sender.SoundLevel == SoundLevel.Low && isPodcast))
    {
        if (mediaPlayer.PlaybackSession.PlaybackState == MediaPlaybackState.Playing)
        {
            mediaPlayer.Pause();
            isPausedDueToAudioStateMonitor = true;
        }
    }

}

O usuário pode decidir que eles querem pausar ou continuar a reprodução, mesmo se o áudio for ignorado pelo sistema. Esse exemplo mostra os manipuladores de eventos de um botão de reprodução e pausa. No botão de pausa, o manipulador de clique é pausado. Se a reprodução já foi pausada programaticamente, então atualizamos a variável para indicar que o usuário pausou o conteúdo. No manipulador de clique do botão de reprodução, é possível continuar a reprodução e limpar a variável de rastreamento.

private void PauseButton_User_Click(object sender, RoutedEventArgs e)
{
    if (isPausedDueToAudioStateMonitor)
    {
        isPausedDueToAudioStateMonitor = false;
    }
    else
    {
        mediaPlayer.Pause();
    }
}

public void PlayButton_User_Click()
{
    isPausedDueToAudioStateMonitor = false;
    mediaPlayer.Play();
}