Windows Phone

Captura de vídeo en Windows Phone: un método superior

Chris Barker

Descargar el ejemplo de código

El SDK para Windows Phone 7.1 generó un conjunto de oportunidades nuevas de desarrollo, por ejemplo la posibilidad de acceder a la cámara de los dispositivos con Windows Phone 7.5. El lanzamiento de Windows Phone 8 trajo capacidades nuevas, como la posibilidad de capturar vídeos en alta definición 1080p en el hardware compatible. Aunque el SDK para Windows Phone 7.1 SDK se podría haber extendido para admitir las nuevas funciones del dispositivo, el lanzamiento de Windows Phone 8 también coincidió con un cambio más estructural en el sistema operativo: comparte el kernel con Windows 8. Se realizó una modificación significativa para cambiar varias API al modelo de Windows 8 y usar una superficie de desarrollo llamada Windows en tiempo de ejecución (WinRT).

El SDK para Windows Phone 7.1 es la versión que permite que los desarrolladores empleen las características de Windows Phone 7.5. De aquí en adelante, me referiré a Windows Phone 7.5 para hablar tanto de la versión del sistema operativo como al SDK. (Por si acaso, el nombre en clave de Windows Phone 7.5 fue “Mango” y el nombre en clave de Windows Phone 8 fue “Apollo”; estos nombres pueden aparecer en otros documentos.)

Windows en tiempo de ejecución no solo introdujo varias API coherentes entre Windows Phone y Windows 8, sino que también entregó un tiempo de ejecución nativo mucho más eficiente que permitió que las API propias de Windows Phone se desarrollaran con el mismo modelo. No profundizaré en los cambios nuevos que trajo Windows en tiempo de ejecución, pero debe tener conciencia de que repercutió en diferentes API, por ejemplo las de captura de vídeo.

Entregaré un resumen de lo que cambió entre las versiones, pero el mensaje clave es que aprenderá a conservar su proyecto para Windows Phone 7.5 y entregar al mismo tiempo una experiencia más completa a los usuarios de Windows Phone 8. Una ventaja enorme es que las técnicas presentadas no solo se aplican a la captura de vídeo, sino que a cualquiera de las API que se rediseñaron para Windows Phone 8. Usaré un ejemplo de código público existente para explicar las técnicas que podemos usar, para ilustrar la reutilización de código en los proyectos Windows Phone 7.5 y 8, e incluso los proyectos de Windows 8.

Para comenzar

Uno de los principales objetivos que quiero ilustrar es cómo portar una solución a Windows Phone 8 sin descuidar los dispositivos Windows Phone 7.5. Pero antes, conviene dar un paso atrás y mencionar que en muchos casos no es necesario hacer absolutamente nada. La aplicación para Windows Phone 7.5 seguirá funcionando en Windows Phone 8, aunque debemos realizar pruebas para asegurarnos de que no se genere ningún comportamiento imprevisto. Al escribir para Windows Phone 7.5, Visual Studio 2012 solo nos permite usar las API compatibles. Y recuerde que solo debe preocuparse por escribir código para Windows Phone 8 si desea aprovechar alguna de las API nuevas y las funciones que estas ofrecen.

Supondré que deseamos seguir satisfaciendo a los usuarios de Windows Phone 7.5 y permitir simultáneamente que los usuarios de Windows Phone 8 tengan acceso a las nuevas funciones sofisticadas de sus dispositivos.

Para poder explicar cómo obtener una solución superior, usaré el Ejemplo de la grabadora de vídeo para Windows Phone (7.5) (bit.ly/16tM2c1) como punto de partida. Recuerde que aunque uso la grabación de vídeo como ejemplo, las técnicas que analizo también se pueden aplicar a cualquier característica de plataforma que queramos, para abarcar diferentes versiones de la plataforma.

