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

# Playback

> Embed recorded sessions in your reviewer-facing application

The playback module wires your application to Talview's hosted player so interviewers and reviewers can play back recorded sessions.

<Note>
  Read the main SDK [getting started guide](/sdk/getting-started) first — the loader script and `Proview.init()` are shared by every module and are loaded once per page.
</Note>

## 1. Declare the module

Add `Proview.playback()` to the `modules` array on `Proview.init()`. The `dsn` and `credential` declared at the top level are inherited — you do not repeat them.

```html theme={null}
<div id="playback-root"></div>

<script>
  window.proviewOnLoad = async function () {
    Proview.onError(function (err) {
      console.error('Proview SDK error', err);
    });

    const success = await Proview.init({
      dsn: 'YOUR_PROJECT_DSN',
      credential: Proview.auth.TokenAuthStrategy({
        token: 'session-token',
      }),
      modules: [
        Proview.playback(),
      ],
    });

    if (!success) return;

    const playback = Proview.playback.init({
      uuid: 'session-123',
      root: '#playback-root',
    });

    playback.on('ready', function () {
      console.log('Playback ready');
    });
  };
</script>

<!-- Load the SDK after defining the callback -->
<script
  src="https://sdk.tlv.cx"
  async
  crossorigin="anonymous"
></script>
```

## 2. Create a player

`Proview.playback.init()` creates one player instance. Call it once per session you want to play back.

```js theme={null}
const playback = Proview.playback.init({
  uuid: 'session-123',
  root: '#playback-root',
});
```

The mount target must already exist in the DOM when you call `init()`. In a component framework, call it from the effect that runs after render, not during it.

## 3. Unmount the player

Call `unmount()` to take the player back out of its `root` element.

```js theme={null}
playback.unmount();
```

This removes the player from the DOM and releases any handlers registered with `.on()`, so listeners need no separate cleanup.

The instance is finished once you unmount it — do not reuse the handle. To play another recording, call `Proview.playback.init()` again.

<Warning>
  `unmount()` has no effect when `target` is `"popout"` or `"newtab"`. Those open a separate browser window or tab, which the host page cannot close programmatically. Only the viewer can close them.
</Warning>

In a component framework, unmount from the cleanup path that pairs with wherever you called `init()`:

```jsx theme={null}
useEffect(() => {
  const playback = Proview.playback.init({
    uuid: 'session-123',
    root: '#playback-root',
  });

  return () => playback.unmount();
}, []);
```

## Authentication

