Alexa skills de audio con la interfaz AudioPlayer: built-in intents (IV)

Sigo con la serie donde voy detallando todos los aspectos a la hora de construir una Alexa Skill con la interfaz AudioPlayer.

En este artículo vamos a entrar en detalles en los built-in intents, ya predefinidos por Alexa, que son obligatorios en este tipo de skills. Si nuestro backend falla al responder a algunos de ellos es muy posible que se reporte como fallo en el proceso de certificación.

¿Cómo obtiene mi skill la info necesaria para "reanudar un audio" o para "pasar al siguiente/anterior"?

Antes de avanzar con los intents necesito explicar una parte clave en las skills con AudioPlayer. En el post de introducción comento que este tipo de skills tienen la peculiaridad de permitir interacciones fuera del contexto de una skill activa. Esto quiere decir que un usuario va a poder usar los built-in intents sin abrir nuestra skill y esas requests van a llegar correctamente a nuestro back. Por ejemplo, sin la skill abierta, incluso tiempo después de haberla usado, serán correctas interacciones como: "Alexa, siguiente/anterior" o "Alexa, para/continúa".

Esos intents llegarán al back de nuestra skill si:

  • Es la última skill que ha usado AudioPlayer para reproducir un audio en Alexa.
  • No se ha invocado después algún servicio nativo que reproduzca audio, como el servicio de música o los resúmenes de noticias.
  • No se ha reseteado el dispositivo.

Esto hace necesario el uso de persistencia ya que no vamos a poder contar con tener una sesión que mantenga información sobre el audio que se está reproduciendo o el offset en el cual se ha pausado, por poner un par de ejemplos.

DynamoDB al rescate

Si queremos manejar los built-in intents ofreciendo una buena experiencia al usuario, tendremos que hacer buen uso de los atributos de persistencia que nos ofrece el SDK de Alexa. Además, la información que guardemos habrá que segmentarla por el userId, información que nos llega en cada petición al back.

Por ejemplo, una skill básica de audio de tipo radio, o con un solo audio, necesitará guardar solamente el offset para pausar/reanudar. O, una skill algo más compleja que maneja diferentes audios, como "El informativo de Ángel Martín" (por días, por semanas, etc), necesita saber qué audio se estaba reproduciendo para intents como "anterior/siguiente":

{
  "audio": "chiste-1"; // O una fecha si es por día: 2020-12-24
  "offsetInMillis": 1234456789;
}

Ya he hablado en otro post sobre el uso de los atributos de persistencia en el SDK y cómo configurar el uso de DynamoDB. Lo más importante a destacar es que vamos a crear registros por userId y ahí, con un modelo JSON como el anterior, podremos controlar el estado del playback de una forma consistente para todos los built-in intents.

Built-in intents

El primer paso para usar AudioPlayer en una skill es activar el uso de esa interfaz. Esto lo cuento en el primer post de la serie. Para no repetirme sobre lo que explico en ese artículo voy a ir directo a contar el manejo de algunos built-in intent en el back. Digo "algunos" porque hay todo un listado pero no todos los he usado hasta ahora en las skills de audio que he hecho. Voy a comentar los que han sido más habituales para mí.

Amazon.PauseIntent/Amazon.CancelIntent y Amazon.ResumeIntent

Hay un par de acciones básicas que son obligatorias de cubrir y que van a ser comprobadas en el proceso de certificación: pausar/parar un audio y poder continuarlo luego.

Mientras se esté reproduciendo un audio que hayamos enviado con la directiva de Play, un usuario podrá decir:

  • "Alexa, para" > enviará un Amazon.PauseIntent
  • "Alexa, cancela" > enviará un Amazon.CancelIntent
  • "Alexa, continúa" > enviará un Amazon.ResumeIntent

Tanto la acción final de parar el audio, como de retomarlo, recae en la lógica del back. Y la respuesta esperada siempre tendrá interacción con el AudioPlayer enviando alguna de sus directivas. Concretamente:

Amazon.PauseIntent/Amazon.CancelIntent

Si nos llega el intent de pausar/cancelar, vamos a querer básicamente lo mismo en ambos casos. Parar el audio y persistir en que segundo se ha quedado la reproducción actual. De esa forma luego podremos continuar desde ese punto.

De la forma que funciona AudioPlayer tenemos que dividir esta acción en dos pasos:

1) Le decimos al AudioPlayer que tiene que parar la reproducción actual enviándole la directiva de de Stop:

