<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joaquín Engelmo (a.k.a. Kini)</title>
    <description>The latest articles on DEV Community by Joaquín Engelmo (a.k.a. Kini) (@kini).</description>
    <link>https://dev.to/kini</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F138356%2F898d6c22-ec64-413e-a074-45fb99cd962d.jpg</url>
      <title>DEV Community: Joaquín Engelmo (a.k.a. Kini)</title>
      <link>https://dev.to/kini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kini"/>
    <language>en</language>
    <item>
      <title>Alexa skills de audio con la interfaz AudioPlayer: built-in intents (IV)</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Tue, 13 Apr 2021 13:46:51 +0000</pubDate>
      <link>https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-built-in-intents-iv-35j8</link>
      <guid>https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-built-in-intents-iv-35j8</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0WFZAiW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1518298297175-5b54b92e46b2%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMnwxMTc3M3wwfDF8c2VhcmNofDd8fHBsYXliYWNrfGVufDB8fHx8MTYxODE1OTg5OA%26ixlib%3Drb-1.2.1%26q%3D80%26w%3D1080" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0WFZAiW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1518298297175-5b54b92e46b2%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMnwxMTc3M3wwfDF8c2VhcmNofDd8fHBsYXliYWNrfGVufDB8fHx8MTYxODE1OTg5OA%26ixlib%3Drb-1.2.1%26q%3D80%26w%3D1080" alt="Alexa skills de audio con la interfaz AudioPlayer: built-in intents (IV)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/kini/usando-la-interfaz-audioplayer-para-crear-alexa-skills-que-reproducen-audio-i-3dpo"&gt;Sigo con la serie&lt;/a&gt; donde &lt;a href="https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-directivas-ii-pjm-temp-slug-6616636"&gt;voy detallando&lt;/a&gt; todos los aspectos a la hora de &lt;a href="https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-eventos-de-playback-iii-1jo1-temp-slug-2000796"&gt;construir una Alexa Skill con la interfaz AudioPlayer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;En este artículo vamos a entrar en detalles &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#intents"&gt;en los built-in intents&lt;/a&gt;, 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.&lt;/p&gt;

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

&lt;p&gt;Antes de avanzar con los intents necesito explicar una parte clave en las skills con AudioPlayer. &lt;a href="https://dev.to/kini/usando-la-interfaz-audioplayer-para-crear-alexa-skills-que-reproducen-audio-i-3dpo"&gt;En el post de introducción&lt;/a&gt; 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".&lt;/p&gt;

&lt;p&gt;Esos intents llegarán al back de nuestra skill si:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Es la última skill que ha usado AudioPlayer para reproducir un audio en Alexa.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;No se ha reseteado el dispositivo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esto hace necesario el uso de persistencia ya que no vamos a poder contar con tener una &lt;a href="https://dev.to/kini/conceptos-basicos-de-la-sesion-en-una-alexa-skill-1n84"&gt;sesión&lt;/a&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  DynamoDB al rescate
&lt;/h3&gt;

&lt;p&gt;Si queremos manejar los built-in intents ofreciendo una buena experiencia al usuario, tendremos que hacer buen uso de &lt;a href="https://dev.to/kini/gestion-de-atributos-en-una-alexa-skill-ii-291j"&gt;los atributos de persistencia&lt;/a&gt; que nos ofrece el SDK de Alexa. Además, la información que guardemos habrá que segmentarla por el &lt;code&gt;userId&lt;/code&gt;, información que nos llega en cada petición al back.&lt;/p&gt;

&lt;p&gt;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 "&lt;a href="https://www.amazon.es/Informativo-de-%C3%81ngel-Mart%C3%ADn-Oficial/dp/B08LHJYMY3"&gt;El informativo de Ángel Martín&lt;/a&gt;" (por días, por semanas, etc), necesita saber qué audio se estaba reproduciendo para intents como "anterior/siguiente":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "audio": "chiste-1"; // O una fecha si es por día: 2020-12-24
  "offsetInMillis": 1234456789;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://dev.to/kini/gestion-de-atributos-en-una-alexa-skill-ii-291j"&gt;Ya he hablado en otro post&lt;/a&gt; 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 &lt;code&gt;userId&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2&gt;
  
  
  Built-in intents
&lt;/h2&gt;

&lt;p&gt;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í.&lt;/p&gt;
&lt;h3&gt;
  
  
  Amazon.PauseIntent/Amazon.CancelIntent y Amazon.ResumeIntent
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Mientras se esté reproduciendo un audio que hayamos enviado con la directiva de Play, un usuario podrá decir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Alexa, para" &amp;gt; enviará un Amazon.PauseIntent&lt;/li&gt;
&lt;li&gt;"Alexa, cancela" &amp;gt; enviará un Amazon.CancelIntent&lt;/li&gt;
&lt;li&gt;"Alexa, continúa" &amp;gt; enviará un Amazon.ResumeIntent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon.PauseIntent/Amazon.CancelIntent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;De la forma que funciona AudioPlayer tenemos que dividir esta acción en dos pasos:&lt;/p&gt;

&lt;p&gt;1) Le decimos al AudioPlayer que tiene que parar la reproducción actual enviándole la directiva de de Stop:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



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


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input.attributesManager.persistentAttributes["lastPlayedAudio"] =
            JacksonSerializer().serialize(Audio(audioUrl, offsetInMillis))
input.attributesManager.savePersistentAttributes()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;&lt;strong&gt;Amazon.ResumeIntent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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ó.&lt;/p&gt;

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

&lt;p&gt;2) Enviamos la directiva de Play con la información anterior.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Aquí os pongo una extensión (&lt;a href="https://kotlinlang.org/docs/extensions.html"&gt;magia de Kotlin&lt;/a&gt;) que le añadí a los &lt;code&gt;HandlerInput&lt;/code&gt; 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ó.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon.RepeatIntent y Amazon.StartOverIntent
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon.RepeatIntent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;1) El usuario quiere repetir el audio actual una vez finalice. Esta es una funcionalidad habitual en interfaces de reproducción de audio.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;En el &lt;u&gt;caso 1&lt;/u&gt;, 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.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;En el &lt;u&gt;caso 2&lt;/u&gt;, tendremos el mismo escenario que para Amazon.StartOverIntent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon.StartOverIntent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Amazon.NextIntent y Amazon.PreviousIntent
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Alexa, siguiente" &amp;gt; se envía un Amazon.NextIntent&lt;/li&gt;
&lt;li&gt;"Alexa, anterior" &amp;gt; se envía un Amazon.PreviousIntent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La forma de manejar estos intents ya va a depender muy concretamente de la funcionalidad de la skill que tenemos entre manos. Por ejemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;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.
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;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.
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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).&lt;/p&gt;




&lt;p&gt;Y aquí terminamos con el penúltimo post de la serie. Es posible que aquí hayáis echado en falta &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#intents"&gt;otros built-in intents&lt;/a&gt; relacionados con el AudioPlayer. Con lo contado aquí, combinando las directivas del AudioPlayer, se pueden cubrir todos.&lt;/p&gt;

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

</description>
      <category>audioplayer</category>
      <category>alexa</category>
      <category>alexaskills</category>
      <category>audioskill</category>
    </item>
    <item>
      <title>Alexa skills de audio con la interfaz AudioPlayer: eventos de playback (III)</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Wed, 13 Jan 2021 09:27:07 +0000</pubDate>
      <link>https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-eventos-de-playback-iii-5hh6</link>
      <guid>https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-eventos-de-playback-iii-5hh6</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FRraNKK0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1557077316-a5212159d3bf%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMXwxMTc3M3wwfDF8c2VhcmNofDF8fHBsYXliYWNrfGVufDB8fHw%26ixlib%3Drb-1.2.1%26q%3D80%26w%3D1080" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FRraNKK0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1557077316-a5212159d3bf%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMXwxMTc3M3wwfDF8c2VhcmNofDF8fHBsYXliYWNrfGVufDB8fHw%26ixlib%3Drb-1.2.1%26q%3D80%26w%3D1080" alt="Alexa skills de audio con la interfaz AudioPlayer: eventos de playback (III)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En &lt;a href="https://dev.to/kini/usando-la-interfaz-audioplayer-para-crear-alexa-skills-que-reproducen-audio-i-ip4-temp-slug-4555321"&gt;esta&lt;/a&gt; &lt;a href="https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-directivas-ii-pjm-temp-slug-6616636"&gt;serie&lt;/a&gt; de artículos estoy contando todo lo que necesitas saber para usar la &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html"&gt;interfaz AudioPlayer&lt;/a&gt; en tus skills. Es una funcionalidad de Alexa que tiene bastante por detrás y le dedicaré varios posts.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#requests"&gt;eventos de playback&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eventos de Playback
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;userId&lt;/code&gt;. Esta información la usaremos para la capa de persistencia.&lt;/p&gt;

&lt;p&gt;Los tipos de eventos son:&lt;/p&gt;

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

&lt;p&gt;Los cuatro primeros eventos tienen la misma información en la petición:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "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"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;En el caso de uso de nuestra skill, por el modelo de datos persistente, nos vendrá bien la información del campo &lt;code&gt;offsetInMilliseconds&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Para el evento de &lt;code&gt;AudioPlayer.PlaybackFailed&lt;/code&gt;, 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.&lt;/p&gt;
&lt;h3&gt;
  
  
  AudioPlayer.PlaybackStarted
&lt;/h3&gt;

&lt;p&gt;Al recibir &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#playbackstarted"&gt;este evento&lt;/a&gt; tendremos la confirmación de que el audio que se indicó en la directiva &lt;code&gt;Play&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;A la hora de responder a este tipo de eventos tenemos varias opciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usar las directivas de AudioPlayer &lt;code&gt;Stop&lt;/code&gt; o &lt;code&gt;ClearQueue&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Cualquier otro tipo de respuesta que NO contenga un texto para Alexa u otras directivas (&lt;a href="https://dev.to/kini/dialog-management-en-alexa-introduccion-y-referencias-21a4"&gt;como las de Dialog Management&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una respuesta básica válida podría ser (en Kotlin):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Veremos el caso concreto de nuestra skill de ejemplo en otro post.&lt;/p&gt;

&lt;h3&gt;
  
  
  AudioPlayer.PlaybackFinished
&lt;/h3&gt;

&lt;p&gt;Alexa envía &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#playbackfinished"&gt;esta petición&lt;/a&gt; cuando el audio finaliza por haber llegado al final de la reproducción del mismo.&lt;/p&gt;