Debemos resaltar varios puntos sobre esta solución:

  • No emplea el patrón Model-View-ViewModel (MVVM).
    • La mayor parte de la interfaz de usuario se encuentra en el archivo MainPage.xaml.
    • La mayor parte de la lógica se saca fuera del archivo de código subyacente MainPage.xaml.cs.
  • Usa la API de Windows Phone 7.5 para manipular la cámara; a saber, la clase System.Windows.Media.VideoCaptureDevice. Windows Phone 8 introduce una nueva API en esta área, a la que me referiré más adelante.

El hecho de que esta solución no emplea el patrón MVVM no tiene mucha importancia: es algo que nos gustaría tener (y generalmente se recomienda para el código de producción), ya que nos ahorra trabajo de refactorización más adelante, pero en sí, no es un problema de compatibilidad.

La clase VideoCaptureDevice, sin embargo, nos restringirá cuando pasemos a Windows Phone 8. funcionará perfectamente bien con el tiempo de ejecución de Windows Phone 8, pero no nos entregará el rendimiento máximo y no nos permitirá aprovechar todo el espectro de resoluciones disponibles en el hardware (entre otras cosas).

En las siguientes secciones, emplearé el siguiente método:

  • Crearé una abstracción común para las funciones que se aplican de diferentes maneras en las plataformas. Esto me permitirá compartir el código que consume esta funcionalidad.
  • Colocaré la abstracción de la grabadora de vídeo en una Biblioteca de clases portable (PCL). Esto facilitará el desplazamiento de código adicional a la PCL, cuando haga falta.
  • Compartiré el código subyacente de MainPage mediante la vinculación de archivos (una decisión pragmática, ya que la aplicación existente no emplea el patrón MVVM; de lo contrario, recomendaría trasladar los modelos de vista a una PCL).

Por lo tanto, como quiero ser voraz y emplear un método superior, debo reflexionar sobre lo que puedo hacer para extraer una abstracción de la lógica de la cámara de Windows Phone 7.5 del archivo Main­Page.xaml.cs. Veré cómo solucionar este problema en la siguiente sección.

Desacoplar y abstraer la implementación

Primero, necesitamos un lugar donde factorizar el código. Podría ser una biblioteca de clases, pero podemos usar una PCL. La PCL nos permite especificar las plataformas permitidas y entrega una forma conveniente de restringirnos a las API disponibles en la intersección de estas plataformas; en última instancia, nos permite realizar referencias binarias entre diferentes proyectos (en vez de realizar una simple vinculación y recompilación del código o proyecto para cada plataforma deseada en el momento de la compilación). En este caso, podemos usar un proyecto PCL para Windows Phone 7.5 o posterior y aplicaciones para la Tienda Windows (es decir, “aplicaciones modernas” para Windows 8) y llamarlo VideoRecorder.Shared (ver Figura 1).

Configuring Your Portable Class LibraryFigura 1 Configuración de la biblioteca de clases portable

Si nos orientamos en el patrón de abstracción (bit.ly/YQwsVD), podemos crear una clase abstracta VideoRecorderCore, que nos permitirá escribir código específico para cada plataforma, cuando sea necesario. La clase abstracta se parecerá a la Figura 2.

Figura 2 Creación de la clase abstracta VideoRecorderCore

namespace VideoRecorder.Shared
{
  public abstract class VideoRecorderCore
  {
    public abstract void InitializeVideoRecorder();
    public abstract void StartVideoRecording();
    public abstract void StopVideoRecording();
    public abstract void StartVideoPreview();
    public abstract void DisposeVideoPlayer();
    public abstract void DisposeVideoRecorder();
    public static VideoRecorderCore Instance { get; set; }
  }
}

Nota: en el ejemplo que aparece en la Figura 2, también podríamos usar una interfaz, pero es bastante probable que en muchos casos necesitemos alguna función base común.

