Skip to content

UI接続

まずは最小のブラウザフローから始め、そのあと使っている UI フレームワークに 合わせて広げてください。

最小例

この例では、ボタンひとつで認識を開始し、認識された campaign name を コンソールに出力します。

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>

この例は ephemeral key endpoint がすでにある前提です。標準的なセットアップは Ephemeral Key を参照してください。

Advanced Examples

以下の例は、認識された campaign を UI に表示します。 React、Vue などの Vite ベースフレームワークを使う場合は、先に セットアップ の wasm ローダー設定を適用してください。

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>

注意点

  • マイク権限の要求は、ボタンクリックのようなユーザー操作から始めてください。
  • ページやコンポーネントが破棄される可能性がある場合は、cleanup 時に destroy() を呼んでください。
  • さらに複雑な制御フローが必要であれば、まずこの最小配線が動くことを確認してから stop() や追加イベントを足すのがよいです。