Reproducir audio y vídeo con MediaPlayer

En este artículo se muestra cómo reproducir elementos multimedia en la aplicación universal de Windows mediante la clase MediaPlayer . Con Windows 10, versión 1607, se realizaron mejoras significativas en las API de reproducción multimedia, incluido un diseño simplificado de proceso único para audio en segundo plano, integración automática con los controles de transporte multimedia del sistema (SMTC), la capacidad de sincronizar varios reproductores multimedia, la capacidad de representar fotogramas de vídeo en una superficie Windows.UI.Composition y una interfaz sencilla para crear y programar saltos multimedia en el contenido. Para aprovechar estas mejoras, el procedimiento recomendado para reproducir elementos multimedia es usar la clase MediaPlayer en lugar de MediaElement para reproducir elementos multimedia. El control de XAML ligero, MediaPlayerElement, se introdujo para permitir representar contenido multimedia en una página XAML. Muchas API de estado y de control de la reproducción que proporciona la clase MediaElement están disponibles a través del nuevo objeto MediaPlaybackSession. La clase MediaElement continúa en funcionamiento para permitir la compatibilidad con versiones anteriores, pero no se le agregarán nuevas características.

En este artículo se muestran las características de MediaPlayer que usará una aplicación típica de reproducción de elementos multimedia. Ten en cuenta que MediaPlayer usa la clase MediaSource como un contenedor de todos los elementos multimedia. Esta clase permite cargar y reproducir elementos multimedia de distintos orígenes, como archivos locales, secuencias de memoria y orígenes de redes, todo con la misma interfaz. También hay clases de nivel superior que funcionan con MediaSource, como MediaPlaybackItem y MediaPlaybackList, que proporcionan características más avanzadas, como listas de reproducción y la capacidad para administrar orígenes multimedia con varias pistas de audio, vídeo y metadatos. Para obtener más información sobre MediaSource y las API relacionadas, consulta Elementos multimedia, listas de reproducción y pistas.

Nota

Las ediciones Windows 10 N y Windows 10 KN no incluyen las características multimedia necesarias para usar MediaPlayer para la reproducción. Estas características se pueden instalar manualmente. Para obtener más información, consulta Media Feature Pack para las ediciones Windows 10 N y Windows 10 KN.

Reproducir un archivo multimedia con MediaPlayer

La reproducción básica de elementos multimedia con MediaPlayer es muy fácil de implementar. En primer lugar, crea una nueva instancia de la clase MediaPlayer. Tu aplicación puede tener varias instancias de MediaPlayer activas a la vez. Después, establece la propiedad Source del reproductor en un objeto que implemente IMediaPlaybackSource, como un objeto MediaSource, MediaPlaybackItem o MediaPlaybackList. En este ejemplo, un objeto MediaSource se crea a partir de un archivo en el almacenamiento local de la aplicación, y después se crea un objeto MediaPlaybackItem desde el origen y, se asigna a la propiedad Source del reproductor.

A diferencia de la clase MediaElement, la clase MediaPlayer no inicia automáticamente la reproducción de manera predeterminada. Para iniciar la reproducción, llama al método Play, establece la propiedad AutoPlay en true o espera a que el usuario inicie la reproducción con los controles multimedia integrados.

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

