Este artículo proviene de un motor de traducción automática.

Tocar y listo

Audio en segundo plano con Windows Phone 7.5

Charles Petzold

Descargar el código de ejemplo

Charles PetzoldEn los viejos tiempos de MS-DOS, los programadores podrían implementar una forma cruda de conmutación de tarea con una técnica llamada terminar y permanecer residente (también conocido como TSR). Programas TSR instalan ganchos en interrupciones del teclado u otros mecanismos de OS y luego terminación, dejando el programa en memoria listo para dar inicio a la acción cuando el usuario pulsado una combinación de teclas determinada o algo de interés ocurrió.

MS-DOS no estaba equipado para manejar incluso este nivel de rudimentaria conmutación de tarea, por lo que estos TSR creó algunos problemas graves. Que entran en conflicto entre sí y con frecuencia accidente OS todo. Esta fue una de las principales razones que algunos de nosotros celebra Windows para complementar y reemplazar posteriormente MS-DOS.

Menciono esta historia porque voy a mostrarle cómo reproducir archivos de música en el fondo de una aplicación Windows Phone 7.5, y sé que los desarrolladores tienen una tendencia a pensar fuera de la caja. Generalmente esto es bueno, pero no debería utilizar esta técnica para cualquier propósito distinto de reproducir archivos de música. Hacerlo podría causar su aplicación al ser rechazado por el mercado de Windows Phone.

La técnica a mostrarle es sólo para reproducir archivos de sonido o música desde una ubicación Web o almacenamiento aislado. Si la aplicación necesita reproducir canciones desde la biblioteca de música normal del teléfono, puede hacerlo mediante las clases MediaLibrary y MediaPlayer traté en el número anterior (msdn.microsoft.com/magazine/hh708760).

Aplicación además de DLL

Windows Phone 7.5, un objeto que se ejecuta en segundo plano para ayudar a una aplicación se conoce como un agente. Para reproducir archivos de música en segundo plano, se utiliza una clase que deriva de AudioPlayerAgent. Esta clase debe ser en una biblioteca de vínculos dinámicos que se hace referencia en el programa. Código de programa no se sí ejecuta en segundo plano; lo que se ejecuta en segundo plano es esta clase que se deriva de AudioPlayerAgent.

Incluido con el código de descarga para este artículo es una solución de Visual Studio denominada SimpleBackgroundAudio. Para fines de claridad, este programa contiene sólo la mínima cantidad de código necesario para obtener antecedentes audio para trabajar. He creado la solución del cuadro de diálogo nuevo proyecto especificando Windows Phone aplicación y, a continuación, Windows Phone 7.1. (La designación 7.1 utiliza internamente dentro de las aplicaciones de Windows Phone y Windows Phone OS, pero significa lo mismo que la designación de 7,5 más común).

A la clase de la página principal del proyecto SimpleBackgroundAudio he añadido un botón y un controlador haga clic en este botón:

void OnPlayButtonClick(object sender, RoutedEventArgs args)
{
  AudioTrack audioTrack = 
    new AudioTrack(new Uri("http://www.archive.org/.../Iv.Presto.mp3"), 
                   "Symphony No.
9: 4th Movement", 
                   "Felix Weingartner", 
                   "Beethoven: Symphony No.
9", 
                   null, 
                   null, 
                   EnabledPlayerControls.Pause);
  BackgroundAudioPlayer.Instance.Track = audioTrack;
}

Las clases AudioTrack y BackgroundAudioPlayer se encuentran en el espacio de nombres Microsoft.Phone.BackgroundAudio. La URL (que he abreviado aquí para acomodar el ancho de la columna de la revista) hace referencia a un archivo MP3 en Internet Archive (archive.org) que contiene el último movimiento de Sinfonía nº Beethoven 9 como realizada por Felix Weingartner en una grabación de 1935. (Puede también especificar una dirección URL que se ocupa de un archivo de almacenamiento aislado; Utilice el UriKind.Relative argumento para el constructor de Uri si ese es el caso.) Los siguientes tres argumentos del constructor AudioTrack proporcionan información de álbum, artista y canción.

