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 dePlay
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 deStop
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
oClearQueue
. - 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
oClearQueue
. - 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 de
Stop
. - Directiva de
Play
indicando un comportamiento deREPLACE_ALL
. - Directiva de
ClearQueue
con un comportamiento deCLEAR_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 muchosMEDIA_ERROR_INTERNAL_DEVICE_ERROR
pero no tenía más detalles. La solución para ese caso fue usarmp3
. 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
oMEDIA_ERROR_INTERNAL_SERVER_ERROR
. Esto se debía a que, dependiendo del firmware del dispositivo Alexa, había incluso problemas al reproducir elmp3
. 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 enm4a
que, a su vez, provocaba otroMEDIA_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 :)