diff --git a/include/livekit/data_track_error.h b/include/livekit/data_track_error.h index 5047a8a1..d0e67b19 100644 --- a/include/livekit/data_track_error.h +++ b/include/livekit/data_track_error.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef LIVEKIT_DATA_TRACK_ERROR_H -#define LIVEKIT_DATA_TRACK_ERROR_H +#pragma once #include #include @@ -83,5 +82,3 @@ struct SubscribeDataTrackError { }; } // namespace livekit - -#endif // LIVEKIT_DATA_TRACK_ERROR_H diff --git a/include/livekit/local_audio_track.h b/include/livekit/local_audio_track.h index 53823f7b..8946f077 100644 --- a/include/livekit/local_audio_track.h +++ b/include/livekit/local_audio_track.h @@ -86,7 +86,7 @@ class LIVEKIT_API LocalAudioTrack : public Track { /// Sets the publication that owns this track. /// Note: std::move on a const& silently falls back to a copy, so we assign /// directly. Changing the virtual signature to take by value would enable - /// a true move but is an API-breaking change left for a future revision. + /// a true move but is an API-breaking change hence left for a future revision. void setPublication(const std::shared_ptr& publication) noexcept override { local_publication_ = publication; } @@ -99,4 +99,4 @@ class LIVEKIT_API LocalAudioTrack : public Track { std::shared_ptr local_publication_; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/result.h b/include/livekit/result.h index 635d8930..33e2602b 100644 --- a/include/livekit/result.h +++ b/include/livekit/result.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef LIVEKIT_RESULT_H -#define LIVEKIT_RESULT_H +#pragma once #include #include @@ -187,5 +186,3 @@ class [[nodiscard]] Result { }; } // namespace livekit - -#endif // LIVEKIT_RESULT_H diff --git a/include/livekit/room.h b/include/livekit/room.h index 80b23aaa..46fed58a 100644 --- a/include/livekit/room.h +++ b/include/livekit/room.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef LIVEKIT_ROOM_H -#define LIVEKIT_ROOM_H +#pragma once #include #include @@ -173,6 +172,9 @@ class LIVEKIT_API Room { /// Returns a snapshot of all current remote participants. std::vector> remoteParticipants() const; + /// Returns the current connection state of the room. + ConnectionState connectionState() const; + /* Register a handler for incoming text streams on a specific topic. * * When a remote participant opens a text stream with the given topic, @@ -324,5 +326,3 @@ class LIVEKIT_API Room { void OnEvent(const proto::FfiEvent& event); }; } // namespace livekit - -#endif /* LIVEKIT_ROOM_H */ diff --git a/include/livekit/subscription_thread_dispatcher.h b/include/livekit/subscription_thread_dispatcher.h index bdd41618..d93e863e 100644 --- a/include/livekit/subscription_thread_dispatcher.h +++ b/include/livekit/subscription_thread_dispatcher.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef LIVEKIT_SUBSCRIPTION_THREAD_DISPATCHER_H -#define LIVEKIT_SUBSCRIPTION_THREAD_DISPATCHER_H +#pragma once #include #include @@ -459,5 +458,3 @@ class LIVEKIT_API SubscriptionThreadDispatcher { }; } // namespace livekit - -#endif /* LIVEKIT_SUBSCRIPTION_THREAD_DISPATCHER_H */ diff --git a/include/livekit/video_stream.h b/include/livekit/video_stream.h index 82874ad7..dcc11645 100644 --- a/include/livekit/video_stream.h +++ b/include/livekit/video_stream.h @@ -49,17 +49,17 @@ namespace proto { class FfiEvent; } -// Represents a pull-based stream of decoded PCM audio frames coming from -// a remote (or local) LiveKit track. Similar to VideoStream, but for audio. +// Represents a pull-based stream of decoded video frames coming from +// a remote (or local) LiveKit track. Similar to AudioStream, but for video. // // Typical usage: // // VideoStream::Options opts; // auto stream = VideoStream::fromTrack(remoteVideoTrack, opts); // -// AudioFrameEvent ev; +// VideoFrameEvent ev; // while (stream->read(ev)) { -// // ev.frame contains interleaved int16 PCM samples +// // ev.frame contains the decoded video buffer // } // // stream->close(); // optional, called automatically in destructor diff --git a/src/ffi_client.h b/src/ffi_client.h index 97191586..dc00d565 100644 --- a/src/ffi_client.h +++ b/src/ffi_client.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef LIVEKIT_FFI_CLIENT_H -#define LIVEKIT_FFI_CLIENT_H +#pragma once #include #include @@ -196,5 +195,3 @@ class LIVEKIT_INTERNAL_API FfiClient { std::atomic initialized_{false}; }; } // namespace livekit - -#endif /* LIVEKIT_FFI_CLIENT_H */ diff --git a/src/lk_log.h b/src/lk_log.h index b5995744..e2aa5f15 100644 --- a/src/lk_log.h +++ b/src/lk_log.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef LIVEKIT_LK_LOG_H -#define LIVEKIT_LK_LOG_H +#pragma once #include @@ -51,5 +50,3 @@ LIVEKIT_INTERNAL_API void shutdownLogger(); #define LK_LOG_WARN(...) SPDLOG_LOGGER_WARN(livekit::detail::getLogger(), __VA_ARGS__) #define LK_LOG_ERROR(...) SPDLOG_LOGGER_ERROR(livekit::detail::getLogger(), __VA_ARGS__) #define LK_LOG_CRITICAL(...) SPDLOG_LOGGER_CRITICAL(livekit::detail::getLogger(), __VA_ARGS__) - -#endif /* LIVEKIT_LK_LOG_H */ diff --git a/src/room.cpp b/src/room.cpp index d647b0fc..d9e96a44 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -220,6 +220,11 @@ std::vector> Room::remoteParticipants() const return out; } +ConnectionState Room::connectionState() const { + const std::scoped_lock g(lock_); + return connection_state_; +} + E2EEManager* Room::e2eeManager() const { const std::scoped_lock g(lock_); return e2ee_manager_.get(); diff --git a/src/tests/unit/test_result.cpp b/src/tests/unit/test_result.cpp new file mode 100644 index 00000000..7743e6d5 --- /dev/null +++ b/src/tests/unit/test_result.cpp @@ -0,0 +1,211 @@ +/* + * Copyright 2026 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. + */ + +/// @file test_result.cpp +/// @brief Unit tests for the Result and Result types. +/// +/// Covers the invariants documented in result.h: +/// - ok() / has_error() / bool conversion correctness +/// - value() and error() accessor semantics for lvalue, rvalue, and const +/// overloads +/// - Move construction and forwarding behaviour +/// - void specialization + +#include +#include + +#include +#include +#include + +namespace livekit { + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +struct SimpleError { + int code{0}; + std::string message; +}; + +// --------------------------------------------------------------------------- +// Result — success path +// --------------------------------------------------------------------------- + +TEST(ResultTest, SuccessOkIsTrue) { + auto r = Result::success(42); + EXPECT_TRUE(r.ok()); +} + +TEST(ResultTest, SuccessHasErrorIsFalse) { + auto r = Result::success(42); + EXPECT_FALSE(r.has_error()); +} + +TEST(ResultTest, SuccessBoolConversionIsTrue) { + auto r = Result::success(42); + EXPECT_TRUE(static_cast(r)); +} + +TEST(ResultTest, SuccessValueMatchesInput) { + auto r = Result::success(99); + EXPECT_EQ(r.value(), 99); +} + +TEST(ResultTest, SuccessConstValueMatchesInput) { + const auto r = Result::success(7); + EXPECT_EQ(r.value(), 7); +} + +TEST(ResultTest, SuccessValueCanBeMutated) { + auto r = Result::success(1); + r.value() = 100; + EXPECT_EQ(r.value(), 100); +} + +TEST(ResultTest, SuccessStringValue) { + auto r = Result::success("hello"); + EXPECT_EQ(r.value(), "hello"); +} + +TEST(ResultTest, SuccessMoveValueTransfersOwnership) { + auto r = Result, SimpleError>::success(std::make_unique(55)); + auto ptr = std::move(r).value(); + ASSERT_NE(ptr, nullptr); + EXPECT_EQ(*ptr, 55); +} + +// --------------------------------------------------------------------------- +// Result — failure path +// --------------------------------------------------------------------------- + +TEST(ResultTest, FailureOkIsFalse) { + auto r = Result::failure(SimpleError{1, "oops"}); + EXPECT_FALSE(r.ok()); +} + +TEST(ResultTest, FailureHasErrorIsTrue) { + auto r = Result::failure(SimpleError{1, "oops"}); + EXPECT_TRUE(r.has_error()); +} + +TEST(ResultTest, FailureBoolConversionIsFalse) { + auto r = Result::failure(SimpleError{1, "oops"}); + EXPECT_FALSE(static_cast(r)); +} + +TEST(ResultTest, FailureErrorCodeMatchesInput) { + auto r = Result::failure(SimpleError{42, "bad"}); + EXPECT_EQ(r.error().code, 42); + EXPECT_EQ(r.error().message, "bad"); +} + +TEST(ResultTest, FailureConstErrorMatchesInput) { + const auto r = Result::failure(SimpleError{3, "err"}); + EXPECT_EQ(r.error().code, 3); +} + +TEST(ResultTest, FailureMoveErrorTransfersOwnership) { + auto r = Result>::failure(std::make_unique(SimpleError{9, "moved"})); + auto err = std::move(r).error(); + ASSERT_NE(err, nullptr); + EXPECT_EQ(err->code, 9); +} + +TEST(ResultTest, FailureStringError) { + auto r = Result::failure("something went wrong"); + EXPECT_EQ(r.error(), "something went wrong"); +} + +// --------------------------------------------------------------------------- +// Result — success path +// --------------------------------------------------------------------------- + +TEST(ResultVoidTest, SuccessOkIsTrue) { + auto r = Result::success(); + EXPECT_TRUE(r.ok()); +} + +TEST(ResultVoidTest, SuccessHasErrorIsFalse) { + auto r = Result::success(); + EXPECT_FALSE(r.has_error()); +} + +TEST(ResultVoidTest, SuccessBoolConversionIsTrue) { + auto r = Result::success(); + EXPECT_TRUE(static_cast(r)); +} + +TEST(ResultVoidTest, SuccessValueIsCallable) { + auto r = Result::success(); + EXPECT_NO_THROW(r.value()); +} + +// --------------------------------------------------------------------------- +// Result — failure path +// --------------------------------------------------------------------------- + +TEST(ResultVoidTest, FailureOkIsFalse) { + auto r = Result::failure(SimpleError{5, "void fail"}); + EXPECT_FALSE(r.ok()); +} + +TEST(ResultVoidTest, FailureHasErrorIsTrue) { + auto r = Result::failure(SimpleError{5, "void fail"}); + EXPECT_TRUE(r.has_error()); +} + +TEST(ResultVoidTest, FailureBoolConversionIsFalse) { + auto r = Result::failure(SimpleError{5, "void fail"}); + EXPECT_FALSE(static_cast(r)); +} + +TEST(ResultVoidTest, FailureErrorMatchesInput) { + auto r = Result::failure(SimpleError{7, "nope"}); + EXPECT_EQ(r.error().code, 7); + EXPECT_EQ(r.error().message, "nope"); +} + +TEST(ResultVoidTest, FailureMoveError) { + auto r = Result::failure("void error"); + auto msg = std::move(r).error(); + EXPECT_EQ(msg, "void error"); +} + +// --------------------------------------------------------------------------- +// if-result idiom +// --------------------------------------------------------------------------- + +TEST(ResultTest, IfResultIdiomSuccessEntersBranch) { + auto r = Result::success(1); + bool entered = false; + if (r) { + entered = true; + } + EXPECT_TRUE(entered); +} + +TEST(ResultTest, IfResultIdiomFailureSkipsBranch) { + auto r = Result::failure(SimpleError{}); + bool entered = false; + if (r) { + entered = true; + } + EXPECT_FALSE(entered); +} + +} // namespace livekit diff --git a/src/tests/unit/test_room_callbacks.cpp b/src/tests/unit/test_room_callbacks.cpp index d1f1739e..feee6a3c 100644 --- a/src/tests/unit/test_room_callbacks.cpp +++ b/src/tests/unit/test_room_callbacks.cpp @@ -231,4 +231,45 @@ TEST_F(RoomCallbackTest, ManyDistinctAudioCallbacksCanBeRegisteredAndCleared) { } } +TEST_F(RoomCallbackTest, DefaultConnectionStateIsDisconnected) { + Room room; + EXPECT_EQ(room.connectionState(), ConnectionState::Disconnected); +} + +TEST_F(RoomCallbackTest, ConnectionStateRemainsDisconnectedWithoutConnect) { + // Register callbacks, do other operations — state must stay Disconnected. + Room room; + room.setOnAudioFrameCallback("alice", TrackSource::SOURCE_MICROPHONE, [](const AudioFrame&) {}); + room.setOnVideoFrameCallback("alice", TrackSource::SOURCE_CAMERA, [](const VideoFrame&, std::int64_t) {}); + room.addOnDataFrameCallback("alice", "track", [](const std::vector&, std::optional) {}); + room.registerTextStreamHandler("topic", [](std::shared_ptr, const std::string&) {}); + EXPECT_EQ(room.connectionState(), ConnectionState::Disconnected); +} + +TEST_F(RoomCallbackTest, ConnectionStateIsQueryableFromMultipleThreads) { + Room room; + constexpr int kThreads = 8; + constexpr int kIterations = 200; + + std::vector threads; + threads.reserve(kThreads); + std::atomic disconnected_count{0}; + + for (int t = 0; t < kThreads; ++t) { + threads.emplace_back([&room, &disconnected_count, kIterations]() { + for (int i = 0; i < kIterations; ++i) { + if (room.connectionState() == ConnectionState::Disconnected) { + disconnected_count.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + + for (auto& thread : threads) { + thread.join(); + } + + EXPECT_EQ(disconnected_count.load(), kThreads * kIterations); +} + } // namespace livekit diff --git a/src/trace/event_tracer.h b/src/trace/event_tracer.h index d99bc1cc..9b06e88b 100644 --- a/src/trace/event_tracer.h +++ b/src/trace/event_tracer.h @@ -36,8 +36,7 @@ // // Parameters for the above two functions are described in trace_event.h. -#ifndef LIVEKIT_TRACE_EVENT_TRACER_H_ -#define LIVEKIT_TRACE_EVENT_TRACER_H_ +#pragma once #include "livekit/visibility.h" @@ -76,5 +75,3 @@ using namespace livekit::trace; } // namespace webrtc // NOLINTEND - -#endif // LIVEKIT_TRACE_EVENT_TRACER_H_ diff --git a/src/trace/event_tracer_internal.h b/src/trace/event_tracer_internal.h index 1eff80b6..ccb1b684 100644 --- a/src/trace/event_tracer_internal.h +++ b/src/trace/event_tracer_internal.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef LIVEKIT_TRACE_EVENT_TRACER_INTERNAL_H_ -#define LIVEKIT_TRACE_EVENT_TRACER_INTERNAL_H_ +#pragma once #include #include @@ -52,5 +51,3 @@ void StopTracing(); bool IsTracingEnabled(); } // namespace livekit::trace::internal - -#endif // LIVEKIT_TRACE_EVENT_TRACER_INTERNAL_H_ diff --git a/src/trace/trace_event.h b/src/trace/trace_event.h index bc1bb57d..38b29028 100644 --- a/src/trace/trace_event.h +++ b/src/trace/trace_event.h @@ -21,8 +21,7 @@ // NOLINTBEGIN // External code: this header is derived from Chromium's trace_event.h -#ifndef LIVEKIT_TRACE_TRACE_EVENT_H_ -#define LIVEKIT_TRACE_TRACE_EVENT_H_ +#pragma once #include #include @@ -712,5 +711,3 @@ class TraceEndOnScopeClose { } // namespace webrtc // NOLINTEND - -#endif // LIVEKIT_TRACE_TRACE_EVENT_H_