La clase BackgroundAudioPlayer es similar a las clases MediaElement o MediaPlayer que reproducción archivos de audio en primer plano. BackgroundAudioPlayer no tiene constructor; en su lugar se puede obtener la única instancia con la propiedad de instancia estática. En este código, se establece la propiedad Track en el objeto AudioTrack recién creado.

Puede compilar y ejecutar este programa, pero no hará nada. Para hacer BackgroundAudioPlayer cantar, necesita un archivo DLL que contiene una clase que deriva de AudioPlayerAgent. Visual Studio puede crear este proyecto de clase y biblioteca para usted. Para mi programa, right-clicked el nombre de la solución SimpleBackgroundAudio en Visual Studio, selecciona Agregar nuevo proyecto en el menú y después eligió a agente de reproducción de Audio de Windows Phone de la lista de plantilla. He nombrado a este nuevo proyecto SimpleAudioPlaybackAgent.

Visual Studio crea un proyecto de biblioteca con una clase denominada AudioPlayer que se deriva de AudioPlayerAgent, inicializado con varios métodos de esqueleto. Esta es la clase que se ejecuta en segundo plano.

Muy importante: Crear una referencia de la aplicación a esta DLL! Para hacer esto, right-clicked la sección de referencias en el proyecto SimpleBackgroundAudio, referencia seleccionada de agregar, y luego en el cuadro de diálogo selecciona la ficha proyectos y luego el proyecto SimpleAudioPlaybackAgent.

En el derivado de AudioPlayerAgent, querrá modificar al menos dos métodos: OnPlayStateChanged y OnUserAction. La clase AudioPlayer completa de este proyecto (menos mediante directivas y comentarios) se muestra en la figura 1.

Figura 1 la clase AudioPlayer en SimpleBackgroundAudio

namespace SimpleAudioPlaybackAgent
{
  public class AudioPlayer : AudioPlayerAgent
  {
    protected override void OnPlayStateChanged(BackgroundAudioPlayer player,
                                      AudioTrack track, PlayState playState)
    {
      switch (playState)
      {
        case PlayState.TrackReady:
          player.Play();
          break;

        case PlayState.TrackEnded:
          player.Track = null;
          break;
      }
      NotifyComplete();
    }
    protected override void OnUserAction(BackgroundAudioPlayer player, 
                                      AudioTrack track, UserAction action, 
                                      object param)
    {
      switch (action)
      {
        case UserAction.Pause:
          player.Pause();
          break;

        case UserAction.Play:
          player.Play();
          break;
      }
      NotifyComplete();
    }

    protected override void OnError(BackgroundAudioPlayer player, 
                                      AudioTrack track, Exception error, 
                                      bool isFatal)
    {
        base.OnError(player, track, error, isFatal);
        NotifyComplete();
    }
    protected override void OnCancel()
    {
        base.OnCancel();
        NotifyComplete();
    }
  }
}

Mira primero la anulación de OnPlayStateChanged. Este método se llama cuando cambia la propiedad PlayState de la BackgroundAudioPlayer. El primer argumento es el mismo BackgroundAudioPlayer que se hace referencia en el código del programa; el último argumento es un miembro de la enumeración PlayState.

Cuando el programa establece la propiedad de la pista de la BackgroundAudioPlayer en el controlador del evento Click, el BackgroundAudioPlayer accede a los archivos de música en Internet; la DLL de SimpleAudioPlaybackAgent está cargada y el método OnPlayStateChanged obtiene finalmente una llamada con el argumento de playState establecido en PlayState.TrackReady. Es la responsabilidad de OnPlayStateChanged para llamar al método Play del objeto BackgroundAudioPlayer para empezar a jugar esa pista.

Si el reproductor regular del teléfono pasa a estar jugando algo en el momento, se detendrá. En este punto, puede desplazarse fuera del programa o incluso lo terminar presionando el botón de atrás del teléfono, y la música seguirá jugando.

Puede ejecutar este programa en el emulador de Windows Phone, pero desea probarlo en un teléfono real puede presionar control de volumen del teléfono. Esto invoca un pequeño desplegable conocido como el Control de volumen Universal (UVC), que es una parte importante del audio de fondo. La UVC muestra el nombre de la pista se jugó y el artista, basándose en los argumentos que la aplicación se suministra al constructor AudioTrack. La UVC también muestra los botones para pista anterior y pausa, pista siguiente. En este programa, sólo el botón pausa está habilitado porque eso es lo que he especificado en el argumento pasado al constructor AudioTrack. Al pulsar el botón de pausa, cambia entre pausa y Play.