&lt;p&gt;A la hora de responder a este tipo de eventos tenemos varias opciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usar las directivas de AudioPlayer &lt;code&gt;Stop&lt;/code&gt; o &lt;code&gt;ClearQueue&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Cualquier otro tipo de respuesta que NO contenga un texto para Alexa u otras directivas (&lt;a href="https://dev.to/kini/dialog-management-en-alexa-introduccion-y-referencias-21a4"&gt;como las de Dialog Management&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una respuesta básica válida podría ser (en Kotlin):&lt;/p&gt;

&lt;p&gt;En nuestro ejemplo no necesitaremos manejar este evento para dar una respuesta distinta a la anterior.&lt;/p&gt;

&lt;h3&gt;
  
  
  AudioPlayer.PlaybackStopped
&lt;/h3&gt;

&lt;p&gt;El back recibirá &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#playbackstopped"&gt;esta petición&lt;/a&gt; como respuesta a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Diretiva de&lt;code&gt;Stop&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;Directiva de &lt;code&gt;Play&lt;/code&gt; indicando un comportamiento de &lt;code&gt;REPLACE_ALL&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;Directiva de &lt;code&gt;ClearQueue&lt;/code&gt; con un comportamiento de &lt;code&gt;CLEAR_ALL&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A este tipo de eventos solo está permitido responder algo básico (en Kotlin):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  AudioPlayer.PlaybackNearlyFinished
&lt;/h3&gt;

&lt;p&gt;Se envía cuando la reproducción está llegando a su fin, siendo &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#playbacknearlyfinished"&gt;un evento&lt;/a&gt; especialmente útil cuando se trabaja con una lista de reproducción.&lt;/p&gt;

&lt;p&gt;Posibles respuestas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cualquier directiva de AudioPlayer.&lt;/li&gt;
&lt;li&gt;Cualquier otro tipo de respuesta que NO contenga un texto para Alexa u otras directivas (&lt;a href="https://dev.to/kini/dialog-management-en-alexa-introduccion-y-referencias-21a4"&gt;como las de Dialog Management&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Al recibirlo podríamos responder con una directiva de &lt;code&gt;Play&lt;/code&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  AudioPlayer.PlaybackFailed
&lt;/h3&gt;

&lt;p&gt;Vamos a recibir &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#playbackfailed"&gt;este evento&lt;/a&gt; en el back cuando se produzca algún tipo de problema con la reproducción del audio en el dispositivo.&lt;/p&gt;

&lt;p&gt;El formato de la petición es distinto a los anteriores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "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"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Podemos ver que en este caso vamos a tener información del &lt;code&gt;error&lt;/code&gt; e información del estado del playback en el momento del error (&lt;code&gt;currentPlaybackState&lt;/code&gt;). 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:&lt;/p&gt;

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

&lt;p&gt;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":&lt;/p&gt;

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

&lt;p&gt;A este tipo de eventos podemos responder con:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cualquier directiva de AudioPlayer.&lt;/li&gt;
&lt;li&gt;Cualquier otro tipo de respuesta que NO contenga un texto para Alexa u otras directivas (&lt;a href="https://dev.to/kini/dialog-management-en-alexa-introduccion-y-referencias-21a4"&gt;como las de Dialog Management&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un ejemplo en Kotlin:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Si recibo el error &lt;code&gt;MEDIA_ERROR_INTERNAL_DEVICE_ERROR&lt;/code&gt; &amp;gt; construyo una respuesta vacía (basado en lo que os comentaba antes de mi experiencia con un bucle infinito).&lt;/li&gt;
&lt;li&gt;Si recibo cualquier otro error &amp;gt; genero una respuesta con una directiva de &lt;code&gt;Play&lt;/code&gt; y un audio de fallback.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;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í.&lt;/p&gt;

&lt;p&gt;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 :)&lt;/p&gt;

</description>
      <category>audioplayer</category>
      <category>alexa</category>
    </item>
    <item>
      <title>Alexa skills de audio con la interfaz AudioPlayer: directivas (II)</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Tue, 05 Jan 2021 07:02:47 +0000</pubDate>
      <link>https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-directivas-ii-5bio</link>
      <guid>https://dev.to/kini/alexa-skills-de-audio-con-la-interfaz-audioplayer-directivas-ii-5bio</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pXM0uBhJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1600332003090-0512bbe4a9f8%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMXwxMTc3M3wwfDF8c2VhcmNofDh8fHBsYXklMjBtdXNpY3xlbnwwfHx8%26ixlib%3Drb-1.2.1%26q%3D80%26w%3D1080" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pXM0uBhJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1600332003090-0512bbe4a9f8%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMXwxMTc3M3wwfDF8c2VhcmNofDh8fHBsYXklMjBtdXNpY3xlbnwwfHx8%26ixlib%3Drb-1.2.1%26q%3D80%26w%3D1080" alt="Alexa skills de audio con la interfaz AudioPlayer: directivas (II)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En &lt;a href="https://dev.to/kini/usando-la-interfaz-audioplayer-para-crear-alexa-skills-que-reproducen-audio-i-ip4-temp-slug-4555321"&gt;el primer post de la serie sobre AudioPlayer&lt;/a&gt; hacía una introducción a esa interfaz de Alexa que nos permite crear skills que reproducen audio.  Te recomiendo leerlo primero, si aún no lo hiciste, para tener claros los conceptos básicos antes de entrar en algunos detalles más avanzados aquí.&lt;/p&gt;

&lt;p&gt;En este post vamos a ver &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#directives"&gt;las directivas básicas del AudioPlayer&lt;/a&gt; que nos permitirán o bien reproducir un audio o parar una reproducción en curso. Las directivas son información especial que se añade a una respuesta desde el back para que el dispositivo Alexa que se está usando haga algo.&lt;/p&gt;

&lt;p&gt;Siempre aviso al inicio de este tipo de artículos que voy a usar &lt;a href="https://github.com/alexa/alexa-skills-kit-sdk-for-java"&gt;el SDK de Java&lt;/a&gt;, programando en Kotlin, para el backend. Esto hace diferente la experiencia del desarrollador para esta parte pero los conceptos y la información que cuento aplica igualmente. Al final lo que cambian son detalles derivados por un SDK concreto o la "&lt;a href="https://en.wikipedia.org/wiki/Syntactic_sugar"&gt;syntactic sugar&lt;/a&gt;" de un lenguaje determinado.&lt;/p&gt;

&lt;p&gt;Y otro friendly reminder: para probar este tipo de skills no podemos usar &lt;a href="https://dev.to/kini/creando-un-custom-skill-para-alexa-v-probando-nuestro-skill-con-las-herramientas-de-amazon-3ced"&gt;el simulador de la Alexa Developer Console&lt;/a&gt; ya que no permite reproducir audio. Vamos a necesitar disponer de un dispositivo con Alexa para ir probando cualquier cosa que hagamos con el AudioPlayer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audio Skill de chistes
&lt;/h2&gt;

&lt;p&gt;Antes de entrar en detalles voy  a plantear una skill que pone a disposición del usuario varios audios. Por ejemplo, podríamos hacer una skill que reproduzca chistes ya grabados en mp3, sin usar la voz de Alexa.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alojamiento de los audios
&lt;/h3&gt;

&lt;p&gt;Supongamos que esos audios están alojados en S3 de forma pública con nombres como "chiste-1", "chiste-2", etc. Además hemos decidido que sean archivos MP3. De esa forma estamos cubriendo los requisitos que pone Alexa para que no tengamos problemas al intentar reproducirlos.&lt;/p&gt;

&lt;p&gt;La URL de acceso a un chiste podría ser: &lt;code&gt;https://skill-chistes.s3.[region].amazonaws.com/chiste-[n].mp3&lt;/code&gt;. Así podemos fácilmente movernos entre audios (anterior &amp;gt; chiste-[n-1] o siguiente &amp;gt; chiste-[n+1]) e identificar de forma inequívoca para los intents de pausar/continuar, comenzar desde el principio, repetir, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Directivas de AudioPlayer
&lt;/h2&gt;

&lt;p&gt;Para indicar al dispositivo Alexa que está utilizando el usuario que tiene que realizar algún tipo de operación especial, como reproducir como audio el contenido de una URL, hay que usar una directiva.&lt;/p&gt;

&lt;p&gt;Estas directivas tienen un formato concreto y forman parte de una respuesta normal que se envía desde el back. &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#directives"&gt;Para el AudioPlayer tenemos tres directivas&lt;/a&gt; posibles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AudioPlayer.Play&lt;/code&gt; &amp;gt; Le dice a Alexa que reproduzca el audio indicado por la URL, campo &lt;code&gt;audioItem&lt;/code&gt;, que va en la respuesta.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AudioPlayer.Stop&lt;/code&gt; &amp;gt; Indica parar la reproducción del actual audio, sea cual sea.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AudioPlayer.ClearQueue&lt;/code&gt; &amp;gt; Se usa para limpiar la cola de audios que estuvieran programados para sonar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yo aún no tengo experiencia gestionando colas de reproducción con lo que no voy a contar cómo usar la directiva de &lt;code&gt;AudioPlayer.ClearQueue&lt;/code&gt; pero dejo &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#clearqueue"&gt;el link a la documentación oficial&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Directiva AudioPlayer.Play
&lt;/h3&gt;

&lt;p&gt;A la hora de hacer una skill de audio lo primero que nos interesará será que Alexa reproduzca el fichero MP3 que tenemos alojado, en este caso, en un supuesto S3. Esta directiva la podemos añadir a la respuesta de cualquier petición, ya sea un &lt;code&gt;LaunchRequest&lt;/code&gt; o &lt;code&gt;IntentRequest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cosas a tener en cuenta a la hora de añadir &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#play"&gt;la directiva de play&lt;/a&gt; a una respuesta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El formato de esta directiva es bastante extenso ya que permite, por ejemplo, indicar metadatos si estamos en un dispositivo con pantalla pero no estamos usando APL. De esta forma a la salida de audio se le podría acompañar con una salida visual mínima. &lt;/li&gt;
&lt;li&gt;Podemos incluir también una salida de texto que Alexa dirá justo antes de reproducir el audio.&lt;/li&gt;
&lt;li&gt;Normalmente querremos que &lt;a href="https://dev.to/kini/conceptos-basicos-de-la-sesion-en-una-alexa-skill-1n84"&gt;Alexa finalice la sesión&lt;/a&gt; y reproduzca el audio simplemente. En caso contrario, Alexa comenzará a reproducir el audio y de forma inmediata lo pausará para quedarse a la escucha del usuario. Ese no sería el comportamiento esperado para este tipo de skills.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Antes de ver el código de la directiva es necesario comentar un campo obligatorio de la misma: el &lt;code&gt;PlayBehavior&lt;/code&gt;. El valor de este campo va a indicar el comportamiento que tiene que tener el playback de Alexa con el audio que se le envía. Hay tres posibles valores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;REPLACE_ALL&lt;/code&gt; &amp;gt; que comienza la reproducción del audio que se le está pasando remplazando el actual y la cola pendiente que hubiera. Será el usado en nuestro ejemplo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ENQUEUE&lt;/code&gt; &amp;gt; que encola el audio indicado al final de la actual cola sin afectar a la reproducción en curso.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REPLACE_ENQUEUED&lt;/code&gt; &amp;gt; remplaza la cola actual sin afectar a la reproducción en curso.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para añadir la directiva a la respuesta, usando el SDK de Java y Kotlin, tenemos un &lt;code&gt;builder&lt;/code&gt; que nos facilitará la vida bastante:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;En el &lt;code&gt;builder&lt;/code&gt; añadimos la directiva de AudioPlayer. Esto equivaldría a poner el &lt;code&gt;type: AudioPlayer.Play&lt;/code&gt; si estuviéramos usando Javascript/Typescript.&lt;/li&gt;
&lt;li&gt;Indicar el comportamiento del playback a &lt;code&gt;REPLACE_ALL&lt;/code&gt; como ya dijimos anteriormente, ya que no vamos a usar una cola de reproducción.&lt;/li&gt;
&lt;li&gt;Indicar desde qué punto tiene Alexa que reproducir el audio que se le pasa. Para comenzar desde el principio usamos el valor de 0 ( &lt;code&gt;0L&lt;/code&gt; en Kotlin ya que es un &lt;code&gt;Long&lt;/code&gt;). Cuando lleguemos a la parte de reanudar un audio pausado indicaremos aquí otro valor.&lt;/li&gt;
&lt;li&gt;El valor de un "token previo", obligatorio si estamos usando colas, para identificar cualquier reproducción previa. En este caso no lo necesitamos y lo ponemos a &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Un token para la reproducción que estamos enviando. Es obligatorio siempre. Para nuestro ejemplo vale cualquier valor como: &lt;code&gt;audioSkill&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;La URL que identifica el audio.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Directiva AudioPlayer.Stop
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#stop"&gt;Esta directiva&lt;/a&gt; para parar la reproducción del audio en curso es mucho más sencilla que la de &lt;code&gt;play&lt;/code&gt; al no tener parámetros ni nada de configuración. Basta con añadirla a la respuesta que nos interese:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;





&lt;p&gt;El uso de estas directivas va a provocar que se generen los eventos del playback y tengamos que manejarlos correctamente en nuestro back. Esto va íntimamente relacionado con los built-in que se pueden generar por peticiones del usuario.&lt;/p&gt;

&lt;p&gt;Veremos todos los detalles en siguientes entregas de esta serie de artículos :)&lt;/p&gt;

</description>
      <category>audioplayer</category>
      <category>alexa</category>
    </item>
    <item>
      <title>Codemotion Madrid 2020 - Taller: Crear una skill para Alexa es fácil, sencillo y para toda la familia</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Sun, 03 Jan 2021 12:28:18 +0000</pubDate>
      <link>https://dev.to/kini/codemotion-madrid-2020-taller-crear-una-skill-para-alexa-es-facil-sencillo-y-para-toda-la-familia-155h</link>
      <guid>https://dev.to/kini/codemotion-madrid-2020-taller-crear-una-skill-para-alexa-es-facil-sencillo-y-para-toda-la-familia-155h</guid>
      <description>&lt;p&gt;Aquí os dejo el vídeo completo del taller que impartí en &lt;a href="https://events.codemotion.com/conferences/online/2020/online-tech-conference-spanish-edition/"&gt;Codemotion Madrid 2020&lt;/a&gt;. En poco más de hora y media repaso parte de la teoría básica y conceptos esenciales de los asistentes de voz. En la parte práctica, siguiendo el taller, cualquier persona podrá crear una Alexa skill en cuestión de minutos y tenerla publicada en poco tiempo.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/cfGr_2_krfU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>eventos</category>
      <category>codemotion</category>
      <category>taller</category>
      <category>alexa</category>
    </item>
    <item>
      <title>Usando la interfaz AudioPlayer para crear Alexa skills que reproducen audio (I)</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Wed, 23 Dec 2020 06:57:30 +0000</pubDate>
      <link>https://dev.to/kini/usando-la-interfaz-audioplayer-para-crear-alexa-skills-que-reproducen-audio-i-3dpo</link>
      <guid>https://dev.to/kini/usando-la-interfaz-audioplayer-para-crear-alexa-skills-que-reproducen-audio-i-3dpo</guid>
      <description>&lt;p&gt;En un &lt;a href="https://dev.to/kini/creando-una-alexa-skill-de-audio-en-cuestion-de-minutos-java-kotlin-edition-1cjf"&gt;post anterior contaba cómo crear una Alexa skill de audio&lt;/a&gt; siguiendo una serie de pasos y usando para &lt;a href="https://github.com/Alexa-Community-Spain/alexa-kotlin-lambda-audio-skill"&gt;el back un código ya creado por mi&lt;/a&gt;. Ese tipo de skills que hacen streaming de audio usan la &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html"&gt;interfaz AudioPlayer&lt;/a&gt; de Alexa. El uso de AudioPlayer no es complicado pero todo lo que quiero contar da para varios artículos.&lt;/p&gt;

&lt;p&gt;En este primer post voy a hacer una introducción al AudioPlayer y explicar en qué consiste su funcionamiento. Es un caso bastante particular con respecto al uso habitual de skills. Iré soltando muchos conceptos que enlazaré con la documentación oficial. Para terminar indicaré cómo habilitar la interfaz en una skill y qué efectos tiene eso en el modelo de interacción.&lt;/p&gt;

&lt;p&gt;La carga de trabajo de este tipo de skills recae en el back y es lo que iremos viendo en futuros artículos donde podré detallar y explicar bien todo lo contado en esta introducción.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interfaz AudioPlayer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Qué es?
&lt;/h3&gt;

&lt;p&gt;La interface de AudioPlayer nos permite crear skills que reproduzcan audio y nos ofrece información sobre el estado de la reproducción, playback, para que nosotros en el back podamos reaccionar a eso en caso necesario.&lt;/p&gt;

&lt;p&gt;El AudioPlayer se puede controlar tanto por voz como por botones o vía táctil. Dependiendo del origen de la acción que hagamos sobre el audio nos llegarán distintos tipos de requests al back.&lt;/p&gt;

&lt;p&gt;En el código que tengo publicado se recogen la mayoría de eventos relacionados con el playback, así como intents específicos para el control del audio. Lo que no tengo cubierto en mi ejemplo de código son eventos de botones/táctiles pero los comentaré igualmente aquí porque añadirlo es trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cómo funciona?
&lt;/h3&gt;

&lt;p&gt;La interfaz de AudioPlayer habilita una forma bastante particular de usar Alexa e interactuar con una skill. El punto de entrada inicial siempre va a ser arrancar la skill de forma normal con su nombre de invocación. Una vez que un audio se reproduzca a partir de nuestra skill, Alexa de forma nativa tomará el control del resto de interacciones sin que el usuario tenga que volver a invocar la skill. Además de esto vamos a tener eventos que van a llegar al back cuando pasen ciertas cosas, así como comandos si se está usando una interfaz táctil.&lt;/p&gt;

&lt;p&gt;Por tanto, si tuviéramos una skill que reproduce un audio pasaría lo siguiente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El usuario invoca la skill y se empieza a reproducir un audio. Para ello se usa una directiva, &lt;code&gt;AudioPlayerPlayDirective&lt;/code&gt;, desde el back. &lt;a href="https://dev.to/kini/conceptos-basicos-de-la-sesion-en-una-alexa-skill-1n84"&gt;Se finaliza la sesión&lt;/a&gt; y toma el control Alexa. De esta forma ya no estamos en el contexto de nuestra skill.&lt;/li&gt;
&lt;li&gt;A partir de este momento podemos hacer uso de ciertos &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#intents"&gt;built-in intents&lt;/a&gt;, predefinidos por Amazon, para manejar el audio en curso (pausar, parar, repetir desde el principio, etc). Por ejemplo el usuario va a poder decir: "Alexa, para" o "Alexa, siguiente", sin invocar a la skill.&lt;/li&gt;
&lt;li&gt;Igualmente, &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#intents"&gt;si tenemos un dispositivo con pantalla&lt;/a&gt;, podremos hacer uso de los controles del reproductor de audio. Este controlador enviará distintos comandos por cada operación elegida. Ejemplos aquí serían los típicos botones de cualquier reproductor multimedia.&lt;/li&gt;
&lt;li&gt;También, de forma transparente para el usuario, &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#requests"&gt;Alexa va enviando eventos al back de la skill&lt;/a&gt; que inició la reproducción del audio. Estos eventos están asociados a distintas cosas como, por ejemplo, la reproducción del audio comenzó o falló, la reproducción se ha parado, está a punto de finalizar, ha finalizado, etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un apunte importante aquí: Amazon deja en manos del desarrollador toda la gestión de saber identificar qué audio se ha pausado/ se quiere retomar, en qué offset, cuál sería el siguiente audio o el anterior a reproducir, etc. Cada evento de playback o intent predefinido (como Resume/Previous/Next/Pause) ocurren fuera del contexto de una skill abierta y, por tanto, &lt;a href="https://dev.to/kini/conceptos-basicos-de-la-sesion-en-una-alexa-skill-1n84"&gt;no tienen información de sesión&lt;/a&gt; con lo que no podemos hacer uso de los atributos de ese tipo en estas skills. Esto hace inevitable tirar de algún tipo de solución de persistencia. En mi caso uso &lt;a href="https://aws.amazon.com/dynamodb/"&gt;DynamoDB&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;La "magia" en este tipo de skills radica en cómo Alexa gestiona la reproducción del audio. Una vez que una skill lanza la directiva de play y comienza el playback del audio, Alexa va a recordar esa skill. Gracias a eso, si el usuario dice: "Alexa, siguiente", se generará una petición de &lt;code&gt;Amazon.NextIntent&lt;/code&gt; al back de la skill correcta. Y esto puede ocurrir al poco tiempo de haber usado la skill o mucho después.&lt;/p&gt;

&lt;p&gt;Alexa va a recordar la última skill que reprodujo audio y, de esta forma, va a poder enviarle built-in intents sin necesidad de usar el nombre de invocación. Si, por ejemplo, usamos &lt;a href="https://www.amazon.es/Informativo-de-%C3%81ngel-Mart%C3%ADn-Oficial/dp/B08LHJYMY3"&gt;la skill de "el informativo de Ángel Martín"&lt;/a&gt; por la mañana y por la tarde, sin usar el nombre de invocación, decimos: "Alexa, anterior", la skill del informativo recibirá la petición e intentará enviar el nuevo audio correspondiente.&lt;/p&gt;

&lt;p&gt;Para "olvidar" esta última skill que reprodujo audio tiene que pasar uno de los siguientes casos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Que el usuario use otra skill con AudioPlayer que reproduzca audio.&lt;/li&gt;
&lt;li&gt;Que el usuario use algún servicio nativo de audio,  como el de música o los resúmenes de noticias.&lt;/li&gt;
&lt;li&gt;Que el usuario reinicie el dispositivo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Requisitos para reproducir una audio
&lt;/h3&gt;

&lt;p&gt;Lo que se le proporciona a Alexa para reproducir el audio es una URL que tiene que cumplir ciertos requisitos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El fichero tiene que estar alojado en un endpoint accesible por https en el puerto 443.&lt;/li&gt;
&lt;li&gt;El servidor web debe presentar un certificado SSL válido y de confianza. La mayoría de servicios de almacenamiento son validos como &lt;a href="https://aws.amazon.com/s3/"&gt;S3&lt;/a&gt; o &lt;a href="https://www.dropbox.com/"&gt;Dropbox&lt;/a&gt;. Yo he usado ambos sin problema.&lt;/li&gt;
&lt;li&gt;Si lo que se envía es la URL de una playlist, lo anterior aplica a cada elemento de la misma.&lt;/li&gt;
&lt;li&gt;Formatos soportados: AAC/MP4, MP3, PLS, M3U/M3U8, and HLS. Bitrates: 16kbps to 384 kbps. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Habilitar la interfaz de AudioPlayer en tu skill
&lt;/h2&gt;

&lt;p&gt;Para que un skill pueda usar la interfaz de AudioPlayer primero tenemos que activarla en el listado de interfaces posibles a implementar. Desde la &lt;a href="https://dev.to/kini/creando-un-custom-skill-para-alexa-ii-alexa-developer-console--interaction-model-5gig"&gt;Alexa Developer Console&lt;/a&gt;, dentro de la skill, vamos a Custom &amp;gt; Interfaces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8e1eRQUR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/12/Screenshot-2020-12-21-at-19.26.05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8e1eRQUR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/12/Screenshot-2020-12-21-at-19.26.05.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0E1aMZkP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/12/Screenshot-2020-12-21-at-19.26.18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0E1aMZkP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/12/Screenshot-2020-12-21-at-19.26.18.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Al activarla veremos que se añaden dos nuevos built-in intents, predefinidos por Amazon, para las acciones de pausar (&lt;code&gt;Amazon.PauseIntent&lt;/code&gt;) y de reanudar (&lt;code&gt;Amazon.ResumeIntent&lt;/code&gt;). Estos los tendremos que tener en cuenta de forma obligatoria en nuestro back y manejarlos correctamente. Cualquier cosa que se nos pase la detectarán en el proceso de certificación y nos informarán.&lt;/p&gt;

&lt;p&gt;Aparte de estos dos built-in intents obligatorios se recomienda que una skill de audio controle sin fallar otros tantos intents listados en la documentación oficial de Alexa. Estos otros intents no hay que definirlos en el modelo de interacción, simplemente manejarlos en el back.&lt;/p&gt;

&lt;p&gt;En mi caso no los he controlado nunca todos y eso no ha supuesto un problema en el proceso de certificación. Serían:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.CancelIntent&lt;/code&gt; &amp;gt; Lo he usado para cancelar el audio que en ese momento esté sonando.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.LoopOffIntent&lt;/code&gt; &amp;gt; No lo he usado. Sería para desactivar la reproducción en bucle.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.LoopOnIntent&lt;/code&gt; &amp;gt; No lo he usado. Sería para activar la reproducción en bucle.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.NextIntent&lt;/code&gt; &amp;gt; Lo he usado, en la skill del informativo de Ángel Martín, para reproducir el resumen del día siguiente, a modo playlist.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.PreviousIntent&lt;/code&gt; &amp;gt; Lo he usado, en la skill del informativo de Ángel Martín, para reproducir el resumen del día anterior, a modo playlist.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.RepeatIntent&lt;/code&gt; &amp;gt; Lo he usado para repetir desde el principio el audio que estuviera sonando en el momento.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.ShuffleOffIntent&lt;/code&gt; &amp;gt; No lo he usado. Sería para desactivar el modo aleatorio.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.ShuffleOnIntent&lt;/code&gt; &amp;gt; No lo he usado. Sería para activar el modo aleatorio.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AMAZON.StartOverIntent&lt;/code&gt; &amp;gt; Lo he usado para repetir desde el principio el audio que estuviera sonando en el momento.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;En el siguiente post comenzaré a explicar cómo manejar en back cada uno de los tipos de interacciones que hemos visto aquí: built-in intents, eventos del playback y comandos del reproductor.&lt;/p&gt;

&lt;p&gt;Hay bastante chicha con todo el AudioPlayer y nos dará para unos cuantos artículos. Cero aburrimiento :)&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>alexaskills</category>
      <category>audioskill</category>
      <category>audioplayer</category>
    </item>
    <item>
      <title>Creando una Alexa Skill de video en cuestión de minutos (Java/Kotlin edition)</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Wed, 09 Dec 2020 09:46:19 +0000</pubDate>
      <link>https://dev.to/kini/creando-una-alexa-skill-de-video-en-cuestion-de-minutos-java-kotlin-edition-a4e</link>
      <guid>https://dev.to/kini/creando-una-alexa-skill-de-video-en-cuestion-de-minutos-java-kotlin-edition-a4e</guid>
      <description>&lt;p&gt;En &lt;a href="https://dev.to/kini/creando-una-alexa-skill-de-audio-en-cuestion-de-minutos-java-kotlin-edition-4enn-temp-slug-5849379"&gt;el artículo anterior&lt;/a&gt; contaba cómo hacer una skill de audio en pocos pasos usando un template del back ya generado en Kotlin. También hice &lt;a href="https://www.youtube.com/watch?v=Tgxtw4luJrg"&gt;un vídeo contando el proceso&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A raíz de ese ejemplo de skill se me ocurrió aplicar el mismo concepto para crear una skill que reproduzca un vídeo, por cubrir el otro elemento multimedia en un dispositivo Alexa. He creado un&lt;a href="https://github.com/Alexa-Community-Spain/alexa-kotlin-lambda-video-skill"&gt;código base a modo de plantilla para tener una skill muy simple que reproduzca un vídeo&lt;/a&gt; a partir de una URL pública, hosteando el back en &lt;a href="https://aws.amazon.com/es/lambda/"&gt;AWS Lambda&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;En este post voy a contar cómo crear esa skill paso a paso (sin entrar en el código o en el &lt;a href="https://dev.to/kini/creando-un-custom-skill-para-alexa-vii-distribucion-y-certificacion-a1m"&gt;proceso de distribución ni certificación&lt;/a&gt;). Si hay muchos conceptos que desconoces puedes ver mis primeros posts, que cuentan gran parte de la teoría. El código está liberado y con licencia GPL-3.0. Hay pasos que son pura configuración y burocracia de trabajar con AWS pero no son complicados.&lt;/p&gt;

