2929#include " livekit/livekit.h"
3030#include " sdl_media_manager.h"
3131#include " wav_audio_source.h"
32-
33- // TODO(shijing), remove this livekit_ffi.h as it should be internal only.
34- #include " livekit_ffi.h"
32+ // TODO, remove the ffi_client from the public usage.
33+ #include " ffi_client.h"
3534
3635// Consider expose this video_utils.h to public ?
3736#include " video_utils.h"
@@ -43,27 +42,40 @@ namespace {
4342std::atomic<bool > g_running{true };
4443
4544void printUsage (const char *prog) {
46- std::cerr << " Usage:\n "
47- << " " << prog << " <ws-url> <token>\n "
48- << " or:\n "
49- << " " << prog << " --url=<ws-url> --token=<token>\n "
50- << " " << prog << " --url <ws-url> --token <token>\n\n "
51- << " Env fallbacks:\n "
52- << " LIVEKIT_URL, LIVEKIT_TOKEN\n " ;
45+ std::cerr
46+ << " Usage:\n "
47+ << " " << prog
48+ << " <ws-url> <token> [--enable_e2ee] [--e2ee_key <key>]\n "
49+ << " or:\n "
50+ << " " << prog
51+ << " --url=<ws-url> --token=<token> [--enable_e2ee] [--e2ee_key=<key>]\n "
52+ << " " << prog
53+ << " --url <ws-url> --token <token> [--enable_e2ee] [--e2ee_key "
54+ " <key>]\n\n "
55+ << " E2EE:\n "
56+ << " --enable_e2ee Enable end-to-end encryption (E2EE)\n "
57+ << " --e2ee_key <key> Optional shared key (UTF-8). If omitted, "
58+ " E2EE is enabled\n "
59+ << " but no shared key is set (advanced "
60+ " usage).\n\n "
61+ << " Env fallbacks:\n "
62+ << " LIVEKIT_URL, LIVEKIT_TOKEN, LIVEKIT_E2EE_KEY\n " ;
5363}
5464
5565void handleSignal (int ) { g_running.store (false ); }
5666
57- bool parseArgs (int argc, char *argv[], std::string &url, std::string &token) {
58- // 1) --help
67+ bool parseArgs (int argc, char *argv[], std::string &url, std::string &token,
68+ bool &enable_e2ee, std::string &e2ee_key) {
69+ enable_e2ee = false ;
70+ // --help
5971 for (int i = 1 ; i < argc; ++i) {
6072 std::string a = argv[i];
6173 if (a == " -h" || a == " --help" ) {
6274 return false ;
6375 }
6476 }
6577
66- // 2) flags: --url= / --token= or split form
78+ // flags: --url= / --token= or split form
6779 auto get_flag_value = [&](const std::string &name, int &i) -> std::string {
6880 std::string arg = argv[i];
6981 const std::string eq = name + " =" ;
@@ -79,18 +91,24 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
7991
8092 for (int i = 1 ; i < argc; ++i) {
8193 const std::string a = argv[i];
82- if (a.rfind (" --url" , 0 ) == 0 ) {
94+ if (a == " --enable_e2ee" ) {
95+ enable_e2ee = true ;
96+ } else if (a.rfind (" --url" , 0 ) == 0 ) {
8397 auto v = get_flag_value (" --url" , i);
8498 if (!v.empty ())
8599 url = v;
86100 } else if (a.rfind (" --token" , 0 ) == 0 ) {
87101 auto v = get_flag_value (" --token" , i);
88102 if (!v.empty ())
89103 token = v;
104+ } else if (a.rfind (" --e2ee_key" , 0 ) == 0 ) {
105+ auto v = get_flag_value (" --e2ee_key" , i);
106+ if (!v.empty ())
107+ e2ee_key = v;
90108 }
91109 }
92110
93- // 3) positional if still empty
111+ // positional if still empty
94112 if (url.empty () || token.empty ()) {
95113 std::vector<std::string> pos;
96114 for (int i = 1 ; i < argc; ++i) {
@@ -118,6 +136,11 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
118136 if (e)
119137 token = e;
120138 }
139+ if (e2ee_key.empty ()) {
140+ const char *e = std::getenv (" LIVEKIT_E2EE_KEY" );
141+ if (e)
142+ e2ee_key = e;
143+ }
121144
122145 return !(url.empty () || token.empty ());
123146}
@@ -211,11 +234,17 @@ class SimpleRoomDelegate : public livekit::RoomDelegate {
211234 SDLMediaManager &media_;
212235};
213236
237+ static std::vector<std::uint8_t > toBytes (const std::string &s) {
238+ return std::vector<std::uint8_t >(s.begin (), s.end ());
239+ }
240+
214241} // namespace
215242
216243int main (int argc, char *argv[]) {
217244 std::string url, token;
218- if (!parseArgs (argc, argv, url, token)) {
245+ bool enable_e2ee = false ;
246+ std::string e2ee_key;
247+ if (!parseArgs (argc, argv, url, token, enable_e2ee, e2ee_key)) {
219248 printUsage (argv[0 ]);
220249 return 1 ;
221250 }
@@ -240,22 +269,41 @@ int main(int argc, char *argv[]) {
240269 // Handle Ctrl-C to exit the idle loop
241270 std::signal (SIGINT, handleSignal);
242271
243- livekit::Room room{} ;
272+ auto room = std::make_unique< livekit::Room>() ;
244273 SimpleRoomDelegate delegate (media);
245- room. setDelegate (&delegate);
274+ room-> setDelegate (&delegate);
246275
247276 RoomOptions options;
248277 options.auto_subscribe = true ;
249278 options.dynacast = false ;
250- bool res = room.Connect (url, token, options);
279+
280+ if (enable_e2ee) {
281+ livekit::E2EEOptions encryption;
282+ encryption.encryption_type = livekit::EncryptionType::GCM;
283+ // Optional shared key: if empty, we enable E2EE without setting a shared
284+ // key. (Advanced use: keys can be set/ratcheted later via
285+ // E2EEManager/KeyProvider.)
286+ if (!e2ee_key.empty ()) {
287+ encryption.key_provider_options .shared_key = toBytes (e2ee_key);
288+ }
289+ options.encryption = encryption;
290+ if (!e2ee_key.empty ()) {
291+ std::cout << " [E2EE] enabled : (shared key length=" << e2ee_key.size ()
292+ << " )\n " ;
293+ } else {
294+ std::cout << " [E2EE] enabled: (no shared key set)\n " ;
295+ }
296+ }
297+
298+ bool res = room->Connect (url, token, options);
251299 std::cout << " Connect result is " << std::boolalpha << res << std::endl;
252300 if (!res) {
253301 std::cerr << " Failed to connect to room\n " ;
254302 FfiClient::instance ().shutdown ();
255303 return 1 ;
256304 }
257305
258- auto info = room. room_info ();
306+ auto info = room-> room_info ();
259307 std::cout << " Connected to room:\n "
260308 << " SID: " << (info.sid ? *info.sid : " (none)" ) << " \n "
261309 << " Name: " << info.name << " \n "
@@ -286,7 +334,7 @@ int main(int argc, char *argv[]) {
286334 try {
287335 // publishTrack takes std::shared_ptr<Track>, LocalAudioTrack derives from
288336 // Track
289- audioPub = room. localParticipant ()->publishTrack (audioTrack, audioOpts);
337+ audioPub = room-> localParticipant ()->publishTrack (audioTrack, audioOpts);
290338
291339 std::cout << " Published track:\n "
292340 << " SID: " << audioPub->sid () << " \n "
@@ -314,7 +362,7 @@ int main(int argc, char *argv[]) {
314362 try {
315363 // publishTrack takes std::shared_ptr<Track>, LocalAudioTrack derives from
316364 // Track
317- videoPub = room. localParticipant ()->publishTrack (videoTrack, videoOpts);
365+ videoPub = room-> localParticipant ()->publishTrack (videoTrack, videoOpts);
318366
319367 std::cout << " Published track:\n "
320368 << " SID: " << videoPub->sid () << " \n "
@@ -337,16 +385,24 @@ int main(int argc, char *argv[]) {
337385 std::this_thread::sleep_for (std::chrono::milliseconds (10 ));
338386 }
339387
340- // Shutdown the audio thread .
388+ // Shutdown the audio / video capture threads .
341389 media.stopMic ();
390+ media.stopCamera ();
342391
343- // Clean up the audio track publishment
344- room.localParticipant ()->unpublishTrack (audioPub->sid ());
392+ // Drain any queued tasks that might still try to update the renderer /
393+ // speaker
394+ MainThreadDispatcher::update ();
345395
346- media.stopCamera ();
396+ // Must be cleaned up before FfiClient::instance().shutdown();
397+ room->setDelegate (nullptr );
398+
399+ // Clean up the audio track publishment
400+ room->localParticipant ()->unpublishTrack (audioPub->sid ());
347401
348402 // Clean up the video track publishment
349- room.localParticipant ()->unpublishTrack (videoPub->sid ());
403+ room->localParticipant ()->unpublishTrack (videoPub->sid ());
404+
405+ room.reset ();
350406
351407 FfiClient::instance ().shutdown ();
352408 std::cout << " Exiting.\n " ;
0 commit comments