Guided Scripted Practice

Presenter mode with predetermined avatar lines and host-controlled learner turns.

When to use this pattern

Use guided scripted practice when avatar lines are predetermined (lessons, drills, rehearsal) and the host app owns turn flow and feedback. The avatar speaks scripted lines via speak(); learner speech is captured between explicit Start and Stop controls; scoring or hints stay in your UI — not in the avatar conversation output.

Session setup

In Svelte, use the lifecycle-owning <Experience> component with presenter mode and manual speech input. Bind its typed handle for scripted speech and explicit Start/Stop controls. The player-owned start button unlocks audio and requests the microphone once.

+page.svelte
<script lang="ts">
  import {
    Experience,
    type ExperienceHandle
  } from '@liforma/client/svelte';

  let experience: ExperienceHandle | undefined = $state();
  let feedback = $state('');
  let audioUnlocked = $state(false);

  async function playTutorLine() {
    audioUnlocked = true;
    await experience?.speech.speak({ text: 'Read this sentence aloud.' });
  }

  async function startAnswer() {
    if (!experience) return;
    await experience.startListening();
  }

  async function finishAnswer() {
    if (!experience) return;
    const utterance = await experience.stopListening();
    feedback = `You said: ${utterance.text}`;
  }
<\/script>

<Experience
  bind:this={experience}
  experienceId="exp_T0I7ACMQLBMPG6K"
  mode="presenter"
  speechInputMode="manual"
  onStarted={playTutorLine}
/>

<button disabled={!audioUnlocked || !experience} onclick={startAnswer}>Start</button>
<button disabled={!audioUnlocked || !experience} onclick={finishAnswer}>Stop</button>
<p>{feedback}</p>

onStarted is the safe point to speak because audio is unlocked. The earlier onReady callback only means the Session Launch is resolved and the player is attached. Calls to speak(), startListening(), stopListening(), and listenOnce() preserve the root API's state checks and do not queue until startup.

Turn loop

Typical sequence for each scripted line:

  1. await experience.speech.speak({ text: line }) — character delivers the tutor line
  2. await experience.startListening() — learner taps Start
  3. await experience.stopListening() — learner taps Stop; pauses do not finalize
  4. Run host-side feedback (pronunciation, rubric, etc.) on utterance.text
  5. Wait for your Next control, then repeat with the next line

The component owns session creation, attachment, callback subscriptions, restart, and cleanup. Keep lesson turn state, transcript display, and feedback in the page.

Vanilla JavaScript

For framework-neutral integrations, use the imperative class and subscribe to its events directly.

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

experience.on('started', async () => {
  await experience.speech.speak({ text: 'Welcome to the lesson.' });
});

await experience.attach({ container: '#avatar' });
async function runPracticeTurn(line) {
  await experience.speech.speak({ text: line });
  await experience.startListening();
}

async function finishPracticeTurn(line) {
  const utterance = await experience.stopListening();
  const feedback = await getReadingFeedback({
    expectedText: line,
    spokenText: utterance.text
  });
  showFeedback(feedback);
  // Host Next button calls runPracticeTurn(nextLine).
}

History and events

Scripted assistant lines and finalized user utterances appear in getConversation(). Feedback objects are application state unless you store them separately. In Svelte, use callback props such as onCharacterSpeechStarted, onCharacterSpeechEnded, onUserTranscript, onUserSpeechStarted, onUserSpeechEnded, and onListeningState. See Events.

For automatic capture without Start/Stop buttons, see Listen Once Capture. For browser-owned replies after each utterance, see Custom Conversation Processor.

Runnable examples

Clone and run the guided-practice examples from examples.liforma.ai:

  • Vanilla JavaScript — port 4003 with ./start
  • SvelteKit./start sveltekit (basic embed on 4001, Spanish Tutor on 4002, guided practice on 4003)

Full API detail: Experience API speech section.