&lt;p&gt;Crearé también un video sobre el proceso, así que si te da pereza leer, puedes esperarte a la película ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Casos de uso
&lt;/h2&gt;

&lt;p&gt;Con una skill de este tipo, con el código base, se puede crear una skill que reproduzca un video al que se debe acceder a partir de una URL pública, HTTPs y en uno de los &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/videoapp-interface-reference.html#supported-video-formats-and-resolutions"&gt;formatos soportados documentados aquí&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;En el ejemplo vamos a hacer uso de la &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/videoapp-interface-reference.html"&gt;VideoApp Interface&lt;/a&gt; de Alexa, que permite reproducir un vídeo en un dispositivo con pantalla. La limitación es que no se pueden encolar varios vídeos, algo que se podía hacer con el audio. Si queremos hacer cosas más elaboradas con vídeo ya habría que crear algo usando &lt;a href="https://dev.to/kini/usando-apl-en-una-alexa-skill-4cb1"&gt;APL&lt;/a&gt;. Lo tengo en la recámara también, espero no tardar mucho en poder contaros cómo :)&lt;/p&gt;

&lt;p&gt;De todas formas ese vídeo vamos a poder "cambiarlo en caliente", sin desplegar código, porque lo he creado usando variables de entorno en AWS Lambda. Así como el vídeo, podrás configurar otros dos campos que son obligatorios: título y subtítulo.&lt;/p&gt;