En el proyecto sdkVideoRecorderCS, lo ideal sería dedicar un poco de tiempo a implementar el patrón MVVM, pero esto se puede dejar para más adelante. Por ahora, la refactorización se concentrará en entregar una implementación concreta de la clase abstracta. Afortunadamente, ya tenemos la implementación concreta: solo que actualmente está acoplada en forma demasiado estrecha a MainPage.xaml.cs. Para solucionar esto, podemos crear una clase WP7VideoRecorder en el proyecto sdkVideoRecorderCS y derivarla de VideoRecorderCore en el proyecto PCL. Lo único que queda por hacer luego es sacar la implementación de MainPage.xaml.cs y dejarla en el método reemplazado adecuado. Como ejemplo, el método InitializeVideoRecorder inicialmente se parecerá a la Figura 3.

Figura 3 Método InitializeVideoRecorder

public override void InitializeVideoRecorder()
{
  if (captureSource == null)
  {
    captureSource = new CaptureSource();
    fileSink = new FileSink();
    videoCaptureDevice =
       CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
    captureSource.CaptureFailed +=
    new EventHandler<ExceptionRoutedEventArgs>(OnCaptureFailed);
    if (videoCaptureDevice != null)
    {
      videoRecorderBrush = new VideoBrush();
      videoRecorderBrush.SetSource(captureSource);
      viewfinderRectangle.Fill = videoRecorderBrush;
      captureSource.Start();
      UpdateUI(ButtonState.Initialized, 
        "Tap record to start recording...");
    }
    else
    {
      UpdateUI(ButtonState.CameraNotSupported,
         "A camera is not supported on this device.");
    }
  }
}

No analizaré cada línea del código de la Figura 3 aquí —la documentación se encarga de esto en forma exhaustiva (bit.ly/YVIf0I)— pero, en resumen, el código inicializa la instancia de VideoCaptureDevice y luego configura la vista previa del vídeo en la interfaz de usuario. Introduje varios problemas al copiar y pegar simplemente del código subyacente en la implementación concreta. El código hace referencia a los elementos y métodos de la interfaz de usuario (por ejemplo, viewfinderRectangle y el método UpdateUI). Estos no tienen cabida en la implementación concreta y, si ya hubiéramos introducido un modelo de vista, estos se extraerían más fácilmente. Por lo tanto, debemos realizar un poco de limpieza:

  1. Trasladar el código de la interfaz de usuario de nuevo al método respectivo de la interfaz de usuario (InitializeVideoRecorder en el archivo MainPage.xaml.cs, en este caso).
  2. Crear un método abstracto nuevo para inicializar VideoRecorderBrush, ya que lo necesitaremos tanto en Windows Phone 7.5 como en Windows Phone 8, pero las implementaciones podrían diferir.

Una vez que estamos listos, el método se parecerá a:

public override void InitializeVideoRecorder()
{
  if (_captureSource == null)
  {
    _captureSource = new CaptureSource();
    _fileSink = new FileSink();
    _videoCaptureDevice = 
      CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
    _captureSource.CaptureFailed +=
       new EventHandler<ExceptionRoutedEventArgs>(OnCaptureFailed);
   }
}

Ahora podemos sacar provecho de un código subyacente de Windows Phone (MainPage.xaml.cs) que funciona en diferentes versiones de Windows Phone 7.5 y Windows Phone 8, tal como se aprecia en la Figura 4.

Figura 4 Código subyacente que funciona en Windows Phone 7.5 y Windows Phone 8

public void InitializeVideoRecorder()
{
  _videoRecorder.InitializeVideoRecorder();
  _videoRecorder.CaptureFailed += OnCaptureFailed;
  if (_videoRecorder.VideoCaptureDevice != null)
  {
    videoRecorderBrush = new VideoBrush();
    videoRecorderBrush.SetSource(
      _videoRecorder.VideoSource as CaptureSource);
    viewfinderRectangle.Fill = videoRecorderBrush;
    _videoRecorder.StartVideoSource();
    UpdateUI(ButtonState.Initialized,
      "Tap record to start recording...");
  }
  else
  {
    UpdateUI(ButtonState.CameraNotSupported,
       "A camera is not supported on this device.");
  }
}

