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
5 changes: 1 addition & 4 deletions include/livekit/data_track_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
* limitations under the License.
*/

#ifndef LIVEKIT_DATA_TRACK_ERROR_H
#define LIVEKIT_DATA_TRACK_ERROR_H
#pragma once

#include <cstdint>
#include <string>
Expand Down Expand Up @@ -84,5 +83,3 @@ struct SubscribeDataTrackError {
};

} // namespace livekit

#endif // LIVEKIT_DATA_TRACK_ERROR_H
2 changes: 1 addition & 1 deletion include/livekit/local_audio_track.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class 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 a API-breaking change hence left for a future revision.
Copy link
Copy Markdown
Contributor

@alan-george-lk alan-george-lk May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should still be an (right?)

void setPublication(const std::shared_ptr<LocalTrackPublication>
&publication) noexcept override {
local_publication_ = publication;
Expand Down
5 changes: 1 addition & 4 deletions include/livekit/result.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
* limitations under the License.
*/

#ifndef LIVEKIT_RESULT_H
#define LIVEKIT_RESULT_H
#pragma once

#include <cassert>
#include <optional>
Expand Down Expand Up @@ -190,5 +189,3 @@ template <typename E> class [[nodiscard]] Result<void, E> {
};

} // namespace livekit

#endif // LIVEKIT_RESULT_H
8 changes: 4 additions & 4 deletions include/livekit/room.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
* limitations under the License.
*/

#ifndef LIVEKIT_ROOM_H
#define LIVEKIT_ROOM_H
#pragma once

#include "livekit/data_stream.h"
#include "livekit/e2ee.h"
Expand Down Expand Up @@ -173,6 +172,9 @@ class Room {
/// Returns a snapshot of all current remote participants.
std::vector<std::shared_ptr<RemoteParticipant>> 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,
Expand Down Expand Up @@ -343,5 +345,3 @@ class Room {
void OnEvent(const proto::FfiEvent &event);
};
} // namespace livekit

#endif /* LIVEKIT_ROOM_H */
5 changes: 1 addition & 4 deletions include/livekit/subscription_thread_dispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
* limitations under the License.
*/

#ifndef LIVEKIT_SUBSCRIPTION_THREAD_DISPATCHER_H
#define LIVEKIT_SUBSCRIPTION_THREAD_DISPATCHER_H
#pragma once

#include "livekit/audio_stream.h"
#include "livekit/video_stream.h"
Expand Down Expand Up @@ -491,5 +490,3 @@ class SubscriptionThreadDispatcher {
};

} // namespace livekit

#endif /* LIVEKIT_SUBSCRIPTION_THREAD_DISPATCHER_H */
8 changes: 4 additions & 4 deletions include/livekit/video_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,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
Expand Down
5 changes: 5 additions & 0 deletions src/room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ Room::remoteParticipants() const {
return out;
}

ConnectionState Room::connectionState() const {
const std::scoped_lock<std::mutex> g(lock_);
return connection_state_;
}

