[Windows Phone 8.1] Capturando la pantalla utilizando la API MediaCapture

Introducción

Entre la enorme cantidad de nuevas APIs, controles y herramientas
disponibles con la llegada de Windows Phone 8.1, desde un principio
llamo mucho la atención las nuevas APIs disponibles para grabar en video o tomar capturas de la actividad de la pantalla. Esta nueva característica la tenemos disponible dentro del namespace Windows.Media.Capture y sera nuestro centro de atención en este artículo.

¿Te apuntas?

Primeros pasos

Comenzamos creando un nuevo proyecto:

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

Nuestro objetivo sera muy sencillo. Nuestra aplicación de ejemplo
contará con un botón para comenzar a grabar lo que ocurre en pantalla y
otro para detener la grabación. Tras terminar la grabación podemos ver
el video resultante.

Antes de comenzar, vamos a añadir la Webcam como capacidad en el archivo Package.manifiest:

Manos a la obra!

Tomar capturas de pantalla de la aplicación e incluso videos es una
funcionalidad fantástica para segun que tipo de aplicaciones y sobretodo
en juegos. En nuestro ejemplo, debíamos contar con “algo” interesante que grabar. Para ello, vamos a añadir un elipse con una animacion Easing de modo que otorgemos el efecto de una bola cayendo y rebotando.

Para gestionar la animación vamos a utilizar el SDK de Behaviors incluido dentro de las extensiones:

Nos centramos en la vista principal, MainView.xaml. Añadimos la elipse:

<Ellipse x:Name="Ball" Height="75" Width="75" Fill="Red" RenderTransformOrigin="0.5,0.5">
     <Ellipse.RenderTransform>
          <TranslateTransform/>
     </Ellipse.RenderTransform>
     <interactivity:Interaction.Behaviors>
          <core:EventTriggerBehavior EventName="Loaded">
               <media:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource MyStoryboard}"/>
          </core:EventTriggerBehavior>
     </interactivity:Interaction.Behaviors>
</Ellipse>

Al cargar lanzaremos una animacion llamada MyStoryBoard que tenemos definida en los recursos de la página:

<Storyboard x:Name="MyStoryboard">
    <DoubleAnimation From="0" To="250" Duration="00:00:10"
                     Storyboard.TargetName="Ball"
                     Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)">
         <DoubleAnimation.EasingFunction>
              <BounceEase Bounces="40" EasingMode="EaseOut"
                          Bounciness="1.2" />
         </DoubleAnimation.EasingFunction>
     </DoubleAnimation>
</Storyboard>

De este modo, al lanzar la aplicación se lanzará la animación con el siguiente resultado:

Ya tenemos listo el elemento visual a grabar. Necesitamos dos botones en
nuetra aplicación. Uno para grabar la pantalla y otro que permita ver
el resultado:

<Page.BottomAppBar>
     <CommandBar>
          <AppBarButton />
          <AppBarButton />
     </CommandBar>
</Page.BottomAppBar>

Creamos una CommandBar y añadimos los botones. El
primer botón nos permitirá comenzar y detener la grabación del video. El
segundo nos permitirá ver el video resultante. Para poder conseguir
este resultado, en la viewmodel necesitaremos una propiedad
bool que nos indique si se esta grabando el video o no, para permitir
comenzar o detener la grabación y otra propiedad bool que nos indique si
ya hay un video grabado:

private bool _recording;
private bool _result;
 
public bool Recording
{
     get { return _recording; }
     set
     {
          _recording = value;
          RaisePropertyChanged("Recording");
     }
}
 
public bool Result
{
     get { return _result; }
     set
     {
          _result = value;
          RaisePropertyChanged("Result");
     }
}

El segundo botón lo mostraremos cuando la propiedad Result sea cierta, necesitaremos un Converter para convertir la propiedad bool a Visibility:

public class BoolToVisibilityConverter : IValueConverter
{
     public object Convert(object value, Type targetType, object parameter, string language)
     {
          if (value is bool)
               return ((bool) value) ? Visibility.Visible : Visibility.Collapsed;
 
          return Visibility.Collapsed;
     }
 
     public object ConvertBack(object value, Type targetType, object parameter, string language)
     {
          return null;
     }
}

El primer botón nos permite controlar dos estados diferentes:

  • Comenzar a grabar.
  • Detener una grabación.

Si estamos grabando o no nos lo indica la propiedad Recording, necesitaremos dos converters para obtener el Label y el Icon del AppBarButton segun el valor de la propiedad:

public class RecordStateToIconElementConverter : IValueConverter
{
     public object Convert(object value, Type targetType, object parameter, string language)
     {
            var recording = value as bool?;
 