Lo único que debemos hacer es inicializar la instancia de VideoRecorder y ya estamos listos para comenzar a consumir una implementación específica para cada plataforma:

_videoRecorder = VideoRecorderCore.Instance = new WP7VideoRecorder();

Aparte de algunos cambios menores, simplemente podemos comenzar a refactorizar los métodos restantes en forma similar.

Uso de las API nuevas de Windows Phone 8

Tal como resumí previamente, portar el código de Windows Phone 7.5 a Windows Phone 8 es bastante trivial: lo único que debemos hacer es abrir el proyecto y seleccionar “Actualizar a Windows Phone 8.0” (ver Figura 5). ¿Pero qué pasa si queremos conservar ambas versiones? Con los pasos que acabamos de realizar, el código ahora está estructurado de tal forma que resulta fácil reutilizar el código. No solo esto, pero como ahora desacoplamos la grabadora de vídeo, podemos aprovechar la API nueva y más poderosa de WinRT para la captura de la cámara de vídeo en Windows Phone 8.

Upgrading Your Project to Target the Windows Phone 8 Runtime
Figura 5 Actualización del proyecto para usar el tiempo de ejecución de Windows Phone 8

El siguiente paso es crear el proyecto de Windows Phone 8 y reutilizar la mayor cantidad de código posible e introducir todas las funciones nuevas que queramos.

A estas alturas, tenemos dos opciones para crear la versión para Windows Phone 8. Podemos crear una copia del proyecto existente y actualizarlo o podemos crear un proyecto nuevo para Windows Phone 8 de cero y agregar los archivos de código y referencias duplicados. La decisión realmente depende del tamaño del proyecto y de la cantidad de código que probablemente reutilizaremos. En este ejemplo, crearemos una copia del proyecto y la actualizaremos: esto significa que escogeremos las partes que queremos reutilizar; así se reducen las posibilidades de olvidar algún archivo esencial. Esto es lo que debemos hacer:

  • Copiar el proyecto sdkVideoRecorderCS y cambiar el nombre de sdkVideoRecorderCS.csproj a sdkVideoRecorderCS.WP8.csproj. Luego, agregar el proyecto nuevo a la solución existente para poder mantener ambas versiones.
  • Actualizar el proyecto nuevo a Windows Phone 8. Vemos que el proyecto actualizado funciona perfectamente en Windows Phone 8 sin ningún cambio: gracias a la compatibilidad con las versiones anteriores de la API. La ventaja de esto es que no se requiere de ningún cambio en el código, pero el inconveniente puede ser un rendimiento inferior; tampoco podemos aprovechar las últimas características más interesantes de la plataforma.
  • Ahora podemos elegir los elementos que deseamos reutilizar. Por ejemplo, MainPage.xaml y MainPage.xaml.cs serán iguales en ambas versiones (al menos en esta solución), así que podemos eliminar la versión existente de estos archivos del proyecto para Windows Phone 8 y agregamos un vínculo de archivo a las versiones del proyecto para Windows Phone, al hacer clic con el botón secundario en el proyecto y seleccionar “Agregar elemento existente” y apuntarlo al archivo MainPage.xaml con “Agregar como vínculo” seleccionado (ver Figura 6).

Adding a File Link
Figura 6 Agregar un vínculo de archivo

Nota: en vez de pasar por los menús según tal como se describió, también puede arrastrar el archivo MainPage.xaml desde un proyecto al proyecto de destino y presionar Ctrl+Mayús. Esto creará un vínculo de archivo y trasladará el código subyacente en forma automática.

Debemos repetir estos pasos para todos los archivos pertinentes, pero para esta demostración este es el único vínculo de archivo que agregaremos y que reutilizaremos por ahora.