&lt;p&gt;No tiene opciones para personalizar respuestas de Alexa porque en principio el caso de uso está orientado a reproducir el video con simplemente arrancar la skill y, aunque tiene un intent por ser obligatorio, invocarlo de forma one-shot tendrá el mismo resultado que arrancar la skill con un launch request. Si el dispositivo que ejecuta la skill no tiene pantalla, Alexa informará al usuario.&lt;/p&gt;

&lt;p&gt;Una vez esté reproduciéndose el vídeo Alexa toma el control de forma nativa. De esa forma vamos a poder realizar acciones táctiles, con un mando o con la propia voz. Por ejemplo podremos pararlo, avanzar unos segundos, volver al inicio del vídeo, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creando la skill en la Alexa Developer Console
&lt;/h2&gt;

&lt;p&gt;Lo primero que tenemos que hacer es crear una skill y para ello podemos hacer uso de la &lt;a href="https://dev.to/kini/creando-un-custom-skill-para-alexa-ii-alexa-developer-console--interaction-model-5gig"&gt;Alexa Developer Console&lt;/a&gt;. Ya tengo otros posts donde se cuentan los pasos iniciales, por lo que no me voy a repetir aquí pero como resumen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crearse una &lt;a href="https://developer.amazon.com/en-US/alexa"&gt;cuenta de desarrollador de Amazon Alexa&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Crear una skill desde la Alexa Developer Console que sea de tipo Custom y provisionaremos el back por nuestra cuenta.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gZDZW63g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-27-at-17.10.18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gZDZW63g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-27-at-17.10.18.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elegimos la plantilla básica para el modelo de interacción. Luego vamos a sustituir lo que se cree, entonces no será problema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x9kPJoe---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.28.15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x9kPJoe---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.28.15.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elegimos el invocation name.&lt;/li&gt;
&lt;li&gt;El modelo de interacción creado ya tiene un Intent custom que se llama HelloWorldIntent. Vamos a sustituir ese Intent por uno propio. Es cierto que para usar esta skill no vamos a necesitar un intent pero es obligatorio tener uno propio. En el editor JSON sustituimos el código de ese intent por:
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--42b_v1-O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.35.16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--42b_v1-O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.35.16.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XPWaJphv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.07.13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XPWaJphv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.07.13.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Al ser una skill que va a reproducir un vídeo tenemos que activar la &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/videoapp-interface-reference.html"&gt;interface de VideoApp&lt;/a&gt; o nuestra skill fallará al intentar reproducirse en un dispositivo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LW_FI_46--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.07.31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LW_FI_46--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.07.31.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Llegados a este punto deberíais poder construir el modelo de forma exitosa. Nos faltaría el paso de declarar el endpoint correcto pero eso lo haremos después de configurar la función lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creando la función AWS Lambda
&lt;/h2&gt;

&lt;p&gt;Al igual que antes ya había explicado en posts anteriores cómo crear una función lambda para usarla como endpoint de nuestra skill. Voy a resumir los pasos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nos tenemos que crear una cuenta de desarrollador de Amazon para los servicios AWS. Así tendremos acceso al servicio de AWS Lambda (entre otros).&lt;/li&gt;
&lt;li&gt;Creamos una función (ojo con la región, para España la mejor opción actual es la de París). El código de mi proyecto está en Kotlin con lo que seleccionaremos el lenguaje Java 8 (es donde lo tengo ya probado). Vamos a crear también un rol nuevo a no ser que ya tengas uno de antes con permisos correctos para el lambda y los logs de &lt;a href="https://aws.amazon.com/es/cloudwatch/"&gt;CloudWatch&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XUOEBpJS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.08.19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XUOEBpJS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.08.19.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Desplegando el código de la función lambda
&lt;/h3&gt;

&lt;p&gt;Llegados a este punto tenemos que generar un paquete para desplegarlo en la función lambda. Aquí hay dos opciones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Se puede construir en local a partir del código en el repositorio ejecutando la task de Gradle de &lt;code&gt;shadowJar&lt;/code&gt;. Esto generará un fichero &lt;code&gt;.jar&lt;/code&gt; en el directorio &lt;code&gt;build/libs&lt;/code&gt; con el nombre &lt;code&gt;alexa-skill-video-1.0-all.jar&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Coger el &lt;a href="https://github.com/Alexa-Community-Spain/alexa-kotlin-lambda-audio-skill/blob/main/alexa-skill-audio-1.0-all.jar"&gt;paquete ya generado&lt;/a&gt; y que he metido en la raíz del repositorio con el nombre de &lt;code&gt;alexa-skill-video-1.0-all.jar&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Una vez tenemos el paquete hay que desplegarlo en la función lambda. Aquí lo vamos a hacer a mano pero hay formas de integrar en Gradle esto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1w1XppcV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.56.24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1w1XppcV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.56.24.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora pasamos a la configuración básica de la lambda (controlador, memoria, etc). Desde la última vez que lo usé he visto un cambio en la interfaz de la consola web de AWS Lambda y han separado en dos partes distintas indicar el controlador de otras opciones como la memoria.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lo primero que hacemos es indicar el controlador poniendo la ruta tal como está en el código fuente: &lt;code&gt;com.kinisoftware.videoSkill.VideoSkillStreamHandler&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NIaGKVOm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.15.31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NIaGKVOm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.15.31.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La memoria se puede bajar a 256 (incluso sobre 200) pero creo que cada ejecución usaba 160 o así (eso se puede ver en los logs).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7QTpAvOF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.11.52-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7QTpAvOF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.11.52-1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enganchando la skill
&lt;/h3&gt;

&lt;p&gt;Ahora ya podemos pasar a enganchar la skill con nuestra función lambda. En la Alexa Developer Console habíamos dejado sin modificar la parte del endpoint. Eso es lo que vamos a preparar.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copiamos el skill id desde la Alexa Developer Console &amp;gt; Endpoint. Ese id lo vamos a meter en la parte de la lambda.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4kaXBrSt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4kaXBrSt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.11.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Añadimos el trigger de Alexa en la función lambda. Y le ponemos el id de la skill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cfLBQHU1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.49.10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cfLBQHU1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.49.10.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qccgqDT7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qccgqDT7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.45.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copiamos el ARN de la función lambda que será lo que llevemos al campo del endpoint en la Alexa Developer Console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mJ0s2qY2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mJ0s2qY2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.57.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--md4-bQjx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.51.42.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--md4-bQjx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.51.42.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y al guardar el endpoint se validará esto que acabamos de configurar de tal forma que sabremos si está todo bien.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Y dónde se indica la URL del video a reproducir?
&lt;/h2&gt;

&lt;p&gt;Como os contaba antes, solo vamos a poder configurar un vídeo a reproducir. Además, junto con el vídeo vamos a poner un título y un subtítulo. Para ello vamos a usar variables de entorno.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dHaJQdhi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.55.23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dHaJQdhi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.55.23.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En la zona de variables de entorno simplemente tenemos que añadir la variable con clave &lt;code&gt;videoUrl&lt;/code&gt; y el valor será la URL que contenga el video.&lt;/li&gt;
&lt;li&gt;De la misma forma, para el título usamos la variable &lt;code&gt;title&lt;/code&gt; y para el subtítulo la de &lt;code&gt;subtitlte&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bg91OIw1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.12.32.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bg91OIw1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.12.32.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iBduQP21--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.13.06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iBduQP21--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-26-at-19.13.06.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una vez tengamos la(s) variable(s) creada(s) y guardada(s) ya podremos usar nuestra skill. Para probarla necesitamos un dispositivo con Alexa que tenga pantalla (aquí el móvil no vale). Con el simulador no vamos a poder ver el vídeo ni inspeccionar que la respuesta es correcta porque al no ser compatible con VideoApp Interface, no se enviará la reproducción del vídeo desde el back.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing...
&lt;/h2&gt;

&lt;p&gt;Cuando haces una skill que reproduce un vídeo surge el reto de la resolución y el tamaño del vídeo. Si pones un vídeo muy pesado o con una resolución muy grande es posible que no tenga mucho sentido si la mayoría de dispositivos de tus usuarios no lo van a aprovechar. Y viceversa, si te basas en resoluciones pequeñas te puedes llevar una sorpresa si un usuario le da por invocar la skill con un FireTV.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cómo puedo saber que dispositivos/resoluciones tienen mis usuarios?
&lt;/h3&gt;

&lt;p&gt;En el template del backend de esta skill he metido un log adicional para así conocer la información del &lt;code&gt;context&lt;/code&gt;, que es la parte de la request que nos da eso. Esto solo lo voy a meter como log si el dispositivo es compatible con la interface de VideoApp. Para los dispositivos que no la soporten no tiene sentido conocer esa info para nuestro caso.&lt;/p&gt;

&lt;p&gt;Una vez vayamos teniendo usos de la skill vamos a poder usar esa información del &lt;code&gt;context&lt;/code&gt; para identificar, por ejemplo, la resolución soportada por los dispositivos de los usuarios. Esto lo podemos hacer con una consulta en CloudWatch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ENpqF2Ig--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-27-at-17.57.32.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ENpqF2Ig--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-27-at-17.57.32.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| stats count_distinct(System.user.userId) as devices by Viewport.currentPixelWidth
| filter @message like /(Context)/
| sort by devices desc

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y8VYb3qQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-27-at-17.55.39.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y8VYb3qQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-27-at-17.55.39.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Y ya solo tendríamos que rellenar la info de distribución de la skill y mandarla a certificar :D&lt;/p&gt;

&lt;p&gt;Para cualquier error no dudéis en contactarme por aquí o por twitter. Igualmente recordad que se pueden ver los logs de la AWS Lambda en la zona de "Monitorización" y ya está el código preparado para meter ahí peticiones y respuestas ;)&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>alexaskills</category>
      <category>videoskill</category>
      <category>aws</category>
    </item>
    <item>
      <title>Creando una Alexa Skill de audio en cuestión de minutos (Java/Kotlin edition)</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Wed, 11 Nov 2020 10:01:47 +0000</pubDate>
      <link>https://dev.to/kini/creando-una-alexa-skill-de-audio-en-cuestion-de-minutos-java-kotlin-edition-1cjf</link>
      <guid>https://dev.to/kini/creando-una-alexa-skill-de-audio-en-cuestion-de-minutos-java-kotlin-edition-1cjf</guid>
      <description>&lt;p&gt;A raíz de &lt;a href="https://www.amazon.es/dp/B08LHJYMY3"&gt;la skill de "El informativo de Ángel Martín"&lt;/a&gt; he tenido varias preguntas relacionadas con la creación de una skill que simplemente reproduzca una URL con audio. Pensando en eso, sin entrar en complejidad de fechas que tiene la skill del informativo, se me ha ocurrido crear un &lt;a href="https://github.com/Alexa-Community-Spain/alexa-kotlin-lambda-audio-skill"&gt;código base, a modo de plantilla, para tener una skill muy simple que reproduzca audio&lt;/a&gt; a partir de una URL pública, hosteando el back en &lt;a href="https://aws.amazon.com/es/lambda/"&gt;AWS Lambda&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;En este post voy a contar cómo crear esa skill paso a paso (sin entrar en el código o en el &lt;a href="https://dev.to/kini/creando-un-custom-skill-para-alexa-vii-distribucion-y-certificacion-a1m"&gt;proceso de distribución ni certificación&lt;/a&gt;). Si hay muchos conceptos que desconoces puedes ver mis primeros posts, que cuentan gran parte de la teoría. El código está liberado y con licencia GPL-3.0. Hay pasos que son pura configuración y burocracia de trabajar con AWS pero no son complicados.&lt;/p&gt;

&lt;p&gt;El proceso en este caso se podría hacer también con Alexa-Hosted y Node.js. Haré otro post para eso pero por ahora quería aprovechar lo que ya tenía en Kotlin. &lt;a href="https://www.youtube.com/watch?v=Tgxtw4luJrg"&gt;He creado también un video sobre el proceso&lt;/a&gt;, por tanto, si te da pereza leer, puedes ver la película ;) &lt;/p&gt;

&lt;h2&gt;
  
  
  Casos de uso
&lt;/h2&gt;

