Skip to content

Conexión con la interfaz de usuario ​

Comience con el flujo de navegador más sencillo posible y, a continuación, adáptelo a su framework de interfaz de usuario.

Ejemplo mínimo ​

Este ejemplo inicia el reconocimiento mediante un único botón y registra el nombre de la campaña reconocida.

html
<button id="start-button" type="button">Start Recognition</button>

<script type="module">
  import { AudioRecognizer } from "@sorisdk/web-audio";

  const startButton = document.querySelector("#start-button");

  const recognizer = new AudioRecognizer({
    appId: "YOUR_APP_ID",
    ephemeralKey: async () => {
      const response = await fetch("/api/ephemeral-key", {
        method: "POST"
      });

      if (!response.ok) {
        throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
      }

      const { ephemeral_key } = await response.json();
      return ephemeral_key;
    }
  });

  recognizer.on("campaign", (event) => {
    console.log(event.campaign.name);
  });

  startButton?.addEventListener("click", async () => {
    await recognizer.start();
  });
</script>

Este ejemplo presupone que ya dispone de un endpoint de claves efímeras. Consulte Clave efímera para conocer la configuración estándar.

Conciliar el segmento de actividad actual ​

El Web SDK gestiona el transporte de refinamiento de la actividad del servidor, pero la aplicación sigue siendo responsable de la cronología renderizada. Utilice el flujo público de eventos en orden de llegada:

  • El evento match expone best.name. Otro nombre de material crea un límite inmediato, aunque ese match no produzca una tarjeta de campaña.
  • Muestre inmediatamente el primer evento campaign. Este expone event.activityId, event.match.name, event.match.position, y event.campaign.
  • Una campaña posterior con el mismo event.activityId no vacío solo puede refinar la fila actual. Si el ID de actividad es null, el fallback mediante el nombre del material solo es seguro para esa fila actual; nunca busque en todo el historial.
  • Conserve la hora de la primera fila y event.match.position. Combine el primer marker y los campos de campaña refinados para que un marker ausente o un resultado posterior menos completo no degrade la fila.
  • Un evento audiomarker es un estado de marker observable de forma independiente y no refina por sí solo una fila de campaña. Otro match, nomatch, stop o destroy cierra el segmento actual.

Lea el ID y el nombre del marcador resueltos por SORI Console en event.marker.id y event.marker.name.

Por tanto, A(activity-1) -> A'(activity-1, marker) -> A(activity-1, marker miss) es una sola fila. A(activity-1) -> B(activity-2) -> delayed A'(activity-1) mantiene B como actual e ignora la modificación retrasada de la A histórica.

Ejemplos avanzados ​

Los siguientes ejemplos muestran las campañas reconocidas en la interfaz de usuario. Si utiliza React, Vue u otro framework basado en Vite, configure primero el cargador Wasm en Configuración.

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SORI Web SDK</title>
    <style>
      #campaign-list {
        padding: 0;
        list-style: none;
      }

      #campaign-list li + li {
        margin-top: 8px;
      }
    </style>
  </head>
  <body>
    <main>
      <button id="start-button" type="button">Start Recognition</button>
      <ul id="campaign-list"></ul>
    </main>

    <script type="module">
      import { AudioRecognizer } from "@sorisdk/web-audio";

      const startButton = document.querySelector("#start-button");
      const campaignList = document.querySelector("#campaign-list");

      const recognizer = new AudioRecognizer({
        appId: "YOUR_APP_ID",
        ephemeralKey: async () => {
          const response = await fetch("/api/ephemeral-key", {
            method: "POST"
          });

          if (!response.ok) {
            throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
          }

          const { ephemeral_key } = await response.json();
          return ephemeral_key;
        }
      });

      recognizer.on("campaign", (event) => {
        console.log(event.campaign.name);
        if (campaignList) {
          const item = document.createElement("li");
          const link = document.createElement("a");

          link.href = String(event.campaign.action_url);
          link.textContent = String(event.campaign.title);
          link.target = "_blank";
          link.rel = "noreferrer";

          item.append(link);
          campaignList.prepend(item);
        }
      });

      startButton?.addEventListener("click", async () => {
        await recognizer.start();
      });

      globalThis.addEventListener("beforeunload", () => {
        void recognizer.destroy();
      });
    </script>
  </body>
</html>
tsx
import { useEffect, useRef, useState } from "react";
import { AudioRecognizer } from "@sorisdk/web-audio";

type CampaignCard = {
  title: string;
  imageUrl: string;
  actionUrl: string;
};