            if (recording != null)
                return recording == true ? new SymbolIcon(Symbol.Stop) : new SymbolIcon(Symbol.Play);
            return new SymbolIcon(Symbol.Stop);
     }
 
     public object ConvertBack(object value, Type targetType, object parameter, string language)
     {
            return null;
     }
}

Cada botón realizará una acción, en la viewmodel, la ejecución de un comando:

private ICommand _recordCommand;
private ICommand _resultCommand;
 
public ICommand RecordCommand
{
     get { return _recordCommand = _recordCommand ?? new DelegateCommandAsync(RecordCommandDelegate); }
}
 
public ICommand ResultCommand
{
     get { return _resultCommand = _resultCommand ?? new DelegateCommand(ResultCommandDelegate); }
}
 
public async Task RecordCommandDelegate()
{   
 
}
 
public void ResultCommandDelegate()
{
 
}

De modo que nuestros dos botones en la CommandBar quedaran:

<AppBarButton
     Label="{Binding Recording, Converter={StaticResource RecordStateToStringConverter}}"
     Icon="{Binding Recording, Converter={StaticResource RecordStateToIconElementConverter}}"
     Command="{Binding RecordCommand}" />
<AppBarButton
     Visibility="{Binding Result, Converter={StaticResource BoolToVisibilityConverter}}"
     Label="Result"
     Icon="Forward"
     Command="{Binding ResultCommand}" />

API Windows.Media.Capture

Llegamos a la parte fundamental del ejemplo y del artículo, el uso de las APIs disponibles en Windows.Media.Capture.
Crearemos un servicio sencillo que nos permita grabar con facilidad lo
que ocurre en la pantalla de nuestro dispositivo. El servicio lo
llamaremos ScreenRecorederService y su definición sera la siguiente:

public interface IScreenRecorderService
{
     ScreenRecorderService.RecordingStatus Status { get; }
     Task Start(string recordName);
     void Stop();
}

Como podemos ver contamos con:

  • Una propieddad Status. Nos permitirá consultar en
    todo momento si el servicio esta grabando la pantalla, esta detenido, ha
    terminado la grabación con éxito o por el contrario ha ocurrido algun
    error.
  • Un evento Start que recibirá como parámetro el
    nombre del video resultante de la grabación. Este evento se encarga de
    comenzar la grabación del video.
  • Un evento Stop que detiene una grabación inciada de video.

Nos centramos en la implementación del servicio. Contaremos con una
sencilla enumeración que nos permita determinar con facilidad el estado
de la grabación:

public enum RecordingStatus
{
     Stopped,
     Recording,
     Failed,
     Sucessfull
};

Sencillo, ¿cierto?.

Comenzar la grabación del video

Nos centramos en el método Start de nuestro servicio. Comenzamos creando un objeto de clase ScreenCapture utilizando el método: GetForCurrentView:

//Inicializamos ScreenCapture.
ScreenCapture screenCapture = ScreenCapture.GetForCurrentView();

A continuación, creamos una instancia de la clase MediaCaptureInitializationSettings y establecemos la fuente de audio y vídeo con las propiedades AudioSource y VideoSource del objeto ScreenCapture previamente definido:

// Establecemos MediaCaptureInitializationSettings para que ScreenCapture capture audio y video.
var mediaCaptureInitializationSettings = new MediaCaptureInitializationSettings
{
     VideoSource = screenCapture.VideoSource,
     AudioSource = screenCapture.AudioSource,
     StreamingCaptureMode = StreamingCaptureMode.AudioAndVideo
};

El objeto MediaCaptureInitializationSettings nos permite establecer la
configuración básica necesaria para crear un objeto de tipo MediaCapture. El objeto MediaCapture es el encargado de ofrecernos la posibilidad de capturar fotos, audio y vídeos.

// Inicializamos MediaCapture con los settings anteriores.
_mediaCapture = new MediaCapture();
await _mediaCapture.InitializeAsync(mediaCaptureInitializationSettings);

Inicializamos utilizando el método InitializeAsync pasándole como parámetro la configuración anterior.

NOTA: InitializeAsync iniciará una solicitud de consentimiento para obtener permisos de usuario.

Continuamos suscribiéndonos a los eventos Failed, RecordLimitationExceeded, y SourceSuspensionChanged:

_mediaCapture.Failed += (s, e) => { _recordingStatus = RecordingStatus.Failed; };
_mediaCapture.RecordLimitationExceeded += s => { _recordingStatus = RecordingStatus.Failed; };
 