&lt;p&gt;Con una skill de este tipo, con el código base, se puede crear una skill que reproduzca un audio al que se debe acceder a partir de una URL pública, HTTPs y en formato MP3 (hay más &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html#audio-stream-requirements"&gt;formatos soportados documentados aquí&lt;/a&gt; pero este parece la opción más segura).&lt;/p&gt;

&lt;p&gt;Se podría crear una radio, por ejemplo, si tenemos una URL que haga streaming continuo o podemos ir cambiando la URL del audio "en caliente", sin desplegar código, porque lo he creado usando variables de entorno en AWS Lambda. Además le he metido la opción de tener otra variable de entorno para un audio de "fallback" por si falla el audio principal por algún motivo.&lt;/p&gt;

&lt;p&gt;No tiene opciones para personalizar respuestas de Alexa porque en principio el caso de uso está orientado a reproducir el audio con simplemente arrancar la skill y, aunque tiene un intent por ser obligatorio, invocarlo de forma one-shot tendrá el mismo resultado que arrancar la skill con un launch request.&lt;/p&gt;

&lt;p&gt;Una vez esté sonando el audio vamos a poder pararlo y continuarlo con "Alexa, para" y "Alexa, continúa" respectivamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creando la skill en la Alexa Developer Console
&lt;/h2&gt;

&lt;p&gt;Lo primero que tenemos que hacer es crear una skill y para ello podemos hacer uso de la &lt;a href="https://dev.to/kini/creando-un-custom-skill-para-alexa-ii-alexa-developer-console--interaction-model-5gig"&gt;Alexa Developer Console&lt;/a&gt;. Ya tengo otros posts donde cuentan los pasos iniciales por lo que no me voy a repetir aquí pero, como resumen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crearse una &lt;a href="https://developer.amazon.com/en-US/alexa"&gt;cuenta de desarrollador de Amazon Alexa&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Crear una skill desde la Alexa Developer Console que sea de tipo Custom y provisionaremos el back por nuestra cuenta&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SXsxBvga--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.27.21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SXsxBvga--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.27.21.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elegimos la plantilla básica para el modelo de interacción. Luego vamos a sustituir lo que se cree entonces no será problema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p7VsOTFU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.28.15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p7VsOTFU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.28.15.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elegimos el invocation name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yMXE0Mz3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.29.19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yMXE0Mz3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.29.19.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El modelo de interacción creado ya tiene un Intent custom que se llama HelloWorldIntent. Vamos a sustituir ese Intent por uno propio. Es cierto que para usar esta skill no vamos a necesitar un intent pero es obligatorio tener uno propio. En el editor JSON sustituimos el código de ese intent por:
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CJXGTTBx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.35.16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CJXGTTBx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.35.16.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZZQaGQdk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.35.57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZZQaGQdk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.35.57.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Al ser una skill de Audio tenemos que activar la &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/audioplayer-interface-reference.html"&gt;interface de Audio Player&lt;/a&gt; o nuestra skill fallará al intentar reproducirse en un dispositivo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qA31QcTf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.36.07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qA31QcTf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.36.07.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Llegados a este punto deberíais poder construir el modelo de forma exitosa. Nos faltaría el paso de declarar el endpoint correcto pero eso lo haremos después de configurar la función lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creando la función AWS Lambda
&lt;/h2&gt;

&lt;p&gt;Al igual que antes ya había explicado en posts anteriores cómo crear una función lambda para usarla como endpoint de nuestra skill. Voy a resumir los pasos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nos tenemos que crear una cuenta de desarrollador de Amazon para los servicios AWS. Así tendremos acceso al servicio de AWS Lambda (entre otros).&lt;/li&gt;
&lt;li&gt;Creamos una función (ojo con la región, para España la mejor opción actual es la de París). El código de mi proyecto está en Kotlin con lo que seleccionaremos el lenguaje Java 8 (es donde lo tengo ya probado). Vamos a crear también un rol nuevo a no ser que ya tengas uno de antes con permisos correctos para el lambda, los logs de &lt;a href="https://aws.amazon.com/es/cloudwatch/"&gt;CloudWatch&lt;/a&gt; y escritura/lectura en &lt;a href="https://aws.amazon.com/es/dynamodb/"&gt;DynamoDB&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kTDiSA7d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.41.01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kTDiSA7d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.41.01.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurando los servicios para usar DynamoDB
&lt;/h3&gt;

&lt;p&gt;Este tipo de skills necesita de persistencia para manejar la acción de "retomar el audio" (pausa/continúa) desde donde lo dejaste si lo paras en algún momento. Para eso haremos uso de DynamoDB.&lt;/p&gt;

&lt;p&gt;Por tanto hay dos cosas que debemos hacer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Añadir los permisos de lectura y escritura en DynamoDB al rol que se creó al crear la función lambda. Para eso desde la pestaña de "Permisos" accedemos a los detalles del rol y le asociamos la política correspondiente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bwQ32W1u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.46.08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bwQ32W1u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.46.08.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_ciS4_Eg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.46.23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_ciS4_Eg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.46.23.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RJWW-Gjm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.46.49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RJWW-Gjm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.46.49.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crear la tabla previamente o la primera ejecución de la skill fallará porque no estará la tabla creada. Nos vamos al servicio de DynamoDB y creamos una tabla con el nombre que ya está configurado en el código de la lambda &lt;code&gt;skill-audio&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--50D4qTZl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.47.46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--50D4qTZl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.47.46.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GQgyU1bg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.49.21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GQgyU1bg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.49.21.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Desplegando el código de la función lambda
&lt;/h3&gt;

&lt;p&gt;Llegados a este punto tenemos que generar un paquete para desplegarlo en la función lambda. Aquí hay dos opciones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Se puede construir en local a partir del código en el repositorio ejecutando la task de Gradle de &lt;code&gt;shadowJar&lt;/code&gt;. Esto generará un fichero &lt;code&gt;.jar&lt;/code&gt; en el directorio &lt;code&gt;build/libs&lt;/code&gt; con el nombre &lt;code&gt;alexa-skill-audio-1.0-all.jar&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Coger el &lt;a href="https://github.com/Alexa-Community-Spain/alexa-kotlin-lambda-audio-skill/blob/main/alexa-skill-audio-1.0-all.jar"&gt;paquete ya generado&lt;/a&gt; y que he metido en la raíz del repositorio con el nombre de &lt;code&gt;alexa-skill-audio-1.0-all.jar&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Una vez tenemos el paquete hay que desplegarlo en la función lambda. Aquí lo vamos a hacer a mano pero hay formas de integrar en Gradle esto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l-GjRlP8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.56.24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l-GjRlP8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.56.24.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora pasamos a la configuración básica de la lambda (controlador, memoria, etc):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bj4_lrx3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.56.55.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bj4_lrx3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.56.55.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hay que poner la ruta del controlado tal como está en el código fuente. Habría que cambiarla a: &lt;code&gt;com.kinisoftware.audioSkill.AudioSkillStreamHandler&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;La memoria se puede bajar a 256 (incluso sobre 200) pero creo que cada ejecución usaba 160 o así (eso se puede ver en los logs).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lhxk3-aI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.57.35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lhxk3-aI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-06-at-18.57.35.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enganchando la skill
&lt;/h3&gt;

&lt;p&gt;Ahora ya podemos pasar a enganchar la skill con nuestra función lambda. En la Alexa Developer Console habíamos dejado sin modificar la parte del endpoint. Eso es lo que vamos a preparar.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copiamos el skill id desde la Alexa Developer Console &amp;gt; Endpoint. Ese id lo vamos a meter en la parte de la lambda.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TObVQvNy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TObVQvNy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.11.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Añadimos el trigger de Alexa en la función lambda. Y le ponemos el id de la skill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CD-BVl7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.49.10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CD-BVl7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.49.10.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9RaswHks--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9RaswHks--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.45.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copiamos el ARN de la función lambda que será lo que llevemos al campo del endpoint en la Alexa Developer Console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ICdFd1ez--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ICdFd1ez--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.50.57.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yA_dAaIv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.51.42.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yA_dAaIv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.51.42.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Y al guardar el endpoint se validará esto que acabamos de configurar de tal forma que sabremos si está todo bien.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ¿Y dónde se indica la URL del audio a reproducir?
&lt;/h2&gt;

&lt;p&gt;Para esta skill básica de audio decidí que solo iba a manejar un audio, es decir, una URL, pero que se pudiera cambiar sin tener que tocar código. Para ello lo que tenemos que hacer es indicar la URL por una variable de entorno de la AWS Lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ctmLBtso--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.55.23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ctmLBtso--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.55.23.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En la zona de variables de entorno simplemente tenemos que añadir la variable con clave &lt;code&gt;audioUrl&lt;/code&gt; y el valor será la URL que contenga el audio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YGU4lYhb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.56.01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YGU4lYhb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.56.01.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;He añadido la opción de indicar también un audio de fallback en el caso de que haya algún error. Para eso tenemos que añadir la variable de &lt;code&gt;fallbackAudioUrl&lt;/code&gt; de la misma forma que la anterior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UN8Jsd0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.57.49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UN8Jsd0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.kinisoftware.com/content/images/2020/11/Screenshot-2020-11-07-at-12.57.49.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una vez tengamos la(s) variable(s) creadas y guardada(s) ya podremos usar nuestra skill. Para probarla podemos usar el simulador de la Alexa Developer Console o un dispositivo con Alexa (ya sea un Echo o la aplicación de Amazon Alexa para el móvil). Con el simulador no vamos a poder escuchar el audio pero si inspeccionar la respuesta de la skill para ver si ha lanzando la operación de play contra la URL correcta:&lt;/p&gt;

&lt;p&gt;Para escuchar el audio tendremos que probar la skill en un dispositivo Alexa o en la aplicación del móvil.&lt;/p&gt;




&lt;p&gt;Y ya solo tendríamos que rellenar la info de distribución de la skill y mandarla a certificar :D&lt;/p&gt;

&lt;p&gt;Para cualquier error no dudéis en contactarme por aquí o por twitter. Igualmente recordad que se pueden ver los logs de la AWS Lambda en la zona de "Monitorización" y ya está el código preparado para meter ahí peticiones y respuestas ;)&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>alexaskills</category>
      <category>aws</category>
      <category>awslambda</category>
    </item>
    <item>
      <title>Profundizando en APL: Pager y navegación por voz</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Tue, 27 Oct 2020 10:59:19 +0000</pubDate>
      <link>https://dev.to/kini/profundizando-en-apl-pager-y-navegacion-por-voz-4jk</link>
      <guid>https://dev.to/kini/profundizando-en-apl-pager-y-navegacion-por-voz-4jk</guid>
      <description>&lt;p&gt;Hacía mucho que no escribía un artículo y ya iba teniendo ganas :)&lt;/p&gt;

&lt;p&gt;Han pasado &lt;a href="https://dev.to/kini/gestion-de-atributos-en-una-alexa-skill-ii-4j55-temp-slug-8265800"&gt;6 meses&lt;/a&gt; de pandemia, eventos online, &lt;a href="https://www.twitch.tv/kinisoware"&gt;retransmisiones por Twich&lt;/a&gt; y &lt;a href="https://developer.amazon.com/en-US/blogs/alexa/alexa-skills-kit"&gt;muchas novedades del mundo Alexa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Quiero profundizar en algunos temas de APL y voy a usar mi skill de &lt;a href="https://www.amazon.com/Kinisoftware-Estrenos-de-cine/dp/B07MKCLZ62"&gt;Estrenos de Cine&lt;/a&gt; para ello. En este post vamos a ver concretamente cómo añadir un Pager a &lt;a href="https://dev.to/kini/usando-apl-en-una-alexa-skill-4cb1"&gt;nuestra skill con APL&lt;/a&gt; y cómo añadirle navegación por voz.&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Cómo ha evolucionado visualmente la skill Estrenos de Cine con APL?
&lt;/h2&gt;

&lt;p&gt;En antiguos post ya conté el paso de la skill de tener salida solo para voz a tener salida para dispositivos con pantalla. He de reconocer que lo que hice en su momento fue muy simple, me faltaban muchos conocimientos y algún que otro copy&amp;amp;paste de skills de ejemplo acabaron en la mía.&lt;/p&gt;

&lt;p&gt;Es posible que cuando estéis leyendo este post esa versión ya no esté en producción y esté la que voy a contar ahora (o una incluso posterior). Por eso os pongo por aquí un gif de cómo era esa versión inicial de la skill con APL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8sS-jV7Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/27813ngjakc30p2f9ruh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8sS-jV7Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/27813ngjakc30p2f9ruh.gif" alt="" width="880" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y con lo que voy a contar en este post, le he dado un lavado de cara que pinta así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rQ0eUhde--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5xf76mbdykjrsl423ku7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rQ0eUhde--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5xf76mbdykjrsl423ku7.gif" alt="" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Además del cambio visual quise añadirle navegación por voz, simplemente por probarlo y jugar con eso.&lt;/p&gt;

&lt;h2&gt;
  
  
  Añadiendo APL Pager
&lt;/h2&gt;

&lt;p&gt;Desde que metí APL a la skill siempre quise añadir el póster a la respuesta por pantalla. En realidad a nivel de arquitectura de información no ha cambiado nada aparte de llegar la imagen. Los otros dos campos por cada película son los mismos de antes: título y fecha de estreno.&lt;/p&gt;

