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()や追加イベントを足すのがよいです。
