Alexa skills de audio con la interfaz AudioPlayer: eventos de playback (III)

En esta serie de artículos estoy contando todo lo que necesitas saber para usar la interfaz AudioPlayer en tus skills. Es una funcionalidad de Alexa que tiene bastante por detrás y le dedicaré varios posts.

En esta tercera entrega voy a hablar de los eventos que se producen relacionados con la reproducción de audio en un dispositivo Alexa, también denominados eventos de playback.

Eventos de Playback

Estos eventos son simplemente peticiones que nos llegarán al back de la skill y nos informan de cambios de estado en la reproducción del audio. Son emitidos por el reproductor nativo de Alexa y llegarán a la skill sin necesidad de tener una sesión abierta.

El back de nuestra skill tiene la responsabilidad de manejar correctamente cada uno de los eventos de manera que no influya de forma negativa en la reproducción en curso. El uso de cada evento dependerá de la skill que tengamos entre manos y a veces bastará con generar una respuesta vacía. Además, no podemos responder a estos eventos con una respuesta estándar que contenga texto para Alexa. Solo podemos responder con las directivas de AudioPlayer que vimos en el artículo anterior.

Como decía antes, estos eventos no van a llevar información de sesión pero sí que tendrán información de contexto con lo que podremos sacar de ahí, por ejemplo, la info del userId. Esta información la usaremos para la capa de persistencia.

Los tipos de eventos son:

  • AudioPlayer.PlaybackStarted > Se envía cuando el audio indicado por la directiva de Play ha comenzado a reproducirse de forma exitosa.
  • AudioPlayer.PlaybackFinished > Vamos a recibir este evento cuando la reproducción finalice.
  • AudioPlayer.PlaybackStopped > Alexa lo envía para responder a la directiva de Stop al parar el audio que se estaba reproduciendo.
  • AudioPlayer.PlaybackNearlyFinished > Se envía para indicar que la reproducción en curso está próxima a terminar.
  • AudioPlayer.PlaybackFailed > Indica que no se pudo reproducir el audio indicado debido a algún problema.

Los cuatro primeros eventos tienen la misma información en la petición:

{
  "type": "AudioPlayer.[type]",
  "requestId": "unique.id.for.the.request",
  "timestamp": "timestamp of request in format: 2018-04-11T15:15:25Z",
  "token": "token representing the currently playing stream",
  "offsetInMilliseconds": 0,
  "locale": "a locale code such as en-US"
}

En el caso de uso de nuestra skill, por el modelo de datos persistente, nos vendrá bien la información del campo offsetInMilliseconds que indica en qué punto de reproducción está el audio en cuestión. Para una explicación del resto de campos os enlazaré la documentación oficial según los detalle.

Para el evento de AudioPlayer.PlaybackFailed, además de lo anterior, vamos a tener información del error y del estado del playback en el momento que ocurre el problema. Lo veremos en su propio apartado.

AudioPlayer.PlaybackStarted

Al recibir este evento tendremos la confirmación de que el audio que se indicó en la directiva Play ha comenzado su reproducción sin problemas. Alexa lo envía tanto si se ha iniciado un audio desde su inicio, como si hemos continuado un audio que estaba pausado.

A la hora de responder a este tipo de eventos tenemos varias opciones:

  • Usar las directivas de AudioPlayer Stop o ClearQueue.
  • Cualquier otro tipo de respuesta que NO contenga un texto para Alexa u otras directivas (como las de Dialog Management).

Una respuesta básica válida podría ser (en Kotlin):

Veremos el caso concreto de nuestra skill de ejemplo en otro post.

AudioPlayer.PlaybackFinished

Alexa envía esta petición cuando el audio finaliza por haber llegado al final de la reproducción del mismo.

A la hora de responder a este tipo de eventos tenemos varias opciones:

  • Usar las directivas de AudioPlayer Stop o ClearQueue.
  • Cualquier otro tipo de respuesta que NO contenga un texto para Alexa u otras directivas (como las de Dialog Management).

Al igual que para el evento de PlaybackStarted podemos construir una respuesta básica vacía. La diferencia con el código anterior sería el tipo de request a comparar en el canHandle. Para este caso sería comparar con PlaybackFinishedRequest::class.java.

AudioPlayer.PlaybackStopped

El back recibirá esta petición como respuesta a:

  • Diretiva deStop .
  • Directiva de Play indicando un comportamiento de REPLACE_ALL .
  • Directiva de ClearQueue con un comportamiento de CLEAR_ALL .
  • Durante una reproducción de audio en curso el usuario hace una petición a Alexa, con lo que el audio se pausa de forma momentánea.

Al igual que para el evento de PlaybackStarted podemos construir una respuesta básica vacía. La diferencia con el código anterior sería el tipo de request a comparar en el canHandle. Para este caso sería comparar con PlaybackStoppedRequest::class.java.