La clase AudioPlayer controla estos comandos pausar y reproducir en el reemplazo de OnUserAction en figura 1. El argumento UserAction indica el particular botón pulsado por el usuario. OnUserAction responde llamando al método correspondiente en el objeto BackgroundAudioPlayer.

Cuando la pista termina de reproducirse, OnPlayStateChanged obtiene una llamada con PlayState.TrackEnded. El método responde estableciendo la propiedad de la pista de la BackgroundAudioPlayer en null, que elimina el elemento de la UVC. Si lo desea, puede volver a la aplicación e iniciar la música de nuevo.

Observe que OnPlayStateChanged y OnUserAction terminar con las llamadas a NotifyComplete: Esto es necesario. También observe que ni método incluye una llamada al método de clase base. Estas llamadas de clase base forman parte de la plantilla de Visual Studio crea automáticamente, pero tuve un problema con el reemplazo de OnUserAction cuando se llama al método base. El código de muestra de audio de fondo de Microsoft (disponible en línea desde el área de documentación de Windows Phone 7.5) también no incluyen llamadas a los métodos base.

Una DLL muy extraña

Al experimentar con audio de fondo, desea mantener una vigilancia sobre la ventana de salida en Visual Studio. Cuando ejecute SimpleBackgroundAudio desde el depurador, la ventana de salida muestra todas las bibliotecas del sistema que se cargan en el teléfono para ejecutar el programa, así como el programa en sí, que es SimpleBackgroundAudio.dll. Las líneas en la ventana de resultados que muestran estas bibliotecas comienzan con las palabras "Tarea de IU," que indica el programa.

Cuando toque el botón en SimpleBackgroundAudio, verá muchos de los mismos archivos DLL que se carga, pero ahora con cada línea precedida de las palabras "Tarea en segundo plano". Estas DLL incluyen la DLL de aplicación, así como SimpleAudioPlaybackAgent.dll. La carga de estos archivos DLL es uno de los motivos que lleva un poco de tiempo para la música empezar a jugar después de puntear el botón.

Como experimentar con programas que reproducir audio de fondo, podrá probablemente desee insertar declaraciones Debug.WriteLine en todos los reemplazos de método en la clase AudioPlayer y, a continuación, estudiar su comportamiento en tiempo real en la ventana de salida.

También puede crear un constructor para la clase AudioPlayer con otra instrucción Debug.WriteLine. Descubrirá que una nueva instancia de AudioPlayer se crea una instancia siempre debe ser una llamada a OnPlayStateChanged o OnUserAction. Cada llamada obtiene una nueva instancia!

Este simple hecho tiene una implicación profunda: Si necesita esta clase de AudioPlayer para mantener la información entre las llamadas a OnPlayStateChanged y OnUserAction, debe mantener esa infor­ción en campos estáticos o propiedades.

¿Qué sucede si necesita pasar información de una clase en el programa de SimpleBackgroundAudio a la clase AudioPlayer en la biblioteca de SimpleAudioPlaybackAgent? Parece razonable definir un método estático público en la clase AudioPlayer y luego llamar a ese método de página principal o de otra clase en el programa. De hecho puede hacerlo, pero no lo que desea: Nada se salva de este método no estará disponible durante las llamadas OnPlayStateChanged y OnUserAction.

¿Por qué no estarán disponible? Recordar las designaciones tareas de IU y de fondo en la ventana resultados de Visual Studio. Estos son dos procesos separados. La instancia del archivo DLL al que hace referencia el programa no es lo mismo que la instancia que se ejecuta en segundo plano, y por lo tanto datos no incluso estáticos en una clase en este archivo DLL se compartirá entre la tarea de IU y la tarea en segundo plano.