&lt;p&gt;Hay un par de soluciones provistas por APL para hacer "un tipo de carrusel":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-sequence.html"&gt;Sequences&lt;/a&gt; &amp;gt; te darían algo como lo que tenía en la versión inicial: un carrusel por el cual puedes hacer scroll. A ese carrusel le podría haber añadido la imagen y haberlo usado perfectamente. Así a la vez normalmente tendrías más de un elemento visible.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-pager.html"&gt;Pager&lt;/a&gt; &amp;gt; es un carrusel pero a pantalla completa. Es decir, tienes un elemento de carrusel en cada momento. Mi intención era crear una experiencia de "showroom" y por eso opté por esta opción.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La idea de esta primera evolución visual es darle un lavado de cara aprovechando las pantallas con el detalle de la imagen.&lt;/p&gt;

&lt;p&gt;Antes de entrar en detalle técnico del Pager quería decir que la imagen usa el componente &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-alexa-image-layout.html"&gt;AlexaImage&lt;/a&gt; en vez de &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-image.html"&gt;APL Image&lt;/a&gt;. El primero sería como una versión vitaminada del segundo con temas como responsive entre otros. Veréis el código y por eso no merece la pena explicar mucho más de esta parte.&lt;/p&gt;

&lt;p&gt;Eso si, no seáis como yo. &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-image.html#source"&gt;Leed bien la documentación&lt;/a&gt; porque la URL tiene que cumplir ciertos requisitos y me tiré un tiempo para descubrir por qué a mi no me hacía render en el Echo Show pero sí en el simulador :D&lt;/p&gt;

&lt;h3&gt;
  
  
  Documento APL
&lt;/h3&gt;

&lt;p&gt;Yo ya usaba un componente Sequence para el listado vertical de estrenos. Lo que hice fue reestructurar un poco el layout para meter el Pager ocupando todo el ancho y la parte de alto restante de la cabecera y el footer (del footer os hablo luego).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;El resto, los params de entrada y tal, es todo igual que lo que había ya antes. Es casi cambiar un componente por el otro. Fijaos en que tiene un "id" definido. Esto lo usaremos luego para la navegación por voz.&lt;/p&gt;

&lt;p&gt;En el back no hay cambios que hacer para empezar a usar este componente. Si el dispositivo que tienes es táctil o tienes mando, podrás pasar de página sin hacer nada más.&lt;/p&gt;

&lt;h2&gt;
  
  
  Añadiendo navegación por voz
&lt;/h2&gt;

&lt;p&gt;A modo experimento quería añadir la capacidad de navegación por voz a la skill. Con navegación me refiero en este caso a pasar de página diciendo a Alexa: "Alexa, siguiente estreno", "Alexa, siguiente", etc&lt;/p&gt;

&lt;p&gt;Aunque para este caso de uso puede no resultar muy interesante, sí que creo que puede haber otros casos de uso (en la cocina por ejemplo) donde tengas alguna skill con Pager y se pudiera implementar esto así.&lt;/p&gt;

&lt;p&gt;Para el experimento implementé la capacidad de "navegar hacía el siguiente elemento" pero no "navegar hacía el anterior" aunque aplica lo mismo que voy a contar y sería bastante simple. Comentar relacionado con esto que el Pager tiene distintas configuraciones para la navegación:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;normal&lt;/code&gt; – Por defecto. Te puedes mover libremente hacía adelante o atrás.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;none&lt;/code&gt; – El usuario no puede cambiar de página. En este caso se usa un comando, &lt;code&gt;AutoPage&lt;/code&gt;, para moverlo de forma programática.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wrap&lt;/code&gt; – Aun no he conseguido ver la diferencia entre este modo y el modo normal :D&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;forward-only&lt;/code&gt; - Solo te puedes mover hacia delante.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A nivel del documento APL no hay que añadir nada para tener navegación por voz, todo el peso aquí recae en el modelo de interacción y el back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interaction Model
&lt;/h3&gt;

&lt;p&gt;Para reconocer la acción vocal de avanzar en la paginación tenemos que crear un nuevo intent y meterle los utterances correspondientes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vauEdQ3C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/10/Screenshot-2020-10-20-at-20.01.33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vauEdQ3C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/10/Screenshot-2020-10-20-at-20.01.33.png" alt="" width="562" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;Ahora la parte más interesante de esto. Desde el back tenemos que hacer varias cosas para que todo funcione. Pero, antes de eso, voy a contar un concepto nuevo que vamos a usar: &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-commands.html"&gt;APL Commands&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Los APL Commands nos permiten cambiar la presentación, visual en este caso, de la skill en el dispositivo. Os pongo &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-commands.html"&gt;el link a la documentación oficial&lt;/a&gt; y me voy a centrar en los dos que nos interesan hoy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-standard-commands.html#autopage-command"&gt;&lt;code&gt;AutoPage&lt;/code&gt;&lt;/a&gt;: este comando permite configurar la transición automática de cada elemento del Pager. Permite configurar el delay de comienzo y cada cuánto tiempo cambia de elemento.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SetPage&lt;/code&gt;: este comando permite cambiar el elemento representado en el Pager indicando o un valor relativo (desde donde estás se mueve N posiciones) o un valor absoluto (se mueve al índice que se le indique). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En mi caso, al querer hacer navegación por voz, con una transición hacía delante de los estrenos, opté por usar el comando de &lt;code&gt;SetPage&lt;/code&gt; de forma relativa. Esto lo podemos ver en el código del handle para nuestro nuevo &lt;code&gt;NextPageIntent&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;En el método &lt;code&gt;handle&lt;/code&gt; es donde está toda la magia y creamos una respuesta que ejecutará el comando de &lt;code&gt;SetPage&lt;/code&gt;. Al comando le vamos a decir que usaremos posición relativa y que el valor a mover será &lt;code&gt;1&lt;/code&gt;. Con ese valor podemos jugar para distintos efectos como saltarnos elementos (con un &lt;code&gt;2&lt;/code&gt; nos saltamos uno por ejemplo), ir hacía detrás (con un número negativo), etc.&lt;/li&gt;
&lt;li&gt;Necesitamos indicarle el id del componente Pager sobre el cual queremos ejecutar el comando. Este id será el mismo que le hayamos puesto en el documento APL.&lt;/li&gt;
&lt;li&gt;Por último, indicar un token es obligatorio. Este token es el mismo con el que se hace el comando de &lt;code&gt;RenderDocumentDirective&lt;/code&gt; del documento en pantalla. De esta forma queda relacionado el documento donde está el componente sobre el que tiene que aplicar el comando. Yo no hacía uso de ese token por lo que tuve que añadirlo en los dos handlers donde se crea el documento con los estrenos  &lt;code&gt;.withToken("newReleasesSkillAPLToken")&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  El Footer
&lt;/h3&gt;

&lt;p&gt;Al tener ahora más interacción disponible para el usuario en modo vocal, es recomendable hacer uso de un footer en la skill para dar pistas o sugerencias de interacciones.&lt;/p&gt;

&lt;p&gt;Para ello añadí un footer al documento APL con, de momento, una cadena estática que dice "siguiente estreno", para que el usuario tenga una pista de que puede navegar de esa forma.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Para ello he usado el componente &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-alexa-footer-layout.html"&gt;AlexaFooter&lt;/a&gt; pero lo oculto si el dispositivo Alexa es redondo (el Echo Spot) porque no se ve bien ahí.&lt;/p&gt;

&lt;p&gt;Para añadir la "wake word" que cada usuario tenga en su dispositivo le tenemos que añadir una función en el template de datos que ya nos creará la frase hint final.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;





&lt;p&gt;Y con eso tendríamos la skill funcionando con el Pager y la navegación por voz. Es verdad que en este caso igual no es tan útil pero quería hacer el experimento y ver la complejidad. En una segunda evolución quiero añadir un segundo nivel de detalle para cada película donde tener información concreta como duración, puntuación, sinopsis, etc. Quizás haya información que no sea visual, si la sinopsis es grande igual es mejor que esa parte la dé por voz, y así combinar ambas experiencias. Ya veré qué hago cuando llegue a ese punto ;)&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>alexaskills</category>
      <category>apl</category>
      <category>voicenavigation</category>
    </item>
    <item>
      <title>Gestión de atributos en una Alexa Skill (II)</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Tue, 28 Apr 2020 08:39:02 +0000</pubDate>
      <link>https://dev.to/kini/gestion-de-atributos-en-una-alexa-skill-ii-291j</link>
      <guid>https://dev.to/kini/gestion-de-atributos-en-una-alexa-skill-ii-291j</guid>
      <description>&lt;p&gt;En el &lt;a href="https://kinisoftware.com/gestion-de-atributos-i/"&gt;anterior artículo sobre la gestión de atributos&lt;/a&gt; hice una introducción básica a los tipos existentes y comenté con algo más de detalle los de &lt;code&gt;Request&lt;/code&gt; y los de &lt;code&gt;Session&lt;/code&gt;. En este post veremos los que nos quedaban: &lt;code&gt;Persistent attributes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Como ya conté en el artículo de introducción, &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-nodejs/manage-attributes.html#persistenceadapter"&gt;los atributos persistentes&lt;/a&gt; se caracterizan por tener un ciclo de vida que va más allá de la sesión. Es información que será guardada en el sistema de almacenamiento que configuremos al crear la skill en el back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurando la capa de almacenamiento
&lt;/h3&gt;

&lt;p&gt;Para poder hacer uso de atributos persistentes tenemos que configurar un &lt;code&gt;PersistenceAdapter&lt;/code&gt; cuando creamos la skill. Este adapter será usado por el &lt;code&gt;AttributesManager&lt;/code&gt; para guardar y leer los atributos persistentes.&lt;/p&gt;

&lt;p&gt;Podemos hacer uso de algunos adapters que ya existen en el SDK o podemos crear los nuestros propios siempre que siga la interfaz marcada. Podéis consultarla en la &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-nodejs/manage-attributes.html#interface"&gt;doc oficial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;En mi caso he usado &lt;a href="https://aws.amazon.com/es/dynamodb/"&gt;DynamoDB&lt;/a&gt;, una base de datos NoSQL de Amazon, como capa de persistencia y &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-nodejs/manage-attributes.html#dynamodbpersistenceadapter"&gt;el SDK ya te ofrece un adapter&lt;/a&gt; que puedes usar y configurar para tus necesidades. Para &lt;a href="https://aws.amazon.com/es/s3/"&gt;S3&lt;/a&gt;, un sistema de almacenamiento también de Amazon, &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-nodejs/manage-attributes.html#s3persistenceadapter"&gt;existe un adapter&lt;/a&gt; que podrás igualmente usar.&lt;/p&gt;

&lt;p&gt;Cada sistema tiene sus propias opciones de configuración. Los valores que necesites los podrás indicar en la creación del adapter. Vamos a ver un ejemplo básico en código:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Aquí podemos ver:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un método llamado &lt;code&gt;withPersistenceAdapter&lt;/code&gt; al cual tenemos que pasarle el adapter del sistema de almacenamiento ya configurado.&lt;/li&gt;
&lt;li&gt;La creación de &lt;code&gt;DynamoDbPersistenceAdapter&lt;/code&gt; con la config que nos hace falta. Hay varios atributos posibles que están &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-nodejs/manage-attributes.html#config-options"&gt;documentados&lt;/a&gt;. Como mínimo requerido hay que indicar el nombre de la tabla. El resto son opcionales. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un punto importante antes de arrancar nuestra skill con persistencia. En el caso de estar usando AWS Lambda tendremos que configurar los permisos del role para poder leer/escribir en la base de datos. En mi caso añadí un permiso bastante completo que, entre otras cosas, te da acceso a DynamoDB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C-FwJwFJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/04/Screenshot-2020-04-26-at-11.15.46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C-FwJwFJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/04/Screenshot-2020-04-26-at-11.15.46.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Otro punto interesante a destacar es la posibilidad de indicar una función generadora del &lt;code&gt;partitionKey&lt;/code&gt;. Si no indicamos nada, ya que es opcional, por defecto el SDK usará el campo completo &lt;code&gt;userId&lt;/code&gt; que viene en cada &lt;a href="https://kinisoftware.com/alexa-skill-session/#request"&gt;request&lt;/a&gt;. Este campo tiene una parte que no varía por usuario y no nos aporta valor al guardarlo. Por eso, en mi caso, hice una función que se queda solo con la parte identificadora del usuario, la parte final de la cadena &lt;code&gt;"amzn1.ask.account.[unique-value-here]"&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uso del AttributesManager con los atributos persistentes
&lt;/h3&gt;