2) Al recibir lo anterior, Alexa parará el audio y nos enviará un evento de reproducción. Concretamente una PlaybackStoppedRequest. Desde esa request podremos saber el segundo en cuál se ha pausado:

Siempre tendremos primero que persistir el audio en curso, por ejemplo antes de lanzar una directiva de Play. Así después podemos recuperarlo de la siguiente forma:

input.attributesManager.persistentAttributes["lastPlayedAudio"] =
            JacksonSerializer().serialize(Audio(audioUrl, offsetInMillis))
input.attributesManager.savePersistentAttributes()

Con ese modelo de persistencia tan simple, un objeto Audio con un par de campos: audioUrl y offsetInMillis; sería suficiente para el control de toda la lógica de pausado y continuar.

Amazon.ResumeIntent

Si nos llega el intent de continuar, tomando la información persistida para el usuario, lanzaremos una directiva de Play con la URL del audio correspondiente en el segundo que se pausó.

1) Recuperamos de persistencia la información del último Audio reproducido por el usuario. Supuestamente ahí tendremos la URL y el momento en el que se quedó la reproducción.

2) Enviamos la directiva de Play con la información anterior.

Aquí os pongo una extensión (magia de Kotlin) que le añadí a los HandlerInput para crear una respuesta de PlayDirective donde reemplazo cualquier cosa que esté sonando y meto el audio que quiero en el segundo donde se quedó.

Amazon.RepeatIntent y Amazon.StartOverIntent

A partir de lo anterior, donde hemos asentado la base del modelo de persistencia y cómo lo usaremos, nos resultará mucho más fácil manejar estos dos intents.

Amazon.RepeatIntent

Cuando el usuario diga: "Alexa, repetir", por ejemplo, nos llegará este built-in intent. Aquí, según mi opinión, podemos ver la acción deseada de dos forma:

1) El usuario quiere repetir el audio actual una vez finalice. Esta es una funcionalidad habitual en interfaces de reproducción de audio.

2) El usuario quiere reiniciar el audio actual sin esperar a que finalice. Pueden darse casos de uso, igual con audios orientados al aprendizaje o similar, que el usuario quiera repetir un audio corto desde el principio inmediatamente.

En el caso 1, lo que tenemos que hacer es, a partir de la información persistida del audio en curso, enviar una directiva al AudioPlayer de Play pero "encolando el audio" en vez de "reemplazando" (que es como lo hemos venido haciendo en ejemplos anteriores). Encolar el audio, desde el principio, lo que hará será meterlo en la lista de reproducción del AudioPlayer para que suene una vez acabe el actual.

En el caso 2, tendremos el mismo escenario que para Amazon.StartOverIntent.

Amazon.StartOverIntent

Tal como yo entiendo este caso de uso, el usuario diría: "Alexa, ponlo desde el principio" o similar. La intención es poner, de forma inmediata, el audio actual desde el principio.

Amazon.NextIntent y Amazon.PreviousIntent

Aquí tenemos otra pareja de built-in intents que suelen ir de la mano. Cubren los casos de uso de "navegar" en una lista de reproducción:

  • "Alexa, siguiente" > se envía un Amazon.NextIntent
  • "Alexa, anterior" > se envía un Amazon.PreviousIntent

La forma de manejar estos intents ya va a depender muy concretamente de la funcionalidad de la skill que tenemos entre manos. Por ejemplo:

  • Si es una skill con solamente un audio podemos, o no manejarlos (tendremos un fallo en el back si le llega este intent) o manejarlos por igual y retornar una respuesta vacía para no impactar al AudioPlayer.
  • Si es una skill, como "el informativo de Ángel Martín", que permite navegar entre audios (días del informativo en este caso), podremos manejar estos intents para reemplazar el audio actual por el nuevo que corresponda.

En el código que pongo, sacado de la skill del informativo, lo que hago es recoger esos intents y, según el que sea, busco la fecha anterior/siguiente al audio actualmente en reproducción y lo envío al AudioPlayer con la directiva de Play (lo tengo en una función auxiliar).


Y aquí terminamos con el penúltimo post de la serie. Es posible que aquí hayáis echado en falta otros built-in intents relacionados con el AudioPlayer. Con lo contado aquí, combinando las directivas del AudioPlayer, se pueden cubrir todos.

En el siguiente post veremos lo último que falta, el AudioPlayer y qué implica usarlo en dispositivos Alexa con pantallas táctiles :)