{"_id":"@antcdn/live-signaling","_rev":"2-89be17287f88114850dbbbc6b48e2b7d","name":"@antcdn/live-signaling","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@antcdn/live-signaling","version":"0.1.0","keywords":["antcdn","live","streaming","interactive","polls","websocket"],"license":"MIT","_id":"@antcdn/live-signaling@0.1.0","maintainers":[{"name":"davidquartz","email":"quartzdavid@gmail.com"}],"homepage":"https://antcdn.net/interactive-live","bugs":{"url":"https://github.com/DavidQuartz/antcdn/issues"},"dist":{"shasum":"c5c15863e9520244de9c2b9a34d7df0d4998ab0c","tarball":"https://registry.npmjs.org/@antcdn/live-signaling/-/live-signaling-0.1.0.tgz","fileCount":13,"integrity":"sha512-aCL+V/v0EqNxTL9sjt/MkMMe9ZSkjbgAazQrhl5LAkr96KnDQzdVInOq+ISLJXDJ0J0kezp+ji6bDg9AHdW2hg==","signatures":[{"sig":"MEYCIQDnHZXN3qO/OGEvTBba0moIZc3mOZvpcxP/3E8zSW3DrwIhAM9n7Vk9w8hO8tYJe61vKjqhVp6nfB/yiCMeBfILN0dX","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":39523},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"e56848fd27e54eb08078c8326ecd881916cc4c70","scripts":{"tsc":"tsc --noEmit","test":"vitest run","build":"tsc -p tsconfig.build.json","prepublishOnly":"npm run tsc && npm run test && npm run build"},"_npmUser":{"name":"davidquartz","email":"quartzdavid@gmail.com"},"repository":{"url":"git+https://github.com/DavidQuartz/antcdn.git","type":"git","directory":"typescript_root/live-signaling-sdk"},"_npmVersion":"11.6.2","description":"Interactivity for AntCDN live streams and recordings: viewer count, questions, and voting, independent of any video player.","directories":{},"sideEffects":false,"_nodeVersion":"23.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.6.1","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/live-signaling_0.1.0_1789148383859_0.611994470994933","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@antcdn/live-signaling","version":"0.1.1","description":"Interactivity for AntCDN live streams and recordings: viewer count, questions, and voting, independent of any video player.","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"sideEffects":false,"scripts":{"build":"tsc -p tsconfig.build.json","tsc":"tsc --noEmit","test":"vitest run","prepublishOnly":"npm run tsc && npm run test && npm run build"},"keywords":["antcdn","live","streaming","interactive","polls","websocket"],"license":"MIT","devDependencies":{"typescript":"^5.9.3","vitest":"^1.6.1"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/DavidQuartz/antcdn.git","directory":"typescript_root/live-signaling-sdk"},"homepage":"https://antcdn.net/interactive-live","bugs":{"url":"https://github.com/DavidQuartz/antcdn/issues"},"gitHead":"e56848fd27e54eb08078c8326ecd881916cc4c70","_id":"@antcdn/live-signaling@0.1.1","_nodeVersion":"23.11.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-ayVZG+1UGIzQRrjUVI/wfBeRIPm62X9VusWjM8aLASgv2GIk1x8FNezTiJE0v1KE4UbsKEazOHey7SAsNPPCcA==","shasum":"24ed27628e6d93eb9bc8c9cc7203ff716961a16f","tarball":"https://registry.npmjs.org/@antcdn/live-signaling/-/live-signaling-0.1.1.tgz","fileCount":13,"unpackedSize":42013,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIE9FlmgLMdLFWwEUAfsi2vYD4KAstAotvbyTnCML6+5QAiBpjFxYUh0k/n7JjPxIq0+2rYgeVBJ4K7hEFlzu0diqCQ=="}]},"_npmUser":{"name":"davidquartz","email":"quartzdavid@gmail.com"},"directories":{},"maintainers":[{"name":"davidquartz","email":"quartzdavid@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/live-signaling_0.1.1_1789268056939_0.21968468947091946"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-11T17:39:43.603Z","modified":"2026-09-13T02:54:17.257Z","0.1.0":"2026-09-11T17:39:43.971Z","0.1.1":"2026-09-13T02:54:17.086Z"},"bugs":{"url":"https://github.com/DavidQuartz/antcdn/issues"},"license":"MIT","homepage":"https://antcdn.net/interactive-live","keywords":["antcdn","live","streaming","interactive","polls","websocket"],"repository":{"type":"git","url":"git+https://github.com/DavidQuartz/antcdn.git","directory":"typescript_root/live-signaling-sdk"},"description":"Interactivity for AntCDN live streams and recordings: viewer count, questions, and voting, independent of any video player.","maintainers":[{"name":"davidquartz","email":"quartzdavid@gmail.com"}],"readme":"# @antcdn/live-signaling\n\nInteractivity for AntCDN live streams and their recordings: viewer presence, host-driven questions, and voting. It is independent of any video player.\n\nWorks 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.\n\n## Install\n\n```\npnpm add @antcdn/live-signaling\n```\n\n## Live\n\n```ts\nimport { connectLiveRoom } from '@antcdn/live-signaling';\n\nconst room = connectLiveRoom({\n  streamId: 'ls_2VQDR4DO8RTVADPQ21X95U',\n  // Optional. Prefer the id from your playback session; otherwise one is\n  // generated and persisted in localStorage.\n  sessionId: playbackSession?.sessionId,\n});\n\nroom.on('count', ({ viewerCount }) => renderViewerCount(viewerCount));\nroom.on('question', (q) => showQuestion(q));      // fires at reveal time\nroom.on('tally', (t) => renderResults(t.counts));\nroom.on('closed', (t) => renderFinal(t.counts));\nroom.on('error', ({ code }) => { if (code === 'already_voted') markAnswered(); });\n\n// When the viewer answers\nroom.vote(questionId, optionIndex);\n```\n\n### Reveal timing, the part that matters\n\nA 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.\n\nSo questions marked `cued` are **held** until you report the matching in-band cue:\n\n```ts\n// Shaka Player\nplayer.addEventListener('metadata', (e) => {\n  const questionId = readQuestionId(e);   // from the ID3/emsg payload\n  if (questionId) room.cue(questionId);\n});\n```\n\nOrder does not matter. A cue arriving before its question is remembered, and the question reveals the moment it lands.\n\nHosts and moderators are not on delay and need to see a question the instant it opens:\n\n```ts\nconnectLiveRoom({ streamId, revealImmediately: true });\n```\n\nNever set that for ordinary viewers; it reintroduces exactly the early-reveal problem cues exist to prevent.\n\n## Recorded\n\nThe same events, driven by playback position instead of a socket:\n\n```ts\nimport { replayLiveRoom } from '@antcdn/live-signaling';\n\nconst replay = replayLiveRoom({ questions });   // from the recording's question timeline\n\nreplay.on('question', (q) => showQuestion(q));\nreplay.on('tally', (t) => renderResults(t.counts));   // what the live audience answered\n\nvideo.addEventListener('timeupdate', () => replay.update(video.currentTime));\n```\n\nSeeking 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.\n\nBecause the event shape is identical, one set of handlers serves both live and recorded playback.\n\n## Identity and voting\n\nVotes are deduplicated by session id, one vote per session per question. A second vote returns `error` with code `already_voted`.\n\nSupplying 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.\n\n## Protocol\n\nThe SDK is a convenience over a plain JSON-over-WebSocket protocol. Implement it directly on any platform.\n\n```\nGET  wss://worker.antcdn.net/v1/{streamId}/live-room?sessionId={sessionId}\nGET  https://worker.antcdn.net/v1/{streamId}/live-room/count\n```\n\n**Server → client**\n\n| `type` | Payload |\n|---|---|\n| `welcome` | `streamId`, `viewerCount`, `activeQuestion?`, `hasVoted?`, `tally?` |\n| `count` | `viewerCount` |\n| `question` | `question: { id, prompt, options, cued? }` |\n| `tally` | `tally: { questionId, counts[], total }` |\n| `closed` | `tally`, final |\n| `error` | `code`, `message` |\n\n**Client → server**\n\n| `type` | Payload |\n|---|---|\n| `vote` | `questionId`, `optionIndex` |\n\nEvery 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.\n\n`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.\n","readmeFilename":"README.md"}