Cuando la aplicación haya terminado de usar una clase MediaPlayer, debes llamar al método Close (previsto para Dispose en C#), para liberar los recursos usados por el reproductor.

mediaPlayer.Dispose();

Usar MediaPlayerElement para representar vídeo en XAML

Puedes reproducir elementos multimedia en una clase MediaPlayer sin que se muestren en XAML, pero muchas aplicaciones de reproducción multimedia querrán representar el contenido multimedia en una página XAML. Para ello, usa el control de MediaPlayerElement ligero. Como MediaElement, el elemento MediaPlayerElement te permite especificar si se deben mostrar los controles de transporte integrados.

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

Para establecer la instancia MediaPlayer con la que está enlazado el elemento, llama a SetMediaPlayer.

_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

También puedes establecer el origen de reproducción en el elemento MediaPlayerElement y este creará automáticamente una nueva instancia de MediaPlayer a la que podrás acceder con la propiedad MediaPlayer.

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

Nota

Si deshabilitas la clase MediaPlaybackCommandManager de MediaPlayer al establecer la propiedad IsEnabled en "false", se interrumpirá el vínculo entre MediaPlayer y TransportControls que proporciona el elemento MediaPlayerElement, de modo que los controles de transporte integrados ya no controlarán automáticamente la reproducción del reproductor. En su lugar, debes implementar tus propios controles para controlar la clase MediaPlayer.

Tareas comunes de MediaPlayer

En esta sección se muestra cómo usar algunas de las características de la clase MediaPlayer.

Establecer la categoría de audio

Establece la propiedad AudioCategory de una clase MediaPlayer en uno de los valores de la enumeración MediaPlayerAudioCategory para que el sistema sepa qué tipo de elemento multimedia se está reproduciendo. Los juegos deben clasificar sus secuencias de música como GameMedia para que la música del juego se silencie automáticamente si otra aplicación reproduce música en segundo plano. Las aplicaciones de música o vídeo deben clasificar sus secuencias como Media o Movie para que tengan prioridad sobre las secuencias GameMedia.

mediaPlayer.AudioCategory = MediaPlayerAudioCategory.Media;

Salida a un punto de conexión de audio específico

De manera predeterminada, la salida de audio de una clase MediaPlayer se enruta hacia el punto de conexión de audio predeterminado para el sistema, pero puedes especificar un punto de conexión de audio que la clase MediaPlayer deba usar para la salida. En el ejemplo siguiente, el elemento MediaDevice.GetAudioRenderSelector devuelve una cadena que exclusivamente identifica la categoría de representación de audio de los dispositivos. A continuación, se llama al método DeviceInformationFindAllAsync para obtener una lista de todos los dispositivos disponibles del tipo seleccionado. Se puede determinar mediante programación el dispositivo que quieres usar o agregar los dispositivos devueltos a un control ComboBox para permitir al usuario seleccionar un 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);
}

En el evento SelectionChanged del cuadro combinado de dispositivos, se establece la propiedad AudioDevice de la clase MediaPlayer en el dispositivo seleccionado, que se guardó en la propiedad Tag del elemento ComboBoxItem.

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

Sesión de reproducción

Como se describió anteriormente en este artículo, muchas de las funciones que expone la clase MediaElement se movieron a la clase MediaPlaybackSession. Esto incluye información sobre el estado de reproducción del reproductor, como la posición de reproducción actual, si está en pausa o en reproducción y la velocidad de reproducción actual. MediaPlaybackSession también proporciona varios eventos para notificarte si cambia el estado, como el almacenamiento en búfer actual y el estado de descarga del contenido que se reproduce, y el tamaño natural y la relación de aspecto del contenido de vídeo que se reproduce actualmente.

El siguiente ejemplo muestra cómo implementar un controlador de clic del botón que salta 10 segundos adelante en el contenido. Primero, se recupera el objeto MediaPlaybackSession del reproductor con la propiedad PlaybackSession. A continuación, se establece la propiedad Position en la posición de reproducción actual más 10 segundos.

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

En el siguiente ejemplo se muestra cómo usar un botón de alternancia para alternar entre la velocidad de reproducción normal y la velocidad 2X con la configuración de la propiedad PlaybackRate de la sesión.

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 de Windows 10, versión 1803, puedes establecer la rotación con la que se presenta el vídeo en MediaPlayer en incrementos de 90 grados.

mediaPlayer.PlaybackSession.PlaybackRotation = MediaRotation.Clockwise90Degrees;

Detección de almacenamiento en búfer esperado e inesperado

El objeto MediaPlaybackSession descrito en la sección anterior proporciona dos eventos para detectar cuándo comienza y finaliza el almacenamiento en búfer, BufferingStarted y BufferingEnded. Esto le permite actualizar la interfaz de usuario para mostrar al usuario que se está produciendo el almacenamiento en búfer. Se espera el almacenamiento en búfer inicial cuando se abre un archivo multimedia por primera vez o cuando el usuario cambia a un nuevo elemento de una lista de reproducción. El almacenamiento en búfer inesperado puede producirse cuando la velocidad de red se degrada o si el sistema de administración de contenido proporciona problemas técnicos de experiencias de contenido. A partir de RS3, puede usar el evento BufferingStarted para determinar si se espera el evento de almacenamiento en búfer o si es inesperado e interrumpe la reproducción. Puede usar esta información como datos de telemetría para la aplicación o el servicio de entrega multimedia.

