Compartir a través de


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 fácil para crear y programar interrupciones multimedia en el contenido. Para aprovechar estas mejoras, el procedimiento recomendado para reproducir contenido multimedia es usar la clase MediaPlayer en lugar de MediaElement para la reproducción multimedia. El control XAML ligero, MediaPlayerElement, se ha introducido para permitir representar contenido multimedia en una página XAML. Muchas de las API de estado y control de reproducción proporcionadas por MediaElement ahora están disponibles a través del nuevo objeto MediaPlaybackSession. MediaElement sigue funcionando para admitir la compatibilidad con versiones anteriores, pero no se agregarán características adicionales a esta clase.

Este artículo te guiará a través de las características de MediaPlayer que usará una aplicación típica de reproducción multimedia. Tenga en cuenta que MediaPlayer usa la clase MediaSource como contenedor para todos los elementos multimedia. Esta clase permite cargar y reproducir medios de muchos orígenes diferentes, incluidos archivos locales, secuencias de memoria y orígenes de red, todo ello mediante 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 de 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, consulte 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 Paquete de características multimedia para las ediciones Windows 10 N y Windows 10 KN.

Reproducir un archivo multimedia con MediaPlayer

La reproducción multimedia básica con MediaPlayer es muy sencilla de implementar. En primer lugar, cree una nueva instancia de la clase MediaPlayer . La aplicación puede tener varias instancias de MediaPlayer activas a la vez. A continuación, establezca la propiedad Source del reproductor en un objeto que implemente IMediaPlaybackSource, como MediaSource, MediaPlaybackItem o MediaPlaybackList. En este ejemplo, se crea un objeto MediaSource a partir de un archivo en el almacenamiento local de la aplicación y, a continuación, se crea un objeto MediaPlaybackItem a partir del origen y, a continuación, se asigna a la propiedad Source del reproductor.

A diferencia de MediaElement, MediaPlayer no inicia automáticamente la reproducción de forma predeterminada. Puedes iniciar la reproducción llamando a Play, estableciendo la propiedad AutoPlay en true o esperando 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 con mediaPlayer, debes llamar al método Close (proyectado a Dispose en C#) para limpiar los recursos usados por el reproductor.

mediaPlayer.Dispose();

Usar MediaPlayerElement para representar vídeo en XAML

Puedes reproducir contenido multimedia en un objeto MediaPlayer sin mostrarlo en XAML, pero muchas aplicaciones de reproducción multimedia querrán representar los medios en una página XAML. Para ello, usa el control Ligero MediaPlayerElement. Al igual que MediaElement, MediaPlayerElement te permite especificar si se deben mostrar los controles de transporte integrados.

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

Puedes establecer la instancia de MediaPlayer a la que está enlazado el elemento llamando a SetMediaPlayer.

_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

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

Nota:

Establecer las propiedades MediaPlayerElement establecerá las propiedades correspondientes en su mediaPlayer subyacente. Tienes la opción de usar mediaPlayer subyacente directamente en lugar de usar las propiedades MediaPlayerElement. Tenga en cuenta que el uso de MediaPlayer directamente donde una propiedad MediaPlayerElement equivalente podría usarse de otro modo puede provocar un comportamiento inesperado. Esto se debe a que MediaPlayerElement no es consciente de todo lo que sucede con su MediaPlayer subyacente. Por ejemplo, si estableces el origen directamente en MediaPlayer, la propiedad Source mediaPlayerElement no reflejará el cambio. Por este motivo, debes ser coherente en el uso de las propiedades MediaPlayerElement o directamente mediante el objeto MediaPlayer subyacente.

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

Nota:

Si deshabilitas el MediaPlaybackCommandManager de MediaPlayer estableciendo IsEnabled en false, interrumpirá el vínculo entre mediaPlayer los TransportControls proporcionados por MediaPlayerElement, por lo 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 mediaPlayer.

MediaPlayer se desasocia de MediaPlayerElement cuando se destruye MediaPlayerElement o cuando se establece un nuevo Objeto MediaPlayer mediante SetMediaPlayer. Cuando se separa, MediaPlayerElement trata el objeto MediaPlayer subyacente de forma diferente en función de si mediaPlayerElement lo creó o se estableció mediante SetMediaPlayer.

Si MediaPlayer fue creado por MediaPlayerElement, cerrará correctamente el objeto MediaPlayer automáticamente. Si mediaPlayer se estableció en MediaPlayerElement con SetMediaPlayer, usted es responsable de asegurarse de que MediaPlayer está cerrado correctamente. Si no lo hace, se pueden producir errores de reproducción irrecuperables en MediaPlayer. El siguiente fragmento de código muestra cómo desasociar y cerrar correctamente en el código.

// Get a reference to the current media source.
IMediaPlaybackSource _mediaPlayerElement = _mediaPlayerElement.Source;

// Pause playback if able.
if (mediaPlayer.PlaybackSession.CanPause)
{
    mediaPlayer.Pause();
}

// Disconnect the MediaPlayer from its source. This can be done by setting 
// the MediaPlayerElement Source property to null or by directly setting the
// source to null on the underlying MediaPlayer.
_mediaPlayerElement.Source = null;

// Disconnect the MediaPlayer from MediaPlayerElement.
_mediaPlayerElement.SetMediaPlayer(null);

// Dispose of the MediaPlayer or Source if they're no longer needed.
if (source is MediaSource mediaSource)
{
    mediaSource.Dispose();
}

mediaPlayer.Dispose();

Tareas comunes de MediaPlayer

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

Establecer la categoría de audio

Establece la propiedad AudioCategory de un objeto MediaPlayer en uno de los valores de la enumeración MediaPlayerAudioCategory para que el sistema sepa qué tipo de medios estás reproduciendo. Los juegos deben clasificar sus secuencias de música como GameMedia para que la música del juego mute 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 , por lo que tendrán prioridad sobre las secuencias de GameMedia .

mediaPlayer.AudioCategory = MediaPlayerAudioCategory.Media;

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

De forma predeterminada, la salida de audio de un objeto MediaPlayer se enruta al punto de conexión de audio predeterminado para el sistema, pero puedes especificar un punto de conexión de audio específico que mediaPlayer debe usar para la salida. En el ejemplo siguiente, MediaDevice.GetAudioRenderSelector devuelve una cadena que identifica de forma única la categoría de representación de audio de los dispositivos. A continuación, se llama al método DeviceInformation FindAllAsync para obtener una lista de todos los dispositivos disponibles del tipo seleccionado. Puede determinar mediante programación qué dispositivo desea usar o agregar los dispositivos devueltos a un 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, la propiedad AudioDevice de MediaPlayer se establece en el dispositivo seleccionado, que se almacenó en la propiedad Tag del 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 ha descrito anteriormente en este artículo, muchas de las funciones expuestas por la clase MediaElement se han movido 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 el reproductor está en pausa o reproducción, y la velocidad de reproducción actual. MediaPlaybackSession también proporciona varios eventos para notificarle cuándo cambia el estado, incluido el almacenamiento en búfer actual y el estado de descarga del contenido que se está reproduciendo y el tamaño natural y la relación de aspecto del contenido de vídeo que se está reproduciendo actualmente.

En el ejemplo siguiente se muestra cómo implementar un controlador de clic de botón que omite 10 segundos de avance en el contenido. En primer lugar, el objeto MediaPlaybackSession del reproductor se recupera con la propiedad PlaybackSession. A continuación, la propiedad Position se establece 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 ejemplo siguiente se muestra el uso de un botón de alternancia para alternar entre la velocidad de reproducción normal y la velocidad 2X estableciendo 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. 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 interrumpir 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 del 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
}