Como breve paréntesis, frecuentemente tenemos esos sueños utópicos con respecto a la reutilización del código, pero, en el mundo real, siempre existirán situaciones donde desearíamos emplear el mismo código en diferentes plataformas y solo hay una dos diferencias sutiles. En estos casos, puede resultar poco práctico mantener dos conjuntos de código y sería excesivo extraer abstracciones de los elementos específicos para las diferentes plataformas. En esta situación, a menudo resulta preferible usar directivas para el preprocesador en las líneas destinadas a una plataforma específica. Veremos un ejemplo dentro de poco, pero es importante no dejarse llevar demasiado por esta técnica; de lo contrario, el código se puede convertir rápidamente en un revoltijo imposible.

Un ejemplo bueno de cuándo usar abstracciones especiales para las plataformas es cuando tenemos una porción de funcionalidad aislada que se aplica específicamente a una plataforma. En el ejemplo actual, esto será la lógica misma de la grabadora de vídeo. Actualmente, en el proyecto de Windows Phone 8 tenemos un archivo WP7VideoRecorder.cs que funciona perfectamente bien, pero que no usa la API nueva de Windows Phone 8, así que vamos a cambiar esto. Eliminaremos el archivo WP7VideoRecorder.cs del proyecto para Windows Phone 8 y crearemos uno nuevo, llamado WP8VideoRecorder.cs, que también se derivará de VideoRecorderCore.

Igual que antes, implementaremos todos los métodos. La diferencia clave es que esta vez usaremos el espacio de nombres Windows.Phone.Media.Capture en vez de System.Windows.Media. Windows.Phone.Media.Capture es el espacio de nombres más nuevo de WinRT e introduce una clase llamada AudioVideoCaptureDevice. Esta tiene una finalidad similar a la clase VideoCaptureDevice antigua, pero cuenta con funciones mucho más sofisticadas.

Las API de WinRT difieren en varios aspectos de sus precursores. Uno de los cambios con el que nos toparemos aquí es que muchas de las API son sincrónicas (más adelante trataremos de esto). Otra diferencia es que tratamos con el almacenamiento con el espacio de nombres Windows.Storage en vez de usar el familiar System.IO.IsolatedFileStream (aunque seguimos usando el último para la reproducción de multimedia).

Ahora recorreré algunos de los cambios más importantes, para mostrar unas cuantas diferencias en el contexto de una grabadora de vídeo y cómo nos enfrentamos a este tipo de diferencias en forma más general, mientras conservamos un elemento de reutilización del código.

En la versión para Windows Phone 7.5 del código de la grabadora de vídeo se definieron varias variables privadas:

private CaptureSource _captureSource;
private VideoCaptureDevice _videoCaptureDevice;
private IsolatedStorageFileStream _isoVideoFile;
private FileSink _fileSink;
private string _isoVideoFileName = "CameraMovie.mp4";

Idealmente, queremos que ambas bases de código queden lo más parecidas posible, pero la API nueva no tiene lugar para CaptureSource o FileSink: CaptureSource se reemplazó por la instancia concreta de VideoCaptureDevice, que puede actuar como origen, y recibimos la funcionalidad de FileSink en forma automática, así que necesitamos StorageFile para guardarlo:

private AudioVideoCaptureDevice _videoCaptureDevice;
private IsolatedStorageFileStream _isoVideoFile;
private StorageFile sfVideoFile;
private string _isoVideoFileName = "CameraMovie.mp4";

Los siguientes pasos lógicos requieren repasar la implementación concreta de los métodos. Comenzaré nuevamente con Initialize­VideoRecorder. Ya vimos el aspecto previo de este método y simplemente debemos inicializar la instancia de AudioVideoCaptureDevice de manera similar:

public async override void InitializeVideoRecorder()
{
  CameraSensorLocation location = CameraSensorLocation.Back;
  var captureResolutions =
     AudioVideoCaptureDevice.GetAvailableCaptureResolutions(location);
  _videoCaptureDevice =
     await AudioVideoCaptureDevice.OpenAsync(location, 
       captureResolutions[0]);
  _videoCaptureDevice.RecordingFailed += OnCaptureFailed;
}

