Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ add_library(livekit
include/livekit/audio_source.h
include/livekit/audio_stream.h
include/livekit/data_stream.h
include/livekit/e2ee.h
include/livekit/room.h
include/livekit/room_event_types.h
include/livekit/room_delegate.h
Expand All @@ -186,6 +187,7 @@ add_library(livekit
src/audio_source.cpp
src/audio_stream.cpp
src/data_stream.cpp
src/e2ee.cpp
src/ffi_handle.cpp
src/ffi_client.cpp
src/local_audio_track.cpp
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ export LIVEKIT_TOKEN=<jwt-token>
./build/examples/SimpleRoom
```

**End-to-End Encryption (E2EE)**
You can enable E2E encryption for the streams via --enable_e2ee and --e2ee_key flags,
by running the following cmds in two terminals or computers. **Note, jwt_token needs to be different identity**
```bash
./build/examples/SimpleRoom --url $URL --token <jwt-token> --enable_e2ee --e2ee_key="your_key"
```
**Note**, **all participants must use the exact same E2EE configuration and shared key.**
If the E2EE keys do not match between participants:
- Media cannot be decrypted
- Video tracks will appear as a black screen
- Audio will be silent
- No explicit error may be shown at the UI level

Press Ctrl-C to exit the example.

### SimpleRpc
Expand Down
25 changes: 14 additions & 11 deletions examples/simple_data_stream/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ std::string randomHexId(std::size_t nbytes = 16) {
}

// Greeting: send text + image
void greetParticipant(Room &room, const std::string &identity) {
void greetParticipant(Room *room, const std::string &identity) {
std::cout << "[DataStream] Greeting participant: " << identity << "\n";

LocalParticipant *lp = room.localParticipant();
LocalParticipant *lp = room->localParticipant();
if (!lp) {
std::cerr << "[DataStream] No local participant, cannot greet.\n";
return;
Expand Down Expand Up @@ -209,33 +209,33 @@ int main(int argc, char *argv[]) {
std::signal(SIGTERM, handleSignal);
#endif

Room room{};
auto room = std::make_unique<Room>();
RoomOptions options;
options.auto_subscribe = true;
options.dynacast = false;

bool ok = room.Connect(url, token, options);
bool ok = room->Connect(url, token, options);
std::cout << "[DataStream] Connect result: " << std::boolalpha << ok << "\n";
if (!ok) {
std::cerr << "[DataStream] Failed to connect to room\n";
FfiClient::instance().shutdown();
return 1;
}

auto info = room.room_info();
auto info = room->room_info();
std::cout << "[DataStream] Connected to room '" << info.name
<< "', participants: " << info.num_participants << "\n";

// Register stream handlers
room.registerTextStreamHandler(
room->registerTextStreamHandler(
"chat", [](std::shared_ptr<TextStreamReader> reader,
const std::string &participant_identity) {
std::thread t(handleChatMessage, std::move(reader),
participant_identity);
t.detach();
});

room.registerByteStreamHandler(
room->registerByteStreamHandler(
"files", [](std::shared_ptr<ByteStreamReader> reader,
const std::string &participant_identity) {
std::thread t(handleWelcomeImage, std::move(reader),
Expand All @@ -245,25 +245,25 @@ int main(int argc, char *argv[]) {

// Greet existing participants
{
auto remotes = room.remoteParticipants();
auto remotes = room->remoteParticipants();
for (const auto &rp : remotes) {
if (!rp)
continue;
std::cout << "Remote: " << rp->identity() << "\n";
greetParticipant(room, rp->identity());
greetParticipant(room.get(), rp->identity());
}
}

// Optionally: greet on join
//
// If Room API exposes a participant-connected callback, you could do:
//
// room.onParticipantConnected(
// room->onParticipantConnected(
// [&](RemoteParticipant& participant) {
// std::cout << "[DataStream] participant connected: "
// << participant.sid() << " " << participant.identity()
// << "\n";
// greetParticipant(room, participant.identity());
// greetParticipant(room.get(), participant.identity());
// });
//
// Adjust to your actual event API.
Expand All @@ -274,6 +274,9 @@ int main(int argc, char *argv[]) {
}

std::cout << "[DataStream] Shutting down...\n";
// It is important to clean up the delegate and room in order.
room->setDelegate(nullptr);
room.reset();
FfiClient::instance().shutdown();
return 0;
}
95 changes: 74 additions & 21 deletions examples/simple_room/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,27 +43,40 @@ namespace {
std::atomic<bool> g_running{true};

void printUsage(const char *prog) {
std::cerr << "Usage:\n"
<< " " << prog << " <ws-url> <token>\n"
<< "or:\n"
<< " " << prog << " --url=<ws-url> --token=<token>\n"
<< " " << prog << " --url <ws-url> --token <token>\n\n"
<< "Env fallbacks:\n"
<< " LIVEKIT_URL, LIVEKIT_TOKEN\n";
std::cerr
<< "Usage:\n"
<< " " << prog
<< " <ws-url> <token> [--enable_e2ee] [--e2ee_key <key>]\n"
<< "or:\n"
<< " " << prog
<< " --url=<ws-url> --token=<token> [--enable_e2ee] [--e2ee_key=<key>]\n"
<< " " << prog
<< " --url <ws-url> --token <token> [--enable_e2ee] [--e2ee_key "
"<key>]\n\n"
<< "E2EE:\n"
<< " --enable_e2ee Enable end-to-end encryption (E2EE)\n"
<< " --e2ee_key <key> Optional shared key (UTF-8). If omitted, "
"E2EE is enabled\n"
<< " but no shared key is set (advanced "
"usage).\n\n"
<< "Env fallbacks:\n"
<< " LIVEKIT_URL, LIVEKIT_TOKEN, LIVEKIT_E2EE_KEY\n";
}

void handleSignal(int) { g_running.store(false); }

bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
// 1) --help
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token,
bool &enable_e2ee, std::string &e2ee_key) {
enable_e2ee = false;
// --help
for (int i = 1; i < argc; ++i) {
std::string a = argv[i];
if (a == "-h" || a == "--help") {
return false;
}
}

// 2) flags: --url= / --token= or split form
// flags: --url= / --token= or split form
auto get_flag_value = [&](const std::string &name, int &i) -> std::string {
std::string arg = argv[i];
const std::string eq = name + "=";
Expand All @@ -79,18 +92,24 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {

for (int i = 1; i < argc; ++i) {
const std::string a = argv[i];
if (a.rfind("--url", 0) == 0) {
if (a == "--enable_e2ee") {
enable_e2ee = true;
} else if (a.rfind("--url", 0) == 0) {
auto v = get_flag_value("--url", i);
if (!v.empty())
url = v;
} else if (a.rfind("--token", 0) == 0) {
auto v = get_flag_value("--token", i);
if (!v.empty())
token = v;
} else if (a.rfind("--e2ee_key", 0) == 0) {
auto v = get_flag_value("--e2ee_key", i);
if (!v.empty())
e2ee_key = v;
}
}

// 3) positional if still empty
// positional if still empty
if (url.empty() || token.empty()) {
std::vector<std::string> pos;
for (int i = 1; i < argc; ++i) {
Expand Down Expand Up @@ -118,6 +137,11 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
if (e)
token = e;
}
if (e2ee_key.empty()) {
const char *e = std::getenv("LIVEKIT_E2EE_KEY");
if (e)
e2ee_key = e;
}

return !(url.empty() || token.empty());
}
Expand Down Expand Up @@ -211,11 +235,17 @@ class SimpleRoomDelegate : public livekit::RoomDelegate {
SDLMediaManager &media_;
};

static std::vector<std::uint8_t> toBytes(const std::string &s) {
return std::vector<std::uint8_t>(s.begin(), s.end());
}

} // namespace

int main(int argc, char *argv[]) {
std::string url, token;
if (!parseArgs(argc, argv, url, token)) {
bool enable_e2ee = false;
std::string e2ee_key;
if (!parseArgs(argc, argv, url, token, enable_e2ee, e2ee_key)) {
printUsage(argv[0]);
return 1;
}
Expand All @@ -240,22 +270,41 @@ int main(int argc, char *argv[]) {
// Handle Ctrl-C to exit the idle loop
std::signal(SIGINT, handleSignal);

livekit::Room room{};
auto room = std::make_unique<livekit::Room>();
SimpleRoomDelegate delegate(media);
room.setDelegate(&delegate);
room->setDelegate(&delegate);

RoomOptions options;
options.auto_subscribe = true;
options.dynacast = false;
bool res = room.Connect(url, token, options);

if (enable_e2ee) {
livekit::E2EEOptions e2ee;
e2ee.encryption_type = livekit::EncryptionType::GCM;
// Optional shared key: if empty, we enable E2EE without setting a shared
// key. (Advanced use: keys can be set/ratcheted later via
// E2EEManager/KeyProvider.)
if (!e2ee_key.empty()) {
e2ee.shared_key = toBytes(e2ee_key);
}
options.e2ee = e2ee;
if (!e2ee_key.empty()) {
std::cout << "[E2EE] enabled : (shared key length=" << e2ee_key.size()
<< ")\n";
} else {
std::cout << "[E2EE] enabled: (no shared key set)\n";
}
}

bool res = room->Connect(url, token, options);
std::cout << "Connect result is " << std::boolalpha << res << std::endl;
if (!res) {
std::cerr << "Failed to connect to room\n";
FfiClient::instance().shutdown();
return 1;
}

auto info = room.room_info();
auto info = room->room_info();
std::cout << "Connected to room:\n"
<< " SID: " << (info.sid ? *info.sid : "(none)") << "\n"
<< " Name: " << info.name << "\n"
Expand Down Expand Up @@ -286,7 +335,7 @@ int main(int argc, char *argv[]) {
try {
// publishTrack takes std::shared_ptr<Track>, LocalAudioTrack derives from
// Track
audioPub = room.localParticipant()->publishTrack(audioTrack, audioOpts);
audioPub = room->localParticipant()->publishTrack(audioTrack, audioOpts);

std::cout << "Published track:\n"
<< " SID: " << audioPub->sid() << "\n"
Expand Down Expand Up @@ -314,7 +363,7 @@ int main(int argc, char *argv[]) {
try {
// publishTrack takes std::shared_ptr<Track>, LocalAudioTrack derives from
// Track
videoPub = room.localParticipant()->publishTrack(videoTrack, videoOpts);
videoPub = room->localParticipant()->publishTrack(videoTrack, videoOpts);

std::cout << "Published track:\n"
<< " SID: " << videoPub->sid() << "\n"
Expand All @@ -341,12 +390,16 @@ int main(int argc, char *argv[]) {
media.stopMic();

// Clean up the audio track publishment
room.localParticipant()->unpublishTrack(audioPub->sid());
room->localParticipant()->unpublishTrack(audioPub->sid());

media.stopCamera();

// Clean up the video track publishment
room.localParticipant()->unpublishTrack(videoPub->sid());
room->localParticipant()->unpublishTrack(videoPub->sid());

// Must be cleaned up before FfiClient::instance().shutdown();
room->setDelegate(nullptr);
room.reset();

FfiClient::instance().shutdown();
std::cout << "Exiting.\n";
Expand Down
Loading