# Pause the avatar for video/interactive content, then resume

How to pause a live avatar conversation while you show a video, quiz, or any other on-screen content, then hand the turn back to the avatar cleanly. This covers both the happy path (content plays to completion) and the case where it's skipped or never finishes (don't leave the avatar stuck paused). Built entirely from two existing `KalturaAvatarSession` methods, with no new SDK surface and no server-side glue.

---

## The mechanism

Two methods on a connected `KalturaAvatarSession` (see [Wire Protocol](https://kaltura.github.io/intelligent-agents-sdk/reference/wire-protocol/index.md)'s `pauseConversation`/`resumeConversation` for the wire shape):

```js
session.pause();          // sync: stops the turn loop
await session.resume();   // async: hands the turn loop back
```

**`pause()`** sets `session.paused = true` and tells the server to stop the turn loop (`pauseConversation`). It's synchronous, so there's nothing to await. It throws `invalid_state` unless the session is connected. A `speak()` call made while paused is accepted client-side (no throw), but the server produces no reply. The avatar simply doesn't respond until you resume. Don't drive the avatar with `speak()`/ASR while your content is on screen; that's on your app, the SDK doesn't block it for you.

**`resume()`** always sets `session.paused = false` immediately, then takes the right path for how the pause played out. There are two common paths, depending on how long you were paused (a third, rarer one is covered below):

- **Short pause (the common case):** `resume()` sends `resumeConversation` and returns. It's near-instant.
- **Long pause (the pause window ran out):** if the window expired before you called `resume()`, the SDK rebuilds the audio (and, in video mode, video) connection before it hands the turn loop back. This takes as long as a fresh connect, not the near-instant short path.

**You never need to branch on which path it takes.** Always just `await session.resume()`. It picks the right one for you. The length of the pause window isn't a published constant. Don't rely on an exact number. Always resume via one of the triggers below, rather than assuming a pause lasts as long as you need it to.

**On a connected session, `resume()` is safe in every case that matters for this recipe:** immediately after `pause()` (zero delay), when the session was never paused, and when it was already resumed. On a session that is not connected it rejects with `invalid_state`.

It's also safe when the SDK's own connectivity recovery rebuilt the session *while* you were paused. A stalled or expired media channel makes the SDK rebuild the transports on its own, without your app calling `resume()` (see [Wire Protocol](https://kaltura.github.io/intelligent-agents-sdk/reference/wire-protocol/index.md) for the recovery events). Your pause survives that rebuild. The SDK keeps `session.paused` true and *holds* the rebuilt session's start signal instead of letting the avatar speak over your content.

The moment you call `resume()`, the SDK releases that held signal: the rebuilt session starts and the avatar speaks its opening line. (No `resumeConversation` is sent on this path, since the fresh session was never paused server-side. There's nothing to "resume," only a start to release.)

In every one of these cases, `resume()` returns cleanly with `session.paused === false` and never hangs. That's why the edge-case handling below is just "call `resume()` from every exit path, unconditionally."

`resume()` can still reject for reasons unrelated to pause duration (a genuinely dead session, a real network failure mid-rebuild). Treat any rejection defensively regardless: catch it, and if `session.state` is still `'connected'`, the session is fine and there's nothing further to do. If the long-path rebuild itself fails, the session is never left half-connected: `resume()` rejects with the rebuild error, and the session emits `error` and then `ended {reason:'resume_failed'}` with `session.state === 'disconnected'`. Call `connect()` again to start over.

You don't need to listen for any event to know resume worked. `await session.resume()` resolving is the only signal your app needs. Two events exist for optional UX polish, but neither is required:

| Event | When it fires | Use it for |
|---|---|---|
| `'resumed'` | The server's own `conversationResumed` ack arrives, after a `resumeConversation` you sent from your own `resume()` call. (One path sends no `resumeConversation`, since it releases a session the SDK rebuilt mid-pause, so no `'resumed'` fires there. The promise resolving is still your signal.) | Optional confirmation toast/log. `resume()`'s promise already told you it's done. |
| `'resumeReady'` / `'timeExpired'` (`{type:'pause_expiry'}`) | The pause window expired *while you were still paused, before you called `resume()`* | Optional "still there?" UI while paused long. Not required: `resume()` handles this transparently whenever you do call it. |

---

## Minimal runnable example

Plain HTML/JS, no build step. This matches the pattern in `examples/browser-experience.html`. Assumes a server endpoint `/appInit` that calls `Management.application.appInit()` for you (see [Getting Started](https://kaltura.github.io/intelligent-agents-sdk/getting-started/index.md) and [Widget & Runtime Init § Initialize the Runtime](https://kaltura.github.io/intelligent-agents-sdk/reference/api/deploy/index.md#initialize-the-runtime)).

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Pause for video, then resume</title>
</head>
<body>
  <div id="avatar"></div>

  <button id="play">▶ Show video</button>
  <div id="overlay" hidden>
    <video id="clip" src="/your-video.mp4" controls></video>
    <button id="skip">Skip</button>
  </div>

  <!-- socket.io is YOUR dependency, injected, never bundled by the SDK. -->
  <script src="https://cdn.socket.io/4.7.5/socket.io.min.js" integrity="sha384-2huaZvOR9iDzHqslqwpR87isEmrfxqyWOF7hr7BY6KG0+hVKLoEXMPUJw3ynWuhO" crossorigin="anonymous"></script>
  <script type="module">
    import { KalturaAvatarSession } from '@kaltura/intelligent-agents/experience';

    const init = await fetch('/appInit').then((r) => r.json());

    const video = document.createElement('video');
    video.autoplay = true; video.playsInline = true;
    document.getElementById('avatar').appendChild(video);

    const session = new KalturaAvatarSession({
      token: init.ks,
      conversationManagerUrl: init.conversationManagerUrl,
      srsBaseUrl: init.srsBaseUrl,
      turnServerUrl: init.turnServerUrl,
      videoEl: video,
      socketFactory: (url, opts) => io(url, opts),
    });
    await session.connect();

    const overlay = document.getElementById('overlay');
    const clip = document.getElementById('clip');

    document.getElementById('play').onclick = () => {
      // 1) Pause BEFORE the content starts. The avatar goes quiet immediately.
      session.pause();
      overlay.hidden = false;
      clip.currentTime = 0;
      clip.play();

      // 2) Resume from EVERY exit path, exactly once. resume() is safe to call
      //    more than once (or when not paused at all). This guard just avoids
      //    a redundant resumeConversation emit, not a correctness requirement.
      let resumed = false;
      const resumeOnce = () => {
        if (resumed) return;
        resumed = true;
        overlay.hidden = true;
        session.resume().catch((e) => {
          // Defensive: resume() can still reject for reasons unrelated to this recipe (a
          // genuinely dead session, a network failure mid-rebuild). Check session.state
          // before treating it as fatal.
          if (session.state !== 'connected') console.error('resume failed', e);
        });
      };

      clip.addEventListener('ended', resumeOnce, { once: true });   // happy path: content finished
      clip.addEventListener('error', resumeOnce, { once: true });   // content failed to load
      document.getElementById('skip').onclick = resumeOnce;          // user skipped it
      setTimeout(resumeOnce, 60_000);                                 // safety net: never leave the avatar stuck paused
    };
  </script>
</body>
</html>
```

That's the whole recipe: one `pause()` call before the content shows, one `resume()` call wired to every way the content can end (finished, failed, skipped, or simply taking too long). No custom protocol handling, no state machine of your own to build.

---

## The edge case: don't leave the avatar stuck paused

The issue this recipe answers isn't "how do I pause." `pause()` alone is trivial. It's "what if the content never cleanly finishes." Four ways that happens, and why each is already covered above:

| What happens | Why `resumeOnce()` still fires |
|---|---|
| Content plays to the end | `ended` |
| Content fails to load (404, codec error, network) | `error` |
| User clicks away / hits a "skip" control | your own `skip` handler |
| Content hangs, or you simply forget to wire an end event | the `setTimeout` safety net |

Whichever path fires, it calls the same `resume()`. `resume()` handles the cheap path, the rebuild-transports path, and a session the SDK's own connectivity recovery already rebuilt during the pause, all transparently. A short safety-net `setTimeout` (the example above uses 60 seconds) is still good practice: it hands the turn loop back promptly instead of leaving the avatar paused indefinitely if nothing else fires.

---

## Related docs

| Doc | What it adds |
|---|---|
| [Client-Side Commands](https://kaltura.github.io/intelligent-agents-sdk/guides/client-commands/index.md) | The sibling mechanism for letting the avatar drive *your* UI (as opposed to this recipe, where *you* drive the avatar's pause state) |
| [Wire Protocol](https://kaltura.github.io/intelligent-agents-sdk/reference/wire-protocol/index.md) | The exact `pauseConversation`/`resumeConversation`/`pauseSessionExpired`/`sessionReadyForResume`/`conversationResumed` wire shapes |
| [Getting Started](https://kaltura.github.io/intelligent-agents-sdk/getting-started/index.md) | Where `/appInit` and the live avatar session come from in the first place |
| [examples/browser-experience.html](https://github.com/kaltura/intelligent-agents-sdk/blob/main/examples/browser-experience.html) | The base live-avatar page this recipe extends |