&lt;p&gt;Una vez tenemos configurada nuestra capa de persistencia ya vamos a poder hacer uso de los atributos persistentes en nuestra skill. Al igual que con los otros tipo de atributos vamos a usar el &lt;code&gt;AttributesManager&lt;/code&gt; para leer/guardar la información.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Antes de explicar los métodos hay que reseñar que, por temas de rendimiento, el &lt;code&gt;AttributesManager&lt;/code&gt; gestiona una caché local. Esto afecta al funcionamiento de los métodos que nos ofrece el SDK:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getPersistentAttributes&lt;/code&gt; =&amp;gt; lee la información, primero de la caché y, en caso de no tenerla, irá al sistema de almacenamiento. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setPersistentAttributes&lt;/code&gt; =&amp;gt; guarda la información pero solo en la caché local. De esta forma el método de lectura siempre tendrá los últimos cambios.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;savePersistentAttributes&lt;/code&gt; =&amp;gt; este método es el que realmente guarda la información en el sistema de almacenamiento.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deletePersistentAttributes&lt;/code&gt; =&amp;gt; este método elimina la información, tanto de la caché como del sistema de almacenamiento final. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una práctica bastante extendida a la hora de trabajar con atributos persistentes es crear un par de interceptores, uno para la &lt;code&gt;request&lt;/code&gt; y otro para la &lt;code&gt;response&lt;/code&gt;, que se encargarán de lectura y escritura de los mismos respectivamente. Lo habitual es volcar información a la sesión y trabajar desde ahí.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Para el interceptor de lectura podemos destacar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solo carga los atributos persistentes si la sesión es nueva. En caso contrario se supone que ya los debería tener de la primera petición.&lt;/li&gt;
&lt;li&gt;Crea la estructura necesaria para volcarlos a los atributos de sesión. En mi caso creé una interfaz para tipar esa estructura y así facilitar su uso en las otras partes de código. De esta forma evitamos equivocarnos al coger los atributos y nos lo valida en tiempo de compilación.&lt;/li&gt;
&lt;li&gt;Al final usamos el método &lt;code&gt;setSessionAttributes&lt;/code&gt; para, a partir de este punto, trabajar con la información a nivel de sesión.
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para el interceptor de escritura podemos ver:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La capa de persistencia solo se maneja si la &lt;code&gt;response&lt;/code&gt; es sobre finalizar la sesión del usuario. De esta forma se optimiza la escritura contra el sistema de almacenamiento.&lt;/li&gt;
&lt;li&gt;Recuperamos los atributos de sesión y cogemos lo que necesitemos llevar a la capa de persistencia. Los volcamos a los &lt;code&gt;PersistentAttributes&lt;/code&gt; con el método &lt;code&gt;setPersistentAttributes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Por último, guardamos la info anterior en el sistema de almacenamiento final configurado.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Y con esto tendríamos la información necesaria para usar información persistente en nuestra Alexa Skill. Esta claro que, además de este sistema, podríamos tener nuestra propia conexión con una base de datos y gestionarla a mano. Lo interesante en este caso es ver si el SDK nos facilita un poco la vida y exprimirlo al máximo :)&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>alexaskills</category>
      <category>attributesmanagement</category>
    </item>
    <item>
      <title>Programar el back de una Alexa Skill con Typescript</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Wed, 08 Apr 2020 09:12:29 +0000</pubDate>
      <link>https://dev.to/kini/programar-el-back-de-una-alexa-skill-con-typescript-12ao</link>
      <guid>https://dev.to/kini/programar-el-back-de-una-alexa-skill-con-typescript-12ao</guid>
      <description>&lt;p&gt;Gran parte de la comunidad de desarrolladores de Alexa Skills usa &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript"&gt;Javascript&lt;/a&gt; como lenguaje del back con &lt;a href="https://nodejs.org/en/"&gt;node.js&lt;/a&gt; como runtime. Desde luego tiene mucha popularidad y se ha convertido en todo un referente en la industria. En el mundo de Alexa hay más herramientas a día de hoy para JS o incluso &lt;a href="https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-nodejs/overview.html"&gt;el propio SDK de JS&lt;/a&gt; va siempre por delante de los otros.&lt;/p&gt;

&lt;p&gt;Mis conocimientos de JS son mínimos ya que nunca he trabajado profesionalmente con el lenguaje, si acaso alguna cosa puntual, y me faltan los conocimientos relacionados con el runtime, con el sistema de dependencias, librerías, tooling, etc. Pero ahora me he puesto a aprender &lt;a href="https://www.typescriptlang.org/"&gt;Typescript&lt;/a&gt; y eso me lleva de cabeza a todo el universo node.js. Al igual que pasa con Kotlin y Java, con Typescript y Javascript vas a tener de la mano la interoperabilidad de ambos lenguajes y todo su ecosistema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mi setup para usar Typescript en una Alexa Skill
&lt;/h2&gt;

&lt;p&gt;De momento solo estoy empezando y creando de cero &lt;a href="https://github.com/kinisoftware/alexa-skill-mis-series"&gt;una skill con Typescript&lt;/a&gt;. De todas formas, los pasos serán los mismos, ya sea empezar algo nuevo o migrar el back de una skill con Javascript. De echo, en mi caso, yo empecé con una skill creada en base a una plantilla con Javascript.&lt;/p&gt;

&lt;p&gt;Ya de paso introduciré brevemente &lt;a href="https://developer.amazon.com/en-US/docs/alexa/smapi/quick-start-alexa-skills-kit-command-line-interface.html"&gt;ASK CLI&lt;/a&gt;, la herramienta por línea de comandos que ofrece Amazon para la gestión de skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  ASK CLI
&lt;/h3&gt;

&lt;p&gt;Seré breve ya que no es el objetivo de este post y se puede consultar todo muy bien en la documentación oficial. Alexa Skills Kit Command Line Interface es una herramienta que, mediante línea de comandos, te permite gestionar todo lo relacionado con una Alexa Skill, incluso temas de back como lambda, S3, etc.&lt;/p&gt;

&lt;p&gt;En la documentación oficial verás cómo puedes instalarla y configurar todo para su uso. &lt;a href="https://developer.amazon.com/en-US/docs/alexa/smapi/ask-cli-intro.html#create-new-skill"&gt;Podrás crear skills&lt;/a&gt; a base de plantillas (node.js y python por ahora), ya sean alexa-hosted o no. En el caso de tener el back en AWS lambda podrás también gestionar esto de forma sencilla.&lt;/p&gt;

&lt;p&gt;Desde la propia herramienta podrás hacer también &lt;a href="https://developer.amazon.com/en-US/docs/alexa/smapi/ask-cli-intro.html#deploy-skill"&gt;deploy de la skill&lt;/a&gt; y usar ciertos comandos para probar la skill que te permite iniciar diálogos o simular conversaciones. Además hay herramientas en el mercado que se basan en ASK CLI para construir por encima temas de integración continúa o testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creando una Alexa Skill básica en JS con ASK CLI
&lt;/h3&gt;

&lt;p&gt;Como decía antes, yo partí de una plantilla básica de skill que te facilita usar ASK CLI con el comando &lt;code&gt;new&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ie8UhcPV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/04/Screenshot-2020-04-06-at-17.08.23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ie8UhcPV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/04/Screenshot-2020-04-06-at-17.08.23.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la imagen anterior se puede ver los tres puntos básicos que te pedirá el comando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elegir el runtime &amp;gt; por el momento, node.js v10 o python.&lt;/li&gt;
&lt;li&gt;Elegir una plantilla &amp;gt; crea la skill basándose en una plantilla previa que puede tener más o menos features ya cubiertas (hay algunas ya con APL). No sólo puedes usar los templates de Amazon sino que puedes usar otros. En la doc oficial lo cuentan. &lt;a href="https://github.com/xavidop/alexa-typescript-lambda-helloworld"&gt;Aquí os dejo una plantilla&lt;/a&gt; creada por &lt;a href="https://twitter.com/xavidop"&gt;Xavi Portilla&lt;/a&gt; que sigue más o menos la estructura que cuento en este post con Typescript y todo configurado.&lt;/li&gt;
&lt;li&gt;Darle nombre a la skill &amp;gt; el nombre que tendrá el directorio que se crea y la skill en el listado de skills de tu Alexa Developer Console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lEnmt8Ph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/04/Screenshot-2020-04-06-at-17.24.41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lEnmt8Ph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/04/Screenshot-2020-04-06-at-17.24.41.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la estructura básica que nos crea (yo he eliminado ficheros que no me interesaban) vamos a centrarnos en la parte de la lambda. Para que funcione correctamente tenemos que seguir dos pasos: configurar el proyecto para usar Typescript y configurar el despliegue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurar el proyecto
&lt;/h3&gt;

&lt;p&gt;Aquí debemos configurar todo para que se pueda escribir código Typescript que luego se transpilará a Javascript. Podría bastar con añadir la dependencia en el &lt;code&gt;package.json&lt;/code&gt; pero yo he usado &lt;a href="https://github.com/google/gts"&gt;GTS&lt;/a&gt;, una guía de estilo de Google, que además te configura &lt;a href="https://prettier.io/"&gt;prettier&lt;/a&gt;, &lt;a href="https://eslint.org/"&gt;eslint&lt;/a&gt;, etc. Además, si no tienes Typescript también te lo pone.&lt;/p&gt;

&lt;p&gt;Soy consciente de que la magia conlleva un precio y que las decisiones que tome Google sobre esta guía de estilo son cuestionables pero a mi, de momento, me vale. Lo único que hay que hacer dentro del directorio &lt;code&gt;lambda/custom&lt;/code&gt; es instalar GTS tal como viene en el readme del proyecto. Esto nos pondrá las dependencias necesarias, creará los ficheros de eslint y prettier, y creará algunos scripts para &lt;code&gt;npm&lt;/code&gt;. Además, creará un directorio &lt;code&gt;src&lt;/code&gt; donde pondrá un primer fichero &lt;code&gt;index.ts&lt;/code&gt;. Me gusta eso de separar los ficheros de "configuración", como el &lt;code&gt;package.json&lt;/code&gt; y tal, del código fuente.&lt;/p&gt;

&lt;p&gt;El siguiente paso es tan simple como llevar los ficheros Javascript que ya existían en el proyecto al directorio &lt;code&gt;src&lt;/code&gt; y cambiar su extensión a &lt;code&gt;ts&lt;/code&gt;. Con eso ya tenemos el backend en Typescrit! Esto es así porque Typescript no es más que un superset de Javascript aunque, probablemente, el código que ya existía no vaya a compilar :) Tendremos que arreglar los errores, basado en tipos en su mayoría, antes de poder generar el código final que será desplegado en la lambda.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurar el despliegue
&lt;/h3&gt;

&lt;p&gt;Partiendo del proyecto de cero no tenemos información de despliegue de ninguna AWS lambda. Eso lo podemos ver mirando el fichero &lt;code&gt;.ask/config&lt;/code&gt; que, en este punto, estará prácticamente vacío.&lt;/p&gt;

&lt;p&gt;Con el primer &lt;code&gt;ask deploy&lt;/code&gt; se va a crear todo lo necesario para usar AWS lambda como hosting del back. Para que todo funcione correctamente es necesario que tengas configuradas unas &lt;a href="https://developer.amazon.com/en-US/docs/alexa/smapi/manage-credentials-with-ask-cli.html#create-aws-credentials"&gt;credenciales AWS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Una vez finalice la ejecución del deploy se habrá modificado el fichero &lt;code&gt;.ask/config&lt;/code&gt; con la información referente a la lambda. Aquí simplemente debemos indicar el directorio donde estará el código Javascript que se generará desde Typescript. En mi caso, por defecto lo deja así GTS, estará en &lt;code&gt;lambda/custom/build/src&lt;/code&gt;. Esto es totalmente personalizable.&lt;/p&gt;

&lt;p&gt;Pero no basta solo con el código en ese directorio sino que harán falta el &lt;code&gt;package.json&lt;/code&gt; y las dependencias. Para esto &lt;a href="https://medium.com/alexa-tips/ask-deploy-con-typescript-34f87554e5f9"&gt;os recomiendo mirar el artículo de Javi Mora&lt;/a&gt;, que lo explica muy bien y nos enseña algunos scripts para &lt;code&gt;npm&lt;/code&gt; muy útiles. Es el artículo que tomé de referencia para toda esta parte del despliegue.&lt;/p&gt;




&lt;p&gt;Aunque no está exento de la típica burocracia de configuración, una vez que hemos realizado lo contado en este post, podremos usar Typescript como lenguaje de nuestro back tranquilamente :)&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>alexaskills</category>
      <category>typescript</category>
      <category>awslambda</category>
    </item>
    <item>
      <title>Probando nuestras skills con Alexa Skill Beta Testing</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Tue, 24 Mar 2020 09:25:35 +0000</pubDate>
      <link>https://dev.to/kini/probando-nuestras-skills-con-alexa-skill-beta-testing-213b</link>
      <guid>https://dev.to/kini/probando-nuestras-skills-con-alexa-skill-beta-testing-213b</guid>
      <description>&lt;p&gt;En este post voy a hablar de uno de los grandes desconocidos dentro de las herramientas que Amazon pone a nuestra disposición para probar Alexa skills: el Beta Testing. Es una pregunta que sale a menudo en charlas y talleres: ¿cómo puedo probar mi skill y hacer que otros la prueben sin tener que certificar? La respuesta en este post :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Beta Testing
&lt;/h2&gt;

&lt;p&gt;El concepto de Beta Testing no es nuevo y es usado por la industria del software desde siempre. Al final el concepto reside en poner a disposición de ciertos usuarios nuestro producto, ya sea una web, una aplicación mobile, una skill de Alexa, etc. pero orientado a recoger feedback, probar cosas, tener solo ciertas features habilitadas, etc. Hay muchas variantes y aspectos que se pueden tener en cuenta en un Beta Testing.&lt;/p&gt;

