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

# Run a simulive webinar (pre-recorded video, played as live)

> Schedule a pre-recorded recording to play out wall-clock-synced to a live audience: configure the source and start time on a scheduled room, poll the deterministic playhead from your player, layer live chat and Q&A over the playout, cancel or reschedule before go-live, and read attendance like any live event.

# Run a simulive webinar (pre-recorded video, played as live)

Simulive is a pre-recorded recording presented to an audience at a scheduled
time as if it were live. Every viewer who joins sees the same place in the
recording at the same wall-clock moment, with live chat and Q\&A running
alongside the playout. You get the production quality of a recorded file and
the shared-event feel of a live broadcast, without a presenter or an encoder
on air.

On Orbit, simulive is a configuration on a scheduled video room. You point the
room at a recording, set a start time, and Orbit computes a deterministic
playhead that every viewer's player resolves identically. This guide walks
through configuring the broadcast, driving a player from the playout
endpoint, canceling or rescheduling, layering the audience tools, and reading
the results.

## What simulive is (and is not)

Three ways to put video in front of an audience on Orbit:

* **Real live** — cameras or an encoder stream into the room over WHIP/RTMP,
  and viewers watch the live egress. The presenter is really on air.
* **Simulive** — a pre-recorded file plays out on a schedule. There is no
  live ingest; the "broadcast" is a wall-clock computation.
* **VOD** — each viewer opens the recording on their own and starts at
  zero. There is no shared position and no schedule.

The load-bearing primitive of simulive is the deterministic playhead. Given
the scheduled start and the recording duration, the position is a pure
function of the current wall clock, so every player in the audience computes
the same number at the same instant:

```text theme={null}
elapsed = now - scheduled_start_at

position_seconds =
    0                       while elapsed < 0             (upcoming)
    elapsed                 while 0 <= elapsed < duration (live)
    duration                after the recording ends      (ended)
    elapsed mod duration    when loop is on               (live, wrapped)
```

Nothing is streamed server-side: each player seeks its own copy of the
recording to `position_seconds`. Because the position derives from the wall
clock and not from when a viewer joined, a viewer who joins ten minutes late
lands ten minutes into the recording, exactly where the rest of the audience
is.

## Prerequisites

1. **A scheduled video room.** Simulive hangs off the scheduled-rooms
   surface (`/api/v1/video/rooms-scheduled/{roomId}`), the same object
   webinars and recurring events use. Create the room with your broadcast
   layout before configuring simulive. See [Video](/channels/video) for room
   creation.
2. **A playable source recording.** An HLS manifest or MP4 your viewers'
   browsers can fetch over `https`, at most 2,048 characters long. If the
   recording already lives in Orbit's recording library, you can also pass
   `source_recording_id` as the provenance pointer alongside `source_url`.
3. **The recording's duration in seconds.** You supply it; the playhead math
   and the ended/loop states depend on it. It must be positive and at most
   86,400 (24 hours).
4. **API credentials with the right scopes.** Reads
   (`GET .../simulive`, `GET .../simulive/playout`) need `video:read` or
   `video:write`. Writes (`PUT .../simulive`, `POST .../simulive/cancel`)
   need `video:write` plus an owner, admin, or developer role.

## Configure the broadcast

`PUT /api/v1/video/rooms-scheduled/{roomId}/simulive` merges the fields you
send into the room's simulive configuration, so you can build it up
incrementally and flip `status` to `scheduled` once everything is in place:

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/ROOM_ID/simulive" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q3 product deep-dive",
    "source_url": "https://cdn.example.com/recordings/q3-deep-dive.mp4",
    "duration_seconds": 2745,
    "scheduled_start_at": "2026-10-21T17:00:00Z",
    "loop": false,
    "chat_enabled": true,
    "qa_enabled": true,
    "status": "scheduled"
  }'
