A comprehensive guide for developers looking to migrate their video conferencing applications
LiveKit offers an open-source, self-hostable alternative to Dolby.io with competitive pricing, greater flexibility, and robust features. As a fully open-source solution, you gain complete control over your infrastructure.
LiveKit provides comprehensive SDKs for web, iOS, Android, React, Flutter, and more with excellent documentation and a thriving community of developers.
Unlike Dolby.io, LiveKit can be self-hosted, giving you complete control over your infrastructure and data. Deploy on your own servers for maximum privacy and customization.
LiveKit is built with security and scalability in mind. It uses WebRTC Selective Forwarding Unit (SFU) architecture to efficiently handle thousands of concurrent users while maintaining low latency.
LiveKit's flexible architecture makes it perfect for integrating AI agents into your video applications. Build intelligent virtual assistants, real-time transcription, and more with ease.
| Feature | Dolby.io | LiveKit.io |
|---|---|---|
| Pricing Model | Usage-based pricing | Usage-based pricing or self-hosted (free) |
| Self-Hosting | Full control | |
| Open Source | ||
| Video Quality | Up to 720p | Up to 4K with adaptive bitrate |
| Audio Processing | Dolby Voice | Standard audio processing |
| Recording | ||
| Screen Sharing | ||
| Chat | ||
| Custom Layouts | ||
| WebRTC Support | Optimized SFU | |
| AI Integration | Limited | Extensive support |
| Mobile SDKs | iOS, Android | iOS, Android, Flutter, React Native |
| Server APIs | REST API | REST API, gRPC |
| Scalability | Limited | Highly scalable architecture |
| Security | Standard | Enterprise-grade with customizable options |
Choose between LiveKit Cloud or self-hosting the LiveKit server.
# For self-hosting with Docker
docker run --rm -p 7880:7880 \
-p 7881:7881 \
-p 7882:7882/udp \
-v $PWD/livekit.yaml:/livekit.yaml \
livekit/livekit-server \
--config /livekit.yaml
Replace Dolby.io SDKs with LiveKit equivalents.
# For web applications
npm install livekit-client
# For React applications
npm install @livekit/components-react
Replace Dolby.io token generation with LiveKit token generation.
// Server-side token generation with Node.js
const { AccessToken } = require('livekit-server-sdk');
const createToken = (roomName, participantName) => {
const at = new AccessToken('API_KEY', 'API_SECRET');
at.addGrant({ roomJoin: true, room: roomName, canPublish: true, canSubscribe: true });
at.identity = participantName;
return at.toJwt();
};
Replace Dolby.io client code with LiveKit equivalents.
// We'll see detailed code examples in the next section
Thoroughly test your application and deploy the updated version.
LiveKit's flexible architecture makes it easy to integrate with various AI models and services, enabling powerful features like speech-to-text, text-to-speech, and intelligent virtual agents.
LiveKit can be easily integrated with various speech-to-text models and services:
// Example: Integrating OpenAI Whisper with LiveKit
import { Room, RoomEvent } from 'livekit-client';
import { AudioProcessor } from './audio-processor';
import { WhisperTranscriber } from './whisper-transcriber';
// Set up LiveKit room
const room = new Room();
await room.connect('wss://your-livekit-server.com', token);
// Initialize transcriber
const transcriber = new WhisperTranscriber({
apiKey: 'your-openai-api-key',
language: 'en'
});
// Process audio for transcription
room.localParticipant.on(RoomEvent.TrackPublished, async (publication) => {
if (publication.kind === 'audio') {
const audioTrack = publication.track;
const processor = new AudioProcessor(audioTrack);
// Process audio chunks and send to Whisper
processor.onAudioChunk((audioChunk) => {
transcriber.transcribe(audioChunk).then((text) => {
console.log('Transcription:', text);
// Use the transcription in your application
});
});
}
});
Convert text to natural-sounding speech in your LiveKit applications:
// Example: Integrating ElevenLabs TTS with LiveKit
import { Room } from 'livekit-client';
import { ElevenLabsTTS } from './elevenlabs-tts';
import { AudioStreamer } from './audio-streamer';
// Set up LiveKit room
const room = new Room();
await room.connect('wss://your-livekit-server.com', token);
// Initialize TTS service
const tts = new ElevenLabsTTS({
apiKey: 'your-elevenlabs-api-key',
voiceId: 'voice-id-here',
model: 'eleven_multilingual_v2'
});
// Function to speak text in the room
async function speakInRoom(text) {
// Generate audio from text
const audioBuffer = await tts.generateSpeech(text);
// Create audio track from buffer and publish to room
const audioStreamer = new AudioStreamer(audioBuffer);
const audioTrack = audioStreamer.createTrack();
// Publish the track to the room
await room.localParticipant.publishTrack(audioTrack);
// Start playing the audio
audioStreamer.play();
}
Create intelligent AI agents that can participate in your LiveKit sessions:
// Example: Creating an AI agent participant in LiveKit
import { Room, LocalParticipant } from 'livekit-client';
import { OpenAIClient } from './openai-client';
import { AudioProcessor, TextToSpeech } from './audio-utils';
class AIAgentParticipant {
constructor(room, options) {
this.room = room;
this.openai = new OpenAIClient(options.apiKey);
this.tts = new TextToSpeech(options.ttsService);
this.stt = new AudioProcessor();
this.agentName = options.name || 'AI Assistant';
this.setupEventListeners();
}
setupEventListeners() {
// Listen for audio from participants
this.room.on('trackSubscribed', (track, publication, participant) => {
if (track.kind === 'audio') {
// Process audio for speech recognition
this.stt.processAudioTrack(track, async (text) => {
if (text) {
// Get AI response
const response = await this.openai.getCompletion({
messages: [{ role: 'user', content: text }],
model: 'gpt-4'
});
// Convert response to speech and publish to room
const audioTrack = await this.tts.createTrackFromText(response);
await this.room.localParticipant.publishTrack(audioTrack);
}
});
}
});
}
// Join the room as an AI agent
async join() {
// Announce the AI agent has joined
const welcomeMessage = `Hello, I'm ${this.agentName}, your AI assistant in this meeting.`;
const audioTrack = await this.tts.createTrackFromText(welcomeMessage);
await this.room.localParticipant.publishTrack(audioTrack);
}
}
// Initialize Dolby.io
VoxeetSDK.initialize('CONSUMER_KEY', 'CONSUMER_SECRET');
// Join a conference
const conferenceOptions = {
alias: 'conference-name',
params: {
dolbyVoice: true
}
};
VoxeetSDK.conference.create(conferenceOptions)
.then((conference) => {
const joinOptions = {
constraints: {
audio: true,
video: true
}
};
return VoxeetSDK.conference.join(conference, joinOptions);
})
.then(() => {
console.log('Joined conference');
})
.catch((error) => {
console.error(error);
});
// Publish local stream
VoxeetSDK.conference.startVideo(VoxeetSDK.session.participant)
.then(() => {
console.log('Local video started');
})
.catch((error) => {
console.error(error);
});
// Handle participant joined
VoxeetSDK.conference.on('participantAdded', (participant) => {
console.log(`${participant.info.name} joined`);
});
// Handle stream added
VoxeetSDK.conference.on('streamAdded', (participant, stream) => {
if (participant.id !== VoxeetSDK.session.participant.id) {
// Render remote participant's stream
const videoNode = document.getElementById('remote-video');
navigator.attachMediaStream(videoNode, stream);
}
});
// Initialize LiveKit
import { Room, RoomEvent } from 'livekit-client';
const room = new Room();
// Connect to room with token from your server
async function connectToRoom(token) {
await room.connect('wss://your-livekit-server.com', token);
console.log('Connected to room:', room.name);
// Publish local tracks
const localTrackPublications = await room.localParticipant.enableCameraAndMicrophone();
console.log('Published local tracks:', localTrackPublications);
}
// Handle participant joined
room.on(RoomEvent.ParticipantConnected, participant => {
console.log(`${participant.identity} joined`);
});
// Handle track subscribed
room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
if (track.kind === 'video') {
// Attach video track to element
const videoElement = document.createElement('video');
videoElement.id = `video-${participant.identity}`;
videoElement.autoplay = true;
document.getElementById('remote-videos').appendChild(videoElement);
track.attach(videoElement);
}
});
// Connect with token from your server
connectToRoom('your-token-here')
.catch(error => {
console.error('Error connecting to room:', error);
});
// Using Dolby.io with React
import React, { useEffect } from 'react';
import { VoxeetSDK } from '@voxeet/voxeet-web-sdk';
function VideoConference() {
useEffect(() => {
// Initialize Dolby.io
VoxeetSDK.initialize('CONSUMER_KEY', 'CONSUMER_SECRET');
// Join conference
const joinConference = async () => {
try {
const conferenceOptions = {
alias: 'conference-name',
params: { dolbyVoice: true }
};
const conference = await VoxeetSDK.conference.create(conferenceOptions);
const joinOptions = {
constraints: {
audio: true,
video: true
}
};
await VoxeetSDK.conference.join(conference, joinOptions);
console.log('Joined conference');
// Start local video
await VoxeetSDK.conference.startVideo(VoxeetSDK.session.participant);
} catch (error) {
console.error('Error joining conference:', error);
}
};
joinConference();
// Set up event listeners
const onStreamAdded = (participant, stream) => {
if (participant.id !== VoxeetSDK.session.participant.id) {
const videoNode = document.getElementById('remote-video');
navigator.attachMediaStream(videoNode, stream);
}
};
VoxeetSDK.conference.on('streamAdded', onStreamAdded);
// Cleanup
return () => {
VoxeetSDK.conference.off('streamAdded', onStreamAdded);
VoxeetSDK.conference.leave();
};
}, []);
return (
);
}
export default VideoConference;
// Using LiveKit with React
import React from 'react';
import {
LiveKitRoom,
VideoConference,
GridLayout,
ParticipantTile,
RoomAudioRenderer,
ControlBar
} from '@livekit/components-react';
import '@livekit/components-styles';
function VideoConferenceRoom() {
// Token should be fetched from your server
const token = 'your-token-here';
const serverUrl = 'wss://your-livekit-server.com';
return (
{/* This provides a complete video conference UI */}
{/* Or build your own UI with these components */}
);
}
export default VideoConferenceRoom;
// For more custom control:
import React, { useEffect } from 'react';
import { useRoom, useParticipant } from '@livekit/components-react';
function CustomVideoConference() {
const room = useRoom();
useEffect(() => {
// Custom room event handling
const handleParticipantConnected = (participant) => {
console.log(`${participant.identity} connected`);
};
room.on('participantConnected', handleParticipantConnected);
return () => {
room.off('participantConnected', handleParticipantConnected);
};
}, [room]);
return (
{/* Your custom UI */}
);
}
// Dolby.io iOS SDK (Swift)
import VoxeetSDK
class ConferenceViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Initialize SDK
VoxeetSDK.shared.initialize(consumerKey: "CONSUMER_KEY", consumerSecret: "CONSUMER_SECRET")
// Create a conference
let conferenceOptions = VTConferenceOptions()
conferenceOptions.alias = "conference-name"
VoxeetSDK.shared.conference.create(options: conferenceOptions) { conference, error in
guard let conference = conference, error == nil else {
print("Error creating conference: \(error?.localizedDescription ?? "")")
return
}
// Join the conference
let joinOptions = VTJoinOptions()
joinOptions.constraints.audio = true
joinOptions.constraints.video = true
VoxeetSDK.shared.conference.join(conference: conference, options: joinOptions) { error in
if let error = error {
print("Error joining conference: \(error.localizedDescription)")
return
}
print("Joined conference successfully")
// Start video
VoxeetSDK.shared.conference.startVideo(participant: VoxeetSDK.shared.session.participant) { error in
if let error = error {
print("Error starting video: \(error.localizedDescription)")
return
}
print("Local video started")
}
}
}
}
}
// LiveKit iOS SDK (Swift)
import LiveKit
class ConferenceViewController: UIViewController {
private var room: Room?
override func viewDidLoad() {
super.viewDidLoad()
// Create room
room = Room()
// Set up delegates
room?.delegate = self
// Connect to room with token from your server
let url = "wss://your-livekit-server.com"
let token = "your-token-here"
Task {
do {
try await room?.connect(url: url, token: token)
print("Connected to room: \(room?.name ?? "")")
// Publish local tracks
try await room?.localParticipant.setCamera(enabled: true)
try await room?.localParticipant.setMicrophone(enabled: true)
print("Published local tracks")
} catch {
print("Error connecting to room: \(error.localizedDescription)")
}
}
}
}
// Room delegate
extension ConferenceViewController: RoomDelegate {
func room(_ room: Room, didConnect isReconnect: Bool) {
print("Room connected, reconnect: \(isReconnect)")
}
func room(_ room: Room, didDisconnect error: Error?) {
if let error = error {
print("Room disconnected with error: \(error.localizedDescription)")
} else {
print("Room disconnected")
}
}
func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) {
print("Subscribed to track: \(publication.trackSid) from participant: \(participant.identity)")
if track.kind == .video {
// Attach video track to view
let videoView = VideoView()
track.attach(videoView)
// Add videoView to your UI
// ...
}
}
}
// Dolby.io Android SDK (Kotlin)
import com.voxeet.VoxeetSDK
import com.voxeet.android.media.MediaStream
import com.voxeet.android.media.MediaStreamType
class ConferenceActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_conference)
// Initialize SDK
VoxeetSDK.initialize("CONSUMER_KEY", "CONSUMER_SECRET")
// Create and join conference
val conferenceOptions = ConferenceCreateOptions.Builder()
.setConferenceAlias("conference-name")
.build()
VoxeetSDK.conference().create(conferenceOptions)
.then { conference ->
val joinOptions = ConferenceJoinOptions.Builder(conference)
.setConstraints(MediaConstraints(true, true))
.build()
VoxeetSDK.conference().join(joinOptions)
}
.then {
// Start video
VoxeetSDK.conference().startVideo()
.then {
Log.d("Conference", "Local video started")
}
.error { error ->
Log.e("Conference", "Error starting video: ${error.message}")
}
}
.error { error ->
Log.e("Conference", "Error joining conference: ${error.message}")
}
// Set up listeners
VoxeetSDK.conference().addListener(object : ConferenceListener {
override fun onParticipantAdded(participant: Participant) {
Log.d("Conference", "${participant.info.name} joined")
}
override fun onStreamAdded(participant: Participant, stream: MediaStream) {
if (participant.id != VoxeetSDK.session().participant.id) {
runOnUiThread {
// Attach remote video
val videoView = findViewById(R.id.remote_video)
VoxeetSDK.conference().attachMediaStream(stream, videoView)
}
}
}
// Other listener methods...
})
}
}
// LiveKit Android SDK (Kotlin)
import io.livekit.android.LiveKit
import io.livekit.android.room.Room
import io.livekit.android.room.track.Track
import io.livekit.android.room.track.VideoTrack
class ConferenceActivity : AppCompatActivity() {
private lateinit var room: Room
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_conference)
// Initialize LiveKit
room = LiveKit.create(applicationContext)
// Set up room listeners
room.addEventListener(object : RoomListener {
override fun onParticipantConnected(participant: Participant) {
Log.d("LiveKit", "${participant.identity} connected")
}
override fun onTrackSubscribed(
track: Track,
publication: RemoteTrackPublication,
participant: RemoteParticipant
) {
if (track is VideoTrack) {
runOnUiThread {
// Attach video track to view
val videoView = findViewById(R.id.remote_video)
track.addRenderer(videoView)
}
}
}
// Other listener methods...
})
// Connect to room with token from your server
val url = "wss://your-livekit-server.com"
val token = "your-token-here"
lifecycleScope.launch {
try {
room.connect(url, token)
Log.d("LiveKit", "Connected to room: ${room.name}")
// Publish local tracks
room.localParticipant.enableCameraAndMicrophone()
Log.d("LiveKit", "Published local tracks")
} catch (e: Exception) {
Log.e("LiveKit", "Error connecting to room: ${e.message}")
}
}
}
override fun onDestroy() {
super.onDestroy()
room.disconnect()
}
}
Start your migration journey from Dolby.io to LiveKit today and experience the benefits of an open-source, flexible video conferencing solution.
Begin Migration