Al probar una aplicación de audio de fondo desde el depurador de Visual Studio, usted experimentará algunas rarezas adicionales. Al salir de un programa que ha iniciado algunos audio de fondo, el audio sigue jugando y Visual Studio indica que aún se ejecuta el código. Para reanudar su programa de edición, desea detener depuración directamente desde Visual Studio, y aun entonces la música se suele mantener en jugando. Durante el desarrollo de un programa de audio de fondo, probablemente te encontrarás frecuentemente desinstalar el programa desde el teléfono.

Mejora de la aplicación

El programa de SimpleBackgroundAudio tiene un gran problema. Aunque tiene un botón para iniciar la reproducción de música, no tiene ningún camino para pausarlo o lo apaga. Aún no sabe cuando concluye la música o si cualquier otra cosa está sucediendo. Sí, un usuario siempre puede invocar la UVC para controlar el audio de fondo, pero cualquier programa que inicia la reproducción de música también debe incluir sus propios controles que lo apaga.

Estas mejoras se incluyen en el proyecto denominado Playlist­Player. Como su nombre lo indica, este programa desempeña una serie de pistas consecutivas: en este caso, las 12 piezas para piano poco de preludios de Claude Debussy, libro 1, interpretada por Alfred Cortot en un 1949 grabación disponible en Internet Archive.

Originalmente quería crear todos los objetos AudioTrack dentro del programa y, a continuación, les mano fuera para el audio de fondo DLL. Que parecía la estructura del programa más generalizada, pero descubrí que no funcionó porque la aplicación obtiene acceso a una instancia diferente de la DLL que la que se ejecuta en segundo plano. En cambio, escribí la clase AudioPlayer para crear todos los objetos AudioTrack propio y almacenarlos en una lista genérica denominada lista de reproducción.

Para hacer el programa algo más desafiante, decidí que no sería circular la lista de reproducción: No quería saltar desde la última pista a la primera pista, o desde el primero hasta el último. Por esa razón, el primero de los constructores AudioTrack tiene un último argumento que combina las banderas EnabledPlayerControls.Pause y EnabledPlayerControls.SkipNext; el último AudioTrack combina las banderas de pausa y SkipPrevious. Todos los demás tienen todos tres banderas. Se trata de cómo los tres botones están habilitados en la UVC para varias pistas.

Figura 2 muestra la reemplaza OnPlayStateChanged y OnUserAction. En OnPlayStateChanged, cuando se termina una pista, la siguiente pista se establece en la propiedad de la pista de la BackgroundAudioPlayer. El OnUserAction reemplazar asas la pista anterior y pista siguiente comandos de la UVC estableciendo la propiedad Track en el AudioTrack anterior o siguiente en la lista de reproducción.

Figura 2 las anulaciones de AudioPlayer en PlaylistPlayer

static List<AudioTrack> playlist = new List<AudioTrack>();
static int currentTrack = 0;