&lt;p&gt;Y para nuestras Alexa Skill tenemos también esta opción en dos sabores: &lt;a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/skills-beta-testing-for-alexa-skills.html"&gt;integrado en la consola de desarrolladores&lt;/a&gt;, la que veremos en este post, o en &lt;a href="https://developer.amazon.com/en-US/docs/alexa/smapi/skill-beta-testing-api.html"&gt;versión de API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alexa Beta Testing en la Alexa Developer Console
&lt;/h2&gt;

&lt;p&gt;La herramienta de beta testing de la consola nos va a permitir, como decíamos antes, probar nuestra skill antes de certificar poniéndola a disposición de los usuarios que nosotros decidamos.&lt;/p&gt;

&lt;p&gt;Al final puedes hacer lo que se denomina un Friends &amp;amp; Family, invitar a amigos y familiares, y que usen tu skill para que te den feedback y puedas recoger datos de esas interacciones. El límite son 500 invitaciones por skill y tienes la gestión completa de los testers.&lt;/p&gt;

&lt;p&gt;Para acceder a la herramienta de Beta Testing tienes que ir a la pestaña de "Distribution", para una skill concreta, en la Alexa Developer Consola. Una vez ahí tendrás que hacer click en la opción de "Availability". Esta claro que el equipo de Amazon tiene que hacer esto más visible ya que hay que navegar por varios sitios hasta encontrarlo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_09VoZ_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.20.38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_09VoZ_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.20.38.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para que una skill pueda optar a usar esta herramienta tiene que cumplir algunos criterios que se evalúan. Básicamente, si tu skill pasa los tests básicos para empezar el proceso de certificación, entonces, podrás usar Beta Testing. En la doc oficial dan detalles sobre los criterios específicos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s3LJCSik--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.20.52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s3LJCSik--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.20.52.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si la skill que quieres usar cumple los criterios, podrás acceder a la zona que se muestra en la anterior imagen. Desde ahí gestionarás:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El mail del administrador del Beta Testing que no tendría que ser la misma persona que esté desarrollando la skill.&lt;/li&gt;
&lt;li&gt;Añadir invitaciones usando el mail de los testers. Esto lo puedes añadir de diferentes formas. Ahí te pone lo que decía antes del límite de 500 invitaciones. Sobre el tema de no querer usar el mail de los testers o en caso de querer hacer esto más amplio a gente cuyo correo no conoces, una vez habilites el beta testing tendrás una URL que podrás compartir con quien quieras para que formen parte.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para habilitar la fase de beta testing tienes que incluir al menos un beta tester por mail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8RnN5s2u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.23.46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8RnN5s2u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.23.46.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una vez lo hagas verás cómo aparece una nueva tabla con los beta testers ya añadidos y podrás hacer cosas como mandarles un recordatorio o quitarlos. Además, en la parte superior, aparece ahora la opción de copiar una URL para invitar a gente a probar tu skill.&lt;/p&gt;

&lt;p&gt;La gente que use la invitación por URL tendrá que usar una cuenta asociada a un dispositivo Alexa. Esa cuenta se la puede crear en ese momento o usar una que ya tenga. Esta invitación se abre en la aplicación Alexa del móvil si usas la URL ahí o en la aplicación Alexa en el navegador. En cualquier caso saldrá una pantalla como la siguiente:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mA4xz6y2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.41.23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mA4xz6y2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.41.23.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y desde ahí podrás aceptar si quieres formar parte. Además te informa que si ya estabas usando una versión de esa skill empezarás a usar la versión en beta testing en las siguientes interacciones.&lt;/p&gt;

&lt;p&gt;Si añades los mails de los testers les llegará un correo con:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El link para habilitar la skill.&lt;/li&gt;
&lt;li&gt;Un correo en el cual van a poder dar feedback.&lt;/li&gt;
&lt;li&gt;Una sección de FAQ.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sY3Mx2Rv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-23-at-18.59.43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sY3Mx2Rv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-23-at-18.59.43.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una vez que un usuario acepta formar parte del beta testing cambiará su estado y las acciones para gestionar. En este punto lo más interesante es la opción de pedir feedback a los testers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4FGZ3GOj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.48.09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4FGZ3GOj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-05-at-20.48.09.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Con esa opción se le envía un correo al beta tester recordando que el desarrollador de la skill valora mucho su feedback y le pasa el correo directo del mismo para que se ponga en contacto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CdZFmFvJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-23-at-19.08.49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CdZFmFvJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-23-at-19.08.49.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Por último, podríamos borrar a un beta tester del programa y sería notificado, igual que en los pasos anteriores, vía correo electrónico.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4P7u0Xa5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-23-at-19.11.03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4P7u0Xa5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/03/Screenshot-2020-03-23-at-19.11.03.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Con este post quería mostrar una herramienta para poder poner a prueba nuestras skills sin tener que pasar por certificación. Es una opción que mucha gente parece desconocer porque me lo preguntan habitualmente. Creo que con un poco más de visibilidad esta herramienta sería muy usada por todos los developers.&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>betatesting</category>
      <category>alexaskills</category>
    </item>
    <item>
      <title>Gestión de atributos en una Alexa Skill (I)</title>
      <dc:creator>Joaquín Engelmo (a.k.a. Kini)</dc:creator>
      <pubDate>Wed, 26 Feb 2020 08:02:03 +0000</pubDate>
      <link>https://dev.to/kini/gestion-de-atributos-en-una-alexa-skill-i-5dcm</link>
      <guid>https://dev.to/kini/gestion-de-atributos-en-una-alexa-skill-i-5dcm</guid>
      <description>&lt;p&gt;En el anterior post hablé del concepto de sesión en Alexa y qué tipo de información podemos manejar entre peticiones. Sentar esa base me venía bien para comentar lo que veremos en este post. Parte de esa información que podemos manejar en la sesión son los "atributos de sesión" pero no son los únicos que el SDK nos ofrece.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tipos de atributos
&lt;/h2&gt;

&lt;p&gt;El SDK de Alexa nos permite gestionar, persistir y recuperar atributos en nuestra skill a varios niveles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Request:&lt;/strong&gt; Los atributos de request viven dentro del contexto de una petición al back de nuestra skill. Están inicialmente vacíos en cada petición y son descartados una vez se produce una respuesta al front. Pueden ser de utilidad usados conjuntamente con los interceptores de request para incluir información adicional por cada petición y así tenerla disponible en el handler que toque.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session:&lt;/strong&gt; Los atributos de sesión persisten dentro de la sesión de una skill hasta que se cierre. Todas las peticiones realizadas dentro de una misma sesión tienen acceso a estos atributos y viajan en el JSON de request/response. No usan un sistema de almacenamiento aparte y solo están disponibles si se opera dentro de esa sesión. Se descartan cuando la sesión en curso muere. Son especialmente válidos para mantener información sobre la interacción actual del usuario con la skill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistentes:&lt;/strong&gt; Los atributos persistentes son el siguiente paso y están disponibles en todo momento. El sistema de almacenamiento es configurable, así como la key que se usa entre otras cosas. Para poder usarlos hay que configurar de forma explícita un adaptador en nuestra skill. Sin ese paso obtendremos un error si intentamos hacer uso de ellos. Son la solución usada si necesitamos persistir información que se recuperará en futuros usos de la skill o que serán consultados por otros sistemas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Haciendo uso de estos atributos desde nuestro código
&lt;/h2&gt;

&lt;p&gt;Para poder gestionar esos atributos en el back de nuestra skill el SDK nos ofrece un &lt;code&gt;AttributesManager&lt;/code&gt;. Esta clase expone una serie de métodos que nos permiten recuperar y actualizar todos los tipos de atributos comentados antes.&lt;/p&gt;

&lt;p&gt;El acceso al &lt;code&gt;AttributesManager&lt;/code&gt; se hace vía el &lt;code&gt;HandlerInput&lt;/code&gt;. Veamos con código de ejemplo cómo se maneja cada uno de los tipos disponibles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request Attributes
&lt;/h3&gt;

&lt;p&gt;El uso de los atributos a nivel de request es muy sencillo. No es más que un mapa de clave/valor al cual se accede a través del &lt;code&gt;InputHanlder&lt;/code&gt;. Pongo aquí un código de ejemplo cualquiera porque yo no lo uso en las skills que tengo.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Session Attributes
&lt;/h3&gt;

&lt;p&gt;El manejo de los atributos de sesión es igual que lo visto para los de request. La diferencia radica en el tiempo de vida de cada uno. Los atributos de sesión estarán disponibles durante todo el tiempo que dure la sesión y viajan en cada ciclo de petición/respuesta.&lt;/p&gt;

&lt;p&gt;Para ver su uso voy a usar el código de la skill de ejemplo que preparé para la charla conjunta con Jordi y Diego "Asistentes de voz, una charla para dominarlos a todos". Pensamos en una skill que pudiera servir para ilustrar ciertas features de cada una de las plataformas (Google, Alexa y Microsoft). Lo que hicimos fue un ejemplo básico de aplicación de voz para pedir comida a domicilio.&lt;/p&gt;

&lt;p&gt;En esta skill necesitamos ir pidiendo al usuario cierta información dependiendo del contexto o estado en el cual se encuentra la interacción. Por ejemplo, el usuario no debería poder realizar el pedido final si antes no dijo qué comida quiere y a dónde hacer el pedido. Esos flujos de recogida de información los dividimos en distintos intents. Como si una máquina de estado (muy rudimentaria) se tratara usamos los atributos de sesión para persistir esa información e ir controlando el flujo de la skill.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;En el código vemos un &lt;code&gt;IntentHandler&lt;/code&gt; que recibe la comida que ha indicado el usuario y la guarda como atributo de sesión. Como decía antes el mecanismo es igual al de atributos de request pero cambiando el mapa donde guardamos ese valor. Eso hará que luego esa información persista a través de toda la sesión.&lt;/p&gt;

&lt;p&gt;Para recuperar ese valor solo tenemos que acceder a través del mapa. Siguiendo nuestro ejemplo, el usuario ya ha elegido el tipo de comida y el restaurante donde quiere pedirla. El último paso sería realizar la orden de pedido. Una de las características de las aplicaciones de voz es que no fuerzan un flujo de pasos secuenciales y el usuario podría haber llegado al último paso con cierto utterance. Para controlar esto podemos validar los atributos de sesión que tenemos en el handler, ya sea en el método que controla la ejecución, &lt;code&gt;canHandle&lt;/code&gt;, o en el método que lo procesa, &lt;code&gt;handle&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;En este código podemos ver el handler que realiza el pedido. Para su correcto funcionamiento necesita que el usuario ya haya indicado la comida que quiere y el restaurante. Eso lo controlamos mirando si tenemos esos valores en los atributos de sesión.&lt;/p&gt;

&lt;p&gt;Ahora veamos cómo viajan los atributos de sesión entre petición y respuesta. Para eso usaremos la información que nos da el simulador de la Alexa Developer Console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ebxKC0-s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/02/Screenshot-2020-02-22-at-12.50.37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ebxKC0-s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/02/Screenshot-2020-02-22-at-12.50.37.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;En la petición inicial no tenemos nada referente a los atributos de sesión. Tal y como contamos en el anterior post, la sesión se crea y se lanza un &lt;code&gt;LaunchRequest&lt;/code&gt; al back.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Y en la respuesta del back tampoco tenemos nada referente a los atributos. Aquí por ahora simplemente tenemos el texto que tiene que decir Alexa y el flag que indica si se debe cerrar la sesión.&lt;/p&gt;

&lt;p&gt;Sigamos evolucionando en el flujo de la skill y vamos a decirle qué nos apetece comer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9dsYRikI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/02/Screenshot-2020-02-22-at-12.56.27.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9dsYRikI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/02/Screenshot-2020-02-22-at-12.56.27.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;En la petición vemos cómo la sesión ya no es nueva y en la request llevamos el valor del slot correspondiente a la comida. Ese valor lo vamos a persistir en la sesión ya que nos hará falta para el paso final de la orden de pedido. Esto hará que sea parte de la siguiente respuesta al usuario.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Ahora ya ese valor será parte de la sesión durante el tiempo que ésta dure. Si avanzamos en el flujo de la skill veremos cómo aparece ya en la información de la request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1zs8X-Cu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/02/Screenshot-2020-02-22-at-12.55.21-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1zs8X-Cu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kinisoftware.com/content/images/2020/02/Screenshot-2020-02-22-at-12.55.21-1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;En el anterior JSON ya vemos que el tipo de comida elegido por el usuario anteriormente viaje entre peticiones. Así es como el back podrá hacer uso del valor tal como vimos en el código de los &lt;code&gt;IntentHandler&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Aún nos falta por ver los atributos persistentes pero este post ya se hizo demasiado largo y he decidido dividir en dos el contenido. En el próximo veremos cómo manejar información persistente y elegir entre diferentes tipos de almacenamiento :)&lt;/p&gt;

</description>
      <category>alexa</category>
      <category>alexaskills</category>
      <category>skillsession</category>
      <category>attributesmanagemen</category>
    </item>
  </channel>
</rss>