Reducir y acercar vídeo

MediaPlayer te permite especificar el rectángulo de origen dentro del contenido de vídeo que se debe representar, lo que te permite acercar el vídeo de forma eficaz. El rectángulo especificado es relativo a un rectángulo normalizado (0,0,1,1), donde 0,0 es la parte superior izquierda del marco y 1,1 especifica el ancho completo y el alto del marco. Por ejemplo, para establecer el rectángulo de zoom para que se represente el cuadrante superior derecho del vídeo, debe especificar el rectángulo (.5,0,.5,.5). Es importante comprobar los valores para asegurarse de que el rectángulo de origen está dentro del rectángulo normalizado (0,0,1,1). Si intenta establecer un valor fuera de este intervalo, se producirá una excepción.

Para implementar reducir y acercar con gestos multitáctil, primero debe especificar qué gestos desea admitir. En este ejemplo, se solicitan gestos de escala y traducción. El evento ManipulationDelta se genera cuando se produce uno de los gestos suscritos. El evento DoubleTapped se usará para restablecer el zoom al marco completo.

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

A continuación, declare 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 traducción del rectángulo de zoom. Si el valor de escala delta no es 1, significa que el usuario realizó un gesto de reducción. Si el valor es mayor que 1, el rectángulo de origen debe ser más pequeño para acercar el contenido. Si el valor es menor que 1, el rectángulo de origen se debe hacer más grande para alejar. 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 traducción. El rectángulo se traduce simplemente por el número de píxeles en gesto dividido por el ancho y el alto del control. 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, normalizedSourceRect de MediaPlaybackSession se establece en el rectángulo recién ajustado, especificando el área dentro del marco 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 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 está reproduciendo mediante un controlador de vídeo sin firmar. Puede llamar a MediaPlaybackSession.GetOutputDegradationPolicyState para determinar si y por qué se está produciendo esta degradación basada en directivas y alertar al usuario o registrar el motivo para 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 la razón de la directiva por la que el vídeo está constrictado. 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 ahorrar el uso de datos, ya que el vídeo está constrictado 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 ICompositionSurface, lo que permite al reproductor interoperar con las API en el espacio de nombres Windows.UI.Composition. El marco de composición te permite trabajar con gráficos en la capa visual entre XAML y las API de gráficos 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 el uso de las API de composición, consulte Capa visual.

En el ejemplo siguiente 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 un compositor como argumento y recupera una instancia de la clase MediaPlayerSurface. Esta clase proporciona acceso a MediaPlayer y Compositor que se usa para crear la superficie y expone la propia superficie a través de la propiedad CompositionSurface.

