Listen Once Capture

Automatic end-of-speech capture for host-owned turn loops.

When to use this pattern

Use listenOnce() when your app owns the turn loop but you want automatic end-of-speech detection instead of Start/Stop buttons. Good fits: short quiz answers, voice form fields, booking intake, or presenter sessions where the avatar asks a question and waits for one reply.

For deliberate pauses mid-utterance (reading practice, long rehearsal), use manual startListening() / stopListening() instead — see Guided Scripted Practice.

Requirements

  • speechInputMode: 'auto' on Experience.startSession()
  • attach() completed and started event fired
  • Only one active listenOnce() at a time

Basic flow

const experience = await Experience.startSession({
  experienceId: 'exp_01DEMO1SPANISHCAFE',
  mode: 'presenter',
  speechInputMode: 'auto'
});

experience.on('started', async () => {
  await experience.speak({ text: 'What is your party size?' });
  const answer = await experience.listenOnce({ timeoutMs: 15_000 });
  console.log(answer.utteranceId, answer.text);
});

await experience.attach({ container: '#avatar' });

Quiz coach loop

Combine speak() and listenOnce() for a simple question-and-answer loop. Grade on the host; feedback stays in your UI unless you call speak() again.

async function runQuizQuestion(question, expectedKeyword) {
  await experience.speak({ text: question });
  const answer = await experience.listenOnce({ timeoutMs: 20_000 });
  const correct = answer.text.toLowerCase().includes(expectedKeyword);
  await experience.speak({
    text: correct ? 'Correct!' : 'Not quite — try the next one.',
    behavior: 'interrupt'
  });
}

Options and failures

OptionBehavior
timeoutMsReject if no finalized utterance within the window (default 30 seconds)
signalAbort the wait; rejects with AbortError

Timeout, abort, and permission failures reject without adding a final user message to conversation history.

Events vs listenOnce()

userSpeechStarted and userSpeechEnded are ambient VAD telemetry — useful for mic indicators. listenOnce() is a scoped promise that resolves with finalized text when your code is explicitly waiting for one answer. You can use both: events for UI, listenOnce() for turn logic.

For live captions while the user speaks, subscribe to userTranscript with partial revisions — see Events.

Custom processor alternative

If Liforma should orchestrate listen → respond → speak automatically after every user utterance, use conversationProcessor instead of hand-writing listenOnce() loops. See Custom Conversation Processor.