Playback requires a credential. It will not mount without one — the [default strategy](/sdk/authentication#defaultauthstrategy) is not sufficient.

Pass a token strategy as `credential` on `Proview.init()`. Authentication is handled by the [auth module](/sdk/authentication), which loads automatically — you do not declare it in `modules`.

```js theme={null}
await Proview.init({
  dsn: 'YOUR_PROJECT_DSN',
  credential: Proview.auth.TokenAuthStrategy({
    token: 'session-token',
  }),
  modules: [
    Proview.playback(),
  ],
});
```

<Note>
  **Where the token comes from.** Talview provides the token API as part of your integration — the endpoint and the request format for generating a playback token scoped to a specific session. Contact Talview if you have not received these details.
</Note>

### Why the token is required

**The DSN identifies the project, not the viewer.** It tells Talview *which project* a request belongs to — not who is asking or what they are allowed to see.

**Playback serves back recorded session material.** Without a token, playback cannot know who is viewing or which recordings they should see. The generated token is how the viewer is authenticated and their access to the session material is confirmed.

### Handling tokens safely

* **Request the token from your backend, not the browser.** Your server calls the Talview token endpoint and passes the result to the page. Keeping that call server-side keeps the endpoint's own credential out of the browser.
* **Do your own authorization check first.** Talview issues the token; deciding whether this viewer should see this recording is your application's job.
* **Treat it like a session credential.** Keep it out of URLs, logs, and analytics payloads. Never ship a shared long-lived token to the browser.

For the full strategy list and how the default is chosen, see [Authentication](/sdk/authentication).

## `Proview.playback.init` Options

Pass a configuration object when invoking `Proview.playback.init()`. Each field is required unless noted otherwise.

* `uuid` – Identifier of the session (for example, `session-123`) you want to play back.
* `root` – **CSS selector** for the element the player mounts into when `target` is `"embed"` — for example `'#playback-root'`. Only ID selectors are supported today, but the value must still be a valid selector, so the leading `#` is required. The element must exist before you call `init()`. Not used when `target` is `"popout"` or `"newtab"`.
* `target` *(optional)* – Where the playback opens. Defaults to `"embed"`. Accepted values:
  * `"embed"` – renders the player inside the current page, mounted into `root`. Use `wrapper` to control the in-page layout.
  * `"popout"` – opens the player in a separate popup browser window.
  * `"newtab"` – opens the player in a new browser tab.
* `wrapper` *(optional)* – In-page layout for an embedded player. Only applies when `target` is `"embed"`; it is ignored for `"popout"` and `"newtab"`. Defaults to `"inline"`. Accepted values:
  * `"inline"` – mounts in the normal document flow inside `root`.
  * `"sidepanel"` – slides in as a panel from the side of the viewport.
  * `"modal"` – opens as a centered overlay dialog.
* `player_config` *(optional)* – Object grouping the player's sizing options:
  * `width` *(optional)* – Width of the player. Accepts a CSS length string (`"800px"`, `"90%"`, `"80vh"`) or a number, which is treated as pixels. Its effect depends on `target`/`wrapper` — see [Dimensions](#dimensions). Ignored when `target` is `"newtab"`.
  * `height` *(optional)* – Height of the player. Same value format as `width`. Ignored for `wrapper: "sidepanel"` (a side panel always spans the full viewport height) and when `target` is `"newtab"`. See [Dimensions](#dimensions).
* `session_config` *(optional)* – Object grouping view options for the playback session:
  * `report` *(optional, boolean)* – Controls whether the session report is shown on mount. Defaults to `true`. When `false`, the report is skipped and the player opens directly on the playback dashboard; the report is then unreachable (there is no "Back to Report" button).

### Placement: `target` vs `wrapper`

`target` and `wrapper` control two independent things:

* `target` decides **where** the player opens — inside the current page (`"embed"`), in a popup window (`"popout"`), or in a new tab (`"newtab"`).
* `wrapper` decides the **in-page layout** of an embedded player (`"inline"`, `"sidepanel"`, or `"modal"`), and therefore only has an effect when `target` is `"embed"`.

The player is always rendered inside an iframe — that is the delivery mechanism, not a configurable option, so there is no `"iframe"` value on either field. An embedded `"modal"`, for example, is a modal dialog whose contents are the player iframe.

### Dimensions

`width` and `height` (both under `player_config`) set the size of the player. Both are optional; when omitted the SDK falls back to sensible defaults for the chosen placement (auto-resizing to content or fitting the viewport). What each value controls depends on `target` and `wrapper`:

| `target` / `wrapper`  | `width`                                        | `height`                                                                                      |
| --------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `embed` + `inline`    | Width of the mounted player                    | Height of the mounted player. You can also size the `root` element yourself with CSS instead. |
| `embed` + `sidepanel` | Width of the side panel                        | Ignored — the panel always spans the full viewport height.                                    |
| `embed` + `modal`     | Preferred dialog width, capped to the viewport | Preferred dialog height, capped to the viewport.                                              |
| `popout`              | Popup window width                             | Popup window height.                                                                          |
| `newtab`              | Ignored                                        | Ignored.                                                                                      |

Notes:

* A number is treated as pixels (`player_config.width: 800` ≡ `player_config.width: "800px"`).
* `popout` opens a real browser window via `window.open`, whose size can only be expressed in pixels. Percentage or viewport units (`"90%"`, `"80vh"`) are ignored for `popout` and fall back to the default window size — pass pixel values instead.

## Events

Subscribe to player events with `.on(event, handler)` on the instance returned by `Proview.playback.init()`. Handlers are released when you [unmount the player](#3-unmount-the-player), so they need no separate cleanup.

| Event   | Payload                     | Fires when                                                             |
| ------- | --------------------------- | ---------------------------------------------------------------------- |
| `ready` | `{ sessionUuid, duration }` | The player iframe has bootstrapped and is ready to accept interaction. |
| `error` | [`PlaybackError`](#errors)  | The player could not load or continue.                                 |

```js theme={null}
const playback = Proview.playback.init({
  uuid: 'session-123',
  root: '#playback-root',
});

playback.on('ready', function (payload) {
  console.log('Playback ready', payload.sessionUuid, payload.duration);
});

playback.on('error', function (err) {
  console.error('Playback failed', err.code, err.message);
});
```

## Errors

Playback raises its own error codes rather than the SDK-wide ones.

### `PlaybackError`

Extends the standard SDK error object — see [Error Reference → The error object](/sdk/errors#the-error-object) for `code`, `type`, and `message`. Playback adds two fields:

| Field         | Type         | Description                                                                                   |
| ------------- | ------------ | --------------------------------------------------------------------------------------------- |
| `module`      | `'playback'` | Always `'playback'`. Lets a global handler tell playback errors apart from other modules'.    |
| `sessionUuid` | `string`     | The recording the error relates to. Identifies which player failed when several are embedded. |

### Error codes

**Recording lifecycle**

| Code                    | Meaning                                              | What to do                         |
| ----------------------- | ---------------------------------------------------- | ---------------------------------- |
| `RECORDING_NOT_FOUND`   | No recording with this `uuid` exists in the project. | Check the `uuid`. Do not retry.    |
| `RECORDING_UNAVAILABLE` | The recording exists but cannot be served right now. | Retry with backoff, then escalate. |

**Credential**

| Code            | Meaning                                        | What to do                                                                                       |
| --------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `TOKEN_INVALID` | The token is malformed or could not be parsed. | Check how your backend is requesting and forwarding the token. Do not retry with the same token. |
| `TOKEN_EXPIRED` | The token was valid but has passed its expiry. | Request a fresh token and re-initialize. See [Authentication](#authentication).                  |

**Mount**

| Code                 | Meaning                                       | What to do                                                                                               |
| -------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `ROOT_INVALID`       | `root` is not a valid CSS selector.           | Pass a selector, not a bare id — `'#playback-root'`, not `'playback-root'`. See the `root` option above. |
| `ROOT_NOT_FOUND`     | The selector is valid but matched no element. | Ensure the element is in the DOM before you call `init()`.                                               |
| `PLAYER_LOAD_FAILED` | The player iframe could not load.             | Check network and CSP. See [Troubleshooting](/sdk/troubleshooting).                                      |

### Handling errors

```js theme={null}
playback.on('error', function (err) {
  switch (err.code) {
    case 'RECORDING_NOT_FOUND':
      showMessage('This recording could not be found.');
      break;

    case 'ROOT_NOT_FOUND':
      console.error('Mount target not found:', err.message);
      break;

    default:
      showMessage('The player could not be loaded.');
  }
});
```

## Examples

### Open directly on the playback dashboard

Skips the report and opens the playback dashboard on mount. The report is not reachable.

```js theme={null}
const playback = Proview.playback.init({
  uuid: 'session-123',
  root: '#playback-root',
  session_config: {
    report: false,
  },
});
```

### Embed with custom dimensions

Sizes the embedded player via `player_config`.

```js theme={null}
const playback = Proview.playback.init({
  uuid: 'session-123',
  root: '#playback-root',
  player_config: {
    width: '800px',
    height: '600px',
  },
});
```

### Open in a side panel

```js theme={null}
const playback = Proview.playback.init({
  uuid: 'session-123',
  root: '#playback-root',
  wrapper: 'sidepanel',
  player_config: {
    width: '640px',
  },
});
```