Registre controladores para los eventos BufferingStarted y BufferingEnded para recibir notificaciones de estado de almacenamiento en búfer.

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

En el controlador de eventos BufferingStarted , convierta los argumentos de evento pasados al evento en un objeto MediaPlaybackSessionBufferingStartedEventArgs y compruebe la propiedad IsPlaybackInterruption . Si este valor es true, el almacenamiento en búfer que desencadenó el evento es inesperado e interrumpir la reproducción. De lo contrario, se espera el almacenamiento en búfer inicial.

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
}

Acercar los dedos y hacer zoom de vídeo

MediaPlayer te permite especificar el rectángulo de origen con el contenido de vídeo que se debe representar y permite hacer zoom en el vídeo de forma eficaz. El rectángulo que especifiques es relativo a un rectángulo normalizado (0,0,1,1), donde 0,0 es la parte superior izquierda del fotograma y 1,1 especifica el ancho y alto total del fotograma. Por lo tanto, por ejemplo, para establecer el rectángulo de zoom para que se represente el cuadrante superior derecho del vídeo, se especifica el rectángulo (.5,0,.5,.5). Es importante comprobar los valores para asegurarte de que el rectángulo de origen está dentro del rectángulo normalizado (0,0,1,1). Intentar establecer un valor fuera de este intervalo hará que se inicie una excepción.

Para implementar la opción de acercar los dedos y hacer zoom con gestos multitoque, antes debes especificar qué gestos quieres admitir. En este ejemplo, se solicitan los gestos ampliar y trasladar. El evento ManipulationDelta se genera cuando se produce uno de los gestos suscritos. El evento DoubleTapped se usará para restablecer el zoom a un fotograma completo.

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

A continuación, declara un objeto Rect que almacenará el rectángulo de origen de zoom actual.

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

El controlador ManipulationDelta ajusta la escala o la traslación del rectángulo de zoom. Si el valor de escala delta no es 1, significa que el usuario realizó un gesto de reducir. Si el valor es mayor que 1, el rectángulo de origen debe hacerse más pequeño para acercar el contenido. Si el valor es menor que 1, el rectángulo de origen debe ser mayor para alejarlo. Antes de establecer los nuevos valores de escala, se comprueba el rectángulo resultante para asegurarse de que se encuentra completamente dentro de los límites (0,0,1,1).

Si el valor de escala es 1, se controla el gesto de traslación. Simplemente, el número de píxeles en el gesto dividido por el ancho y alto del control traslada el rectángulo. De nuevo, se comprueba el rectángulo resultante para asegurarse de que se encuentra dentro de los límites (0,0,1,1).

Por último, el objeto NormalizedSourceRect del elemento MediaPlaybackSession se establece en el rectángulo recién ajustado y se especifica el área del fotograma de vídeo que se debe representar.

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

En el controlador de eventos DoubleTapped, el rectángulo de origen se vuelve a establecer en (0,0,1,1) para hacer que se represente todo el fotograma de vídeo.

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

NOTA En esta sección se describe la entrada táctil. Touchpad envía eventos de puntero y no enviará eventos de manipulación.

Control de la degradación de la reproducción basada en directivas

En algunas circunstancias, el sistema puede degradar la reproducción de un elemento multimedia, como reducir la resolución (constricción), en función de una directiva en lugar de un problema de rendimiento. Por ejemplo, el sistema puede degradar el vídeo si se reproduce mediante un controlador de vídeo sin firmar. Puede llamar a MediaPlaybackSession.GetOutputDegradationPolicyState para determinar si y por qué se está produciendo esta desgredación basada en directivas y alertar al usuario o registrar el motivo de los fines de telemetría.