Como podemos ver, la sintaxis es diferente, pero el código cumple la misma función. Esta parte del código también nos permite configurar propiedades adicionales de la cámara, como la resolución de captura y qué cámara deseamos usar (cuando hay varias disponibles). El control sobre las resoluciones que conseguimos aquí ya es una ventaja sobre las API de Windows Phone 7.5, ya que esto nos permitirá una resolución de alta definición 1080p de tamaño completo, si la cámara del teléfono lo permite. Al igual que con muchas de las API de WinRT, la otra diferencia grata es que los métodos son asincrónicos. Para facilitar las funciones de administración, podemos emplear las palabras clave async y await de C# 5. No ahondaré en estas características aquí, ya que están descritas en otras partes, pero resultan útiles para aprovechar las ventajas del código asincrónico y reducen la complejidad del mismo.

El siguiente método que veremos es InitializeFileSink. Aunque ya no contamos con un FileSink propiamente tal, debemos inicializar los archivos en los que almacenamos la captura de vídeo. Resulta ser que esto también es donde comenzamos a usar el espacio de nombres Windows.Storage con clases tales como StorageFolder y StorageFile:

public async override void InitializeFileSink()
{
  StorageFolder isoStore = ApplicationData.Current.LocalFolder;
  sfVideoFile = await isoStore.CreateFileAsync(
    _isoVideoFileName, 
    CreationCollisionOption.ReplaceExisting);
}

No hace falta repasar cada método, ya que por lo general, estas son las diferencias más importantes que podemos destacar. Cuando los método son iguales en ambas versiones, podemos trasladarlos a la clase base VideoRecorderCore, para garantizar un comportamiento común en todas las clases derivadas.

Una vez que realizamos los cambios restantes, debemos prestar atención a un error de compilación relacionado con la siguiente línea:

_videoRecorder = VideoRecorderCore.Instance = new WP7VideoRecorder();

La causa es obvia, el código de Windows Phone 8 hace referencia a una clase que ya no existe: WP7VideoRecorder. Evidentemente debemos hacer referencia a la clase nueva WP8VideoRecorder, pero este cambio en el archivo MainPage.xaml.cs echaría a perder el código para Windows Phone 7.5, debido al vínculo al mismo archivo.

Aquí tenemos varias alternativas. Podemos usar directivas de preprocesador:

#if WP7
  _videoRecorder = VideoRecorderCore.Instance = new WP7VideoRecorder();
#else
  _videoRecorder = VideoRecorderCore.Instance = new WP8VideoRecorder();
#endif

Si usamos directivas de preprocesador, un método más limpio sería ubicarlas dentro de una clase contenedora; por ejemplo, introducir un método CreateVideoRecorder en la clase base abstracta VideoRecorderCore y dejar la declaración #if dentro de esta.

En este caso opté por evitar las directivas de preprocesador y extraje una abstracción de la búsqueda de la clase concreta a una clase fábrica independiente; esto significa que el código que consume la grabadora es coherente en Windows Phone 7.5 y Windows Phone 8:

_videoRecorder =
  VideoRecorderCore.Instance = VideoRecorderFactory.CreateVideoRecorder();

Otro método sería usar un patrón de inserción de dependencias (DI) o localizador de servicio y dejar que el contenedor o el servicio se encarguen de la resolución de las instancias, respectivamente.

Un problema más discreto tiene relación con establecer VideoBrush para la vista previa de la cámara. En este caso, tenemos nuevamente la opción de usar la directiva de preprocesador #if para diferenciar los detalles menores de implementación:

#if WP7
  videoRecorderBrush.SetSource(_videoRecorder.VideoSource as CaptureSource);
#else
  videoRecorderBrush.SetSource(_videoRecorder.VideoSource);
#endif

Esto es necesario, ya que Windows Phone 8 sobrecarga el método SetSource para permitirnos especificar un objeto como el objeto Source. Es por esta razón que podemos devolver la instancia de AudioVideoCaptureDevice en la propiedad VideoSource de Windows Phone 8. Para permitir esto, necesitaremos un símbolo de compilación condicional nuevo, “WP7”, al proyecto Windows Phone 7.5, bajo Propiedades del proyecto | Compilación (ver Figura 7).