export default function App() {
  const recognizerRef = useRef<AudioRecognizer | null>(null);
  const [campaigns, setCampaigns] = useState<CampaignCard[]>([]);

  useEffect(() => {
    const recognizer = new AudioRecognizer({
      appId: "YOUR_APP_ID",
      ephemeralKey: async () => {
        const response = await fetch("/api/ephemeral-key", {
          method: "POST"
        });

        if (!response.ok) {
          throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
        }

        const { ephemeral_key } = await response.json();
        return ephemeral_key;
      }
    });

    recognizer.on("campaign", (event) => {
      console.log(event.campaign.name);
      setCampaigns((current) => [
        {
          title: String(event.campaign.title),
          imageUrl: String(event.campaign.image_url),
          actionUrl: String(event.campaign.action_url)
        },
        ...current
      ]);
    });

    recognizerRef.current = recognizer;

    return () => {
      void recognizer.destroy();
      recognizerRef.current = null;
    };
  }, []);

  return (
    <main>
      <button
        type="button"
        onClick={() => {
          void recognizerRef.current?.start();
        }}
      >
        Start Recognition
      </button>
      <div>
        {campaigns.map((campaign, index) => (
          <a
            key={`${campaign.title}-${index}`}
            href={campaign.actionUrl}
            target="_blank"
            rel="noreferrer"
          >
            <img src={campaign.imageUrl} alt={campaign.title} />
            <strong>{campaign.title}</strong>
          </a>
        ))}
      </div>
    </main>
  );
}
vue
<script setup lang="ts">
import { onBeforeUnmount } from "vue";
import { ref } from "vue";
import { AudioRecognizer } from "@sorisdk/web-audio";

const campaigns = ref<
  Array<{ title: string; imageUrl: string; actionUrl: string }>
>([]);

const recognizer = new AudioRecognizer({
  appId: "YOUR_APP_ID",
  ephemeralKey: async () => {
    const response = await fetch("/api/ephemeral-key", {
      method: "POST"
    });

    if (!response.ok) {
      throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
    }

    const { ephemeral_key } = await response.json();
    return ephemeral_key;
  }
});

recognizer.on("campaign", (event) => {
  console.log(event.campaign.name);
  campaigns.value.unshift({
    title: String(event.campaign.title),
    imageUrl: String(event.campaign.image_url),
    actionUrl: String(event.campaign.action_url)
  });
});

async function startRecognition() {
  await recognizer.start();
}

onBeforeUnmount(() => {
  void recognizer.destroy();
});
</script>

<template>
  <main>
    <button type="button" @click="startRecognition">Start Recognition</button>
    <div class="campaign-grid">
      <a
        v-for="campaign in campaigns"
        :key="`${campaign.actionUrl}-${campaign.title}`"
        :href="campaign.actionUrl"
        target="_blank"
        rel="noreferrer"
        class="campaign-card"
      >
        <img :src="campaign.imageUrl" :alt="campaign.title" />
        <strong>{{ campaign.title }}</strong>
      </a>
    </div>
  </main>
</template>

<style scoped>
.campaign-grid {
  display: grid;
  gap: 12px;
  margin-top: 16px;
}

.campaign-card {
  display: grid;
  gap: 8px;
  color: inherit;
  text-decoration: none;
}
</style>
svelte
<script lang="ts">
  import { onMount } from "svelte";
  import { AudioRecognizer } from "@sorisdk/web-audio";

  let campaigns: Array<{ title: string; imageUrl: string; actionUrl: string }> = [];
  let recognizer: AudioRecognizer;

  onMount(() => {
    recognizer = new AudioRecognizer({
      appId: "YOUR_APP_ID",
      ephemeralKey: async () => {
        const response = await fetch("/api/ephemeral-key", {
          method: "POST"
        });

        if (!response.ok) {
          throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
        }

        const { ephemeral_key } = await response.json();
        return ephemeral_key;
      }
    });

    recognizer.on("campaign", (event) => {
      console.log(event.campaign.name);
      campaigns = [
        {
          title: String(event.campaign.title),
          imageUrl: String(event.campaign.image_url),
          actionUrl: String(event.campaign.action_url)
        },
        ...campaigns
      ];
    });

    return () => {
      void recognizer.destroy();
    };
  });

  async function startRecognition() {
    await recognizer.start();
  }
</script>

<main>
  <button type="button" on:click={startRecognition}>Start Recognition</button>
  <div class="campaign-grid">
    {#each campaigns as campaign}
      <a
        href={campaign.actionUrl}
        target="_blank"
        rel="noreferrer"
        class="campaign-card"
      >
        <img src={campaign.imageUrl} alt={campaign.title} />
        <strong>{campaign.title}</strong>
      </a>
    {/each}
  </div>
</main>

<style>
  .campaign-grid {
    display: grid;
    gap: 12px;
    margin-top: 16px;
  }

  .campaign-card {
    display: grid;
    gap: 8px;
    color: inherit;
    text-decoration: none;
  }
</style>

Notas ​

  • Solicite acceso al micrófono únicamente a partir de una acción del usuario, como hacer clic en un botón.
  • Si la página o el componente pueden eliminarse, llame a destroy() durante la limpieza.
  • Si necesita un flujo de control más completo, añada stop() y otros handlers de eventos una vez que esta conexión mínima funcione.