En el ejemplo siguiente se muestra una implementación de un controlador para el evento MediaPlayer.MediaOpened que se genera cuando el reproductor abre un nuevo elemento multimedia. Se llama a GetOutputDegradationPolicyState en el objeto MediaPlayer pasado al controlador. El valor de VideoConstrictionReason indica el motivo de la directiva por el que el vídeo está constricdo. Si el valor no es None, en este ejemplo se registra el motivo de degradación con fines de telemetría. En este ejemplo también se muestra cómo establecer la velocidad de bits de AdaptiveMediaSource que se reproduce actualmente en el ancho de banda más bajo para guardar el uso de datos, ya que el vídeo está constricdo y no se mostrará en alta resolución de todos modos. Para obtener más información sobre el uso de AdaptiveMediaSource, consulte Streaming adaptable.

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 representar vídeo en una superficie Windows.UI.Composition

A partir de Windows 10, versión 1607, puedes usar MediaPlayer para representar vídeo en una superficie ICompositionSurface, que permite que el reproductor interopere con las API en el espacio de nombres Windows.UI.Composition. El fotograma de composición te permite trabajar con gráficos en la capa visual entre XAML y las API de gráficos de DirectX de bajo nivel. Esto permite escenarios como la representación de vídeo en cualquier control XAML. Para obtener más información sobre cómo usar las API de composición, consulta Capa visual.

En el siguiente ejemplo se muestra cómo representar el contenido del reproductor de vídeo en un control Canvas. Las llamadas específicas del reproductor multimedia de este ejemplo son SetSurfaceSize y GetSurface. SetSurfaceSize indica al sistema el tamaño del búfer que se debe asignar para representar contenido. GetSurface toma una clase Compositor como argumento y recupera una instancia de la clase MediaPlayerSurface. Esta clase proporciona acceso a las clases MediaPlayer y Compositor usadas para crear la superficie y expone la misma superficie a través de la propiedad CompositionSurface.

El resto del código del ejemplo crea un objeto SpriteVisual por el que se representa el vídeo y establece el tamaño en el tamaño del elemento de lienzo que mostrará el elemento visual. A continuación, se crea un objeto CompositionBrush a partir de la clase MediaPlayerSurface y se asigna a la propiedad Brush del elemento visual. A continuación, se crea una clase ContainerVisual y se inserta un objeto SpriteVisual en la parte superior de su árbol visual. Por último, se llama a un objeto SetElementChildVisual para asignar el elemento visual contenedor a la clase 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);

Usa MediaTimelineController para sincronizar contenido entre varios reproductores.

Como se explicó anteriormente en este artículo, la aplicación puede tener varios objetos MediaPlayer activos a la vez. De manera predeterminada, cada objeto MediaPlayer que crees funciona de forma independiente. En algunos escenarios, como la sincronización de una pista de comentarios en un vídeo, es posible que quieras sincronizar el estado del reproductor, la posición de reproducción y la velocidad de reproducción de varios reproductores. A partir de Windows 10, versión 1607, puedes implementar este comportamiento usando la clase MediaTimelineController.

Implementar controles de reproducción

En el siguiente ejemplo se muestra cómo usar un objeto MediaTimelineController para controlar dos instancias del elemento MediaPlayer. En primer lugar, se crea una instancia de cada instancia de la clase MediaPlayer y la propiedad Source se establece en un archivo multimedia. A continuación, se crea un objeto MediaTimelineController nuevo. Para cada objeto MediaPlayer, se deshabilita la clase MediaPlaybackCommandManager asociada a cada reproductor al establecer la propiedad IsEnabled en false. Y, a continuación, la propiedad TimelineController se establece en el objeto del controlador de escala de tiempo.

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;

Precaución La clase MediaPlaybackCommandManager proporciona integración automática entre MediaPlayer y los controles de transporte de contenido multimedia del sistema (SMTC), pero no se puede usar esta integración automática con reproductores multimedia que se controlen con un objeto MediaTimelineController. Por lo tanto, debes deshabilitar el administrador de comandos del reproductor multimedia antes de establecer el controlador de línea de tiempo del reproductor. Si no lo hace, se producirá una excepción con el siguiente mensaje: "Adjuntar controlador de escala de tiempo multimedia está bloqueado debido al estado actual del objeto". Para obtener más información sobre la integración del reproductor multimedia con SMTC, consulte Integración con los controles de transporte multimedia del sistema. Si estás usando un objeto MediaTimelineController, aún puedes controlar los SMTC manualmente. Para obtener más información, consulta Controles de transporte de contenido multimedia del sistema.