Adding a Custom Conditional Compilation Symbol to Your Windows Phone 7.5 Project
Figura 7 Adición de un símbolo de compilación condicional al proyecto de Windows Phone 7.5

Tal como se mencionó previamente, Windows en tiempo de ejecución se vale fuertemente de los métodos asincrónicos para mejorar la capacidad de respuesta de las aplicaciones; pero si compartimos código entre diferentes versiones de la plataforma, ¿qué impacto tiene esto? Aunque podemos escribir código C# 5 en un proyecto destinado a Windows Phone 7.5, algunos detalles de la implementación no están disponibles cuando usamos las palabras clave async y await. Tenga en cuenta el siguiente método:

public async override void InitializeVideoRecorder()
{
  CameraSensorLocation location = CameraSensorLocation.Back;
  var captureResolutions =
     AudioVideoCaptureDevice.GetAvailableCaptureResolutions(location);
  _videoCaptureDevice =
     await AudioVideoCaptureDevice.OpenAsync(location, 
       captureResolutions[0]);
  _videoCaptureDevice.RecordingFailed += OnCaptureFailed;
}

Esto establece realmente la propiedad VideoCaptureDevice, pero el código que lo consume es este:

_videoRecorder.InitializeVideoRecorder();
if (_videoRecorder.VideoCaptureDevice != null)
{
  ...
}
else
{
  UpdateUI(ButtonState.CameraNotSupported,
     "A camera is not supported on this device.");
}

Probablemente no resulte evidente a primera vista que cuando esperamos la llamada al método AudioVideoCaptureDevice.OpenAsync, es posible que la comprobación en _videoRecorder.VideoCaptureDevice ya se haya producido. En otras palabras, la comprobación de VideoCaptureDevice se puede evaluar en null, ya que no ha tenido la oportunidad de inicializarse aún.

Nuevamente, tenemos varias posibilidades aquí. Si convertimos el código consumidor en asincrónico, debemos adaptar el código un poco; dependiendo de la solución, esto podría significar bastante trabajo. Para evitar esto, podemos encapsular simplemente el código asincrónico dentro de un método contenedor sincrónico. Otra alternativa sería tener un mecanismo basado en eventos para notificar al consumidor cuando la inicialización está lista.

Quizás queramos aprovechar esta oportunidad para pasar al modelo async nuevo y esperar la llamada InitializeVideoRecorder, ¿pero cómo hacer esto si Windows Phone 7.5 SDK no es compatible con las construcciones necesarias? Una solución sería descargar el paquete NuGet “Async for .NET Framework 4, Silverlight 4 and 5, and Windows Phone 7.5” dentro de Visual Studio, tal como se ilustra en la Figura 8.

Find the Async NuGet Package for Windows Phone 7.5 by Searching for “microsoft.bcl.async”
Figura 8 Encontrará el paquete NuGet para Windows Phone 7.5 al buscar “microsoft.bcl.async”

En el momento de la redacción, este paquete se encuentra en una versión preliminar, pero se prevé un lanzamiento pronto de la versión estable. Si prefiere un método más probado, deberá reelaborar el código, tal como se describió previamente, e introducir un patrón de evento o contenedor sincrónico.

Nota: asegúrese de que tenga instalada al menos la versión 2.1 del administrador de paquetes NuGet; de lo contrario, recibirá mensajes de error inesperados al usar este paquete. Puede descargar la última versión en bit.ly/dUeqlu.

Una vez que descargó el paquete, puede alinear el código de cada proyecto. Por ejemplo, podemos incorporar la funcionalidad async en la clase base abstracta, al devolver un tipo Task en lugar de void; esto nos permitirá esperar el resultado en vez de usar el mecanismo actual de “enviar y olvidar”. Otra convención útil es agregar “Async” a los nombres de los métodos asincrónicos, para que los desarrolladores puedan consumir estos métodos en forma más intuitiva. Por ejemplo, algunos de los métodos ahora tendrán una firma parecida a:

