Skip to content

Commit 23ff4cc

Browse files
user_timestamped_video
1 parent f231c0c commit 23ff4cc

8 files changed

Lines changed: 605 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,4 @@ add_subdirectory(simple_joystick_sender)
9393
add_subdirectory(simple_joystick_receiver)
9494
add_subdirectory(ping_pong_ping)
9595
add_subdirectory(ping_pong_pong)
96+
add_subdirectory(user_timestamped_video)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2026 LiveKit, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
add_subdirectory(producer)
16+
add_subdirectory(consumer)

user_timestamped_video/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# UserTimestampedVideo
2+
3+
This example is split into two executables and can demonstrate all four
4+
producer/consumer combinations:
5+
6+
- `UserTimestampedVideoProducer` publishes a synthetic video track named
7+
`"timestamped-camera"` and stamps each frame with
8+
`VideoCaptureOptions::metadata.user_timestamp_us`.
9+
- `UserTimestampedVideoConsumer` subscribes to the remote
10+
`"timestamped-camera"` track by name with either the rich or legacy callback
11+
path.
12+
13+
Run them in the same room with different participant identities:
14+
15+
```sh
16+
LIVEKIT_URL=ws://localhost:7880 LIVEKIT_TOKEN=<producer-token> ./UserTimestampedVideoProducer
17+
LIVEKIT_URL=ws://localhost:7880 LIVEKIT_TOKEN=<consumer-token> ./UserTimestampedVideoConsumer
18+
```
19+
20+
Requirements:
21+
22+
- LiveKit C++ SDK `v0.3.4` or newer. This example uses
23+
`VideoFrameMetadata` and `setOnVideoFrameEventCallback`, which are not
24+
available in older SDK releases.
25+
- To pin the SDK version when configuring the examples, pass
26+
`-DLIVEKIT_SDK_VERSION=0.3.4` to CMake.
27+
28+
Flags:
29+
30+
- Producer default: sends user timestamps
31+
- Producer `--with-user-timestamp`: explicitly sends user timestamps
32+
- Producer `--without-user-timestamp`: does not send user timestamps
33+
- Consumer default: reads user timestamps through `setOnVideoFrameEventCallback`
34+
- Consumer `--with-user-timestamp`: explicitly reads user timestamps through
35+
`setOnVideoFrameEventCallback`
36+
- Consumer `--without-user-timestamp`: ignores metadata through the legacy
37+
`setOnVideoFrameCallback`
38+
39+
Matrix:
40+
41+
```sh
42+
# 1. Producer sends, consumer reads
43+
./UserTimestampedVideoProducer
44+
./UserTimestampedVideoConsumer
45+
46+
# 2. Producer sends, consumer ignores
47+
./UserTimestampedVideoProducer
48+
./UserTimestampedVideoConsumer --without-user-timestamp
49+
50+
# 3. Producer does not send, consumer ignores
51+
./UserTimestampedVideoProducer --without-user-timestamp
52+
./UserTimestampedVideoConsumer --without-user-timestamp
53+
54+
# 4. Producer does not send, consumer reads
55+
./UserTimestampedVideoProducer --without-user-timestamp
56+
./UserTimestampedVideoConsumer
57+
```
58+
59+
Timestamp note:
60+
61+
- `user_ts_us` is application metadata and is the value to compare end to end.
62+
- `capture_ts_us` on the producer is the timestamp submitted to `captureFrame`.
63+
- `capture_ts_us` on the consumer is the received WebRTC frame timestamp.
64+
- Producer and consumer `capture_ts_us` values are not expected to match exactly,
65+
because WebRTC may translate frame timestamps onto its own internal
66+
capture-time timeline before delivery.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2026 LiveKit
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <atomic>
20+
#include <csignal>
21+
#include <cstdlib>
22+
#include <iostream>
23+
#include <string>
24+
#include <vector>
25+
26+
namespace user_timestamped_video {
27+
28+
enum class ParseResult { Ok, Help, Error };
29+
30+
struct CliOptions {
31+
std::string url;
32+
std::string token;
33+
bool use_user_timestamp = true;
34+
};
35+
36+
inline std::atomic<bool> g_running{true};
37+
38+
inline void handleSignal(int) { g_running.store(false); }
39+
40+
inline bool isRunning() { return g_running.load(std::memory_order_relaxed); }
41+
42+
inline void installSignalHandlers() {
43+
std::signal(SIGINT, handleSignal);
44+
#ifdef SIGTERM
45+
std::signal(SIGTERM, handleSignal);
46+
#endif
47+
}
48+
49+
inline std::string getenvOrEmpty(const char *name) {
50+
const char *value = std::getenv(name);
51+
return value ? std::string(value) : std::string{};
52+
}
53+
54+
inline void printUsage(const char *program) {
55+
std::cerr << "Usage:\n"
56+
<< " " << program << " <ws-url> <token> "
57+
<< "[--with-user-timestamp|--without-user-timestamp]\n"
58+
<< "or:\n"
59+
<< " LIVEKIT_URL=... LIVEKIT_TOKEN=... " << program
60+
<< " [--with-user-timestamp|--without-user-timestamp]\n";
61+
}
62+
63+
inline ParseResult parseArgs(int argc, char *argv[], CliOptions &options) {
64+
std::vector<std::string> positional;
65+
options = CliOptions{};
66+
67+
for (int i = 1; i < argc; ++i) {
68+
const std::string arg = argv[i];
69+
if (arg == "-h" || arg == "--help") {
70+
return ParseResult::Help;
71+
}
72+
if (arg == "--without-user-timestamp") {
73+
options.use_user_timestamp = false;
74+
continue;
75+
}
76+
if (arg == "--with-user-timestamp") {
77+
options.use_user_timestamp = true;
78+
continue;
79+
}
80+
if (!arg.empty() && arg[0] == '-') {
81+
return ParseResult::Error;
82+
}
83+
84+
positional.push_back(arg);
85+
}
86+
87+
if (positional.size() > 2) {
88+
return ParseResult::Error;
89+
}
90+
91+
options.url = getenvOrEmpty("LIVEKIT_URL");
92+
options.token = getenvOrEmpty("LIVEKIT_TOKEN");
93+
94+
if (positional.size() == 2) {
95+
options.url = positional[0];
96+
options.token = positional[1];
97+
} else if (positional.size() == 1) {
98+
return ParseResult::Error;
99+
}
100+
101+
return (options.url.empty() || options.token.empty()) ? ParseResult::Error
102+
: ParseResult::Ok;
103+
}
104+
105+
} // namespace user_timestamped_video
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2026 LiveKit, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
add_executable(UserTimestampedVideoConsumer
16+
main.cpp
17+
)
18+
19+
target_include_directories(UserTimestampedVideoConsumer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
20+
target_link_libraries(UserTimestampedVideoConsumer PRIVATE ${LIVEKIT_CORE_TARGET})
21+
22+
livekit_copy_windows_runtime_dlls(UserTimestampedVideoConsumer)

0 commit comments

Comments
 (0)