> ## Documentation Index
> Fetch the complete documentation index at: https://docs.responsivevoice.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Events

## Event System

ResponsiveVoice gives you two ways to observe speech:

- **Per-call callbacks** — passed inline to `speak()`, bound to that one utterance, and reset on the next `speak()`.
- **Global events** — registered once with `addEventListener()`, firing across every `speak()` call.

> [!TIP]
> Try the [**live Events & Callbacks demo**](https://examples.responsivevoice.org/browser/events-callbacks/) — every per-call callback and global event lights up as you click Speak. The companion [docs page](/examples/events-callbacks) explains the design.

## Per-call callbacks

Pass these in the options object of `speak(text, voice?, options?)`. They belong to a single utterance.

| Callback     | Signature                                   | Fires                                          |
| ------------ | ------------------------------------------- | ---------------------------------------------- |
| `onstart`    | `() => void`                                | speech begins                                  |
| `onend`      | `() => void`                                | speech completes                               |
| `onerror`    | `(error: Error) => void`                    | an error occurs                                |
| `onboundary` | `(charIndex: number, name: string) => void` | crosses a word/sentence boundary (native only) |

```typescript
rv.speak('Hello world', 'UK English Female', {
  onstart: () => console.log('Started speaking'),
  onend: () => console.log('Finished speaking'),
  onerror: (error) => console.error('Error:', error.message),
  onboundary: (charIndex, name) => {
    console.log(`Boundary (${name}) at character ${charIndex}`);
  },
});
```

> [!NOTE]
> `onboundary` only fires with native browser voices, not the fallback (server) audio engine.

## Global events

Register once with `addEventListener(name, handler)`; they fire across every `speak()` call. Event names are PascalCase:

| Event                  | Payload                           | Fires                                       |
| ---------------------- | --------------------------------- | ------------------------------------------- |
| `OnReady`              | —                                 | client initialized and ready                |
| `OnLoad`               | —                                 | alias of `OnReady` (legacy)                 |
| `OnStart`              | —                                 | an utterance starts                         |
| `OnEnd`                | —                                 | an utterance ends                           |
| `OnPause`              | —                                 | speech is paused                            |
| `OnResume`             | —                                 | speech resumes                              |
| `OnError`              | `{ error, message? }`             | an error occurs                             |
| `OnVoiceResolved`      | voice-resolution details          | a voice is resolved for an utterance        |
| `OnServiceSwitched`    | `{ from, to }`                    | engine switches between native and fallback |
| `OnPartStart`          | `{ partIndex, totalParts, text }` | a text chunk starts speaking                |
| `OnPartEnd`            | `{ partIndex, totalParts, text }` | a text chunk finishes                       |
| `OnClickEvent`         | —                                 | a user gesture (click) is detected          |
| `OnAllowSpeechClicked` | `{ allowed }`                     | user responds to the permission prompt      |

```typescript
rv.addEventListener('OnStart', () => console.log('Speech started'));
rv.addEventListener('OnPause', () => console.log('Speech paused'));
rv.addEventListener('OnResume', () => console.log('Speech resumed'));
```

Pause and resume are **only** global events — they are not per-call callbacks. See the [`RVEventType` reference](/api/types/src/#rveventtypeschema) for the complete list of event names.

### Removing listeners

Pass the same handler reference you registered:

```typescript
const handler = () => console.log('Speech started');

rv.addEventListener('OnStart', handler);
rv.removeEventListener('OnStart', handler);
```

## Error handling

The per-call `onerror` callback receives a standard [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error). The `OnError` event delivers a payload `{ error, message? }` whose `error` is that same `Error`:

```typescript
rv.speak('Hello', 'UK English Female', {
  onerror: (error) => {
    console.error('Speech failed:', error.message);
  },
});
```

## Async/await pattern

Wrap `speak()` in a Promise using `onend` and `onerror`:

```typescript
function speakAsync(text: string, voice: string): Promise<void> {
  return new Promise((resolve, reject) => {
    rv.speak(text, voice, {
      onend: () => resolve(),
      onerror: (error) => reject(error),
    });
  });
}

// Usage
async function readParagraphs(paragraphs: string[]) {
  for (const paragraph of paragraphs) {
    await speakAsync(paragraph, 'UK English Female');
  }
  console.log('All paragraphs read');
}
```

## Progress tracking UI

Use `onboundary` to update a progress bar as speech advances (native voices only):

```typescript
let startTime: number;

rv.speak(longText, 'UK English Female', {
  onstart: () => {
    startTime = Date.now();
    progressBar.style.width = '0%';
  },
  onboundary: (charIndex) => {
    const progress = (charIndex / longText.length) * 100;
    progressBar.style.width = `${progress}%`;
  },
  onend: () => {
    progressBar.style.width = '100%';
    console.log(`Completed in ${Date.now() - startTime}ms`);
  },
});
```

## Queue events

Each `speak()` call carries its own callbacks:

```typescript
rv.speak('First sentence', 'UK English Female', {
  onend: () => console.log('First done, starting second'),
});

rv.speak('Second sentence', 'UK English Female', {
  onstart: () => console.log('Second starting'),
  onend: () => console.log('Queue complete'),
});
```