```

| Field | Type | Notes |
| - | - | - |
| `status` | `"draft"` \| `"scheduled"` | Playout only engages when `scheduled`. Omit to leave unchanged. |
| `title` | string, max 200 | Optional title shown to the audience. |
| `source_url` | `http(s)` URL, max 2,048 chars | The recording the players seek and play. |
| `source_recording_id` | string, max 128 | Optional pointer to the Orbit recording behind `source_url`. |
| `duration_seconds` | number, 1 to 86,400 | Total length of the recording; drives the ended and loop states. |
| `scheduled_start_at` | ISO-8601 with offset, e.g. `2026-10-21T17:00:00Z` | Wall-clock time the playout begins. |
| `loop` | boolean, default `false` | When `true`, the recording wraps and repeats instead of ending. |
| `chat_enabled` | boolean, default `true` | Layer live chat over the playout. |
| `qa_enabled` | boolean, default `true` | Layer live Q\&A over the playout. |

Setting `"status": "scheduled"` requires `source_url`, a positive
`duration_seconds`, and `scheduled_start_at` to already be set (in the same
request or an earlier one). If any is missing the API returns `400` naming
exactly which fields are absent, so fix them and retry the same call.

The response echoes the stored `config` plus the `playout` descriptor as of
the request, so a single `PUT` confirms both what was stored and what
viewers will see right now.

## Poll the playout

`GET /api/v1/video/rooms-scheduled/{roomId}/simulive/playout` returns the
lightweight playhead a player polls to stay wall-clock-synced:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/video/rooms-scheduled/ROOM_ID/simulive/playout" \
  -H "X-API-Key: dv_live_sk_..."
```

```json theme={null}
{
  "room_id": "ROOM_ID",
  "playout": {
    "broadcast_state": "live",
    "is_live": true,
    "position_seconds": 612.438,
    "duration_seconds": 2745,
    "seconds_until_start": null,
    "seconds_remaining": 2132.562,
    "loop": false,
    "loop_iteration": 0,
    "source_url": "https://cdn.example.com/recordings/q3-deep-dive.mp4",
    "title": "Q3 product deep-dive",
    "scheduled_start_at": "2026-10-21T17:00:00Z",
    "chat_enabled": true,
    "qa_enabled": true,
    "server_time": "2026-10-21T17:10:12.438Z"
  }
}
```

`broadcast_state` is the wall-clock-derived state of the event:
`upcoming` before the start, `live` while the playhead is inside the
recording, `ended` once a non-looping playout runs past the duration,
`canceled` after you cancel, and `not_configured` when the room has no
schedulable simulive. A player renders each of these as its own screen
(countdown, player, ended card, canceled notice).

The player's whole job is to seek its `<video>` element to the playhead:

```js theme={null}
// On join, and whenever a re-seek is due (below):
video.src = playout.source_url;
video.currentTime = playout.position_seconds;
await video.play();
```

Because the playhead is a pure function of the wall clock, drift between
viewers can only come from client clocks and seek latency, not from the
server. Keep it invisible with this cadence:

* Poll `GET .../simulive/playout` every 5 to 10 seconds while
  `broadcast_state` is `live`.
* Compare `video.currentTime` against `position_seconds` on each poll, and
  re-seek only when the gap exceeds 2 to 3 seconds. Re-seeking on every poll
  causes visible jumps; letting the gap grow past a few seconds breaks the
  shared-audience feel.
* Use `server_time` once at load to estimate the offset between the client
  clock and the server clock, and add that offset to the seek target if it
  is more than a second or two.
* With `loop: true`, `position_seconds` wraps at the duration and
  `loop_iteration` counts completed passes, so a player that shows
  "now playing, round N" has the number already computed for it.

The full `GET .../simulive` route returns the same `playout` plus the stored
`config` and the room status; use it for your operator console and keep the
polling traffic on the lighter `/playout` route.

## Cancel or reschedule before go-live

`POST /api/v1/video/rooms-scheduled/{roomId}/simulive/cancel` (empty body)
moves a scheduled broadcast to `canceled` and stamps `canceled_at`.
Canceling an already-canceled configuration is a no-op that keeps the first
timestamp, so a double-clicked cancel button is safe. Players polling after
a cancel see `broadcast_state: "canceled"` and should show your canceled
notice instead of the player.

The operator lifecycle is:

```text theme={null}
draft  ->  scheduled   PUT {"status": "scheduled"} once source, duration and start are set
scheduled -> canceled  POST /simulive/cancel
canceled -> scheduled  PUT {"status": "scheduled"} again (a reschedule; clears the cancellation)
any -> draft           PUT {"status": "draft"}
```

