# Ad controller

Source: https://docs.open.video/docs/roku-sdk/ad-controller/


`OpenVideo:OpenVideoAdController` is the render-thread playback and ad state machine. It owns your `Video` node's playback fields, stream resolution, ad break routing, bookmarks, the app session and video-start analytics. It does **not** own focus, trick-play chrome, end cards, dialogs, layout, or the screen you return to after playback.

Create it only after the library reports `loadStatus = "ready"`:

```brightscript
controller = CreateObject("roSGNode", "OpenVideo:OpenVideoAdController")
controller.player = m.video
controller.config = m.cfg
controller.callFunc("startSession")
```

## Input fields

| Field | Type | Description |
|---|---|---|
| `player` | `node` | Required. Your `Video` node. The controller sets `content`, `control` and `seek`, observes `state` and `position`, and shows or hides the node. You keep its size, position, z-order and focus. |
| `adFacade` | `node` | Optional full-screen `Group` RAF renders over. If omitted, the controller creates a hidden 1920x1080 group with a backdrop colored from `config.theme.surfaceBase`. |
| `config` | `assocarray` | See [Configuration](../configuration/). |
| `bookmarksEnabled` | `bool` | Default `true`. When `false`, resume and completed reads return empty values and writes are skipped. |

## Functions

| Function | Arguments | Description |
|---|---|---|
| `startSession` | none | Starts the once-per-launch app session. Call during boot, before the first playback. |
| `play` | `{ videoId, streamUrl?, metadata?, resumePosition?, isAutoplay }` | Starts playback: resolves the stream, loads video context, applies the resume position, runs a preroll when one is due, and starts content. `metadata` can carry `title`, `description`, `posterUrl`, `pageUrl`, `durationSeconds`, `captions` and `adBreaks`. |
| `stopPlayback` | `{ reason }` | `user`, `error`, `endcard` or `finished`. `endcard` tears down without emitting `stopped`; `finished` skips saving a bookmark. |
| `pause`, `resume`, `togglePause` | none | Content playback control. No-ops during ad breaks. |
| `seekRelative` | seconds | Relative seek; repeated presses coalesce. |
| `seekTo` | content seconds | Absolute seek in content time (stitched ad breaks are accounted for). |
| `streamToContentSeconds` | stream seconds | Converts stream time to content time, for hosts that observe `player.position` during stitched playback. |
| `getContentDurationSeconds` | none | Content duration, excluding stitched ad breaks. |
| `getResumePosition`, `clearResumePosition`, `isCompleted` | `videoId` | Bookmark helpers for your UI. |
| `listContinueWatching` | none | The continue-watching list, most recent first. |
| `markInterrupted` | none | Records that an ad break was interrupted, so the next session makes it good with a preroll. |

## Output fields

All output fields notify on every write.

| Field | Type | Description |
|---|---|---|
| `state` | `string` | `idle` → `resolving` → zero or more `adBreakStart`/`adBreakEnd` pairs → `contentStart` → `playing` / `paused` / `buffering` → `finished`, `stopped` or `error`. `error` is followed by `stopped`. A client-side midroll resumes as `adBreakEnd` → `buffering` → `playing`, without a second `contentStart`. |
| `adBreak` | `assocarray` | `{ type, lane, podIndex, rendered }`; `type` is `preroll` or `midroll`, `lane` is `csai` (client-side) or `ssai` (stitched). |
| `error` | `assocarray` | `{ code, message, fatal }`. Non-fatal ad errors fall through to content. |
| `controlsHint` | `assocarray` | `{ action, seekTarget? }`, `action` is `show`, `hide` or `hideSeekThumb`. Map it to your own transport chrome, or ignore it. |
| `currentVideo` | `assocarray` | The playing video after enrichment, plus `related` when available. |

## Typical host mapping

| Controller output | Your channel |
|---|---|
| `resolving` | Show a loading state. |
| `adBreakStart` | Hide content controls. |
| `contentStart` / `adBreakEnd` | Restore your preferred focus target. |
| `controlsHint` | Show or hide controls, update the seek preview. |
| `finished` | Show an end card, or call `stopPlayback({ reason: "finished" })`. |
| `error` | Show a playback dialog. |
| `stopped` | Return to the screen you came from. |