public abstract Task InitializeVideoRecorderAsync();
public abstract Task InitializeFileSinkAsync();

Desde luego, ahora deberemos refactorizar el código de las subclases. Por ejemplo:

public override async Task InitializeVideoRecorderAsync()
{

Y, por último, podremos actualizar el código de MainPage.xaml.cs para incorporar la capacidad de esperar operaciones asincrónicas. Este es un ejemplo:

public async void InitializeVideoRecorder()
{
  await _videoRecorder.InitializeVideoRecorderAsync();

Con estos cambios finales, ahora deberíamos poder ejecutar sin problemas los proyectos para Windows Phone 7.5 y para Windows Phone 8. Podemos reutilizar el código en forma significativa y, cuando están disponibles, empleamos las características más recientes. ¡Tenemos una solución superior!

¿Qué pasa con Windows 8?

Muchos de los socios con los que trabajo han desarrollado aplicaciones para Windows Phone con XAML y C#. Al pensar en desarrollar una aplicación para la Tienda Windows para Windows 8, una de las primeras preguntas técnicas es si deben decantarse por HTML5/JavaScript o por XAML/C#. Una solución bien diseñada para Windows Phone permite reutilizar gran cantidad de código con Windows 8, así que esto puede ser el factor decisivo en la decisión del lenguaje que usamos para desarrollar una aplicación para la Tienda Windows.

Aunque podremos reutilizar código entre Windows Phone 7.5 y Windows 8, existen varias situaciones donde deberemos separar estas dos soluciones. La grabadora de vídeo es un ejemplo, pero lo bueno es que los métodos que acabamos de ver nos ahorran mucho tiempo y trabajo.

La implementación de este código en una versión para Windows 8 queda como ejercicio para el lector o, si lo prefiere, puede descargar el ejemplo que acompaña a este artículo y que incluye todo el código que vimos, junto con una solución para Windows 8.

Nota: en el caso de una implementación para Windows 8, debemos usar la clase Windows.Media.Capture.MediaCapture.

Expansión de las fronteras con MVVM

Lo que acabamos de implementar nos permite reutilizar MainPage.xaml al cien por ciento en ambas versiones de Windows Phone. Dado los pasos de abstracción y de refactorización que recorrimos, también logramos una reutilización cercana al cien por ciento del archivo de código subyacente correspondiente. Podríamos extender estas técnicas aún más con el patrón MVVM, lo que serviría para lograr la reutilización no solo en las versiones de Windows Phone, sino que también en las bases de código para Windows 8.

Estas técnicas sirven reutilizar código, al abstraer las características de las plataformas puntuales (como la captura de vídeo que describí), pero también la administración del ciclo de vida de las aplicaciones (ALM), el almacenamiento de archivos y mucho más.

Chris Barker es un evangelizador técnico para Microsoft que reside en Derbyshire, Reino Unido. Ha dedicado muchos años al desarrollo de código para proveedores de software independientes y empresas, y a ejecutar laboratorios de rendimiento y escalabilidad y siente un interés profundo por las técnicas de depuración de bajo nivel. En los últimos años se ha concentrado más en el desarrollo del lado cliente mediante XAML y C#, y ahora trabaja con diferentes socios de consumo mundial en el desarrollo para las plataformas Windows 8 y Windows Phone.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Daniel Plaisted (Microsoft)
Desde que se unió a Microsoft en 2008, Daniel Plaisted ha trabajado en Managed Extensibility Framework (MEF), Bibliotecas de clases portables (PCL) y Microsoft .NET Framework para aplicaciones para la Tienda Windows. Ha presentado en MS TechEd, BUILD y varios grupos locales, campamentos de código y congresos. En su tiempo libre le gusta jugar juegos de consola, leer, y se dedica al senderismo, malabarismo y hacky attack. Tiene un blog en blogs.msdn.com/b/dsplaisted/.