diff --git a/AGENTS.md b/AGENTS.md index b18afe1b..39c09572 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -352,11 +352,21 @@ CI step will fail loudly if its symbols escape the public ABI of Tests are under `src/tests/` using Google Test: -``` +```bash +source .token_helpers/set_data_track_test_tokens.bash ./build.sh debug-tests cd build-debug && ctest ``` +Always source the test tokens before running tests, even for a targeted local +test run. The integration and stress suites require `LIVEKIT_URL`, +`LIVEKIT_TOKEN_A`, and `LIVEKIT_TOKEN_B`; use +`source .token_helpers/set_data_track_test_tokens.bash` from the repository +root so the current shell inherits them. Do not report an integration test as +verified just because it was skipped for missing tokens. If tokens cannot be +sourced or the LiveKit server is not available, say that the integration test +was not actually exercised. + Integration tests (`src/tests/integration/`) cover: room connections, callbacks, data tracks, RPC, logging, audio processing, and the subscription thread dispatcher. The token source HTTP/JSON wire contract (request serialization, response parsing, header passthrough, GET support, sandbox URL/header resolution) is covered by mocked unit tests in `src/tests/unit/test_token_source.cpp`, which inject a stub HTTP transport so no live server is needed. For a full end-to-end check, `TokenSourceEndpointConnectTest` connects a `Room` with a real JWT minted by the `livekit/token-server-action` token server pointed at the local dev `livekit-server`. The action exposes its `/createToken` endpoint as a `token-url` output; `tests.yml` passes that to the integration test step as `LIVEKIT_CREATE_TOKEN_URL`, which the test reads to locate the endpoint. The server is started in the `e2e-testing` jobs via the token server's reusable GitHub Action, pinned by SHA in `tests.yml`. When adding new client facing functionality, add a new test case to the existing test suite. diff --git a/include/livekit/room_event_types.h b/include/livekit/room_event_types.h index f688bbec..11bad343 100644 --- a/include/livekit/room_event_types.h +++ b/include/livekit/room_event_types.h @@ -273,6 +273,19 @@ struct AudioEncodingOptions { std::uint64_t max_bitrate = 0; }; +/// @brief Controls how the encoder degrades quality when bandwidth is constrained. +enum class DegradationPreference { + /// Balance between framerate and resolution degradation. + Balanced = 0, + /// Degrade resolution to maintain framerate (prioritize smooth motion). + MaintainFramerate = 1, + /// Degrade framerate to maintain resolution (prioritize image clarity). + MaintainResolution = 2, + /// Maintain both framerate and resolution. Frames may be dropped before + /// encoding if necessary to avoid overusing network and encoder resources. + MaintainFramerateAndResolution = 4, +}; + /// Optional frame metadata features for published video tracks. struct FrameMetadataFeatures { /// Embed a user-supplied wall-clock timestamp. @@ -324,6 +337,10 @@ struct TrackPublishOptions { /// @deprecated Use frame_metadata_features instead. [[deprecated("TrackPublishOptions::packet_trailer_features is deprecated; use frame_metadata_features instead")]] FrameMetadataFeatures packet_trailer_features{}; + + /// Controls how the encoder trades off between resolution and framerate + /// when bandwidth is constrained. If not set, the server defaults apply. + std::optional degradation_preference; }; // --------------------------------------------------------- diff --git a/src/room_proto_converter.cpp b/src/room_proto_converter.cpp index 010aa545..6e2fdcd0 100644 --- a/src/room_proto_converter.cpp +++ b/src/room_proto_converter.cpp @@ -526,6 +526,9 @@ proto::TrackPublishOptions toProto(const TrackPublishOptions& in) { for (const proto::FrameMetadataFeature feature : toProto(mergedFrameMetadataFeatures(in))) { msg.add_frame_metadata_features(feature); } + if (in.degradation_preference) { + msg.set_degradation_preference(static_cast(*in.degradation_preference)); + } return msg; } @@ -563,6 +566,9 @@ TrackPublishOptions fromProto(const proto::TrackPublishOptions& in) { out.frame_metadata_features = frame_metadata_features; } out.packet_trailer_features = frame_metadata_features; + if (in.has_degradation_preference()) { + out.degradation_preference = static_cast(in.degradation_preference()); + } return out; } diff --git a/src/tests/common/test_common.h b/src/tests/common/test_common.h index 530fdb6b..9ec0e508 100644 --- a/src/tests/common/test_common.h +++ b/src/tests/common/test_common.h @@ -548,7 +548,7 @@ class LiveKitTestBase : public ::testing::Test { /// Fail the test if the required environment variables are not set void failIfNotConfigured() { if (!config_.available) { - FAIL() << "LIVEKIT_URL, LIVEKIT_TOKEN_A, and LIVEKIT_TOKEN_B not set"; + throw std::runtime_error("LIVEKIT_URL, LIVEKIT_TOKEN_A, and LIVEKIT_TOKEN_B not set"); } } diff --git a/src/tests/unit/test_room_event_types.cpp b/src/tests/unit/test_room_event_types.cpp index 050e9776..c653aa10 100644 --- a/src/tests/unit/test_room_event_types.cpp +++ b/src/tests/unit/test_room_event_types.cpp @@ -49,6 +49,7 @@ TEST(RoomEventTypesTest, TrackPublishOptionsDefaults) { EXPECT_FALSE(options.packet_trailer_features.user_timestamp); EXPECT_FALSE(options.packet_trailer_features.frame_id); EXPECT_FALSE(options.packet_trailer_features.user_data); + EXPECT_FALSE(options.degradation_preference.has_value()); } TEST(RoomEventTypesTest, UserPacketDataDefaults) { diff --git a/src/tests/unit/test_video_frame_metadata.cpp b/src/tests/unit/test_video_frame_metadata.cpp index b87bbdab..5c683e84 100644 --- a/src/tests/unit/test_video_frame_metadata.cpp +++ b/src/tests/unit/test_video_frame_metadata.cpp @@ -138,6 +138,20 @@ TEST(TrackPublishOptionsTest, FrameMetadataFeaturesRoundTrip) { EXPECT_TRUE(round_trip.frame_metadata_features->user_data); } +TEST(TrackPublishOptionsTest, DegradationPreferenceRoundTrip) { + TrackPublishOptions options; + options.degradation_preference = DegradationPreference::MaintainFramerateAndResolution; + + proto::TrackPublishOptions proto_options = toProto(options); + ASSERT_TRUE(proto_options.has_degradation_preference()); + EXPECT_EQ(proto_options.degradation_preference(), + proto::DegradationPreference::DEGRADATION_PREFERENCE_MAINTAIN_FRAMERATE_AND_RESOLUTION); + + TrackPublishOptions round_trip = fromProto(proto_options); + ASSERT_TRUE(round_trip.degradation_preference.has_value()); + EXPECT_EQ(*round_trip.degradation_preference, DegradationPreference::MaintainFramerateAndResolution); +} + TEST(TrackPublishOptionsTest, DeprecatedPacketTrailerFeaturesAreMerged) { TrackPublishOptions options;