Recordings
Recordings
A recorded interactive stream keeps its questions. Someone watching later gets them at the same points in the video, in the same order, as the live audience did.
This works because every question is stored with offsetSeconds, the number of seconds from the start of the broadcast at which it was asked. That offset never changes once the question has been asked.
Get the timeline
curl https://api.antcdn.net/v1/live-streams/$STREAM_ID/questions \ -H "X-Api-Key: $ANTCDN_API_KEY"{ "questions": [ { "id": "lsq_71c4a9", "prompt": "Which build should we ship first?", "options": ["The rewrite", "The patch"], "offsetSeconds": 412, "allowFreeText": false, "counts": [1204, 388], "replayCounts": [51, 12] } ]}counts is what the live audience answered. replayCounts is what people watching the recording have answered since. They are reported side by side and never added together, so neither figure can be mistaken for the other.
Replay them in a player
import { replayLiveRoom } from '@antcdn/live-signaling';
const { questions } = await fetch(timelineUrl).then((r) => r.json());
const replay = replayLiveRoom({ questions, streamId: 'ls_9f2a',});
replay.on('question', (question) => showPoll(question));
video.addEventListener('timeupdate', () => { replay.update(video.currentTime);});Call update() with the playback position in seconds. The rest is handled for you.
Options
| Option | Type | Default | Meaning |
|---|---|---|---|
questions | RecordedQuestion[] | required | The timeline from the API. |
streamId | string | none | Needed to submit answers. Leave it out for a read only replay. |
baseUrl | string | https://worker.antcdn.net | Override for self hosting or local development. |
sessionId | string | generated | Identifies the viewer so they cannot answer twice. |
revealWindowSeconds | number | 30 | How long past its offset a question may still appear. |
fetchImpl | function | global fetch | Inject a fetch, for tests or non browser runtimes. |
Seeking
Seeking is handled properly, which matters more than it sounds.
- Jumping backwards re-arms questions, so they appear again.
- Jumping forwards past several questions does not fire a burst of stale prompts. Only the most recent one appears, because that is what a viewer scrubbing into the middle of a talk expects to be looking at.
revealWindowSecondsbounds how late a question can still appear. Without it, seeking to the end of a two hour recording would show the question from minute three.
Collecting answers from a recording
const recorded = await replay.answer(question.id, 1);// recorded === true -> stored// recorded === false -> this viewer had already answeredanswer() resolves to false rather than throwing when the viewer has answered before. That is not an error. A viewer whose connection retried the submission should be told it worked.
Write an answer in words by passing text:
await replay.answer(question.id, FREE_TEXT_ONLY, 'because the rewrite is cleaner');hasAnswered(questionId) tells you whether this viewer has answered during this session, so your interface does not have to wait for a round trip to disable the buttons.
Note
answer() needs streamId. A replay built without one is read only and throws if you try to answer, rather than quietly dropping what the viewer said.
How replay answers are kept
Replay answers are stored against the recording, never against the broadcast. They appear in replayCounts, never in counts.
The reason is simple. A live result is a record of a moment that has passed. A recording collects answers for as long as anyone watches it, which may be years. Folding the two together would let the live result drift long after the stream ended.
Written answers from a recording are stored the same way, and are returned by the responses endpoint with source set to replay.
Submitting from your own backend
If you serve recordings through your own system, you can post answers with your API key instead of using the SDK. See Record a replay answer.