screenCapture.SourceSuspensionChanged += (s, e) => Debug.WriteLine("IsAudioSuspended:" +
                                                                   e.IsAudioSuspended +
                                                                   " IsVideoSuspended:" +
                                                                   e.IsVideoSuspended);

Tenemos:

  • El evento Failed se lanzará cuando cualquier tipo de error ocurra durante la grabacion del video.
  • El evento RecordLimitationExceeded se lanzará cuando se supere el tiempo máximo de grabación. En Windows 8.1 el tiempo máximo de grabación es de 3 horas.
  • El evento SourceSuspensionChanged se lanzará cada
    vez que se cambie el estado de activo y suspensión de la aplicación
    durante la grabación del video. Profundizaremos en la gestión de la
    grabación del video y la suspensión de la aplicación algo más adelante
    en este mismo artículo.

A continuación, definimos el perfil de codificación para el archivo
de video y audio. Para ello, utilizamos un objeto de la clase MediaEncondingProfile. Tenemos disponible los  formatos más habituales tanto de video como de audio:

  • MP3
  • MP4
  • Wav
  • Wma
  • Wmv
// Creamos un encondig a utilizar. Por defecto, MP4 1080P.                  
MediaEncodingProfile mediaEncodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);

Tras crear el perfil de codificación, creamos el archivo donde se guardarán los medios capturados:

// Creamos el fichero resultante de la grabación.            
StorageFile video =
                    await
                        ApplicationData.Current.LocalFolder.CreateFileAsync(string.Format("{0}.mp4", recordName),
                            CreationCollisionOption.ReplaceExisting);

Por último, utilizaremos el método StartRecordToStorageFileAsync para comenzar la grabación:

// Con el formato, calidad y archivo destino definidos, comenzamos a grabar.
IAsyncAction startAction = _mediaCapture.StartRecordToStorageFileAsync(mediaEncodingProfile, video);
startAction.Completed += (info, status) =>
{
     if (status == AsyncStatus.Completed)
          _recordingStatus = RecordingStatus.Recording;
     if (status == AsyncStatus.Error)
     {
          _recordingStatus = RecordingStatus.Failed;
          Debug.WriteLine(info.ErrorCode.Message);
     }
};

Detener la grabación del video

Para detener la grabación del video utilizamos el método StopRecordAsync.

public void Stop()
{
     //Detenemos la grabación.           
     IAsyncAction stopAction = _mediaCapture.StopRecordAsync();
     stopAction.Completed += (info, status) =>
     {
          if (status == AsyncStatus.Completed)
               if (_recordingStatus == RecordingStatus.Recording)
                    _recordingStatus = RecordingStatus.Sucessfull;
     };
}

Gestion de la suspensión

Ya hemos mencionado el evento SourceSuspensionChanged. Ante
ciertas circunstancias la grabación se suspende para ser reanudada tras
finalizar la acción que provocó la suspensión. El video y el audio se
pueden bloquear de manera independiente por lo que en ciertas
circunstancias se detendrá la grabación de video, en otras las de audio y
en ocasiones ambas. Entre alguna de las situaciones que provocan a
detención de la grabación tenemos:

  • Una llamada entrante. Detiene la grabación de video y audio.
  • Notificaciones con información personal del usuario.
  • Cuando la aplicación no esta en primer plano.
  • Cuando se reproduce sonido en background se detiene la grabación de audio.

Con el evento SourceSuspensionChanged podemos detectar si se ha detenido la grabacion de video y audio gracias a las propiedades IsVideoSuspended y IsAudioSuspended
respectivamente. Podemos utilizar las propiedades para notificar al
usuario que se ha detenido la grabacion. Sin embargo, para reanudar la
misma no tenemos que hacer nada.

Buenas prácticas

Para no interferir con otras aplicaciones que el usuario pueda
utilizar cuando suspende nuestra aplicación, debemos limpiar los
recursos de captura utilizados en la suspensión.

Para realizar esta tarea creamos una propiedad pública con un objeto de tipo MediaCapture en el archivo App.xaml.cs:

public MediaCapture MediaCapture { get; set; }

Establecemos la propiedad en nuestro servicio:

// Establecemos el MediaCapture de App.xaml.cs para gestionar la suspensión.
var app = Application.Current as App;
if (app != null) app.MediaCapture = _mediaCapture;

En el evento OnSuspending realizaremos la detención de la grabación y la liberación de recursos:

if (MediaCapture != null)
{
     await MediaCapture.StopRecordAsync();
 
     MediaCapture.Dispose();
}

Podéis descargar el ejemplo realizado a continuación:

Más información

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *