From 402e6fbcc3cb8b2b2aaf80e21b289f27a9060dc6 Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Fri, 29 May 2026 13:46:58 -0600 Subject: [PATCH 1/4] raw ptr to weak_ptr --- basic_room/main.cpp | 14 ++++++++---- hello_livekit/receiver/main.cpp | 2 +- hello_livekit/sender/main.cpp | 2 +- ping_pong/ping/main.cpp | 2 +- ping_pong/pong/main.cpp | 2 +- simple_data_stream/main.cpp | 5 ++-- simple_joystick/receiver/main.cpp | 9 +++++++- simple_joystick/sender/main.cpp | 5 ++-- simple_room/main.cpp | 20 ++++++++++------ simple_rpc/main.cpp | 29 +++++++++++++++++------- user_timestamped_video/consumer/main.cpp | 11 +++++---- user_timestamped_video/producer/main.cpp | 10 ++++---- 12 files changed, 74 insertions(+), 37 deletions(-) diff --git a/basic_room/main.cpp b/basic_room/main.cpp index 6d84959..cc2aa60 100644 --- a/basic_room/main.cpp +++ b/basic_room/main.cpp @@ -132,7 +132,9 @@ int main(int argc, char* argv[]) { std::shared_ptr audioPub; try { - room->localParticipant()->publishTrack(audioTrack, audioOpts); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + lp->publishTrack(audioTrack, audioOpts); audioPub = audioTrack->publication(); std::cout << "Published audio: sid=" << audioPub->sid() << "\n"; } catch (const std::exception& e) { @@ -151,7 +153,9 @@ int main(int argc, char* argv[]) { std::shared_ptr videoPub; try { - room->localParticipant()->publishTrack(videoTrack, videoOpts); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + lp->publishTrack(videoTrack, videoOpts); videoPub = videoTrack->publication(); std::cout << "Published video: sid=" << videoPub->sid() << "\n"; } catch (const std::exception& e) { @@ -179,8 +183,10 @@ int main(int argc, char* argv[]) { // Best-effort unpublish try { - if (audioPub) room->localParticipant()->unpublishTrack(audioPub->sid()); - if (videoPub) room->localParticipant()->unpublishTrack(videoPub->sid()); + if (auto lp = room->localParticipant().lock()) { + if (audioPub) lp->unpublishTrack(audioPub->sid()); + if (videoPub) lp->unpublishTrack(videoPub->sid()); + } } catch (...) { } diff --git a/hello_livekit/receiver/main.cpp b/hello_livekit/receiver/main.cpp index 22f1d42..12ee617 100644 --- a/hello_livekit/receiver/main.cpp +++ b/hello_livekit/receiver/main.cpp @@ -83,7 +83,7 @@ int main(int argc, char* argv[]) { return 1; } - LocalParticipant* lp = room->localParticipant(); + auto lp = room->localParticipant().lock(); assert(lp); std::cout << "[info] [receiver] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name diff --git a/hello_livekit/sender/main.cpp b/hello_livekit/sender/main.cpp index 5d83a16..75b42ac 100644 --- a/hello_livekit/sender/main.cpp +++ b/hello_livekit/sender/main.cpp @@ -84,7 +84,7 @@ int main(int argc, char* argv[]) { return 1; } - LocalParticipant* lp = room->localParticipant(); + auto lp = room->localParticipant().lock(); assert(lp); std::cout << "[info] [sender] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name diff --git a/ping_pong/ping/main.cpp b/ping_pong/ping/main.cpp index 2e92dc5..7f00f80 100644 --- a/ping_pong/ping/main.cpp +++ b/ping_pong/ping/main.cpp @@ -98,7 +98,7 @@ int main(int argc, char* argv[]) { return 1; } - LocalParticipant* local_participant = room->localParticipant(); + auto local_participant = room->localParticipant().lock(); assert(local_participant); std::cout << "[info] ping connected as identity='" << local_participant->identity() << "' room='" diff --git a/ping_pong/pong/main.cpp b/ping_pong/pong/main.cpp index 7692bfc..496f1cd 100644 --- a/ping_pong/pong/main.cpp +++ b/ping_pong/pong/main.cpp @@ -77,7 +77,7 @@ int main(int argc, char* argv[]) { return 1; } - LocalParticipant* local_participant = room->localParticipant(); + auto local_participant = room->localParticipant().lock(); assert(local_participant); std::cout << "[info] pong connected as identity='" << local_participant->identity() << "' room='" diff --git a/simple_data_stream/main.cpp b/simple_data_stream/main.cpp index eb474c3..0cb0ae1 100644 --- a/simple_data_stream/main.cpp +++ b/simple_data_stream/main.cpp @@ -64,7 +64,7 @@ std::string randomHexId(std::size_t nbytes = 16) { void greetParticipant(Room* room, const std::string& identity) { std::cout << "[DataStream] Greeting participant: " << identity << "\n"; - LocalParticipant* lp = room->localParticipant(); + auto lp = room->localParticipant().lock(); if (!lp) { std::cerr << "[DataStream] No local participant, cannot greet.\n"; return; @@ -240,7 +240,8 @@ int main(int argc, char* argv[]) { // Greet existing participants { auto remotes = room->remoteParticipants(); - for (const auto& rp : remotes) { + for (const auto& weak_rp : remotes) { + auto rp = weak_rp.lock(); if (!rp) continue; std::cout << "Remote: " << rp->identity() << "\n"; greetParticipant(room.get(), rp->identity()); diff --git a/simple_joystick/receiver/main.cpp b/simple_joystick/receiver/main.cpp index 67091da..27827d5 100644 --- a/simple_joystick/receiver/main.cpp +++ b/simple_joystick/receiver/main.cpp @@ -78,7 +78,14 @@ int main(int argc, char* argv[]) { std::cout << "[Receiver] Waiting for sender peer (up to 2 minutes)...\n"; // Register RPC handler for joystick commands - LocalParticipant* lp = room->localParticipant(); + auto lp = room->localParticipant().lock(); + if (!lp) { + std::cerr << "[Receiver] No local participant; cannot register RPC handler.\n"; + room->setDelegate(nullptr); + room.reset(); + livekit::shutdown(); + return 1; + } lp->registerRpcMethod("joystick_command", [](const RpcInvocationData& data) -> std::optional { try { auto cmd = simple_joystick::json_to_joystick(data.payload); diff --git a/simple_joystick/sender/main.cpp b/simple_joystick/sender/main.cpp index 7777326..13df9aa 100644 --- a/simple_joystick/sender/main.cpp +++ b/simple_joystick/sender/main.cpp @@ -150,7 +150,6 @@ int main(int argc, char* argv[]) { std::cout << "[Sender] Waiting for 'robot' to join (checking every 2s)...\n"; printControls(); - LocalParticipant* lp = room->localParticipant(); double x = 0.0, y = 0.0, z = 0.0; bool receiver_connected = false; auto last_receiver_check = std::chrono::steady_clock::now(); @@ -160,7 +159,7 @@ int main(int argc, char* argv[]) { auto now = std::chrono::steady_clock::now(); if (now - last_receiver_check >= 2s) { last_receiver_check = now; - bool receiver_present = (room->remoteParticipant("robot") != nullptr); + bool receiver_present = (room->remoteParticipant("robot").lock() != nullptr); if (receiver_present && !receiver_connected) { std::cout << "[Sender] Receiver connected! Use keys to send commands.\n"; @@ -235,6 +234,8 @@ int main(int argc, char* argv[]) { std::cout << "[Sender] Sending: x=" << x << " y=" << y << " z=" << z << "\n"; try { + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); std::string response = lp->performRpc("robot", "joystick_command", payload, 5.0); std::cout << "[Sender] Receiver acknowledged: " << response << "\n"; } catch (const RpcError& e) { diff --git a/simple_room/main.cpp b/simple_room/main.cpp index 1ae15cb..289f1de 100644 --- a/simple_room/main.cpp +++ b/simple_room/main.cpp @@ -304,7 +304,9 @@ int main(int argc, char* argv[]) { audioOpts.dtx = false; audioOpts.simulcast = false; try { - room->localParticipant()->publishTrack(audioTrack, audioOpts); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + lp->publishTrack(audioTrack, audioOpts); const auto audioPub = audioTrack->publication(); std::cout << "Published track:\n" @@ -331,7 +333,9 @@ int main(int argc, char* argv[]) { try { // publishTrack takes std::shared_ptr, LocalAudioTrack derives from // Track - room->localParticipant()->publishTrack(videoTrack, videoOpts); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + lp->publishTrack(videoTrack, videoOpts); const auto videoPub = videoTrack->publication(); @@ -368,11 +372,13 @@ int main(int argc, char* argv[]) { // Must be cleaned up before FfiClient::instance().shutdown(); room->setDelegate(nullptr); - if (audioTrack->publication()) { - room->localParticipant()->unpublishTrack(audioTrack->publication()->sid()); - } - if (videoTrack->publication()) { - room->localParticipant()->unpublishTrack(videoTrack->publication()->sid()); + if (auto lp = room->localParticipant().lock()) { + if (audioTrack->publication()) { + lp->unpublishTrack(audioTrack->publication()->sid()); + } + if (videoTrack->publication()) { + lp->unpublishTrack(videoTrack->publication()->sid()); + } } audioTrack.reset(); videoTrack.reset(); diff --git a/simple_rpc/main.cpp b/simple_rpc/main.cpp index 42e5c46..027baf4 100644 --- a/simple_rpc/main.cpp +++ b/simple_rpc/main.cpp @@ -66,7 +66,7 @@ bool waitForParticipant(Room* room, const std::string& identity, std::chrono::mi auto start = std::chrono::steady_clock::now(); while (std::chrono::steady_clock::now() - start < timeout) { - if (room->remoteParticipant(identity) != nullptr) { + if (room->remoteParticipant(identity).lock() != nullptr) { return true; } std::this_thread::sleep_for(100ms); @@ -207,8 +207,11 @@ std::string parseStringFromJson(const std::string& json) { // RPC handler registration void registerReceiverMethods(Room* greeters_room, Room* math_genius_room) { - LocalParticipant* greeter_lp = greeters_room->localParticipant(); - LocalParticipant* math_genius_lp = math_genius_room->localParticipant(); + auto greeter_lp = greeters_room->localParticipant().lock(); + auto math_genius_lp = math_genius_room->localParticipant().lock(); + if (!greeter_lp || !math_genius_lp) { + throw std::runtime_error("local participant unavailable"); + } // arrival greeter_lp->registerRpcMethod("arrival", [](const RpcInvocationData& data) -> std::optional { @@ -274,7 +277,9 @@ void performGreeting(Room* room) { std::cout << "[Caller] Letting the greeter know that I've arrived\n"; double t0 = nowMs(); try { - std::string response = room->localParticipant()->performRpc("greeter", "arrival", "Hello", std::nullopt); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + std::string response = lp->performRpc("greeter", "arrival", "Hello", std::nullopt); double t1 = nowMs(); std::cout << "[Caller] RTT: " << (t1 - t0) << " ms\n"; std::cout << "[Caller] That's nice, the greeter said: \"" << response << "\"\n"; @@ -291,7 +296,9 @@ void performSquareRoot(Room* room) { double t0 = nowMs(); try { std::string payload = makeNumberJson("number", 16.0); - std::string response = room->localParticipant()->performRpc("math-genius", "square-root", payload, std::nullopt); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + std::string response = lp->performRpc("math-genius", "square-root", payload, std::nullopt); double t1 = nowMs(); std::cout << "[Caller] RTT: " << (t1 - t0) << " ms\n"; double result = parseNumberFromJson(response); @@ -311,8 +318,10 @@ void performQuantumHyperGeometricSeries(Room* room) { double t0 = nowMs(); try { std::string payload = makeNumberJson("number", 42.0); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); std::string response = - room->localParticipant()->performRpc("math-genius", "quantum-hypergeometric-series", payload, std::nullopt); + lp->performRpc("math-genius", "quantum-hypergeometric-series", payload, std::nullopt); double t1 = nowMs(); std::cout << "[Caller] (Unexpected success) RTT=" << (t1 - t0) << " ms\n"; std::cout << "[Caller] Result: " << response << "\n"; @@ -337,7 +346,9 @@ void performDivide(Room* room) { double t0 = nowMs(); try { std::string payload = "{\"dividend\":10,\"divisor\":0}"; - std::string response = room->localParticipant()->performRpc("math-genius", "divide", payload, std::nullopt); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + std::string response = lp->performRpc("math-genius", "divide", payload, std::nullopt); double t1 = nowMs(); std::cout << "[Caller] (Unexpected success) RTT=" << (t1 - t0) << " ms\n"; std::cout << "[Caller] Result = " << response << "\n"; @@ -361,7 +372,9 @@ void performLongCalculation(Room* room) { std::cout << "[Caller] Giving only 10s to respond. EXPECTED RESULT: TIMEOUT.\n"; double t0 = nowMs(); try { - std::string response = room->localParticipant()->performRpc("math-genius", "long-calculation", "{}", 10.0); + auto lp = room->localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + std::string response = lp->performRpc("math-genius", "long-calculation", "{}", 10.0); double t1 = nowMs(); std::cout << "[Caller] (Unexpected success) RTT=" << (t1 - t0) << " ms\n"; std::cout << "[Caller] Result: " << response << "\n"; diff --git a/user_timestamped_video/consumer/main.cpp b/user_timestamped_video/consumer/main.cpp index 8d52403..263e838 100644 --- a/user_timestamped_video/consumer/main.cpp +++ b/user_timestamped_video/consumer/main.cpp @@ -59,8 +59,8 @@ class UserTimestampedVideoConsumerDelegate : public RoomDelegate { : room_(room), read_user_timestamp_(read_user_timestamp) {} void registerExistingParticipants() { - for (const auto& participant : room_.remoteParticipants()) { - if (participant) { + for (const auto& weak_participant : room_.remoteParticipants()) { + if (auto participant = weak_participant.lock()) { registerRemoteVideoCallback(participant->identity()); } } @@ -163,7 +163,8 @@ int main(int argc, char* argv[]) { std::cerr << "[consumer] failed to connect\n"; exit_code = 1; } else { - std::cout << "[consumer] connected as " << room.localParticipant()->identity() << " to room '" + auto lp = room.localParticipant().lock(); + std::cout << "[consumer] connected as " << (lp ? lp->identity() : std::string("")) << " to room '" << room.roomInfo().name << "' with user timestamp " << (cli_options.use_user_timestamp ? "enabled" : "ignored") << "\n"; @@ -173,8 +174,8 @@ int main(int argc, char* argv[]) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); } - for (const auto& participant : room.remoteParticipants()) { - if (participant) { + for (const auto& weak_participant : room.remoteParticipants()) { + if (auto participant = weak_participant.lock()) { room.clearOnVideoFrameCallback(participant->identity(), std::string(kTrackName)); } } diff --git a/user_timestamped_video/producer/main.cpp b/user_timestamped_video/producer/main.cpp index c2b03fc..58e02b4 100644 --- a/user_timestamped_video/producer/main.cpp +++ b/user_timestamped_video/producer/main.cpp @@ -93,7 +93,8 @@ int main(int argc, char* argv[]) { std::cerr << "[producer] failed to connect\n"; exit_code = 1; } else { - std::cout << "[producer] connected as " << room.localParticipant()->identity() << " to room '" + auto lp = room.localParticipant().lock(); + std::cout << "[producer] connected as " << (lp ? lp->identity() : std::string("")) << " to room '" << room.roomInfo().name << "'\n"; auto source = std::make_shared(kFrameWidth, kFrameHeight); @@ -104,7 +105,8 @@ int main(int argc, char* argv[]) { publish_options.source = TrackSource::SOURCE_CAMERA; publish_options.packet_trailer_features.user_timestamp = cli_options.use_user_timestamp; - room.localParticipant()->publishTrack(track, publish_options); + if (!lp) throw std::runtime_error("local participant unavailable"); + lp->publishTrack(track, publish_options); std::cout << "[producer] published camera track with user timestamp " << (cli_options.use_user_timestamp ? "enabled" : "disabled") << "\n"; @@ -147,8 +149,8 @@ int main(int argc, char* argv[]) { exit_code = 1; } - if (track->publication()) { - room.localParticipant()->unpublishTrack(track->publication()->sid()); + if (lp && track->publication()) { + lp->unpublishTrack(track->publication()->sid()); } } } From bdb4cd289adecf8cf498023324fbc57e3caeb58b Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Fri, 29 May 2026 14:39:52 -0600 Subject: [PATCH 2/4] better scoping of local participants --- hello_livekit/sender/main.cpp | 36 +++++++++++---------- ping_pong/ping/main.cpp | 35 +++++++++++---------- ping_pong/pong/main.cpp | 37 ++++++++++++---------- simple_joystick/receiver/main.cpp | 40 ++++++++++++------------ simple_joystick/sender/main.cpp | 2 +- simple_rpc/main.cpp | 7 ++--- user_timestamped_video/consumer/main.cpp | 15 ++++++--- user_timestamped_video/producer/main.cpp | 26 +++++++++------ 8 files changed, 111 insertions(+), 87 deletions(-) diff --git a/hello_livekit/sender/main.cpp b/hello_livekit/sender/main.cpp index 75b42ac..4b8c89b 100644 --- a/hello_livekit/sender/main.cpp +++ b/hello_livekit/sender/main.cpp @@ -84,27 +84,31 @@ int main(int argc, char* argv[]) { return 1; } - auto lp = room->localParticipant().lock(); - assert(lp); + std::shared_ptr data_track; + std::shared_ptr video_track; + std::shared_ptr video_source; + { + auto lp = room->localParticipant().lock(); + assert(lp); - std::cout << "[info] [sender] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name - << "' — pass this identity to HelloLivekitReceiver\n"; + std::cout << "[info] [sender] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name + << "' — pass this identity to HelloLivekitReceiver\n"; - auto video_source = std::make_shared(kWidth, kHeight); + video_source = std::make_shared(kWidth, kHeight); - std::shared_ptr video_track = - lp->publishVideoTrack(kVideoTrackName, video_source, TrackSource::SOURCE_CAMERA); + video_track = lp->publishVideoTrack(kVideoTrackName, video_source, TrackSource::SOURCE_CAMERA); - auto publish_result = lp->publishDataTrack(kDataTrackName); - if (!publish_result) { - const auto& error = publish_result.error(); - std::cerr << "[error] Failed to publish data track: code=" << static_cast(error.code) - << " message=" << error.message << "\n"; - room.reset(); - livekit::shutdown(); - return 1; + auto publish_result = lp->publishDataTrack(kDataTrackName); + if (!publish_result) { + const auto& error = publish_result.error(); + std::cerr << "[error] Failed to publish data track: code=" << static_cast(error.code) + << " message=" << error.message << "\n"; + room.reset(); + livekit::shutdown(); + return 1; + } + data_track = publish_result.value(); } - std::shared_ptr data_track = publish_result.value(); const auto t0 = std::chrono::steady_clock::now(); std::uint64_t count = 0; diff --git a/ping_pong/ping/main.cpp b/ping_pong/ping/main.cpp index 7f00f80..8079aa3 100644 --- a/ping_pong/ping/main.cpp +++ b/ping_pong/ping/main.cpp @@ -98,24 +98,27 @@ int main(int argc, char* argv[]) { return 1; } - auto local_participant = room->localParticipant().lock(); - assert(local_participant); - - std::cout << "[info] ping connected as identity='" << local_participant->identity() << "' room='" - << room->roomInfo().name << "'\n"; - - auto publish_result = local_participant->publishDataTrack(ping_pong::kPingTrackName); - if (!publish_result) { - const auto& error = publish_result.error(); - std::cerr << "[error] Failed to publish ping data track: code=" << static_cast(error.code) - << " message=" << error.message << "\n"; - room->setDelegate(nullptr); - room.reset(); - livekit::shutdown(); - return 1; + std::shared_ptr ping_track; + { + auto local_participant = room->localParticipant().lock(); + assert(local_participant); + + std::cout << "[info] ping connected as identity='" << local_participant->identity() << "' room='" + << room->roomInfo().name << "'\n"; + + auto publish_result = local_participant->publishDataTrack(ping_pong::kPingTrackName); + if (!publish_result) { + const auto& error = publish_result.error(); + std::cerr << "[error] Failed to publish ping data track: code=" << static_cast(error.code) + << " message=" << error.message << "\n"; + room->setDelegate(nullptr); + room.reset(); + livekit::shutdown(); + return 1; + } + ping_track = publish_result.value(); } - std::shared_ptr ping_track = publish_result.value(); std::unordered_map sent_messages; std::mutex sent_messages_mutex; diff --git a/ping_pong/pong/main.cpp b/ping_pong/pong/main.cpp index 496f1cd..711d5b9 100644 --- a/ping_pong/pong/main.cpp +++ b/ping_pong/pong/main.cpp @@ -77,25 +77,28 @@ int main(int argc, char* argv[]) { return 1; } - auto local_participant = room->localParticipant().lock(); - assert(local_participant); - - std::cout << "[info] pong connected as identity='" << local_participant->identity() << "' room='" - << room->roomInfo().name << "'\n"; - - auto publish_result = local_participant->publishDataTrack(ping_pong::kPongTrackName); - if (!publish_result) { - const auto& error = publish_result.error(); - std::cerr << "[error] Failed to publish pong data track: code=" << static_cast(error.code) - << " message=" << error.message << "\n"; - room->setDelegate(nullptr); - room.reset(); - livekit::shutdown(); - return 1; + std::shared_ptr pong_track; + { + // limit the scope of the local participant + auto local_participant = room->localParticipant().lock(); + assert(local_participant); + + std::cout << "[info] pong connected as identity='" << local_participant->identity() << "' room='" + << room->roomInfo().name << "'\n"; + + auto publish_result = local_participant->publishDataTrack(ping_pong::kPongTrackName); + if (!publish_result) { + const auto& error = publish_result.error(); + std::cerr << "[error] Failed to publish pong data track: code=" << static_cast(error.code) + << " message=" << error.message << "\n"; + room->setDelegate(nullptr); + room.reset(); + livekit::shutdown(); + return 1; + } + pong_track = publish_result.value(); } - std::shared_ptr pong_track = publish_result.value(); - const auto callback_id = room->addOnDataFrameCallback( ping_pong::kPingParticipantIdentity, ping_pong::kPingTrackName, [pong_track](const std::vector& payload, std::optional /*user_timestamp*/) { diff --git a/simple_joystick/receiver/main.cpp b/simple_joystick/receiver/main.cpp index 27827d5..81273fa 100644 --- a/simple_joystick/receiver/main.cpp +++ b/simple_joystick/receiver/main.cpp @@ -77,27 +77,27 @@ int main(int argc, char* argv[]) { std::cout << "[Receiver] Connected to room: " << info.name << "\n"; std::cout << "[Receiver] Waiting for sender peer (up to 2 minutes)...\n"; - // Register RPC handler for joystick commands - auto lp = room->localParticipant().lock(); - if (!lp) { - std::cerr << "[Receiver] No local participant; cannot register RPC handler.\n"; - room->setDelegate(nullptr); - room.reset(); - livekit::shutdown(); - return 1; - } - lp->registerRpcMethod("joystick_command", [](const RpcInvocationData& data) -> std::optional { - try { - auto cmd = simple_joystick::json_to_joystick(data.payload); - g_sender_connected.store(true); - std::cout << "[Receiver] Joystick from '" << data.caller_identity << "': x=" << cmd.x << " y=" << cmd.y - << " z=" << cmd.z << "\n"; - return std::optional{"ok"}; - } catch (const std::exception& e) { - std::cerr << "[Receiver] Bad joystick payload: " << e.what() << "\n"; - throw; + { + // Register RPC handler for joystick commands + auto lp = room->localParticipant().lock(); + if (!lp) { + std::cerr << "[Receiver] No local participant; cannot register RPC handler.\n"; + livekit::shutdown(); + return 1; } - }); + lp->registerRpcMethod("joystick_command", [](const RpcInvocationData& data) -> std::optional { + try { + auto cmd = simple_joystick::json_to_joystick(data.payload); + g_sender_connected.store(true); + std::cout << "[Receiver] Joystick from '" << data.caller_identity << "': x=" << cmd.x << " y=" << cmd.y + << " z=" << cmd.z << "\n"; + return std::optional{"ok"}; + } catch (const std::exception& e) { + std::cerr << "[Receiver] Bad joystick payload: " << e.what() << "\n"; + throw; + } + }); + } std::cout << "[Receiver] RPC handler 'joystick_command' registered. " << "Listening for commands...\n"; diff --git a/simple_joystick/sender/main.cpp b/simple_joystick/sender/main.cpp index 13df9aa..8daa777 100644 --- a/simple_joystick/sender/main.cpp +++ b/simple_joystick/sender/main.cpp @@ -159,7 +159,7 @@ int main(int argc, char* argv[]) { auto now = std::chrono::steady_clock::now(); if (now - last_receiver_check >= 2s) { last_receiver_check = now; - bool receiver_present = (room->remoteParticipant("robot").lock() != nullptr); + bool receiver_present = !room->remoteParticipant("robot").expired(); if (receiver_present && !receiver_connected) { std::cout << "[Sender] Receiver connected! Use keys to send commands.\n"; diff --git a/simple_rpc/main.cpp b/simple_rpc/main.cpp index 027baf4..dcf672c 100644 --- a/simple_rpc/main.cpp +++ b/simple_rpc/main.cpp @@ -66,7 +66,7 @@ bool waitForParticipant(Room* room, const std::string& identity, std::chrono::mi auto start = std::chrono::steady_clock::now(); while (std::chrono::steady_clock::now() - start < timeout) { - if (room->remoteParticipant(identity).lock() != nullptr) { + if (!room->remoteParticipant(identity).expired()) { return true; } std::this_thread::sleep_for(100ms); @@ -210,7 +210,7 @@ void registerReceiverMethods(Room* greeters_room, Room* math_genius_room) { auto greeter_lp = greeters_room->localParticipant().lock(); auto math_genius_lp = math_genius_room->localParticipant().lock(); if (!greeter_lp || !math_genius_lp) { - throw std::runtime_error("local participant unavailable"); + throw std::runtime_error("local participants unavailable"); } // arrival @@ -320,8 +320,7 @@ void performQuantumHyperGeometricSeries(Room* room) { std::string payload = makeNumberJson("number", 42.0); auto lp = room->localParticipant().lock(); if (!lp) throw std::runtime_error("local participant unavailable"); - std::string response = - lp->performRpc("math-genius", "quantum-hypergeometric-series", payload, std::nullopt); + std::string response = lp->performRpc("math-genius", "quantum-hypergeometric-series", payload, std::nullopt); double t1 = nowMs(); std::cout << "[Caller] (Unexpected success) RTT=" << (t1 - t0) << " ms\n"; std::cout << "[Caller] Result: " << response << "\n"; diff --git a/user_timestamped_video/consumer/main.cpp b/user_timestamped_video/consumer/main.cpp index 263e838..6ba5d32 100644 --- a/user_timestamped_video/consumer/main.cpp +++ b/user_timestamped_video/consumer/main.cpp @@ -62,6 +62,8 @@ class UserTimestampedVideoConsumerDelegate : public RoomDelegate { for (const auto& weak_participant : room_.remoteParticipants()) { if (auto participant = weak_participant.lock()) { registerRemoteVideoCallback(participant->identity()); + } else { + throw std::runtime_error("unable to lock provided remote participant"); } } } @@ -163,10 +165,13 @@ int main(int argc, char* argv[]) { std::cerr << "[consumer] failed to connect\n"; exit_code = 1; } else { - auto lp = room.localParticipant().lock(); - std::cout << "[consumer] connected as " << (lp ? lp->identity() : std::string("")) << " to room '" - << room.roomInfo().name << "' with user timestamp " - << (cli_options.use_user_timestamp ? "enabled" : "ignored") << "\n"; + if (auto lp = room.localParticipant().lock()) { + std::cout << "[consumer] connected as " << (lp ? lp->identity() : std::string("")) << " to room '" + << room.roomInfo().name << "' with user timestamp " + << (cli_options.use_user_timestamp ? "enabled" : "ignored") << "\n"; + } else { + throw std::runtime_error("unable to lock local participant"); + } delegate.registerExistingParticipants(); @@ -177,6 +182,8 @@ int main(int argc, char* argv[]) { for (const auto& weak_participant : room.remoteParticipants()) { if (auto participant = weak_participant.lock()) { room.clearOnVideoFrameCallback(participant->identity(), std::string(kTrackName)); + } else { + throw std::runtime_error("unable to lock provided remote participant"); } } } diff --git a/user_timestamped_video/producer/main.cpp b/user_timestamped_video/producer/main.cpp index 58e02b4..c58a983 100644 --- a/user_timestamped_video/producer/main.cpp +++ b/user_timestamped_video/producer/main.cpp @@ -93,9 +93,11 @@ int main(int argc, char* argv[]) { std::cerr << "[producer] failed to connect\n"; exit_code = 1; } else { - auto lp = room.localParticipant().lock(); - std::cout << "[producer] connected as " << (lp ? lp->identity() : std::string("")) << " to room '" - << room.roomInfo().name << "'\n"; + { + auto lp = room.localParticipant().lock(); + std::cout << "[producer] connected as " << (lp ? lp->identity() : std::string("")) << " to room '" + << room.roomInfo().name << "'\n"; + } auto source = std::make_shared(kFrameWidth, kFrameHeight); auto track = LocalVideoTrack::createLocalVideoTrack("timestamped-camera", source); @@ -105,10 +107,13 @@ int main(int argc, char* argv[]) { publish_options.source = TrackSource::SOURCE_CAMERA; publish_options.packet_trailer_features.user_timestamp = cli_options.use_user_timestamp; - if (!lp) throw std::runtime_error("local participant unavailable"); - lp->publishTrack(track, publish_options); - std::cout << "[producer] published camera track with user timestamp " - << (cli_options.use_user_timestamp ? "enabled" : "disabled") << "\n"; + { + auto lp = room.localParticipant().lock(); + if (!lp) throw std::runtime_error("local participant unavailable"); + lp->publishTrack(track, publish_options); + std::cout << "[producer] published camera track with user timestamp " + << (cli_options.use_user_timestamp ? "enabled" : "disabled") << "\n"; + } VideoFrame frame = VideoFrame::create(kFrameWidth, kFrameHeight, VideoBufferType::BGRA); const auto capture_start = std::chrono::steady_clock::now(); @@ -149,8 +154,11 @@ int main(int argc, char* argv[]) { exit_code = 1; } - if (lp && track->publication()) { - lp->unpublishTrack(track->publication()->sid()); + // best effort unpublish + if (auto lp = room.localParticipant().lock()) { + if (lp && track->publication()) { + lp->unpublishTrack(track->publication()->sid()); + } } } } From d89c0c734cc79bdcb3082591831c4470c92cf405 Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Fri, 29 May 2026 15:14:36 -0600 Subject: [PATCH 3/4] rm assert calls --- hello_livekit/receiver/main.cpp | 6 ++++-- hello_livekit/sender/main.cpp | 6 ++++-- ping_pong/ping/main.cpp | 6 ++++-- ping_pong/pong/main.cpp | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/hello_livekit/receiver/main.cpp b/hello_livekit/receiver/main.cpp index 12ee617..aa31318 100644 --- a/hello_livekit/receiver/main.cpp +++ b/hello_livekit/receiver/main.cpp @@ -25,11 +25,11 @@ /// LIVEKIT_URL, LIVEKIT_RECEIVER_TOKEN, LIVEKIT_SENDER_IDENTITY #include -#include #include #include #include #include +#include #include #include "livekit/livekit.h" @@ -84,7 +84,9 @@ int main(int argc, char* argv[]) { } auto lp = room->localParticipant().lock(); - assert(lp); + if (!lp) { + throw std::runtime_error("[receiver] local participant is null"); + } std::cout << "[info] [receiver] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name << "'; subscribing to sender identity='" << sender_identity << "'\n"; diff --git a/hello_livekit/sender/main.cpp b/hello_livekit/sender/main.cpp index 4b8c89b..4ddcd39 100644 --- a/hello_livekit/sender/main.cpp +++ b/hello_livekit/sender/main.cpp @@ -24,13 +24,13 @@ /// LIVEKIT_URL, LIVEKIT_SENDER_TOKEN #include -#include #include #include #include #include #include #include +#include #include #include "livekit/livekit.h" @@ -89,7 +89,9 @@ int main(int argc, char* argv[]) { std::shared_ptr video_source; { auto lp = room->localParticipant().lock(); - assert(lp); + if (!lp) { + throw std::runtime_error("[sender] local participant is null"); + } std::cout << "[info] [sender] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name << "' — pass this identity to HelloLivekitReceiver\n"; diff --git a/ping_pong/ping/main.cpp b/ping_pong/ping/main.cpp index 8079aa3..85562f7 100644 --- a/ping_pong/ping/main.cpp +++ b/ping_pong/ping/main.cpp @@ -19,7 +19,6 @@ /// identity is `ping`. #include -#include #include #include #include @@ -27,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -101,7 +101,9 @@ int main(int argc, char* argv[]) { std::shared_ptr ping_track; { auto local_participant = room->localParticipant().lock(); - assert(local_participant); + if (!local_participant) { + throw std::runtime_error("[ping] local participant is null"); + } std::cout << "[info] ping connected as identity='" << local_participant->identity() << "' room='" << room->roomInfo().name << "'\n"; diff --git a/ping_pong/pong/main.cpp b/ping_pong/pong/main.cpp index 711d5b9..22f22ac 100644 --- a/ping_pong/pong/main.cpp +++ b/ping_pong/pong/main.cpp @@ -18,13 +18,13 @@ /// on the "pong" data track. Use a token whose identity is `pong`. #include -#include #include #include #include #include #include #include +#include #include #include #include @@ -81,7 +81,9 @@ int main(int argc, char* argv[]) { { // limit the scope of the local participant auto local_participant = room->localParticipant().lock(); - assert(local_participant); + if (!local_participant) { + throw std::runtime_error("[pong] local participant is null"); + } std::cout << "[info] pong connected as identity='" << local_participant->identity() << "' room='" << room->roomInfo().name << "'\n"; From 008808dcda21bcb188295425908219e64eae395d Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Fri, 29 May 2026 15:58:48 -0600 Subject: [PATCH 4/4] rm simple_joystick --- CMakeLists.txt | 2 - simple_joystick/receiver/CMakeLists.txt | 32 --- simple_joystick/receiver/json_utils.cpp | 45 ---- simple_joystick/receiver/json_utils.h | 38 ---- simple_joystick/receiver/main.cpp | 129 ------------ simple_joystick/receiver/utils.cpp | 80 -------- simple_joystick/receiver/utils.h | 31 --- simple_joystick/sender/CMakeLists.txt | 32 --- simple_joystick/sender/json_utils.cpp | 45 ---- simple_joystick/sender/json_utils.h | 38 ---- simple_joystick/sender/main.cpp | 259 ------------------------ simple_joystick/sender/utils.cpp | 80 -------- simple_joystick/sender/utils.h | 31 --- 13 files changed, 842 deletions(-) delete mode 100644 simple_joystick/receiver/CMakeLists.txt delete mode 100644 simple_joystick/receiver/json_utils.cpp delete mode 100644 simple_joystick/receiver/json_utils.h delete mode 100644 simple_joystick/receiver/main.cpp delete mode 100644 simple_joystick/receiver/utils.cpp delete mode 100644 simple_joystick/receiver/utils.h delete mode 100644 simple_joystick/sender/CMakeLists.txt delete mode 100644 simple_joystick/sender/json_utils.cpp delete mode 100644 simple_joystick/sender/json_utils.h delete mode 100644 simple_joystick/sender/main.cpp delete mode 100644 simple_joystick/sender/utils.cpp delete mode 100644 simple_joystick/sender/utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d382af..a102cc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,8 +89,6 @@ add_subdirectory(logging_levels/basic_usage logging_levels_basic_usage) add_subdirectory(logging_levels/custom_sinks logging_levels_custom_sinks) add_subdirectory(hello_livekit/sender hello_livekit_sender) add_subdirectory(hello_livekit/receiver hello_livekit_receiver) -add_subdirectory(simple_joystick/sender simple_joystick_sender) -add_subdirectory(simple_joystick/receiver simple_joystick_receiver) add_subdirectory(ping_pong/ping ping_pong_ping) add_subdirectory(ping_pong/pong ping_pong_pong) add_subdirectory(user_timestamped_video) diff --git a/simple_joystick/receiver/CMakeLists.txt b/simple_joystick/receiver/CMakeLists.txt deleted file mode 100644 index ddf3a2a..0000000 --- a/simple_joystick/receiver/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2026 LiveKit, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -add_library(simple_joystick_receiver_support STATIC - json_utils.cpp - json_utils.h - utils.cpp - utils.h -) - -target_include_directories(simple_joystick_receiver_support PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(simple_joystick_receiver_support PUBLIC nlohmann_json::nlohmann_json) - -add_executable(SimpleJoystickReceiver - main.cpp -) - -target_include_directories(SimpleJoystickReceiver PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(SimpleJoystickReceiver PRIVATE simple_joystick_receiver_support ${LIVEKIT_CORE_TARGET}) - -livekit_copy_windows_runtime_dlls(SimpleJoystickReceiver) diff --git a/simple_joystick/receiver/json_utils.cpp b/simple_joystick/receiver/json_utils.cpp deleted file mode 100644 index 3c98c1c..0000000 --- a/simple_joystick/receiver/json_utils.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "json_utils.h" - -#include -#include - -namespace simple_joystick { - -std::string joystick_to_json(const JoystickCommand& cmd) { - nlohmann::json j; - j["x"] = cmd.x; - j["y"] = cmd.y; - j["z"] = cmd.z; - return j.dump(); -} - -JoystickCommand json_to_joystick(const std::string& json) { - try { - auto j = nlohmann::json::parse(json); - JoystickCommand cmd; - cmd.x = j.at("x").get(); - cmd.y = j.at("y").get(); - cmd.z = j.at("z").get(); - return cmd; - } catch (const nlohmann::json::exception& e) { - throw std::runtime_error(std::string("Failed to parse joystick JSON: ") + e.what()); - } -} - -} // namespace simple_joystick diff --git a/simple_joystick/receiver/json_utils.h b/simple_joystick/receiver/json_utils.h deleted file mode 100644 index e1f47a8..0000000 --- a/simple_joystick/receiver/json_utils.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -namespace simple_joystick { - -/// Represents a joystick command with three axes. -struct JoystickCommand { - double x = 0.0; - double y = 0.0; - double z = 0.0; -}; - -/// Serialize a JoystickCommand to a JSON string. -/// Example output: {"x":1.0,"y":2.0,"z":3.0} -std::string joystick_to_json(const JoystickCommand& cmd); - -/// Deserialize a JSON string into a JoystickCommand. -/// Throws std::runtime_error if the JSON is invalid or missing fields. -JoystickCommand json_to_joystick(const std::string& json); - -} // namespace simple_joystick diff --git a/simple_joystick/receiver/main.cpp b/simple_joystick/receiver/main.cpp deleted file mode 100644 index 81273fa..0000000 --- a/simple_joystick/receiver/main.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include - -#include "json_utils.h" -#include "livekit/livekit.h" -#include "utils.h" - -using namespace livekit; -using namespace std::chrono_literals; - -namespace { - -std::atomic g_running{true}; -std::atomic g_sender_connected{false}; - -void handleSignal(int) { g_running.store(false); } - -void printUsage(const char* prog) { - std::cerr << "Usage:\n" - << " " << prog << " \n" - << "or:\n" - << " " << prog << " --url= --token=\n\n" - << "Env fallbacks:\n" - << " LIVEKIT_URL, LIVEKIT_TOKEN\n\n" - << "This is the receiver. It waits for a sender peer to\n" - << "connect and send joystick commands via RPC.\n" - << "Exits after 2 minutes if no commands are received.\n"; -} - -} // namespace - -int main(int argc, char* argv[]) { - std::string url, token; - if (!simple_joystick::parseArgs(argc, argv, url, token)) { - printUsage(argv[0]); - return 1; - } - - std::cout << "[Receiver] Connecting to: " << url << "\n"; - std::signal(SIGINT, handleSignal); - - livekit::initialize(livekit::LogLevel::Info); - auto room = std::make_unique(); - RoomOptions options; - options.auto_subscribe = true; - options.dynacast = false; - - bool res = room->connect(url, token, options); - std::cout << "[Receiver] Connect result: " << std::boolalpha << res << "\n"; - if (!res) { - std::cerr << "[Receiver] Failed to connect to room\n"; - livekit::shutdown(); - return 1; - } - - auto info = room->roomInfo(); - std::cout << "[Receiver] Connected to room: " << info.name << "\n"; - std::cout << "[Receiver] Waiting for sender peer (up to 2 minutes)...\n"; - - { - // Register RPC handler for joystick commands - auto lp = room->localParticipant().lock(); - if (!lp) { - std::cerr << "[Receiver] No local participant; cannot register RPC handler.\n"; - livekit::shutdown(); - return 1; - } - lp->registerRpcMethod("joystick_command", [](const RpcInvocationData& data) -> std::optional { - try { - auto cmd = simple_joystick::json_to_joystick(data.payload); - g_sender_connected.store(true); - std::cout << "[Receiver] Joystick from '" << data.caller_identity << "': x=" << cmd.x << " y=" << cmd.y - << " z=" << cmd.z << "\n"; - return std::optional{"ok"}; - } catch (const std::exception& e) { - std::cerr << "[Receiver] Bad joystick payload: " << e.what() << "\n"; - throw; - } - }); - } - - std::cout << "[Receiver] RPC handler 'joystick_command' registered. " - << "Listening for commands...\n"; - - // Wait up to 2 minutes for activity, then exit as failure - auto deadline = std::chrono::steady_clock::now() + 2min; - - while (g_running.load() && std::chrono::steady_clock::now() < deadline) { - std::this_thread::sleep_for(100ms); - } - - if (!g_running.load()) { - std::cout << "[Receiver] Interrupted by signal. Shutting down.\n"; - } else if (!g_sender_connected.load()) { - std::cerr << "[Receiver] Timed out after 2 minutes with no sender connection. " - << "Exiting as failure.\n"; - room->setDelegate(nullptr); - room.reset(); - livekit::shutdown(); - return 1; - } else { - std::cout << "[Receiver] Session complete.\n"; - } - - room->setDelegate(nullptr); - room.reset(); - livekit::shutdown(); - return 0; -} diff --git a/simple_joystick/receiver/utils.cpp b/simple_joystick/receiver/utils.cpp deleted file mode 100644 index fca40fb..0000000 --- a/simple_joystick/receiver/utils.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils.h" - -#include -#include -#include - -namespace simple_joystick { - -bool parseArgs(int argc, char* argv[], std::string& url, std::string& token) { - for (int i = 1; i < argc; ++i) { - std::string a = argv[i]; - if (a == "-h" || a == "--help") { - return false; - } - } - - auto get_flag_value = [&](const std::string& name, int& i) -> std::string { - std::string arg = argv[i]; - const std::string eq = name + "="; - if (arg.rfind(name, 0) == 0) { - if (arg.size() > name.size() && arg[name.size()] == '=') { - return arg.substr(eq.size()); - } else if (i + 1 < argc) { - return std::string(argv[++i]); - } - } - return {}; - }; - - for (int i = 1; i < argc; ++i) { - const std::string a = argv[i]; - 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; - } - } - - // Positional args: - std::vector pos; - for (int i = 1; i < argc; ++i) { - std::string a = argv[i]; - if (a.rfind("--", 0) == 0) continue; - pos.push_back(std::move(a)); - } - if (url.empty() && pos.size() >= 1) url = pos[0]; - if (token.empty() && pos.size() >= 2) token = pos[1]; - - // Environment variable fallbacks - if (url.empty()) { - const char* e = std::getenv("LIVEKIT_URL"); - if (e) url = e; - } - if (token.empty()) { - const char* e = std::getenv("LIVEKIT_TOKEN"); - if (e) token = e; - } - - return !(url.empty() || token.empty()); -} - -} // namespace simple_joystick diff --git a/simple_joystick/receiver/utils.h b/simple_joystick/receiver/utils.h deleted file mode 100644 index c76e623..0000000 --- a/simple_joystick/receiver/utils.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -namespace simple_joystick { - -/// Parse command-line arguments for --url and --token. -/// Supports: -/// - Positional: -/// - Flags: --url= / --url , --token= / --token -/// - Env vars: LIVEKIT_URL, LIVEKIT_TOKEN -/// Returns true if both url and token were resolved, false otherwise. -bool parseArgs(int argc, char* argv[], std::string& url, std::string& token); - -} // namespace simple_joystick diff --git a/simple_joystick/sender/CMakeLists.txt b/simple_joystick/sender/CMakeLists.txt deleted file mode 100644 index fd2a02b..0000000 --- a/simple_joystick/sender/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2026 LiveKit, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -add_library(simple_joystick_sender_support STATIC - json_utils.cpp - json_utils.h - utils.cpp - utils.h -) - -target_include_directories(simple_joystick_sender_support PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(simple_joystick_sender_support PUBLIC nlohmann_json::nlohmann_json) - -add_executable(SimpleJoystickSender - main.cpp -) - -target_include_directories(SimpleJoystickSender PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(SimpleJoystickSender PRIVATE simple_joystick_sender_support ${LIVEKIT_CORE_TARGET}) - -livekit_copy_windows_runtime_dlls(SimpleJoystickSender) diff --git a/simple_joystick/sender/json_utils.cpp b/simple_joystick/sender/json_utils.cpp deleted file mode 100644 index 3c98c1c..0000000 --- a/simple_joystick/sender/json_utils.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "json_utils.h" - -#include -#include - -namespace simple_joystick { - -std::string joystick_to_json(const JoystickCommand& cmd) { - nlohmann::json j; - j["x"] = cmd.x; - j["y"] = cmd.y; - j["z"] = cmd.z; - return j.dump(); -} - -JoystickCommand json_to_joystick(const std::string& json) { - try { - auto j = nlohmann::json::parse(json); - JoystickCommand cmd; - cmd.x = j.at("x").get(); - cmd.y = j.at("y").get(); - cmd.z = j.at("z").get(); - return cmd; - } catch (const nlohmann::json::exception& e) { - throw std::runtime_error(std::string("Failed to parse joystick JSON: ") + e.what()); - } -} - -} // namespace simple_joystick diff --git a/simple_joystick/sender/json_utils.h b/simple_joystick/sender/json_utils.h deleted file mode 100644 index e1f47a8..0000000 --- a/simple_joystick/sender/json_utils.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -namespace simple_joystick { - -/// Represents a joystick command with three axes. -struct JoystickCommand { - double x = 0.0; - double y = 0.0; - double z = 0.0; -}; - -/// Serialize a JoystickCommand to a JSON string. -/// Example output: {"x":1.0,"y":2.0,"z":3.0} -std::string joystick_to_json(const JoystickCommand& cmd); - -/// Deserialize a JSON string into a JoystickCommand. -/// Throws std::runtime_error if the JSON is invalid or missing fields. -JoystickCommand json_to_joystick(const std::string& json); - -} // namespace simple_joystick diff --git a/simple_joystick/sender/main.cpp b/simple_joystick/sender/main.cpp deleted file mode 100644 index 8daa777..0000000 --- a/simple_joystick/sender/main.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#else -#include -#include -#include -#endif - -#include "json_utils.h" -#include "livekit/livekit.h" -#include "utils.h" - -using namespace livekit; -using namespace std::chrono_literals; - -namespace { - -std::atomic g_running{true}; - -void handleSignal(int) { g_running.store(false); } - -// --- Raw terminal input helpers --- - -#ifndef _WIN32 -struct termios g_orig_termios; -bool g_raw_mode_enabled = false; - -void disableRawMode() { - if (g_raw_mode_enabled) { - tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_termios); - g_raw_mode_enabled = false; - } -} - -void enableRawMode() { - tcgetattr(STDIN_FILENO, &g_orig_termios); - g_raw_mode_enabled = true; - std::atexit(disableRawMode); - - struct termios raw = g_orig_termios; - raw.c_lflag &= ~(ECHO | ICANON); // disable echo and canonical mode - raw.c_cc[VMIN] = 0; // non-blocking read - raw.c_cc[VTIME] = 0; - tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); -} - -// Returns -1 if no key is available, otherwise the character code. -int readKeyNonBlocking() { - fd_set fds; - FD_ZERO(&fds); - FD_SET(STDIN_FILENO, &fds); - struct timeval tv = {0, 0}; // immediate return - if (select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv) > 0) { - unsigned char ch; - if (read(STDIN_FILENO, &ch, 1) == 1) return ch; - } - return -1; -} -#else -void enableRawMode() { /* Windows _getch() is already unbuffered */ } -void disableRawMode() {} - -int readKeyNonBlocking() { - if (_kbhit()) return _getch(); - return -1; -} -#endif - -void printUsage(const char* prog) { - std::cerr << "Usage:\n" - << " " << prog << " \n" - << "or:\n" - << " " << prog << " --url= --token=\n\n" - << "Env fallbacks:\n" - << " LIVEKIT_URL, LIVEKIT_TOKEN\n\n" - << "This is the sender. It connects to the room and\n" - << "continuously checks for a receiver peer every 2 seconds.\n" - << "Once connected, use keyboard to send joystick commands:\n" - << " w / s = +x / -x\n" - << " d / a = +y / -y\n" - << " z / c = +z / -z\n" - << " q = quit\n" - << "Automatically reconnects if receiver leaves.\n"; -} - -void printControls() { - std::cout << "\n" - << " Controls:\n" - << " w / s = +x / -x\n" - << " d / a = +y / -y\n" - << " z / c = +z / -z\n" - << " q = quit\n\n"; -} - -} // namespace - -int main(int argc, char* argv[]) { - std::string url, token; - if (!simple_joystick::parseArgs(argc, argv, url, token)) { - printUsage(argv[0]); - return 1; - } - - std::cout << "[Sender] Connecting to: " << url << "\n"; - std::signal(SIGINT, handleSignal); - - livekit::initialize(livekit::LogLevel::Info); - auto room = std::make_unique(); - RoomOptions options; - options.auto_subscribe = true; - options.dynacast = false; - - bool res = room->connect(url, token, options); - std::cout << "[Sender] Connect result: " << std::boolalpha << res << "\n"; - if (!res) { - std::cerr << "[Sender] Failed to connect to room\n"; - livekit::shutdown(); - return 1; - } - - auto info = room->roomInfo(); - std::cout << "[Sender] Connected to room: " << info.name << "\n"; - - // Enable raw terminal mode for immediate keypress detection - enableRawMode(); - - std::cout << "[Sender] Waiting for 'robot' to join (checking every 2s)...\n"; - printControls(); - - double x = 0.0, y = 0.0, z = 0.0; - bool receiver_connected = false; - auto last_receiver_check = std::chrono::steady_clock::now(); - - while (g_running.load()) { - // Periodically check receiver presence every 2 seconds - auto now = std::chrono::steady_clock::now(); - if (now - last_receiver_check >= 2s) { - last_receiver_check = now; - bool receiver_present = !room->remoteParticipant("robot").expired(); - - if (receiver_present && !receiver_connected) { - std::cout << "[Sender] Receiver connected! Use keys to send commands.\n"; - receiver_connected = true; - } else if (!receiver_present && receiver_connected) { - std::cout << "[Sender] Receiver disconnected. Waiting for reconnect...\n"; - receiver_connected = false; - } - } - - // Poll for keypress (non-blocking) - int key = readKeyNonBlocking(); - if (key == -1) { - std::this_thread::sleep_for(20ms); // avoid busy-wait - continue; - } - - // Handle quit - if (key == 'q' || key == 'Q') { - std::cout << "\n[Sender] Quit requested.\n"; - break; - } - - // Map key to axis change - bool changed = false; - switch (key) { - case 'w': - case 'W': - x += 1.0; - changed = true; - break; - case 's': - case 'S': - x -= 1.0; - changed = true; - break; - case 'd': - case 'D': - y += 1.0; - changed = true; - break; - case 'a': - case 'A': - y -= 1.0; - changed = true; - break; - case 'z': - case 'Z': - z += 1.0; - changed = true; - break; - case 'c': - case 'C': - z -= 1.0; - changed = true; - break; - default: - break; - } - - if (!changed) continue; - - if (!receiver_connected) { - std::cout << "[Sender] (no receiver connected) x=" << x << " y=" << y << " z=" << z << "\n"; - continue; - } - - // Send joystick command via RPC - simple_joystick::JoystickCommand cmd{x, y, z}; - std::string payload = simple_joystick::joystick_to_json(cmd); - - std::cout << "[Sender] Sending: x=" << x << " y=" << y << " z=" << z << "\n"; - - try { - auto lp = room->localParticipant().lock(); - if (!lp) throw std::runtime_error("local participant unavailable"); - std::string response = lp->performRpc("robot", "joystick_command", payload, 5.0); - std::cout << "[Sender] Receiver acknowledged: " << response << "\n"; - } catch (const RpcError& e) { - std::cerr << "[Sender] RPC error: " << e.message() << "\n"; - if (static_cast(e.code()) == RpcError::ErrorCode::RECIPIENT_DISCONNECTED) { - std::cout << "[Sender] Receiver disconnected. Waiting for reconnect...\n"; - receiver_connected = false; - } - } catch (const std::exception& e) { - std::cerr << "[Sender] Error sending command: " << e.what() << "\n"; - } - } - - disableRawMode(); - - std::cout << "[Sender] Done. Shutting down.\n"; - room->setDelegate(nullptr); - room.reset(); - livekit::shutdown(); - return 0; -} diff --git a/simple_joystick/sender/utils.cpp b/simple_joystick/sender/utils.cpp deleted file mode 100644 index fca40fb..0000000 --- a/simple_joystick/sender/utils.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils.h" - -#include -#include -#include - -namespace simple_joystick { - -bool parseArgs(int argc, char* argv[], std::string& url, std::string& token) { - for (int i = 1; i < argc; ++i) { - std::string a = argv[i]; - if (a == "-h" || a == "--help") { - return false; - } - } - - auto get_flag_value = [&](const std::string& name, int& i) -> std::string { - std::string arg = argv[i]; - const std::string eq = name + "="; - if (arg.rfind(name, 0) == 0) { - if (arg.size() > name.size() && arg[name.size()] == '=') { - return arg.substr(eq.size()); - } else if (i + 1 < argc) { - return std::string(argv[++i]); - } - } - return {}; - }; - - for (int i = 1; i < argc; ++i) { - const std::string a = argv[i]; - 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; - } - } - - // Positional args: - std::vector pos; - for (int i = 1; i < argc; ++i) { - std::string a = argv[i]; - if (a.rfind("--", 0) == 0) continue; - pos.push_back(std::move(a)); - } - if (url.empty() && pos.size() >= 1) url = pos[0]; - if (token.empty() && pos.size() >= 2) token = pos[1]; - - // Environment variable fallbacks - if (url.empty()) { - const char* e = std::getenv("LIVEKIT_URL"); - if (e) url = e; - } - if (token.empty()) { - const char* e = std::getenv("LIVEKIT_TOKEN"); - if (e) token = e; - } - - return !(url.empty() || token.empty()); -} - -} // namespace simple_joystick diff --git a/simple_joystick/sender/utils.h b/simple_joystick/sender/utils.h deleted file mode 100644 index c76e623..0000000 --- a/simple_joystick/sender/utils.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2025 LiveKit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -namespace simple_joystick { - -/// Parse command-line arguments for --url and --token. -/// Supports: -/// - Positional: -/// - Flags: --url= / --url , --token= / --token -/// - Env vars: LIVEKIT_URL, LIVEKIT_TOKEN -/// Returns true if both url and token were resolved, false otherwise. -bool parseArgs(int argc, char* argv[], std::string& url, std::string& token); - -} // namespace simple_joystick