El resto del código de este ejemplo crea un SpriteVisual al que se representa el vídeo y establece el tamaño en el tamaño del elemento de lienzo que mostrará el objeto visual. A continuación, se crea un CompositionBrush a partir de MediaPlayerSurface y se asigna a la propiedad Brush del objeto visual. A continuación, se crea un ContainerVisual y se inserta SpriteVisual en la parte superior de su árbol visual. Por último, se llama a SetElementChildVisual para asignar el objeto visual de contenedor al lienzo.

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 el 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 forma predeterminada, cada MediaPlayer que creas funciona de forma independiente. En algunos escenarios, como la sincronización de una pista de comentarios a un vídeo, es posible que desee 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 mediante la clase MediaTimelineController.

Implementar controles de reproducción

En el ejemplo siguiente se muestra cómo usar mediaTimelineController para controlar dos instancias de MediaPlayer. En primer lugar, cada instancia de MediaPlayer se crea una instancia de y el origen se establece en un archivo multimedia. A continuación, se crea un nuevo objeto MediaTimelineController . Para cada MediaPlayer, el MediaPlaybackCommandManager asociado a cada reproductor está deshabilitado estableciendo la propiedad IsEnabled en false. Y, a continuación, la propiedad TimelineController se establece en el objeto de 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: MediaPlaybackCommandManager proporciona integración automática entre MediaPlayer y los controles de transporte multimedia del sistema (SMTC), pero esta integración automática no se puede usar con reproductores multimedia que se controlan con mediaTimelineController. Por lo tanto, debe deshabilitar el administrador de comandos para el reproductor multimedia antes de establecer el controlador de escala 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 usa un control MediaTimelineController , puede controlar manualmente el SMTC. Para obtener más información, vea Control manual de los controles de transporte multimedia del sistema.

Una vez que haya asociado un mediaTimelineController a uno o varios reproductores multimedia, puede controlar el estado de reproducción mediante los métodos expuestos por el controlador. En el ejemplo siguiente se llama a Start para iniciar la reproducción de todos los reproductores multimedia asociados al principio del medio.

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

En este ejemplo se muestra la pausa y reanudación de 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, establezca 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 ejemplo siguiente se muestra cómo usar un control Slider para mostrar la posición de reproducción actual del controlador de escala de tiempo en relación con la duración del contenido de uno de los reproductores multimedia conectados. En primer lugar, se crea un nuevo MediaSource y se registra un controlador para OpenOperationCompleted del 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 detectar la duración del contenido de origen multimedia. Una vez determinada la duración, el valor máximo del control Slider se establece en el número total de segundos del elemento multimedia. El valor se establece dentro de una llamada a RunAsync para asegurarse de que se ejecuta 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 llama periódicamente a esto, aproximadamente 4 veces por segundo.

_mediaTimelineController.PositionChanged += _mediaTimelineController_PositionChanged;

En el controlador de PositionChanged, el valor del control deslizante se actualiza para reflejar la posición actual del controlador de escala 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;
        });
    }
}

Desplazamiento de la posición de reproducción desde la posición de escala 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 escala de tiempo se desfase de los otros reproductores. Puedes hacerlo estableciendo la propiedad TimelineControllerPositionOffset del objeto MediaPlayer que quieres desplazar. En el ejemplo siguiente se usan las duraciones del contenido de dos reproductores multimedia para establecer los valores mínimos y máximos de dos controles deslizantes en más y menos 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, timelineControllerPositionOffset para cada reproductor se establece 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);
}

Tenga 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 alcance cero y, a continuación, comenzará 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 fotograma final, igual que cuando un único reproductor multimedia alcanzó 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 el 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 dentro del contenido de vídeo esférico mediante la entrada del mouse o el 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éricos.

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 de 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 dirección 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 nueva 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, como al mostrar 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, el método CopyFrameToVideoSurface se usa para copiar el contenido del marco en un IDirect3DSurface. También puede usar CopyFrameToStereoscopicVideoSurfaces para copiar contenido 3D en dos superficies, para procesar el ojo izquierdo y el contenido del ojo 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 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 usa esto 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 el CanvasImageSource 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 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 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, seleccione la pestaña Examinar .
  3. En el cuadro de búsqueda, escriba Win2D.
  4. Seleccione Win2D.uwp y, a continuación, seleccione Instalar en el panel derecho.
  5. El cuadro de diálogo Revisar cambios muestra el paquete que se va a instalar. Haga clic en OK.
  6. Acepte la licencia del paquete.

Detección y respuesta a cambios de nivel de audio por parte del sistema

A partir de Windows 10, versión 1803, la aplicación puede detectar cuándo el sistema baja o cambia 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. Obtenga acceso a la propiedad AudioStateMonitor de un objeto MediaPlayer y registre un controlador para el evento SoundLevelChanged para recibir una notificación cuando el sistema cambie el nivel de audio de ese 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 está reproduciendo música actualmente, es posible que quiera 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 cuando se pausa 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 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 sea podcast. Si cualquiera 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 un seguimiento 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 usa 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 clics 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();
}