Rescheduling is therefore just another `PUT`: send a new
`scheduled_start_at` (and anything else that changed) with
`"status": "scheduled"`. Moving back to `draft` or `scheduled` clears a
prior cancellation, so a room can be reused for the next broadcast with no
cleanup call.

## Layer chat, Q\&A, and captions during playout

Simulive is a one-way video surface with the room's audience tools layered
on top, and the `chat_enabled` / `qa_enabled` toggles are exactly those
layers:

* **Live chat** runs alongside the playout so the audience reacts together.
  The chat and Q\&A consoles your moderators work during a simulive event
  are the same ones as for any live room; see
  [Video room consoles](/guides/video-room-consoles).
* **Q\&A** collects questions from viewers for your moderators and
  presenters to answer live in the room or afterwards in writing.
* **Captions and interpretation** for the broadcast layer on the room the
  same way as for live events; see
  [Live interpretation](/guides/video-live-interpretation) for the
  captioned-audio surface.

Both toggles default to on; send `"chat_enabled": false` or
`"qa_enabled": false` on the `PUT` to run a quieter premiere. Toggling
either mid-broadcast takes effect on the next config read, so you can quiet
a chat that got loud without touching the playout.

## Measure the audience

Viewers join a simulive broadcast the same way they join any scheduled room,
so attendance is measured at the room and session layer exactly as for a
live event: who joined, who never joined, peak concurrent participants, and
per-participant engagement. Read it with the same surface you use for live
webinars, [Video engagement analytics](/guides/video-engagement-analytics).

What simulive adds is the interpretive key: because every player derives
`position_seconds` from the same wall clock, any moment of audience activity
maps onto one position in the recording for everyone. A chat spike at
17:12:00 for a 17:00:00 start is position 720 of the source file, for every
viewer, which makes "which segment held the room" a direct read of the
engagement timeline against the playhead rather than a per-viewer
reconstruction.

Pair the session engagement read with the playback-side retention curve in
[Recording view analytics](/guides/recording-view-analytics) when the
recording later circulates as VOD.

## Simulive vs real live vs VOD

| | Simulive | Real live | VOD |
| - | - | - | - |
| Source | Pre-recorded file | Cameras or encoder over WHIP/RTMP | Pre-recorded file |
| Shared wall-clock position | Yes | Yes (live edge) | No, per-viewer |
| Schedule | Fixed start time | Fixed or ad hoc | None |
| Production quality | Guaranteed, edited | Depends on the moment | Guaranteed, edited |
| Presenter on air | No | Yes | No |
| Live chat / Q\&A | Yes | Yes | No |
| Best fit | Premieres, launches, evergreen broadcasts at a fixed time | Demos, panels, anything unpredictable | Self-serve replay, knowledge base |

Reach for **simulive** when the content is finished but the event should
feel live: a product premiere, a launch broadcast, an evergreen training
session aired on a weekly slot. Reach for **real live** when the presenter
must react in the moment; see
[WHIP/RTMP ingest](/guides/video-room-ingress-whip-rtmp) and
[HLS/DASH egress](/guides/video-room-hls-dash-egress). Reach for **VOD**
when there is no event at all; see
[Recording share player](/guides/video-recording-share-player).

Simulive combines with the registration gate: a
[registration-gated webinar](/guides/video-webinar-registration-gate)
fronting a simulive room works exactly as it does for live rooms, because
the gate sits on the room, not on the playout.

## Errors

| Status | Meaning |
| - | - |
| `422` | A field failed validation; the `issues` array names each one (bad URL scheme, `duration_seconds` over 86,400, malformed `scheduled_start_at`). |
| `400` | `"status": "scheduled"` with `source_url`, `duration_seconds`, or `scheduled_start_at` missing; the message lists what to set. |
| `404` | The room id does not exist in your organization. |

## See also

* [Registration-gated video webinars](/guides/video-webinars)
* [Registration gate guide](/guides/video-webinar-registration-gate)
* [Video engagement analytics](/guides/video-engagement-analytics)
* [HLS/DASH livestream egress](/guides/video-room-hls-dash-egress)
* [Recording share player](/guides/video-recording-share-player)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.