E2EEManager *Room::e2eeManager() const {
const std::scoped_lock<std::mutex> g(lock_);
return e2ee_manager_.get();
Expand Down
213 changes: 213 additions & 0 deletions src/tests/unit/test_result.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* 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<T, E> and Result<void, E> 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 <gtest/gtest.h>
#include <livekit/result.h>

#include <memory>
#include <string>
#include <utility>

namespace livekit {

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

struct SimpleError {
int code{0};
std::string message;
};

// ---------------------------------------------------------------------------
// Result<T, E> — success path
// ---------------------------------------------------------------------------

TEST(ResultTest, SuccessOkIsTrue) {
auto r = Result<int, SimpleError>::success(42);
EXPECT_TRUE(r.ok());
}

TEST(ResultTest, SuccessHasErrorIsFalse) {
auto r = Result<int, SimpleError>::success(42);
EXPECT_FALSE(r.has_error());
}

TEST(ResultTest, SuccessBoolConversionIsTrue) {
auto r = Result<int, SimpleError>::success(42);
EXPECT_TRUE(static_cast<bool>(r));
}

TEST(ResultTest, SuccessValueMatchesInput) {
auto r = Result<int, SimpleError>::success(99);
EXPECT_EQ(r.value(), 99);
}

TEST(ResultTest, SuccessConstValueMatchesInput) {
const auto r = Result<int, SimpleError>::success(7);
EXPECT_EQ(r.value(), 7);
}

TEST(ResultTest, SuccessValueCanBeMutated) {
auto r = Result<int, SimpleError>::success(1);
r.value() = 100;
EXPECT_EQ(r.value(), 100);
}

TEST(ResultTest, SuccessStringValue) {
auto r = Result<std::string, SimpleError>::success("hello");
EXPECT_EQ(r.value(), "hello");
}

TEST(ResultTest, SuccessMoveValueTransfersOwnership) {
auto r = Result<std::unique_ptr<int>, SimpleError>::success(
std::make_unique<int>(55));
auto ptr = std::move(r).value();
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(*ptr, 55);
}

// ---------------------------------------------------------------------------
// Result<T, E> — failure path
// ---------------------------------------------------------------------------

TEST(ResultTest, FailureOkIsFalse) {
auto r = Result<int, SimpleError>::failure(SimpleError{1, "oops"});
EXPECT_FALSE(r.ok());
}

TEST(ResultTest, FailureHasErrorIsTrue) {
auto r = Result<int, SimpleError>::failure(SimpleError{1, "oops"});
EXPECT_TRUE(r.has_error());
}

TEST(ResultTest, FailureBoolConversionIsFalse) {
auto r = Result<int, SimpleError>::failure(SimpleError{1, "oops"});
EXPECT_FALSE(static_cast<bool>(r));
}

TEST(ResultTest, FailureErrorCodeMatchesInput) {
auto r = Result<int, SimpleError>::failure(SimpleError{42, "bad"});
EXPECT_EQ(r.error().code, 42);
EXPECT_EQ(r.error().message, "bad");
}

TEST(ResultTest, FailureConstErrorMatchesInput) {
const auto r = Result<int, SimpleError>::failure(SimpleError{3, "err"});
EXPECT_EQ(r.error().code, 3);
}

TEST(ResultTest, FailureMoveErrorTransfersOwnership) {
auto r = Result<int, std::unique_ptr<SimpleError>>::failure(
std::make_unique<SimpleError>(SimpleError{9, "moved"}));
auto err = std::move(r).error();
ASSERT_NE(err, nullptr);
EXPECT_EQ(err->code, 9);
}

TEST(ResultTest, FailureStringError) {
auto r = Result<int, std::string>::failure("something went wrong");
EXPECT_EQ(r.error(), "something went wrong");
}

// ---------------------------------------------------------------------------
// Result<void, E> — success path
// ---------------------------------------------------------------------------

TEST(ResultVoidTest, SuccessOkIsTrue) {
auto r = Result<void, SimpleError>::success();
EXPECT_TRUE(r.ok());
}

TEST(ResultVoidTest, SuccessHasErrorIsFalse) {
auto r = Result<void, SimpleError>::success();
EXPECT_FALSE(r.has_error());
}

TEST(ResultVoidTest, SuccessBoolConversionIsTrue) {
auto r = Result<void, SimpleError>::success();
EXPECT_TRUE(static_cast<bool>(r));
}

TEST(ResultVoidTest, SuccessValueIsCallable) {
auto r = Result<void, SimpleError>::success();
EXPECT_NO_THROW(r.value());
}

// ---------------------------------------------------------------------------
// Result<void, E> — failure path
// ---------------------------------------------------------------------------

TEST(ResultVoidTest, FailureOkIsFalse) {
auto r = Result<void, SimpleError>::failure(SimpleError{5, "void fail"});
EXPECT_FALSE(r.ok());
}

TEST(ResultVoidTest, FailureHasErrorIsTrue) {
auto r = Result<void, SimpleError>::failure(SimpleError{5, "void fail"});
EXPECT_TRUE(r.has_error());
}

TEST(ResultVoidTest, FailureBoolConversionIsFalse) {
auto r = Result<void, SimpleError>::failure(SimpleError{5, "void fail"});
EXPECT_FALSE(static_cast<bool>(r));
}

TEST(ResultVoidTest, FailureErrorMatchesInput) {
auto r = Result<void, SimpleError>::failure(SimpleError{7, "nope"});
EXPECT_EQ(r.error().code, 7);
EXPECT_EQ(r.error().message, "nope");
}

TEST(ResultVoidTest, FailureMoveError) {
auto r = Result<void, std::string>::failure("void error");
auto msg = std::move(r).error();
EXPECT_EQ(msg, "void error");
}

// ---------------------------------------------------------------------------
// if-result idiom
// ---------------------------------------------------------------------------

TEST(ResultTest, IfResultIdiomSuccessEntersBranch) {
auto r = Result<int, SimpleError>::success(1);
bool entered = false;
if (r) {
entered = true;
}
EXPECT_TRUE(entered);
}

TEST(ResultTest, IfResultIdiomFailureSkipsBranch) {
auto r = Result<int, SimpleError>::failure(SimpleError{});
bool entered = false;
if (r) {
entered = true;
}
EXPECT_FALSE(entered);
}

} // namespace livekit
47 changes: 47 additions & 0 deletions src/tests/unit/test_room_callbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,51 @@ 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::uint8_t> &, std::optional<std::uint64_t>) {});
room.registerTextStreamHandler("topic",
[](std::shared_ptr<TextStreamReader>,
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<std::thread> threads;
threads.reserve(kThreads);
std::atomic<int> 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
Loading