...
protected override void OnPlayStateChanged(BackgroundAudioPlayer player, 
  AudioTrack track, PlayState playState)
{
  switch (playState)
  {
    case PlayState.TrackReady:
      player.Play();
      break;

    case PlayState.TrackEnded:
      if (currentTrack < playlist.Count - 1)
      {
        currentTrack += 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;
  }
  NotifyComplete();
}

protected override void OnUserAction(BackgroundAudioPlayer player, 
  AudioTrack track, UserAction action, object param)
{
  switch (action)
  {
    case UserAction.Play:
      if (player.Track == null)
      {
        currentTrack = 0;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Play();
      }
      break;

    case UserAction.Pause:
      player.Pause();
      break;

    case UserAction.SkipNext:
      if (currentTrack < playlist.Count - 1)
      {
        currentTrack += 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;

    case UserAction.SkipPrevious:
      if (currentTrack > 0)
      {
        currentTrack -= 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;

    case UserAction.Seek:
      player.Position = (TimeSpan)param;
      break;
  }
  NotifyComplete();
}

Clase de página principal del programa tiene una serie de cuatro botones de barra de aplicación estándar para realizar las mismas funciones que la UVC. También establece un controlador de eventos para el evento de PlayStateChanged de BackgroundAudioPlayer para actualizar la pantalla con la información actual de la pista y un controlador de CompositionTarget.Rendering para actualizar un control deslizante con progreso actual de la pista, como se muestra en figura 3.

The PlaylistPlayer Program
Figura 3 el programa de PlaylistPlayer

La lógica para activar y desactivar los botones de la barra de aplicación es bastante simple: se activan los botones pista anterior y pista siguiente basándose en la propiedad PlayerControls de la AudioTrack actual; por lo tanto debe ser consistentes con la UVC. El botón de pausa se activa si el jugador está jugando; el botón Reproducir se activa si el jugador está en pausa. Si la pista actual es null, Play está habilitado y se desactivan todos los botones.

Los controladores de clic de los botones de la barra de cuatro aplicación hacen llamadas a los métodos BackgroundAudioPlayer, SkipPrevious, Play, pausa y SkipNext, respectivamente. Es importante comprender que estas llamadas de método no controlan directamente la operación de reproducción de música. En su lugar, estas llamadas al método desde el programa activan llamadas OnUserAction en la clase AudioPlayer, y es el código AudioPlayer que realmente inicia y detiene la música.

Esto es algo peculiar, porque significa que llamadas a los métodos de reproducción y pausa de BackgroundAudioPlayer comportan de forma diferente dependiendo de si eres llamaron de un programa o de la anulación de OnUserAction.

También he añadido un controlador ValueChanged para el regulador mover a una ubicación concreta en la pista. El controlador calcula una nueva posición de la pista y establece un objeto TimeSpan apropiado a la propiedad posición de la BackgroundAudioPlayer. Similar al caso con llamadas de reproducción y pausa, al establecer esta propiedad no cambia la posición de la pista. Por el contrario, genera una llamada a la anulación de OnUserAction en AudioPlayer con un argumento de acción de UserAction.Seek y el TimeSpan codificado en el argumento de param. El OnUserAction reemplazar a continuación, este objeto TimeSpan se establece en la propiedad posición de la BackgroundAudioPlayer, y eso es lo que realmente hace el salto.

En la práctica, este regulador funciona bien cuando toque sólo para mover hacia adelante o hacia atrás en la pista de un 10 por ciento. Si intenta deslícelo varias llamadas de búsqueda parecen acumularse, y el resultado es un lío audible. Que prefieren usar un ScrollBar regular en lugar de un control deslizante, porque entonces yo podía esperar para un evento EndScroll, que se produce cuando el usuario deja de manipular el control. Lamentablemente, nunca he podido convencer a la barra de desplazamiento de Windows Phone para trabajar en todos.

Las limitaciones

Ha sido interesante ver cómo Windows Phone 7.5 ha dado a programadores acceso más directo al hardware del teléfono (como el vídeo de alimentación), así como la capacidad para realizar algún procesamiento en segundo plano. Pero no puedo dejar de pensar que hay una pieza que falta en esta aplicación de audio de fondo.

Suponga que un programa desea que el usuario pueda elegir de una lista de archivos de música que desempeñar en la secuencia. El programa no puede entregar la lista completa a la DLL, por lo que debe asumir la responsabilidad para establecer la propiedad de pista de los BackgroundAudioPlayer cuando termina cada pista. Pero esto puede ocurrir sólo cuando se ejecuta el programa en primer plano.

Es posible pasar información entre la aplicación y el fondo DLL a través de la cadena propiedad de etiqueta de la clase AudioTrack. Pero no te molestes en derivar una clase nueva de AudioTrack con la esperanza de pasar información adicional a la DLL: Se realiza una copia del objeto AudioTrack creado por la aplicación para pasar a la DLL reemplaza.

Afortunadamente, el archivo DLL tiene acceso al área de la aplicación del almacenamiento aislado, por lo que el programa puede guardar un archivo pequeño que describe la lista de reproducción y, a continuación, el archivo DLL puede acceder a la lista de reproducción desde el archivo. El programa de bonificación para esta columna es un proyecto denominado PlaylistFilePlayer, que muestra un enfoque simple para esta técnica.

Charles Petzold es un viejo editor colaborador de MSDN Magazine. Su reciente libro, "Programación Windows Phone 7" (Microsoft Press, 2010), está disponible como descarga gratuita en bit.ly/cpebookpdf.

Gracias al siguiente experto técnico para revisar este artículo: Mark Hopkins