Cuando hayas adjuntado un objeto MediaTimelineController a uno o más reproductores multimedia, puedes controlar el estado de reproducción con los métodos que expone el controlador. El siguiente ejemplo llama al método Start para iniciar la reproducción de todos los reproductores multimedia adjuntos al inicio del elemento multimedia.

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

En este ejemplo se muestra cómo pausar y reanudar todos los reproductores multimedia adjuntos.

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 avanzar rápidamente todos los reproductores multimedia conectados, establece la velocidad de reproducción en un valor mayor que 1.

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

En el siguiente ejemplo se muestra cómo usar un control deslizante para mostrar la posición de reproducción actual del controlador de línea de tiempo relativo a la duración del contenido de uno de los reproductores multimedia conectados. En primer lugar, se crea un objeto MediaSource nuevo y se registra un controlador para el objeto OpenOperationCompleted de origen multimedia.

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

El controlador OpenOperationCompleted se usa como una oportunidad para descubrir la duración del contenido de origen multimedia. Cuando se haya determinado la duración, el valor máximo del control deslizante se establece en el número total de segundos del elemento multimedia. El valor se establece dentro de una llamada al método RunAsync para asegurarse de que se ejecute en el subproceso de la interfaz de usuario.

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

A continuación, se registra un controlador para el evento PositionChanged del controlador de escala de tiempo. El sistema lo llama periódicamente, aproximadamente 4 veces por segundo.

_mediaTimelineController.PositionChanged += _mediaTimelineController_PositionChanged;

En el controlador del objeto PositionChanged, el valor del control deslizante se actualiza para reflejar la posición actual del controlador de la línea de tiempo.

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

Desplazar la posición de reproducción desde la posición de línea de tiempo

En algunos casos, es posible que quieras que la posición de reproducción de uno o más reproductores multimedia asociados a un controlador de línea de tiempo se desplace desde los otros reproductores. Para ello, establece la propiedad TimelineControllerPositionOffset del objeto MediaPlayer que quieras desplazar. En el siguiente ejemplo, se usan las duraciones del contenido de dos reproductores multimedia para establecer los valores mínimos y máximos de dos control deslizantes para aumentar y reducir la longitud del elemento.

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

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

En el evento ValueChanged de cada control deslizante, el objeto TimelineControllerPositionOffset de cada reproductor está establecido en el valor correspondiente.

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

Ten en cuenta que si el valor de desplazamiento de un reproductor se asigna a una posición de reproducción negativa, el clip permanecerá en pausa hasta que el desplazamiento llegue a cero y, a continuación, se iniciará la reproducción. Del mismo modo, si el valor de desplazamiento se asigna a una posición de reproducción mayor que la duración del elemento multimedia, se mostrará el último fotograma, igual que cuando un único reproductor multimedia alcanza el final de su contenido.

Reproducir vídeo esférico con MediaPlayer

A partir de Windows 10, versión 1703, MediaPlayer admite la proyección equirectangular para la reproducción de vídeo esférico. El contenido de vídeo esférico no es diferente del vídeo normal y plano en que MediaPlayer representará el vídeo siempre que se admita la codificación de vídeo. Para vídeo esférico que contiene una etiqueta de metadatos que especifica que el vídeo usa la proyección equirectangular, MediaPlayer puede representar el vídeo mediante una orientación de campo de vista y vista especificada. Esto permite escenarios como la reproducción de vídeo de realidad virtual con una pantalla montada en la cabeza o simplemente permite al usuario desplazarse por el contenido de vídeo esférico mediante la entrada del mouse o del teclado.

Para reproducir vídeo esférico, siga los pasos para reproducir contenido de vídeo descrito anteriormente en este artículo. El paso adicional es registrar un controlador para el evento MediaPlayer.MediaOpened . Este evento le ofrece la oportunidad de habilitar y controlar los parámetros de reproducción 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();

