What we're building
A translation. If you have a LiveKit integration, this is what changes and what does not.
Livqeno's media plane was itself migrated off LiveKit, so this mapping comes from having done it rather than from reading a comparison table.
Prerequisites
- An existing LiveKit integration.
- A Livqeno project and API key.
Implementation
The concepts map closely
| LiveKit | Livqeno | Notes |
|---|---|---|
| Room | Room | Same idea. Livqeno's is a control-plane record; a server is allocated on first join. |
| Participant | Participant | Identity is a string your backend chooses, signed into the token. |
| Track | Track | Livqeno declares the source — camera, microphone, screenShare. |
| Access token | Token | Minted server-side. Max 6 hours; no non-expiring option. |
| API key / secret | API key | One rvk_<env>_id.secret string rather than a key/secret pair. |
| Server SDK | @ravenkash/server, raven-sdk | Same job. |
| Egress / Ingress | — | No equivalent. Livqeno has no recording and no RTMP ingest. |
| Webhooks | Webhooks | Livqeno's fire for chat and live-stream events, not RTC lifecycle. |
Token minting
// Livqeno
const credentials = await raven.tokens.create({
room: room.id, // the room's id, not its name
identity: 'user-42',
permissions: { join: true, subscribe: true, publish: true },
expiresIn: 600,
});Two differences that matter:
- Permissions are denied unless granted. LiveKit's unset
canPublish/canSubscribemeant both granted. Livqeno resolves every flag to an explicit boolean at mint time, so there is no permissive default to inherit. - You forward the whole response.
endpoint,iceServersandtelemetryUrlcome back with the token and the client needs all of them. There is no separately-configured server URL, and you never build aniceServersarray yourself.
Client connection
import { createRTCClient } from '@ravenkash/rtc';
const client = createRTCClient(credentials);
const room = await client.join(credentials.roomId);
await room.enableCamera();
await room.enableMicrophone();enableCamera()/enableMicrophone() replace the create-then-publish
dance for the common case. The explicit form is still there when you need
it:
const track = await client.createCameraTrack(deviceId);
await room.publish(track);Events
Livqeno's event names are its own. The mapping is mostly mechanical —
participantJoined, participantLeft, trackSubscribed,
trackUnsubscribed, trackMuted, connectionStateChanged — and the full
list is in RTC events.
No WebRTC type is ever exposed. There is no RTCPeerConnection,
RTCRtpSender or MediaStreamTrack in Livqeno's public API except
track.mediaStreamTrack when you deliberately reach for it.
How it works
A client has exactly one peer: the media server serving its room. Same SFU topology, so your mental model transfers.
Clients never learn which media server they got. They connect to a signaling endpoint and Livqeno allocates on their behalf. That indirection is what let Livqeno replace its own media plane without an SDK release — and it means there is no server address for you to configure.
The signaling protocol is not LiveKit's. If you wrote anything against the wire protocol rather than the SDK, that work does not carry over. See Signaling protocol.
Production considerations
- Nothing recording-shaped will port. No Egress, no Ingress, no composite output, no RTMP. If your product depends on any of it, Livqeno cannot replace LiveKit for that part today.
- Re-audit your permissions. Code that relied on "unset means allowed" will produce participants who cannot publish. That is the intended direction of the change, but it will surface as a bug during migration.
- Verify on your own networks. Livqeno's relay path is tested and its browser coverage is Chromium-only so far. See Known limitations.
Next steps
- Concepts — the vocabulary in full.
- RTC quickstart · Signaling protocol