@antcdn/live-signaling
v0.1.1
Published
Interactivity for AntCDN live streams and recordings: viewer count, questions, and voting, independent of any video player.
Maintainers
Readme
@antcdn/live-signaling
Interactivity for AntCDN live streams and their recordings: viewer presence, host-driven questions, and voting. It is independent of any video player.
Works with player-ant, hls.js, Shaka Player, a native app shell, or a page with no video at all. It opens a WebSocket and emits events; it never touches a video element.
Install
pnpm add @antcdn/live-signalingLive
import { connectLiveRoom } from '@antcdn/live-signaling';
const room = connectLiveRoom({
streamId: 'ls_2VQDR4DO8RTVADPQ21X95U',
// Optional. Prefer the id from your playback session; otherwise one is
// generated and persisted in localStorage.
sessionId: playbackSession?.sessionId,
});
room.on('count', ({ viewerCount }) => renderViewerCount(viewerCount));
room.on('question', (q) => showQuestion(q)); // fires at reveal time
room.on('tally', (t) => renderResults(t.counts));
room.on('closed', (t) => renderFinal(t.counts));
room.on('error', ({ code }) => { if (code === 'already_voted') markAnswered(); });
// When the viewer answers
room.vote(questionId, optionIndex);Reveal timing, the part that matters
A question asked inside the video must appear when the viewer hears it asked, not when the socket delivered it. A viewer eight seconds behind the live edge would otherwise get a prompt about something that has not happened yet.
So questions marked cued are held until you report the matching in-band cue:
// Shaka Player
player.addEventListener('metadata', (e) => {
const questionId = readQuestionId(e); // from the ID3/emsg payload
if (questionId) room.cue(questionId);
});Order does not matter. A cue arriving before its question is remembered, and the question reveals the moment it lands.
Hosts and moderators are not on delay and need to see a question the instant it opens:
connectLiveRoom({ streamId, revealImmediately: true });Never set that for ordinary viewers; it reintroduces exactly the early-reveal problem cues exist to prevent.
Recorded
The same events, driven by playback position instead of a socket:
import { replayLiveRoom } from '@antcdn/live-signaling';
const replay = replayLiveRoom({ questions }); // from the recording's question timeline
replay.on('question', (q) => showQuestion(q));
replay.on('tally', (t) => renderResults(t.counts)); // what the live audience answered
video.addEventListener('timeupdate', () => replay.update(video.currentTime));Seeking behaves the way a viewer expects: scrubbing backwards re-arms questions so a rewatch works, and scrubbing far forward does not fire a backlog. Only a question within revealWindowSeconds (default 30) of where you landed is shown.
Because the event shape is identical, one set of handlers serves both live and recorded playback.
Identity and voting
Votes are deduplicated by session id, one vote per session per question. A second vote returns error with code already_voted.
Supplying your own sessionId, ideally the one AntCDN's secure-playback handshake issues, makes that meaningful. The generated fallback is deliberately weak: clearing site data earns another vote. That matches the bar the feature sets; do not build anything on it that needs a verified identity.
Protocol
The SDK is a convenience over a plain JSON-over-WebSocket protocol. Implement it directly on any platform.
GET wss://worker.antcdn.net/v1/{streamId}/live-room?sessionId={sessionId}
GET https://worker.antcdn.net/v1/{streamId}/live-room/countServer → client
| type | Payload |
|---|---|
| welcome | streamId, viewerCount, activeQuestion?, hasVoted?, tally? |
| count | viewerCount |
| question | question: { id, prompt, options, cued? } |
| tally | tally: { questionId, counts[], total } |
| closed | tally, final |
| error | code, message |
Client → server
| type | Payload |
|---|---|
| vote | questionId, optionIndex |
Every message carries v, the protocol version. The client warns on a mismatch rather than failing. Unknown fields are ignored, so additive server changes are safe.
welcome is sent on every connect, including reconnects, and carries enough state to resume: the open question, whether this session already answered it, and the tally so far. Reconnection is automatic with jittered backoff.