En el controlador MediaOpened , compruebe primero el formato de fotograma del elemento multimedia recién abierto comprobando la propiedad PlaybackSession.SphericalVideoProjection.FrameFormat . Si este valor es SphericaVideoFrameFormat.Equirectangular, el sistema puede proyectar automáticamente el contenido del vídeo. En primer lugar, establezca la propiedad PlaybackSession.SphericalVideoProjection.IsEnabled en true. También puede ajustar propiedades como la orientación de la vista y el campo de vista que usará el reproductor multimedia para proyectar el contenido del vídeo. En este ejemplo, el campo de vista se establece en un valor ancho de 120 grados estableciendo la propiedad HorizontalFieldOfViewInDegrees .

Si el contenido del vídeo es esférico, pero tiene un formato distinto del equirectangular, puede implementar su propio algoritmo de proyección mediante el modo de servidor de fotogramas del reproductor multimedia para recibir y procesar fotogramas individuales.

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

En el código de ejemplo siguiente se muestra cómo ajustar la orientación de la vista de vídeo esférica mediante las teclas de flecha izquierda y derecha.

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

Si la aplicación admite listas de reproducción de vídeo, es posible que quiera identificar los elementos de reproducción que contienen vídeo esférico en la interfaz de usuario. Las listas de reproducción multimedia se describen en detalle en el artículo Elementos multimedia, listas de reproducción y pistas. En el ejemplo siguiente se muestra cómo crear una lista de reproducción, agregar un elemento y registrar un controlador para el evento MediaPlaybackItem.VideoTracksChanged , que se produce cuando se resuelven las pistas de vídeo de un elemento multimedia.

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;

En el controlador de eventos VideoTracksChanged , obtenga las propiedades de codificación de las pistas de vídeo agregadas llamando a VideoTrack.GetEncodingProperties. Si la propiedad SphericalVideoFrameFormat de las propiedades de codificación es un valor distinto de SphericaVideoFrameFormat.None, la pista de vídeo contiene vídeo esférico y puede actualizar la interfaz de usuario según corresponda si lo desea.

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 MediaPlayer en modo de servidor de fotogramas

A partir de Windows 10, versión 1703, puedes usar MediaPlayer en modo de servidor de fotogramas. En este modo, MediaPlayer no representa automáticamente fotogramas en un objeto MediaPlayerElement asociado. En su lugar, la aplicación copia el marco actual de MediaPlayer en un objeto que implementa IDirect3DSurface. El escenario principal que permite esta característica es usar sombreadores de píxeles para procesar fotogramas de vídeo proporcionados por MediaPlayer. La aplicación es responsable de mostrar cada fotograma después del procesamiento, por ejemplo, mostrando el marco en un control imagen XAML.

En el ejemplo siguiente, se inicializa un nuevo objeto MediaPlayer y se carga el contenido de vídeo. A continuación, se registra un controlador para VideoFrameAvailable . El modo de servidor frame está habilitado estableciendo la propiedad IsVideoFrameServerEnabled del objeto MediaPlayer en true. Por último, la reproducción multimedia se inicia con una llamada 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();

En el ejemplo siguiente se muestra un controlador para VideoFrameAvailable que usa Win2D para agregar un efecto de desenfoque simple a cada fotograma de un vídeo y, a continuación, muestra los fotogramas procesados en un control Imagen XAML.

Cada vez que se llama al controlador VideoFrameAvailable , se usa el método CopyFrameToVideoSurface para copiar el contenido del fotograma en un IDirect3DSurface. También puede usar CopyFrameToStereoscopicVideoSurfaces para copiar contenido 3D en dos superficies, para procesar el contenido de los ojos izquierdo y derecho por separado. Para obtener un objeto que implementa IDirect3DSurface en este ejemplo, se crea un objeto SoftwareBitmap y, a continuación, se usa ese objeto para crear un CanvasBitmap de Win2D, que implementa la interfaz necesaria. CanvasImageSource es un objeto Win2D que se puede usar como origen para un control Image, por lo que se crea uno nuevo y se establece como el origen de la imagen en la que se mostrará el contenido. A continuación, se crea canvasDrawingSession . Win2D lo usa para representar el efecto de desenfoque.