AudioPlayer.PlaybackNearlyFinished

Se envía cuando la reproducción está llegando a su fin, siendo un evento especialmente útil cuando se trabaja con una lista de reproducción.

Posibles respuestas:

  • Cualquier directiva de AudioPlayer.
  • Cualquier otro tipo de respuesta que NO contenga un texto para Alexa u otras directivas (como las de Dialog Management).

Al recibirlo podríamos responder con una directiva de Play y de esa forma preparar siguientes audios sin interferir en la reproducción actual. Alexa automáticamente se encarga de ir transicionando entre los elementos de la playlist.

AudioPlayer.PlaybackFailed

Vamos a recibir este evento en el back cuando se produzca algún tipo de problema con la reproducción del audio en el dispositivo.

El formato de la petición es distinto a los anteriores:

{
  "type": "AudioPlayer.PlaybackFailed",
  "requestId": "unique.id.for.the.request",
  "timestamp": "timestamp of request in format: 2018-04-11T15:15:25Z",
  "token": "token representing the currently playing stream",
  "offsetInMilliseconds": 0,
  "locale": "a locale code such as en-US",
  "error": {
    "type": "error code",
    "message": "description of the error that occurred"
  },
  "currentPlaybackState": {
    "token": "token representing stream playing when error occurred",
    "offsetInMilliseconds": 0,
    "playerActivity": "player state when error occurred, such as PLAYING"
  }
}

Podemos ver que en este caso vamos a tener información del error e información del estado del playback en el momento del error (currentPlaybackState). En el caso de las skills con AudioPlayer que yo he creado, y en el ejemplo que estamos desarrollando, no nos aporta nada conocer el estado del playback, pero sí los tipos de errores que nos vamos a poder encontrar:

  • MEDIA_ERROR_UNKNOWN > Error desconocido al intentar reproducir el audio.
  • MEDIA_ERROR_INVALID_REQUEST > Suele ser un error con el acceso al audio, como por ejemplo: no es público, no se encuentra, etc.
  • MEDIA_ERROR_SERVICE_UNAVAILABLE > No es posible conectarse a la URL indicada.
  • MEDIA_ERROR_INTERNAL_SERVER_ERROR > En este caso no hay problema con la URL pero Alexa encontró algún problema.
  • MEDIA_ERROR_INTERNAL_DEVICE_ERROR > Error en el dispositivo al intentar reproducir el audio.

En mi opinión, estos errores son bastante pobres y no aportan mucho sobre lo que está ocurriendo realmente. Por poner unos ejemplos con la skill de "El informativo de Ángel Martín":

  • Tuve un problema con los formatos del audio. Al principio usaba audios en formato m4a, las pruebas fueron bien y pasó el proceso de certificación. Al publicar la skill empecé a ver en los logs muchos MEDIA_ERROR_INTERNAL_DEVICE_ERROR pero no tenía más detalles. La solución para ese caso fue usar mp3. No fue nada intuitivo sino que tuve que buscar posibles problemas por otros sitios.
  • Una vez realizado el cambio anterior y desaparecer esos errores vi que seguía recibiendo algunos de MEDIA_ERROR_UNKNOWN o MEDIA_ERROR_INTERNAL_SERVER_ERROR. Esto se debía a que, dependiendo del firmware del dispositivo Alexa, había incluso problemas al reproducir el mp3. En esos casos la solución consistía en que los usuarios actualizaran el firmware.
  • Y, por último, a modo anécdota (y metedura buena de pata mía), cuando recibía un  MEDIA_ERROR_INTERNAL_DEVICE_ERROR estaba retornando un audio en m4a que, a su vez, provocaba otro MEDIA_ERROR_INTERNAL_DEVICE_ERROR... os podéis imaginar el bucle infinito de peticiones a la lambda y a s3.

A este tipo de eventos podemos responder con:

  • Cualquier directiva de AudioPlayer.
  • Cualquier otro tipo de respuesta que NO contenga un texto para Alexa u otras directivas (como las de Dialog Management).

Un ejemplo en Kotlin:

  • Si recibo el error MEDIA_ERROR_INTERNAL_DEVICE_ERROR > construyo una respuesta vacía (basado en lo que os comentaba antes de mi experiencia con un bucle infinito).
  • Si recibo cualquier otro error > genero una respuesta con una directiva de Play y un audio de fallback.

Y hasta aquí el post sobre los eventos de playback. Me ha quedado un poco largo pero había ciertas cosas necesarias de explicar que me han dado más de un dolor de cabeza a mí.

En el siguiente artículo veremos los built-in intents, la tercera pata principal de una skill de audio, y nos permitirá ir dibujando la imagen global del back :)