Una vez creados todas las instancias de todos los objetos necesarios, se llama a CopyFrameToVideoSurface , que copia el marco actual de MediaPlayer en CanvasBitmap. A continuación, se crea una clase GaussianBlurEffect win2D, con canvasBitmap establecido como origen de la operación. Por último, se llama a CanvasDrawingSession.DrawImage para dibujar la imagen de origen, con el efecto de desenfoque aplicado, en canvasImageSource que se ha asociado con el control Image , lo que hace que se dibuje en la interfaz de usuario.

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 obtener más información sobre Win2D, consulte el repositorio de GitHub de Win2D. Para probar el código de ejemplo mostrado anteriormente, deberá agregar el paquete NuGet Win2D al proyecto con las instrucciones siguientes.

Para agregar el paquete de NuGet Win2D al proyecto de efecto

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Administrar paquetes NuGet.
  2. En la parte superior de la ventana, selecciona la pestaña Examinar.
  3. En el cuadro de búsqueda, escribe Win2D.
  4. Selecciona Win2D.uwpy luego selecciona Instalar en el panel derecho.
  5. En el cuadro de diálogo Revisar cambios se muestra el paquete que se instalará. Haga clic en Aceptar.
  6. Acepta la licencia del paquete.

Detectar y responder a los cambios en el nivel de audio por parte del sistema

A partir de Windows 10, versión 1803, la aplicación puede detectar cuándo el sistema reduce omuta el nivel de audio de un objeto MediaPlayer que se está reproduciendo actualmente. Por ejemplo, el sistema puede bajar o "pato", el nivel de reproducción de audio cuando una alarma suena. El sistema silenciará la aplicación cuando entre en segundo plano si la aplicación no ha declarado la funcionalidad backgroundMediaPlayback en el manifiesto de la aplicación. La clase AudioStateMonitor permite registrarse para recibir un evento cuando el sistema modifica el volumen de una secuencia de audio. Accede a la propiedad AudioStateMonitor de un objeto MediaPlayer y registra un controlador para que el evento SoundLevelChanged se notifique cuando el sistema cambie el nivel de audio de ese Objeto MediaPlayer .

mediaPlayer.AudioStateMonitor.SoundLevelChanged += AudioStateMonitor_SoundLevelChanged;

Al controlar el evento SoundLevelChanged , puede realizar diferentes acciones en función del tipo de contenido que se reproduce. Si actualmente estás reproduciendo música, es posible que quieras dejar que la música continúe reproduciendo mientras el volumen está pato. Sin embargo, si estás reproduciendo un podcast, es probable que quieras pausar la reproducción mientras el audio está pato para que el usuario no pierda ninguno de los contenidos.

En este ejemplo se declara una variable para realizar un seguimiento de si el contenido que se está reproduciendo actualmente es un podcast, se supone que se establece en el valor adecuado al seleccionar el contenido de MediaPlayer. También se crea una variable de clase para realizar un seguimiento al pausar la reproducción mediante programación cuando cambia el nivel de audio.

bool isPodcast;
bool isPausedDueToAudioStateMonitor;

En el controlador de eventos SoundLevelChanged , compruebe la propiedad SoundLevel del remitente AudioStateMonitor para determinar el nuevo nivel de sonido. En este ejemplo se comprueba si el nuevo nivel de sonido es de volumen completo, lo que significa que el sistema ha dejado de silenciar o evitar el volumen, o si el nivel de sonido se ha reducido, pero está reproduciendo contenido que no es podcast. Si alguno de estos valores es true y el contenido se ha pausado previamente mediante programación, se reanuda la reproducción. Si el nuevo nivel de sonido está silenciado o si el contenido actual es un podcast y el nivel de sonido es bajo, la reproducción está en pausa y la variable se establece para realizar una pista de que la pausa se inició mediante programación.

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

}

El usuario puede decidir que desea pausar o continuar la reproducción, incluso si el sistema está encajonado por el audio. En este ejemplo se muestran los controladores de eventos para una reproducción y un botón de pausa. En el controlador de clic del botón de pausa se pausa, si la reproducción ya se había pausado mediante programación, actualizamos la variable para indicar que el usuario ha pausado el contenido. En el controlador de clic del botón reproducir, reanudamos la reproducción y borramos nuestra variable de seguimiento.

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