diff --git a/AGENTS.md b/AGENTS.md index 366d95af..69912f04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -209,6 +209,105 @@ with the same library loaded elsewhere in the host process. - Use `LK_LOG_WARN` for non-fatal unexpected conditions. - Use `Result` for operations that can fail with typed errors (e.g., data track publish/subscribe). +### Public API Documentation (Doxygen) + +The public API (`include/livekit/*.h`) is what consumers read first and is also +published as a Doxygen site (`docs/Doxyfile`, `.github/workflows/publish-docs.yml`). +Every public class, struct, free function, type alias, enum, enum value, and +method in `include/livekit/` must follow the rules below. **PRs adding or +modifying public symbols are gated on these rules during code review.** + +#### Comment style + +- Use triple-slash `///` Doxygen comments. Do **not** use `/** ... */` Javadoc + blocks for new code, and prefer migrating existing ones when you touch them. +- Apache license headers stay as `/* ... */` block comments — they are not + documentation. +- Implementation comments inside `.cpp` files (non-Doxygen) may use `//` freely. +- Keep the first line a short one-sentence brief. Follow with a blank `///` + line and a longer description if needed. Example: + + ```cpp + /// Publish a local track to the room. + /// + /// Blocks until the FFI publish response arrives. + /// + /// @param track Track to publish. Must be non-null. + /// @param options Publish options (codec, simulcast, etc.). + /// @throws std::runtime_error if the FFI reports an error. + /// + /// @note Thread-safety: Not thread-safe. Must be externally synchronized. + void publishTrack(const std::shared_ptr& track, const TrackPublishOptions& options); + ``` + +#### Parameter documentation + +- Every parameter on every public function and method must be documented with + `@param name Description.` +- Document `@return` for any non-void return value. Omit `@return` for `void`. +- Document `@throws ExceptionType When/why it's thrown.` for anything that + can throw a typed exception. Operations that can fail without throwing + should return `Result` (see Error Handling above) and the variants + should be documented in the doc block. +- Trivial inline accessors (e.g. `int sample_rate() const noexcept`) only need + a one-line `///` brief and may omit `@param`/`@return` when they take no + arguments and the brief already describes the returned value. +- Use `@param[out]` or `@param[in,out]` when a non-const reference or pointer + is used as an output or in-out parameter. + +#### Thread-safety annotations + +Every public class and every public method must declare its thread-safety in a +consistent, grep-able form. Two labels are used: + +- **`Thread-safe.`** — Safe to call concurrently from multiple threads. If the + safety comes from internal synchronization (mutex, queue, atomic) rather + than statelessness, briefly say so. Methods whose callers are *serialized + internally* (e.g. `TextStreamWriter::write` via an internal `write_mutex_`) + are documented as thread-safe with the prose noting the serialization. +- **`Not thread-safe.`** — Must be externally synchronized; concurrent calls + from multiple threads are undefined behavior. Briefly say what the caller + must ensure (e.g. "must be called from a single capture thread"). + +Use the `@note` Doxygen tag so it renders as a styled callout and is easy to +search for: + +```cpp +/// @note Thread-safety: Thread-safe. Internal `std::mutex` protects all +/// mutable state. +``` + +```cpp +/// @note Thread-safety: Not thread-safe. Concurrent `captureFrame` calls +/// from multiple threads are undefined behavior. +``` + +Placement: +- For classes where all (or all non-trivially) methods share the same + thread-safety guarantee, put the `@note Thread-safety:` line on the class + doc block. Individual methods only re-document thread-safety when they + *deviate* from the class-level guarantee. +- For classes where guarantees differ per-method (`Room`, `LocalParticipant`, + stream writers/readers, etc.), document `@note Thread-safety:` on every + public method. + +The authoritative thread-safety table for the SDK as a whole lives in the +[Threading Model](#threading-model) section above; per-symbol annotations +must agree with it. If you add a new public type, also add an entry there. + +#### Review checklist for new/modified public APIs + +Before approving a PR that touches `include/livekit/*.h`, confirm: + +- [ ] Every new/changed public symbol has a `///` doc block (no `/** */`). +- [ ] Every parameter is covered by `@param`. +- [ ] `@return` documents non-void returns; `@throws` documents thrown + exceptions; `Result` variants are described. +- [ ] `@note Thread-safety: ...` is present, either at the class level or on + each affected method, using the two-tier vocabulary above. +- [ ] The Threading Model table at the top of this file is updated if a new + public type was introduced. + ### Integer Types - Prefer fixed-width integer types from `` (`std::int32_t`, `std::uint64_t`, etc.) over raw primitive integer types when size or signedness matters. diff --git a/include/livekit/audio_frame.h b/include/livekit/audio_frame.h index 1a28d4d0..0ca4bfda 100644 --- a/include/livekit/audio_frame.h +++ b/include/livekit/audio_frame.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -29,65 +29,81 @@ class AudioFrameBufferInfo; class OwnedAudioFrameBuffer; } // namespace proto -/** - * @brief Represents a raw PCM audio frame with interleaved int16 samples. - * - * AudioFrame holds decoded audio data along with metadata such as sample rate, - * number of channels, and samples per channel. It is used for capturing and - * processing audio in the LiveKit SDK. - */ +/// A raw PCM audio frame with interleaved `int16` samples. +/// +/// `AudioFrame` holds decoded audio data along with metadata such as +/// sample rate, number of channels, and samples per channel. It is used +/// for capturing and processing audio in the LiveKit SDK. +/// +/// @note Thread-safety: Not thread-safe. A single `AudioFrame` instance +/// must not be mutated concurrently from multiple threads; concurrent +/// const-access is safe. class LIVEKIT_API AudioFrame { public: - /** - * Construct an AudioFrame from raw PCM samples. - * - * @param data Interleaved PCM samples (int16). - * @param sample_rate Sample rate (Hz). - * @param num_channels Number of channels. - * @param samples_per_channel Number of samples per channel. - * - * Throws std::invalid_argument if the data size is inconsistent with - * num_channels * samples_per_channel. - */ + /// Construct an `AudioFrame` from raw PCM samples. + /// + /// @param data Interleaved PCM samples (`int16`). + /// @param sample_rate Sample rate (Hz). + /// @param num_channels Number of channels. + /// @param samples_per_channel Number of samples per channel. + /// + /// @throws std::invalid_argument if `data.size()` is inconsistent with + /// `num_channels * samples_per_channel`. AudioFrame(std::vector data, int sample_rate, int num_channels, int samples_per_channel); - AudioFrame(); // Default constructor + + /// Construct an empty `AudioFrame` (all fields zero-initialized). + AudioFrame(); + virtual ~AudioFrame() = default; - /** - * Create a new zero-initialized AudioFrame instance. - */ + /// Create a new zero-initialized `AudioFrame` instance. + /// + /// @param sample_rate Sample rate (Hz). + /// @param num_channels Number of channels. + /// @param samples_per_channel Number of samples per channel. + /// @return A frame whose buffer is sized to + /// `num_channels * samples_per_channel` + /// and zero-filled. + /// + /// @note Thread-safety: Thread-safe. Pure factory function. static AudioFrame create(int sample_rate, int num_channels, int samples_per_channel); - /** - * Construct an AudioFrame by copying data out of an OwnedAudioFrameBuffer. - */ + /// Construct an `AudioFrame` by copying data out of an FFI-owned + /// audio buffer. + /// + /// @param owned Buffer received from the FFI (data is copied). + /// @return A new `AudioFrame` owning a copy of the samples. + /// + /// @note Thread-safety: Thread-safe. Pure transformation; no global + /// state. static AudioFrame fromOwnedInfo(const proto::OwnedAudioFrameBuffer& owned); - // ---- Accessors ---- - + /// @return Mutable reference to the underlying sample buffer. const std::vector& data() const noexcept { return data_; } + + /// @return Mutable reference to the underlying sample buffer. std::vector& data() noexcept { return data_; } - /// Number of samples in the buffer (per all channels). + /// @return Total number of samples in the buffer across all channels. std::size_t total_samples() const noexcept { return data_.size(); } - /// Sample rate in Hz. + /// @return Sample rate in Hz. int sample_rate() const noexcept { return sample_rate_; } - /// Number of channels. + /// @return Number of channels. int num_channels() const noexcept { return num_channels_; } - /// Samples per channel. + /// @return Samples per channel. int samples_per_channel() const noexcept { return samples_per_channel_; } - /// Duration in seconds (samples_per_channel / sample_rate). + /// @return Duration in seconds (`samples_per_channel / sample_rate`). double duration() const noexcept; - /// A human-readable description. + /// @return Human-readable description (sample rate, channels, size). std::string to_string() const; protected: - // Build a proto AudioFrameBufferInfo pointing at this frame’s data. + // Build a proto AudioFrameBufferInfo pointing at this frame's data. // Used internally by AudioSource. proto::AudioFrameBufferInfo toProto() const; friend class AudioSource; @@ -99,4 +115,4 @@ class LIVEKIT_API AudioFrame { int samples_per_channel_; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/audio_processing_module.h b/include/livekit/audio_processing_module.h index ba37cc7b..f6b49d15 100644 --- a/include/livekit/audio_processing_module.h +++ b/include/livekit/audio_processing_module.h @@ -24,135 +24,131 @@ namespace livekit { -/** - * @brief WebRTC Audio Processing Module (APM) for real-time audio enhancement. - * - * AudioProcessingModule exposes WebRTC's built-in audio processing capabilities - * including echo cancellation, noise suppression, automatic gain control, and - * high-pass filtering. - * - * This class is designed for scenarios where you need explicit control over - * audio processing, separate from the built-in processing in AudioSource. - * - * Typical usage pattern for echo cancellation: - * 1. Create an APM with desired features enabled - * 2. Call processReverseStream() with speaker/playback audio (reference signal) - * 3. Call processStream() with microphone audio (near-end signal) - * 4. The processed microphone audio will have echo removed - * - * Note: Audio frames must be exactly 10ms in duration. - */ +/// WebRTC Audio Processing Module (APM) for real-time audio enhancement. +/// +/// `AudioProcessingModule` exposes WebRTC's built-in audio processing +/// capabilities including echo cancellation, noise suppression, +/// automatic gain control, and high-pass filtering. +/// +/// This class is designed for scenarios where you need explicit control +/// over audio processing, separate from the built-in processing in +/// `AudioSource`. +/// +/// Typical usage pattern for echo cancellation: +/// 1. Create an APM with desired features enabled. +/// 2. Call `processReverseStream()` with speaker/playback audio +/// (reference signal). +/// 3. Call `processStream()` with microphone audio (near-end signal). +/// 4. The processed microphone audio will have echo removed. +/// +/// Note: Audio frames must be exactly 10 ms in duration. +/// +/// @note Thread-safety: Not thread-safe. Each `AudioProcessingModule` +/// instance is intended to be driven by a single processing thread. +/// Callers must externally serialize `processStream`, +/// `processReverseStream`, and `setStreamDelayMs` on a given instance. class LIVEKIT_API AudioProcessingModule { public: - /** - * @brief Configuration options for the Audio Processing Module. - */ + /// Configuration options for the Audio Processing Module. + /// + /// @note Thread-safety: Not thread-safe. POD-like struct. struct Options { - /// Enable acoustic echo cancellation (AEC3). - /// Removes acoustic echo in two-way communication scenarios. + /// Enable acoustic echo cancellation (AEC3). Removes acoustic echo + /// in two-way communication scenarios. bool echo_cancellation = false; - /// Enable noise suppression. - /// Reduces background noise from non-speech sources. + /// Enable noise suppression. Reduces background noise from + /// non-speech sources. bool noise_suppression = false; - /// Enable high-pass filter. - /// Removes low-frequency noise below ~80 Hz (DC offset, rumble). + /// Enable high-pass filter. Removes low-frequency noise below + /// ~80 Hz (DC offset, rumble). bool high_pass_filter = false; - /// Enable automatic gain control (AGC). - /// Auto-adjusts microphone gain to maintain consistent audio levels. + /// Enable automatic gain control (AGC). Auto-adjusts microphone + /// gain to maintain consistent audio levels. bool auto_gain_control = false; - /// Default constructor. Options() = default; }; - /** - * @brief Create a new Audio Processing Module with default options (all - * disabled). - * - * @throws std::runtime_error if the APM could not be created. - */ + /// Create a new Audio Processing Module with default options (all + /// disabled). + /// + /// @throws std::runtime_error if the APM could not be created. AudioProcessingModule(); - /** - * @brief Create a new Audio Processing Module with the specified options. - * - * @param options Configuration for which processing features to enable. - * @throws std::runtime_error if the APM could not be created. - */ + /// Create a new Audio Processing Module with the specified options. + /// + /// @param options Configuration for which processing features to + /// enable. + /// + /// @throws std::runtime_error if the APM could not be created. explicit AudioProcessingModule(const Options& options); virtual ~AudioProcessingModule() = default; - // Non-copyable AudioProcessingModule(const AudioProcessingModule&) = delete; AudioProcessingModule& operator=(const AudioProcessingModule&) = delete; - - // Movable AudioProcessingModule(AudioProcessingModule&&) noexcept = default; AudioProcessingModule& operator=(AudioProcessingModule&&) noexcept = default; - /** - * @brief Process the forward (near-end/microphone) audio stream. - * - * This method processes audio captured from the local microphone. It applies - * the enabled processing features (noise suppression, gain control, etc.) - * and removes echo based on the reference signal provided via - * processReverseStream(). - * - * The audio data is modified in-place. - * - * @param frame The audio frame to process (modified in-place). - * - * @throws std::runtime_error if processing fails. - * - * @note The frame must contain exactly 10ms of audio. - */ + /// Process the forward (near-end / microphone) audio stream. + /// + /// Processes audio captured from the local microphone. It applies the + /// enabled processing features (noise suppression, gain control, + /// etc.) and removes echo based on the reference signal provided via + /// `processReverseStream()`. The audio data is modified in-place. + /// + /// @param frame The audio frame to process (modified in-place). Must + /// contain exactly 10 ms of audio. + /// + /// @throws std::runtime_error if processing fails. + /// + /// @note Thread-safety: Not thread-safe. Must be externally + /// serialized with other calls on the same instance. void processStream(AudioFrame& frame); - /** - * @brief Process the reverse (far-end/speaker) audio stream. - * - * This method provides the reference signal for echo cancellation. Call this - * with the audio that is being played through the speakers, so the APM can - * learn the acoustic characteristics and remove the echo from the microphone - * signal. - * - * The audio data is modified in-place. - * - * @param frame The audio frame to process (modified in-place). - * - * @throws std::runtime_error if processing fails. - * - * @note The frame must contain exactly 10ms of audio. - */ + /// Process the reverse (far-end / speaker) audio stream. + /// + /// Provides the reference signal for echo cancellation. Call this + /// with the audio that is being played through the speakers, so the + /// APM can learn the acoustic characteristics and remove the echo + /// from the microphone signal. The audio data is modified in-place. + /// + /// @param frame The audio frame to process (modified in-place). Must + /// contain exactly 10 ms of audio. + /// + /// @throws std::runtime_error if processing fails. + /// + /// @note Thread-safety: Not thread-safe. Must be externally + /// serialized with other calls on the same instance. void processReverseStream(AudioFrame& frame); - /** - * @brief Set the estimated delay between the reverse and forward streams. - * - * This must be called if and only if echo processing is enabled. - * - * Sets the delay in ms between processReverseStream() receiving a far-end - * frame and processStream() receiving a near-end frame containing the - * corresponding echo. On the client-side this can be expressed as: - * - * delay = (t_render - t_analyze) + (t_process - t_capture) - * - * where: - * - t_analyze is the time a frame is passed to processReverseStream() and - * t_render is the time the first sample of the same frame is rendered by - * the audio hardware. - * - t_capture is the time the first sample of a frame is captured by the - * audio hardware and t_process is the time the same frame is passed to - * processStream(). - * - * @param delay_ms Delay in milliseconds. - * - * @throws std::runtime_error if setting the delay fails. - */ + /// Set the estimated delay between the reverse and forward streams. + /// + /// Must be called if and only if echo processing is enabled. Sets the + /// delay in ms between `processReverseStream()` receiving a far-end + /// frame and `processStream()` receiving a near-end frame containing + /// the corresponding echo. On the client-side this can be expressed + /// as: + /// + /// `delay = (t_render - t_analyze) + (t_process - t_capture)` + /// + /// where: + /// - `t_analyze` is the time a frame is passed to + /// `processReverseStream()` and `t_render` is the time the first + /// sample of the same frame is rendered by the audio hardware. + /// - `t_capture` is the time the first sample of a frame is captured + /// by the audio hardware and `t_process` is the time the same + /// frame is passed to `processStream()`. + /// + /// @param delay_ms Delay in milliseconds. + /// + /// @throws std::runtime_error if setting the delay fails. + /// + /// @note Thread-safety: Not thread-safe. Must be externally + /// serialized with other calls on the same instance. void setStreamDelayMs(int delay_ms); private: diff --git a/include/livekit/audio_source.h b/include/livekit/audio_source.h index d8c0fd6c..2f08b5bd 100644 --- a/include/livekit/audio_source.h +++ b/include/livekit/audio_source.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -31,45 +31,54 @@ class FfiResponse; class FfiClient; -/** - * Represents a real-time audio source with an internal audio queue. - */ +/// A real-time audio source with an internal audio queue. +/// +/// `AudioSource` is the writer side of an audio track. Applications feed +/// PCM frames into it via `captureFrame`, and the SDK forwards them to +/// the Rust core for encoding and transmission. +/// +/// @note Thread-safety: Not thread-safe. `AudioSource::captureFrame` is +/// **not** safe to call concurrently from multiple threads. Callers must +/// drive `captureFrame` from a single capture thread (typically a +/// hardware audio callback). Other accessors (`sample_rate`, +/// `num_channels`, `ffi_handle_id`) may be read concurrently while no +/// `captureFrame` call is in flight. class LIVEKIT_API AudioSource { public: - /** - * Create a new native audio source. - * - * @param sample_rate Sample rate in Hz. - * @param num_channels Number of channels. - * @param queue_size_ms Max buffer duration for the internal queue in ms. - * - * Buffering behavior: - * ------------------- - * - queue_size_ms == 0 (recommended for real-time capture): - * Disables internal buffering entirely. Audio frames are forwarded - * directly to WebRTC sinks and consumed synchronously. - * - * This mode is optimized for real-time audio capture driven by hardware - * media callbacks (e.g. microphone capture). The caller is expected to - * provide fixed-size real-time frames (typically 10 ms per call). - * - * Because the native side consumes frames immediately, this mode - * minimizes latency and jitter and is the best choice for live capture - * scenarios. - * - * - queue_size_ms > 0 (buffered / blocking mode): - * Enables an internal queue that buffers audio up to the specified - * duration. Frames are accumulated and flushed asynchronously once the buffer - * reaches its threshold. - * - * This mode is intended for non-real-time producers (e.g. TTS engines, - * file-based audio, or agents generating audio faster or slower than - * real-time). The buffering layer smooths timing and allows the audio to - * be streamed out in real time even if the producer is bursty. - * - * queue_size_ms must be a multiple of 10. - */ + /// Create a new native audio source. + /// + /// @param sample_rate Sample rate in Hz. + /// @param num_channels Number of channels. + /// @param queue_size_ms Max buffer duration for the internal queue + /// in ms. + /// + /// ### Buffering behavior + /// + /// - `queue_size_ms == 0` (recommended for real-time capture): + /// Disables internal buffering entirely. Audio frames are forwarded + /// directly to WebRTC sinks and consumed synchronously. + /// + /// This mode is optimized for real-time audio capture driven by + /// hardware media callbacks (e.g. microphone capture). The caller is + /// expected to provide fixed-size real-time frames (typically 10 ms + /// per call). Because the native side consumes frames immediately, + /// this mode minimizes latency and jitter and is the best choice for + /// live capture scenarios. + /// + /// - `queue_size_ms > 0` (buffered / blocking mode): + /// Enables an internal queue that buffers audio up to the specified + /// duration. Frames are accumulated and flushed asynchronously once + /// the buffer reaches its threshold. + /// + /// This mode is intended for non-real-time producers (e.g. TTS + /// engines, file-based audio, or agents generating audio faster or + /// slower than real-time). The buffering layer smooths timing and + /// allows the audio to be streamed out in real time even if the + /// producer is bursty. + /// + /// `queue_size_ms` must be a multiple of 10. AudioSource(int sample_rate, int num_channels, int queue_size_ms = 0); + virtual ~AudioSource() = default; AudioSource(const AudioSource&) = delete; @@ -77,81 +86,93 @@ class LIVEKIT_API AudioSource { AudioSource(AudioSource&&) noexcept = default; AudioSource& operator=(AudioSource&&) noexcept = default; - /// The sample rate of the audio source in Hz. + /// @return The sample rate of the audio source in Hz. int sample_rate() const noexcept { return sample_rate_; } - /// The number of audio channels. + /// @return The number of audio channels. int num_channels() const noexcept { return num_channels_; } - /// Underlying FFI handle ID used in FFI requests. + /// @return The underlying FFI handle ID used in FFI requests. std::uint64_t ffi_handle_id() const noexcept { return static_cast(handle_.get()); } - /// Current duration of queued audio (in seconds). + /// @return Current duration of queued audio, in seconds. + /// + /// @note Thread-safety: Not thread-safe. Must not be called + /// concurrently with `captureFrame` on the same instance. double queuedDuration() const noexcept; - /** - * Clears the internal audio queue on the native side and resets local - * queue tracking. - */ + /// Clears the internal audio queue on the native side and resets local + /// queue tracking. + /// + /// @note Thread-safety: Not thread-safe. Must not be called + /// concurrently with `captureFrame` on the same instance. void clearQueue(); - /** - * Push an AudioFrame into the audio source and BLOCK until the FFI callback - * confirms that the native side has finished processing (consuming) the - * frame. Safe usage: The frame's internal buffer must remain valid only until - * this function returns. Because this call blocks until the corresponding FFI - * callback arrives, the caller may safely destroy or reuse the frame - * afterward. - * @param frame The audio frame to send. No-op if the frame contains - * zero samples. - * @param timeout_ms Maximum time to wait for the FFI callback. - * - If timeout_ms > 0: block up to this duration. - * A timeout will cause std::runtime_error. - * - If timeout_ms == 0: wait indefinitely until the - * callback arrives (recommended for production unless the caller needs - * explicit timeout control). - * - * Blocking semantics: - * The blocking behavior of this call depends on the buffering mode selected - * at construction time: - * - * - queue_size_ms == 0 (real-time capture mode): - * Frames are consumed synchronously by the native layer. The FFI callback - * is invoked immediately as part of the capture call, so this function - * returns quickly. - * - * This mode relies on the caller being paced by a real-time media - * callback (e.g. audio hardware interrupt / capture thread). It provides the - * lowest possible latency and is ideal for live microphone capture. - * - * - queue_size_ms > 0 (buffered / non-real-time mode): - * Frames are queued internally and flushed asynchronously. This function - * will block until the buffered audio corresponding to this frame has - * been consumed by the native side and the FFI callback fires. - * - * This mode is best suited for non-real-time audio producers (such as TTS - * engines or agents) that generate audio independently of real-time - * pacing, while still streaming audio out in real time. - * - * Safety notes: - * May throw std::runtime_error if: - * - the FFI reports an error - * - a timeout occurs in bounded-wait mode - */ + /// Push an `AudioFrame` into the audio source and BLOCK until the FFI + /// callback confirms that the native side has finished processing + /// (consuming) the frame. + /// + /// Safe usage: The frame's internal buffer must remain valid only + /// until this function returns. Because this call blocks until the + /// corresponding FFI callback arrives, the caller may safely destroy + /// or reuse the frame afterward. + /// + /// @param frame The audio frame to send. No-op if the frame + /// contains zero samples. + /// @param timeout_ms Maximum time to wait for the FFI callback. + /// - If `timeout_ms > 0`: block up to this + /// duration. A timeout causes + /// `std::runtime_error`. + /// - If `timeout_ms == 0`: wait indefinitely until + /// the callback arrives (recommended for + /// production unless the caller needs explicit + /// timeout control). + /// + /// ### Blocking semantics + /// + /// The blocking behavior of this call depends on the buffering mode + /// selected at construction time: + /// + /// - `queue_size_ms == 0` (real-time capture mode): + /// Frames are consumed synchronously by the native layer. The FFI + /// callback is invoked immediately as part of the capture call, so + /// this function returns quickly. + /// + /// This mode relies on the caller being paced by a real-time media + /// callback (e.g. audio hardware interrupt / capture thread). It + /// provides the lowest possible latency and is ideal for live + /// microphone capture. + /// + /// - `queue_size_ms > 0` (buffered / non-real-time mode): + /// Frames are queued internally and flushed asynchronously. This + /// function will block until the buffered audio corresponding to + /// this frame has been consumed by the native side and the FFI + /// callback fires. + /// + /// This mode is best suited for non-real-time audio producers (such + /// as TTS engines or agents) that generate audio independently of + /// real-time pacing, while still streaming audio out in real time. + /// + /// @throws std::runtime_error if the FFI reports an error or a timeout + /// occurs in bounded-wait mode. + /// + /// @note Thread-safety: Not thread-safe. Concurrent `captureFrame` + /// calls from multiple threads are undefined behavior. void captureFrame(const AudioFrame& frame, int timeout_ms = 20); private: - // Internal helper to reset the local queue tracking (like _release_waiter). + /// Reset the local queue tracking (like `_release_waiter`). void resetQueueTracking() noexcept; int sample_rate_; int num_channels_; int queue_size_ms_; - // RAII wrapper for this audio source's FFI handle + /// RAII wrapper for this audio source's FFI handle. FfiHandle handle_; - // Queue tracking (all in seconds; based on steady_clock in the .cpp). + /// Queue tracking (all in seconds; based on `steady_clock` in the + /// .cpp). mutable double last_capture_{0.0}; mutable double q_size_{0.0}; }; diff --git a/include/livekit/audio_stream.h b/include/livekit/audio_stream.h index 57be0a89..11059165 100644 --- a/include/livekit/audio_stream.h +++ b/include/livekit/audio_stream.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -36,85 +36,115 @@ namespace proto { class FfiEvent; } -/** - * @brief Event containing an audio frame received from an AudioStream. - * - * This struct wraps an AudioFrame and is used as the output type when - * reading from an AudioStream. - */ +/// Event containing an audio frame received from an `AudioStream`. +/// +/// This struct wraps an `AudioFrame` and is used as the output type when +/// reading from an `AudioStream`. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. // NOLINTBEGIN(bugprone-exception-escape) -// AudioFrame can throw in various places monitored by bugprone-exception-escape -// Suppressing for now, would require significant refactor to fix +// AudioFrame can throw in various places monitored by +// bugprone-exception-escape. Suppressing for now, would require +// significant refactor to fix. struct AudioFrameEvent { - AudioFrame frame; ///< The decoded PCM audio frame. + /// The decoded PCM audio frame. + AudioFrame frame; }; // NOLINTEND(bugprone-exception-escape) -/** - * Represents a pull-based stream of decoded PCM audio frames coming from - * a remote (or local) LiveKit track. Similar to VideoStream, but for audio. - * - * Typical usage: - * - * AudioStream::Options opts; - * auto stream = AudioStream::fromTrack(remoteAudioTrack, opts); - * - * AudioFrameEvent ev; - * while (stream->read(ev)) { - * // ev.frame contains interleaved int16 PCM samples - * } - * - * stream->close(); // optional, called automatically in destructor - */ +/// A pull-based stream of decoded PCM audio frames from a remote (or +/// local) LiveKit track. Similar to `VideoStream`, but for audio. +/// +/// Typical usage: +/// ``` +/// AudioStream::Options opts; +/// auto stream = AudioStream::fromTrack(remoteAudioTrack, opts); +/// +/// AudioFrameEvent ev; +/// while (stream->read(ev)) { +/// // ev.frame contains interleaved int16 PCM samples +/// } +/// +/// stream->close(); // optional, called automatically in destructor +/// ``` +/// +/// @note Thread-safety: Thread-safe. An internal `std::mutex` + +/// `std::condition_variable` coordinate the FFI producer thread and the +/// consumer reader thread. Concurrent `read()` and `close()` from +/// different threads is safe. class LIVEKIT_API AudioStream { public: - /// Configuration options for AudioStream creation. + /// Configuration options for `AudioStream` creation. + /// + /// @note Thread-safety: Not thread-safe. POD-like struct. struct Options { - /// Maximum number of AudioFrameEvent items buffered in the internal queue. - /// 0 means "unbounded" (the queue can grow without limit). + /// Maximum number of `AudioFrameEvent` items buffered in the + /// internal queue. `0` means "unbounded" (the queue can grow + /// without limit). /// - /// Using a small non-zero capacity gives ring-buffer semantics: - /// if the queue is full, the oldest frame is dropped when a new one + /// Using a small non-zero capacity gives ring-buffer semantics: if + /// the queue is full, the oldest frame is dropped when a new one /// arrives. std::size_t capacity{0}; - /// Optional: name of a noise cancellation module to enable for this stream. - /// Empty string means "no noise cancellation". + /// Optional: name of a noise cancellation module to enable for + /// this stream. Empty string means "no noise cancellation". std::string noise_cancellation_module; - /// Optional: JSON-encoded configuration for the noise cancellation module. - /// Empty string means "use module defaults". + /// Optional: JSON-encoded configuration for the noise cancellation + /// module. Empty string means "use module defaults". std::string noise_cancellation_options_json; }; - /// Factory: create an AudioStream bound to a specific Track + /// Create an `AudioStream` bound to a specific `Track`. + /// + /// @param track Track to read audio from. + /// @param options Stream configuration. + /// @return A new `AudioStream` instance. + /// + /// @note Thread-safety: Thread-safe. static std::shared_ptr fromTrack(const std::shared_ptr& track, const Options& options); - /// Factory: create an AudioStream from a Participant + TrackSource + /// Create an `AudioStream` from a `Participant` + `TrackSource`. + /// + /// @param participant Participant whose track will be read. + /// @param track_source Source (microphone, screen-share audio, etc.). + /// @param options Stream configuration. + /// @return A new `AudioStream` instance. + /// + /// @note Thread-safety: Thread-safe. static std::shared_ptr fromParticipant(Participant& participant, TrackSource track_source, const Options& options); virtual ~AudioStream(); - /// No copy, assignment constructors. AudioStream(const AudioStream&) = delete; AudioStream& operator=(const AudioStream&) = delete; AudioStream(AudioStream&&) noexcept; AudioStream& operator=(AudioStream&&) noexcept; - /// Blocking read: waits until there is an AudioFrameEvent available in the - /// internal queue, or the stream reaches EOS / is closed. + /// Blocking read: waits until there is an `AudioFrameEvent` available + /// in the internal queue, or the stream reaches EOS / is closed. + /// + /// @param out_event On success, filled with the next audio frame. + /// @return `true` if a frame was delivered; `false` if the + /// stream ended (end-of-stream or `close()`) and no + /// more data is available. /// - /// \param out_event On success, filled with the next audio frame. - /// \return true if a frame was delivered; false if the stream ended - /// (end-of-stream or close()) and no more data is available. + /// @note Thread-safety: Thread-safe. Multiple consumer threads may + /// call `read()` concurrently; each delivered frame is observed by + /// exactly one caller. bool read(AudioFrameEvent& out_event); /// Signal that we are no longer interested in audio frames. /// - /// This disposes the underlying FFI audio stream, unregisters the listener - /// from FfiClient, marks the stream as closed, and wakes any blocking read(). - /// After calling close(), further calls to read() will return false. + /// Disposes the underlying FFI audio stream, unregisters the listener + /// from `FfiClient`, marks the stream as closed, and wakes any + /// blocking `read()`. After calling `close()`, further calls to + /// `read()` will return `false`. + /// + /// @note Thread-safety: Thread-safe. May be called from any thread, + /// including concurrently with `read()`. void close(); private: @@ -123,10 +153,8 @@ class LIVEKIT_API AudioStream { void initFromTrack(const std::shared_ptr& track, const Options& options); void initFromParticipant(Participant& participant, TrackSource track_source, const Options& options); - // FFI event handler (registered with FfiClient) void onFfiEvent(const proto::FfiEvent& event); - // Queue helpers void pushFrame(AudioFrameEvent&& ev); void pushEos(); @@ -139,10 +167,8 @@ class LIVEKIT_API AudioStream { Options options_; - // Underlying FFI audio stream handle FfiHandle stream_handle_; - // Listener id registered on FfiClient std::int32_t listener_id_{0}; }; diff --git a/include/livekit/data_stream.h b/include/livekit/data_stream.h index ed81cf83..02cde3eb 100644 --- a/include/livekit/data_stream.h +++ b/include/livekit/data_stream.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -33,17 +33,21 @@ namespace livekit { class LocalParticipant; -// Chunk size for data streams (matches Python STREAM_CHUNK_SIZE). -// Chosen to balance throughput and latency, and to work well with WebRTC data -// channels. +/// Chunk size for data streams (matches Python `STREAM_CHUNK_SIZE`). +/// +/// Chosen to balance throughput and latency, and to work well with +/// WebRTC data channels. constexpr std::size_t kStreamChunkSize = 15'000; // 15 KB /// Base metadata for any stream (text or bytes). +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct BaseStreamInfo { /// Unique identifier for this stream. std::string stream_id; - /// MIME type of the stream (e.g. "text/plain", "application/octet-stream"). + /// MIME type of the stream (e.g. `"text/plain"`, + /// `"application/octet-stream"`). std::string mime_type; /// Application-defined topic name. @@ -55,17 +59,21 @@ struct BaseStreamInfo { /// Total size of the stream in bytes, if known. std::optional size; - /// Arbitrary key–value attributes attached to the stream. + /// Arbitrary key/value attributes attached to the stream. std::map attributes; }; /// Metadata for a text stream. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct TextStreamInfo : BaseStreamInfo { /// IDs of any attached streams (for replies / threads). std::vector attachments; }; /// Metadata for a byte stream. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct ByteStreamInfo : BaseStreamInfo { /// Optional name of the binary object (e.g. filename). std::string name; @@ -76,34 +84,50 @@ struct ByteStreamInfo : BaseStreamInfo { // - ByteStreamReader: yields raw bytes (std::vector) /// Reader for incoming text streams. +/// /// Created internally by the SDK when a text stream header is received. +/// +/// @note Thread-safety: Thread-safe. An internal `std::mutex` + +/// `std::condition_variable` coordinate the Room-side producer thread +/// and the consumer reader thread. class LIVEKIT_API TextStreamReader { public: /// Construct a reader from initial stream metadata. + /// + /// @param info Stream metadata, typically constructed from an + /// incoming data stream header. explicit TextStreamReader(TextStreamInfo info); TextStreamReader(const TextStreamReader&) = delete; TextStreamReader& operator=(const TextStreamReader&) = delete; /// Blocking read of next text chunk. - /// Returns false when the stream has ended. + /// + /// @param out On success, filled with the next text chunk. + /// @return `false` when the stream has ended. + /// + /// @note Thread-safety: Thread-safe. bool readNext(std::string& out); - /// Convenience: read entire stream into a single string. - /// Blocks until the stream is closed. + /// Convenience: read entire stream into a single string. Blocks until + /// the stream is closed. + /// + /// @return The full concatenated stream contents. + /// + /// @note Thread-safety: Thread-safe. std::string readAll(); - /// Metadata associated with this stream. + /// @return Metadata associated with this stream. const TextStreamInfo& info() const noexcept { return info_; } private: friend class Room; - /// Called by the Room when a new chunk arrives. + /// Called by the `Room` when a new chunk arrives. void onChunkUpdate(const std::string& text); - /// Called by the Room when the stream is closed. - /// Additional trailer attributes are merged into info().attributes. + /// Called by the `Room` when the stream is closed. Additional trailer + /// attributes are merged into `info().attributes`. void onStreamClose(const std::map& trailer_attrs); TextStreamInfo info_; @@ -117,29 +141,40 @@ class LIVEKIT_API TextStreamReader { }; /// Reader for incoming byte streams. +/// +/// @note Thread-safety: Thread-safe. An internal `std::mutex` + +/// `std::condition_variable` coordinate the Room-side producer thread +/// and the consumer reader thread. class LIVEKIT_API ByteStreamReader { public: /// Construct a reader from initial stream metadata. + /// + /// @param info Stream metadata, typically constructed from an + /// incoming data stream header. explicit ByteStreamReader(ByteStreamInfo info); ByteStreamReader(const ByteStreamReader&) = delete; ByteStreamReader& operator=(const ByteStreamReader&) = delete; /// Blocking read of next byte chunk. - /// Returns false when the stream has ended. + /// + /// @param out On success, filled with the next byte chunk. + /// @return `false` when the stream has ended. + /// + /// @note Thread-safety: Thread-safe. bool readNext(std::vector& out); - /// Metadata associated with this stream. + /// @return Metadata associated with this stream. const ByteStreamInfo& info() const noexcept { return info_; } private: friend class Room; - /// Called by the Room when a new chunk arrives. + /// Called by the `Room` when a new chunk arrives. void onChunkUpdate(const std::vector& bytes); - /// Called by the Room when the stream is closed. - /// Additional trailer attributes are merged into info().attributes. + /// Called by the `Room` when the stream is closed. Additional + /// trailer attributes are merged into `info().attributes`. void onStreamClose(const std::map& trailer_attrs); ByteStreamInfo info_; @@ -152,31 +187,58 @@ class LIVEKIT_API ByteStreamReader { }; /// Base class for sending data streams. -/// Concrete subclasses are TextStreamWriter and ByteStreamWriter. +/// +/// Concrete subclasses are `TextStreamWriter` and `ByteStreamWriter`. +/// +/// @note Thread-safety: Thread-safe. `close()` and subclass `write()` +/// calls are serialized by an internal mutex. class LIVEKIT_API BaseStreamWriter { public: virtual ~BaseStreamWriter() = default; - /// Stream id assigned to this writer. + /// @return Stream id assigned to this writer. const std::string& streamId() const noexcept { return stream_id_; } - /// Topic of this stream. + /// @return Topic of this stream. const std::string& topic() const noexcept { return topic_; } - /// MIME type for this stream. + /// @return MIME type for this stream. const std::string& mimeType() const noexcept { return mime_type_; } - /// Timestamp (ms) when the stream was created. + /// @return Timestamp (ms) when the stream was created. std::int64_t timestampMs() const noexcept { return timestamp_ms_; } - /// Whether the stream has been closed. + /// @return Whether the stream has been closed. bool isClosed() const noexcept { return closed_; } /// Close the stream with optional reason and attributes. - /// Throws on FFI error or if already closed. + /// + /// @param reason Optional human-readable termination reason. + /// @param attributes Optional trailer attributes to merge into the + /// final stream state. + /// + /// @throws std::runtime_error on FFI error or if already closed. + /// + /// @note Thread-safety: Thread-safe. Serialized with concurrent + /// writes. void close(const std::string& reason = "", const std::map& attributes = {}); protected: + /// Construct a base writer with the given local participant and + /// metadata. + /// + /// @param local_participant Local participant that owns this + /// writer. + /// @param topic Stream topic. + /// @param attributes Stream attributes. + /// @param stream_id Optional stream id (auto-generated + /// if empty). + /// @param total_size Optional total size of the + /// stream. + /// @param mime_type MIME type of the stream contents. + /// @param destination_identities Recipient identities, or empty for + /// broadcast. + /// @param sender_identity Optional sender identity override. BaseStreamWriter(LocalParticipant& local_participant, std::string topic = "", std::map attributes = {}, std::string stream_id = "", std::optional total_size = std::nullopt, std::string mime_type = "", @@ -186,7 +248,6 @@ class LIVEKIT_API BaseStreamWriter { LocalParticipant& local_participant_; - // Public-ish metadata (mirrors BaseStreamInfo, but kept simple here) std::string stream_id_; std::string mime_type_; std::string topic_; @@ -203,22 +264,37 @@ class LIVEKIT_API BaseStreamWriter { std::string reply_to_id_; std::string byte_name_; // Used by ByteStreamWriter - /// Ensure the header has been sent once. - /// Throws on error. + /// Ensure the header has been sent once. Throws on error. void ensureHeaderSent(); - /// Send a raw chunk of bytes. - /// Throws on error or if stream is closed. + /// Send a raw chunk of bytes. Throws on error or if stream is closed. void sendChunk(const std::vector& content); - /// Send the trailer with given reason and attributes. - /// Throws on error. + /// Send the trailer with given reason and attributes. Throws on + /// error. void sendTrailer(const std::string& reason, const std::map& attributes); }; /// Writer for outgoing text streams. +/// +/// @note Thread-safety: Thread-safe. `write()` is serialized by an +/// internal `write_mutex_`. class LIVEKIT_API TextStreamWriter : public BaseStreamWriter { public: + /// Construct a text stream writer. + /// + /// @param local_participant Local participant that owns this + /// writer. + /// @param topic Stream topic. + /// @param attributes Stream attributes. + /// @param stream_id Optional stream id (auto-generated + /// if empty). + /// @param total_size Optional total size of the stream. + /// @param reply_to_id Optional id of the stream this one + /// replies to. + /// @param destination_identities Recipient identities, or empty for + /// broadcast. + /// @param sender_identity Optional sender identity override. TextStreamWriter(LocalParticipant& local_participant, const std::string& topic = "", const std::map& attributes = {}, const std::string& stream_id = "", std::optional total_size = std::nullopt, const std::string& reply_to_id = "", @@ -226,11 +302,19 @@ class LIVEKIT_API TextStreamWriter : public BaseStreamWriter { const std::string& sender_identity = ""); /// Write a UTF-8 string to the stream. - /// Data will be split into chunks of at most kStreamChunkSize bytes. - /// Throws on error or if the stream is closed. + /// + /// Data will be split into chunks of at most `kStreamChunkSize` + /// bytes. + /// + /// @param text UTF-8 text to send. + /// + /// @throws std::runtime_error on error or if the stream is closed. + /// + /// @note Thread-safety: Thread-safe. Serialized by an internal + /// `write_mutex_`. void write(const std::string& text); - /// Metadata associated with this stream. + /// @return Metadata associated with this stream. const TextStreamInfo& info() const noexcept { return info_; } private: @@ -239,8 +323,26 @@ class LIVEKIT_API TextStreamWriter : public BaseStreamWriter { }; /// Writer for outgoing byte streams. +/// +/// @note Thread-safety: Thread-safe. `write()` is serialized by an +/// internal `write_mutex_`. class LIVEKIT_API ByteStreamWriter : public BaseStreamWriter { public: + /// Construct a byte stream writer. + /// + /// @param local_participant Local participant that owns this + /// writer. + /// @param name Name of the binary object (e.g. + /// filename). + /// @param topic Stream topic. + /// @param attributes Stream attributes. + /// @param stream_id Optional stream id (auto-generated + /// if empty). + /// @param total_size Optional total size of the stream. + /// @param mime_type MIME type for the binary content. + /// @param destination_identities Recipient identities, or empty for + /// broadcast. + /// @param sender_identity Optional sender identity override. ByteStreamWriter(LocalParticipant& local_participant, const std::string& name, const std::string& topic = "", const std::map& attributes = {}, const std::string& stream_id = "", std::optional total_size = std::nullopt, @@ -249,11 +351,18 @@ class LIVEKIT_API ByteStreamWriter : public BaseStreamWriter { const std::string& sender_identity = ""); /// Write binary data to the stream. - /// Data will be chunked into kStreamChunkSize-sized chunks. - /// Throws on error or if the stream is closed. + /// + /// Data will be chunked into `kStreamChunkSize`-sized chunks. + /// + /// @param data Binary payload to send. + /// + /// @throws std::runtime_error on error or if the stream is closed. + /// + /// @note Thread-safety: Thread-safe. Serialized by an internal + /// `write_mutex_`. void write(const std::vector& data); - /// Metadata associated with this stream. + /// @return Metadata associated with this stream. const ByteStreamInfo& info() const noexcept { return info_; } private: @@ -261,21 +370,23 @@ class LIVEKIT_API ByteStreamWriter : public BaseStreamWriter { std::mutex write_mutex_; }; -/* Callback invoked when a new incoming text stream is opened. - * - * The TextStreamReader is provided as a shared_ptr to ensure it remains - * alive for the duration of asynchronous reads (for example, when the - * user spawns a background thread to consume the stream). - */ +/// Callback invoked when a new incoming text stream is opened. +/// +/// The `TextStreamReader` is provided as a `shared_ptr` to ensure it +/// remains alive for the duration of asynchronous reads (for example, +/// when the user spawns a background thread to consume the stream). +/// +/// The callback is invoked on the FFI event thread and must not block. using TextStreamHandler = std::function, const std::string& participant_identity)>; -/* Callback invoked when a new incoming byte stream is opened. - * - * The ByteStreamReader is provided as a shared_ptr to ensure it remains - * alive for the duration of asynchronous reads (for example, file writes - * or background processing). - */ +/// Callback invoked when a new incoming byte stream is opened. +/// +/// The `ByteStreamReader` is provided as a `shared_ptr` to ensure it +/// remains alive for the duration of asynchronous reads (for example, +/// file writes or background processing). +/// +/// The callback is invoked on the FFI event thread and must not block. using ByteStreamHandler = std::function, const std::string& participant_identity)>; diff --git a/include/livekit/data_track_error.h b/include/livekit/data_track_error.h index d0e67b19..cffe0c75 100644 --- a/include/livekit/data_track_error.h +++ b/include/livekit/data_track_error.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -29,55 +29,127 @@ class LocalDataTrackTryPushError; class SubscribeDataTrackError; } // namespace proto +/// Reason a `LocalParticipant::publishDataTrack` call failed. enum class PublishDataTrackErrorCode : std::uint32_t { + /// Unspecified / unknown error. UNKNOWN = 0, + /// The participant or room handle was no longer valid. INVALID_HANDLE = 1, + /// A data track with the same name is already published. DUPLICATE_NAME = 2, + /// The publish call did not complete within the allotted time. TIMEOUT = 3, + /// The participant disconnected before publish completed. DISCONNECTED = 4, + /// The participant is not permitted to publish data tracks. NOT_ALLOWED = 5, + /// The supplied track name is invalid (empty or contains illegal + /// characters). INVALID_NAME = 6, + /// The participant has reached the maximum number of data tracks. LIMIT_REACHED = 7, + /// The server returned an unexpected protocol-level error. PROTOCOL_ERROR = 8, + /// An internal SDK or FFI error occurred. INTERNAL = 9, }; +/// Typed error returned by `LocalParticipant::publishDataTrack`. +/// +/// Carries a structured `code` so callers can branch programmatically and +/// a human-readable `message` for logs/UI. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct; concurrent +/// mutation is undefined behavior. struct PublishDataTrackError { + /// Structured error reason. PublishDataTrackErrorCode code{PublishDataTrackErrorCode::UNKNOWN}; + + /// Human-readable error message (may be empty). std::string message; + /// Convert a `proto::PublishDataTrackError` into the public form. + /// + /// @param error Protobuf-encoded error from the FFI. + /// @return Equivalent public `PublishDataTrackError`. + /// + /// @note Thread-safety: Thread-safe. Pure transformation; no global + /// state. LIVEKIT_API static PublishDataTrackError fromProto(const proto::PublishDataTrackError& error); }; +/// Reason a `LocalDataTrack::tryPush` call failed. enum class LocalDataTrackTryPushErrorCode : std::uint32_t { + /// Unspecified / unknown error. UNKNOWN = 0, + /// The data track handle was no longer valid. INVALID_HANDLE = 1, + /// The track has been unpublished and cannot accept more frames. TRACK_UNPUBLISHED = 2, + /// The internal send queue is full (back-pressure). QUEUE_FULL = 3, + /// An internal SDK or FFI error occurred. INTERNAL = 4, }; +/// Typed error returned by `LocalDataTrack::tryPush`. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct; concurrent +/// mutation is undefined behavior. struct LocalDataTrackTryPushError { + /// Structured error reason. LocalDataTrackTryPushErrorCode code{LocalDataTrackTryPushErrorCode::UNKNOWN}; + + /// Human-readable error message (may be empty). std::string message; + /// Convert a `proto::LocalDataTrackTryPushError` into the public form. + /// + /// @param error Protobuf-encoded error from the FFI. + /// @return Equivalent public `LocalDataTrackTryPushError`. + /// + /// @note Thread-safety: Thread-safe. Pure transformation; no global + /// state. LIVEKIT_API static LocalDataTrackTryPushError fromProto(const proto::LocalDataTrackTryPushError& error); }; +/// Reason a `RemoteDataTrack::subscribe` call failed. enum class SubscribeDataTrackErrorCode : std::uint32_t { + /// Unspecified / unknown error. UNKNOWN = 0, + /// The remote track handle was no longer valid. INVALID_HANDLE = 1, + /// The remote participant unpublished the track before subscription + /// completed. UNPUBLISHED = 2, + /// The subscribe call did not complete within the allotted time. TIMEOUT = 3, + /// The room disconnected before subscription completed. DISCONNECTED = 4, + /// The server returned an unexpected protocol-level error. PROTOCOL_ERROR = 5, + /// An internal SDK or FFI error occurred. INTERNAL = 6, }; +/// Typed error returned by `RemoteDataTrack::subscribe`. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct; concurrent +/// mutation is undefined behavior. struct SubscribeDataTrackError { + /// Structured error reason. SubscribeDataTrackErrorCode code{SubscribeDataTrackErrorCode::UNKNOWN}; + + /// Human-readable error message (may be empty). std::string message; + /// Convert a `proto::SubscribeDataTrackError` into the public form. + /// + /// @param error Protobuf-encoded error from the FFI. + /// @return Equivalent public `SubscribeDataTrackError`. + /// + /// @note Thread-safety: Thread-safe. Pure transformation; no global + /// state. LIVEKIT_API static SubscribeDataTrackError fromProto(const proto::SubscribeDataTrackError& error); }; diff --git a/include/livekit/data_track_frame.h b/include/livekit/data_track_frame.h index 1e82ac5a..eb270bea 100644 --- a/include/livekit/data_track_frame.h +++ b/include/livekit/data_track_frame.h @@ -28,40 +28,48 @@ namespace proto { class DataTrackFrame; } // namespace proto -/** - * A single frame of data published or received on a data track. - * - * Carries an arbitrary binary payload and an optional user-specified - * timestamp. The unit is application-defined; the SDK examples use - * microseconds since the Unix epoch (system_clock). - */ +/// A single frame of data published or received on a data track. +/// +/// Carries an arbitrary binary payload and an optional user-specified +/// timestamp. The unit is application-defined; the SDK examples use +/// microseconds since the Unix epoch (`system_clock`). +/// +/// @note Thread-safety: Not thread-safe. A single `DataTrackFrame` +/// instance must not be mutated concurrently from multiple threads; +/// concurrent const-access is safe. struct DataTrackFrame { - /** Arbitrary binary payload (the frame contents). */ + /// Arbitrary binary payload (the frame contents). std::vector payload; - /** - * Optional application-defined timestamp. - * - * The proto field is a bare uint64 with no prescribed unit. - * By convention the SDK examples use microseconds since the Unix epoch. - */ + /// Optional application-defined timestamp. + /// + /// The proto field is a bare `uint64` with no prescribed unit. By + /// convention the SDK examples use microseconds since the Unix epoch. std::optional user_timestamp; + DataTrackFrame() = default; DataTrackFrame(const DataTrackFrame&) = default; DataTrackFrame(DataTrackFrame&&) noexcept = default; DataTrackFrame& operator=(const DataTrackFrame&) = default; DataTrackFrame& operator=(DataTrackFrame&&) noexcept = default; + /// Construct a frame from a payload and optional timestamp. + /// + /// @param p Payload bytes (consumed via move). + /// @param ts Optional user-defined timestamp. explicit DataTrackFrame(std::vector&& p, std::optional ts = std::nullopt) noexcept : payload(std::move(p)), user_timestamp(ts) {} - /** - * @brief This is a private method used by the SDK to create a DataTrackFrame - * from a proto::DataTrackFrame. - * - * @param owned The proto::DataTrackFrame to create a DataTrackFrame from. - * @return The created DataTrackFrame. - */ + /// Convert an FFI-owned `proto::DataTrackFrame` into the public form. + /// + /// Internal helper used by the SDK when forwarding received frames to + /// the application. + /// + /// @param owned The protobuf-encoded frame received from the FFI. + /// @return Equivalent public `DataTrackFrame`. + /// + /// @note Thread-safety: Thread-safe. Pure transformation; no global + /// state. LIVEKIT_API static DataTrackFrame fromOwnedInfo(const proto::DataTrackFrame& owned); }; diff --git a/include/livekit/data_track_info.h b/include/livekit/data_track_info.h index 45c4fc5f..a1c15755 100644 --- a/include/livekit/data_track_info.h +++ b/include/livekit/data_track_info.h @@ -20,20 +20,21 @@ namespace livekit { -/** - * Metadata about a published data track. - * - * Unlike audio/video tracks, data tracks are not part of the Track class - * hierarchy. They carry their own lightweight info struct. - */ +/// Metadata about a published data track. +/// +/// Unlike audio/video tracks, data tracks are not part of the `Track` +/// class hierarchy. They carry their own lightweight info struct. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct; concurrent +/// mutation is undefined behavior. struct DataTrackInfo { - /** Publisher-assigned track name (unique per publisher). */ + /// Publisher-assigned track name (unique per publisher). std::string name; - /** SFU-assigned track identifier. */ + /// SFU-assigned track identifier. std::string sid; - /** Whether frames on this track use end-to-end encryption. */ + /// Whether frames on this track use end-to-end encryption. bool uses_e2ee = false; }; diff --git a/include/livekit/data_track_stream.h b/include/livekit/data_track_stream.h index 5c8c3cc3..5f4e17e4 100644 --- a/include/livekit/data_track_stream.h +++ b/include/livekit/data_track_stream.h @@ -34,28 +34,34 @@ namespace proto { class FfiEvent; } -/** - * Represents a pull-based stream of frames from a remote data track. - * - * Provides a blocking read() interface similar to AudioStream / VideoStream. - * Frames are delivered via FfiEvent callbacks and stored internally. - * - * Destroying the stream automatically unsubscribes from the remote track by - * releasing the underlying FFI handle. - * - * Typical usage: - * - * auto sub_result = remoteDataTrack->subscribe(); - * if (sub_result) { - * auto sub = sub_result.value(); - * DataTrackFrame frame; - * while (sub->read(frame)) { - * // process frame.payload - * } - * } - */ +/// A pull-based stream of frames from a remote data track. +/// +/// Provides a blocking `read()` interface similar to `AudioStream` / +/// `VideoStream`. Frames are delivered via FFI event callbacks and +/// stored internally. Destroying the stream automatically unsubscribes +/// from the remote track by releasing the underlying FFI handle. +/// +/// Typical usage: +/// ``` +/// auto sub_result = remoteDataTrack->subscribe(); +/// if (sub_result) { +/// auto sub = sub_result.value(); +/// DataTrackFrame frame; +/// while (sub->read(frame)) { +/// // process frame.payload +/// } +/// } +/// ``` +/// +/// @note Thread-safety: Thread-safe. An internal `std::mutex` + +/// `std::condition_variable` coordinate the FFI producer thread and the +/// consumer reader thread. Concurrent `read()` and `close()` from +/// different threads is safe. class LIVEKIT_API DataTrackStream { public: + /// Configuration options for `DataTrackStream` creation. + /// + /// @note Thread-safety: Not thread-safe. POD-like struct. struct Options { /// Maximum frames buffered on the Rust side. Rust defaults to 16. std::optional buffer_size{std::nullopt}; @@ -65,37 +71,44 @@ class LIVEKIT_API DataTrackStream { DataTrackStream(const DataTrackStream&) = delete; DataTrackStream& operator=(const DataTrackStream&) = delete; - // The FFI listener captures `this`, so moving the object would leave the - // registered callback pointing at the old address. + // The FFI listener captures `this`, so moving the object would leave + // the registered callback pointing at the old address. DataTrackStream(DataTrackStream&&) noexcept = delete; - // Instances are created and returned as std::shared_ptr, so value-move - // support is not required by the current API. + // Instances are created and returned as std::shared_ptr, so + // value-move support is not required by the current API. DataTrackStream& operator=(DataTrackStream&&) noexcept = delete; - /** - * Blocking read: waits until a DataTrackFrame is available, or the - * stream reaches EOS / is closed. - * - * @param out On success, filled with the next data frame. - * @return true if a frame was delivered; false if the stream ended. - */ + /// Blocking read: waits until a `DataTrackFrame` is available, or the + /// stream reaches EOS / is closed. + /// + /// @param out On success, filled with the next data frame. + /// @return `true` if a frame was delivered; `false` if the stream + /// ended. + /// + /// @note Thread-safety: Thread-safe. Multiple consumer threads may + /// call `read()` concurrently; each delivered frame is observed by + /// exactly one caller. bool read(DataTrackFrame& out); - /** - * Returns the terminal subscription error reported by the FFI stream. - * - * This is set when read() returns false because subscription establishment - * failed before any frames were emitted. It remains empty for normal EOS or - * when close() ends the stream locally. - */ + /// @return The terminal subscription error reported by the FFI stream, + /// if any. + /// + /// Set when `read()` returns `false` because subscription + /// establishment failed before any frames were emitted. Empty for + /// normal EOS or when `close()` ends the stream locally. + /// + /// @note Thread-safety: Thread-safe. Safe to call concurrently with + /// `read()` or `close()`. std::optional terminalError() const; - /** - * End the stream early. - * - * Releases the FFI handle (which unsubscribes from the remote track), - * unregisters the event listener, and wakes any blocking read(). - */ + /// End the stream early. + /// + /// Releases the FFI handle (which unsubscribes from the remote + /// track), unregisters the event listener, and wakes any blocking + /// `read()`. + /// + /// @note Thread-safety: Thread-safe. May be called from any thread, + /// including concurrently with `read()`. void close(); private: @@ -105,44 +118,44 @@ class LIVEKIT_API DataTrackStream { #endif DataTrackStream() = default; - /// Internal init helper, called by RemoteDataTrack. + + /// Internal init helper, called by `RemoteDataTrack`. void init(FfiHandle subscription_handle); - /// FFI event handler, called by FfiClient. + /// FFI event handler, called by `FfiClient`. void onFfiEvent(const proto::FfiEvent& event); - /// Push a received DataTrackFrame to the internal storage. + /// Push a received `DataTrackFrame` to the internal storage. void pushFrame(DataTrackFrame&& frame); /// Push an end-of-stream signal (EOS). void pushEos(std::optional error = std::nullopt); - /** Protects all mutable state below. */ + /// Protects all mutable state below. mutable std::mutex mutex_; - /** Signalled when a frame is pushed or the subscription ends. */ + /// Signalled when a frame is pushed or the subscription ends. std::condition_variable cv_; - /** - * Received frame awaiting read(). - * NOTE: the Rust side handles buffering, so we should only really ever have - * one item. - */ + /// Received frame awaiting `read()`. The Rust side handles buffering, + /// so we should only really ever have one item. std::optional frame_; - /** True once the remote side signals end-of-stream. */ + /// True once the remote side signals end-of-stream. bool eof_{false}; - /** True after close() has been called by the consumer. */ + /// True after `close()` has been called by the consumer. bool closed_{false}; - /** Typed terminal error reported with EOS, if subscription setup failed. */ + /// Typed terminal error reported with EOS, if subscription setup + /// failed. std::optional terminal_error_; - /** RAII handle for the Rust-owned subscription resource. */ + /// RAII handle for the Rust-owned subscription resource. FfiHandle subscription_handle_; - /** FfiClient listener id for routing FfiEvent callbacks to this object. */ + /// FfiClient listener id for routing FfiEvent callbacks to this + /// object. std::int32_t listener_id_{0}; }; diff --git a/include/livekit/e2ee.h b/include/livekit/e2ee.h index fe087c38..72e12985 100644 --- a/include/livekit/e2ee.h +++ b/include/livekit/e2ee.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -26,44 +26,54 @@ namespace livekit { -/* Encryption algorithm type used by the underlying stack. - * Keep this aligned with your proto enum values. */ +/// Encryption algorithm type used by the underlying stack. +/// +/// Keep this aligned with your proto enum values. enum class EncryptionType { NONE = 0, GCM = 1, CUSTOM = 2, }; -/* Key derivation algorithm used by the key provider. */ +/// Key derivation algorithm used by the key provider. enum class KeyDerivationFunction { PBKDF2 = 0, HKDF = 1, }; -/* Defaults (match Rust KeyProviderOptions::default()). */ +/// Default salt used when deriving ratcheted keys. inline constexpr const char* kDefaultRatchetSalt = "LKFrameEncryptionKey"; + +/// Default ratchet window size (number of previous keys retained). inline constexpr int kDefaultRatchetWindowSize = 16; + +/// Default tolerated ratchet failure count (`-1` = unlimited). inline constexpr int kDefaultFailureTolerance = -1; + +/// Default size of the key ring (number of key slots). inline constexpr int kDefaultKeyRingSize = 16; + +/// Default key derivation function. inline constexpr KeyDerivationFunction kDefaultKeyDerivationFunction = KeyDerivationFunction::PBKDF2; -/** - * Options for configuring the key provider used by E2EE. - * - * Notes: - * - `shared_key` is optional. If omitted, the application may set keys later - * (e.g. via KeyProvider::setSharedKey / per-participant keys). - * - `ratchet_salt` may be empty to indicate "use implementation default". - * - Other key provider fields use SDK defaults unless overridden. - */ +/// Options for configuring the key provider used by E2EE. +/// +/// Notes: +/// - `shared_key` is optional. If omitted, the application may set keys +/// later (e.g. via `KeyProvider::setSharedKey` / per-participant +/// keys). +/// - `ratchet_salt` may be empty to indicate "use implementation +/// default". +/// - Other key provider fields use SDK defaults unless overridden. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct KeyProviderOptions { /// Shared static key for "shared-key E2EE" (optional). /// - /// If set, it must be identical (byte-for-byte) across all participants - /// that are expected to decrypt each other’s media. - /// - /// If not set, keys must be provided out-of-band later (e.g. via KeyProvider - /// APIs). + /// If set, it must be identical (byte-for-byte) across all + /// participants that are expected to decrypt each other's media. If + /// not set, keys must be provided out-of-band later (e.g. via + /// `KeyProvider` APIs). std::optional> shared_key; /// Salt used when deriving ratcheted keys. @@ -72,10 +82,11 @@ struct KeyProviderOptions { std::vector ratchet_salt = std::vector( kDefaultRatchetSalt, kDefaultRatchetSalt + std::char_traits::length(kDefaultRatchetSalt)); - /// Controls how many previous keys are retained during ratcheting. + /// How many previous keys are retained during ratcheting. int ratchet_window_size = kDefaultRatchetWindowSize; - /// Number of tolerated ratchet failures before reporting encryption errors. + /// Number of tolerated ratchet failures before reporting encryption + /// errors. int failure_tolerance = kDefaultFailureTolerance; /// Number of key slots retained by the key provider. @@ -85,49 +96,62 @@ struct KeyProviderOptions { KeyDerivationFunction key_derivation_function = kDefaultKeyDerivationFunction; }; -/** - * End-to-end encryption (E2EE) configuration for a room. - * - * Provide this in RoomOptions to initialize E2EE support. - * - * IMPORTANT: - * - Providing E2EEOptions means "E2EE support is configured for this room". - * - Whether encryption is actively applied can still be toggled at runtime via - * E2EEManager::setEnabled(). - * - A room can be configured for E2EE even if no shared key is provided yet. - * In that case, the app must supply keys later via KeyProvider (shared-key or - * per-participant). - */ +/// End-to-end encryption (E2EE) configuration for a room. +/// +/// Provide this in `RoomOptions` to initialize E2EE support. +/// +/// IMPORTANT: +/// - Providing `E2EEOptions` means "E2EE support is configured for this +/// room". +/// - Whether encryption is actively applied can still be toggled at +/// runtime via `E2EEManager::setEnabled()`. +/// - A room can be configured for E2EE even if no shared key is +/// provided yet. In that case, the app must supply keys later via +/// `KeyProvider` (shared-key or per-participant). +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct E2EEOptions { + /// Configuration for the key provider used by E2EE. KeyProviderOptions key_provider_options{}; + + /// Encryption algorithm to use. EncryptionType encryption_type = EncryptionType::GCM; // default & recommended }; -/** - * E2EE manager for a connected room. - * - * Lifetime: - * - Owned by Room. Applications must not construct E2EEManager directly. - * - * Enablement model: - * - If the Room was created with `RoomOptions.e2ee` set, the room will expose - * a non-null E2EEManager via Room::E2eeManager(). - * - If the Room was created without E2EE options, Room::E2eeManager() may be - * null. - * - * Key model: - * - Keys are managed via KeyProvider (shared-key or per-participant). - * - Providing a shared key up-front is convenient for shared-key E2EE, but is - * not required by the API shape (keys may be supplied later). - */ +/// E2EE manager for a connected room. +/// +/// Lifetime: +/// - Owned by `Room`. Applications must not construct `E2EEManager` +/// directly. +/// +/// Enablement model: +/// - If the `Room` was created with `RoomOptions.e2ee` set, the room +/// will expose a non-null `E2EEManager` via `Room::e2eeManager()`. +/// - If the `Room` was created without E2EE options, +/// `Room::e2eeManager()` may be null. +/// +/// Key model: +/// - Keys are managed via `KeyProvider` (shared-key or +/// per-participant). +/// - Providing a shared key up-front is convenient for shared-key +/// E2EE, but is not required by the API shape (keys may be supplied +/// later). +/// +/// @note Thread-safety: Not thread-safe. Calls into `E2EEManager`, +/// `KeyProvider`, and `FrameCryptor` must be externally serialized. class LIVEKIT_API E2EEManager { public: - /** If your application requires key rotation during the lifetime of a single - * room or unique keys per participant (such as when implementing the MEGOLM - * or MLS protocol), you' can do it via key provider and frame cryptor. refer - * https://docs.livekit.io/home/client/encryption/#custom-key-provider doe - * details - * */ + /// Key provider for E2EE. + /// + /// If your application requires key rotation during the lifetime of a + /// single room or unique keys per participant (such as when + /// implementing the MEGOLM or MLS protocol), you can do it via key + /// provider and frame cryptor. Refer to + /// + /// for details. + /// + /// @note Thread-safety: Not thread-safe. Calls must be externally + /// serialized. class LIVEKIT_API KeyProvider { public: ~KeyProvider() = default; @@ -137,25 +161,48 @@ class LIVEKIT_API E2EEManager { KeyProvider(KeyProvider&&) noexcept = default; KeyProvider& operator=(KeyProvider&&) noexcept = default; - /// Returns the options used to initialize this KeyProvider. + /// @return The options used to initialize this `KeyProvider`. const KeyProviderOptions& options() const; - /// Sets the shared key for the given key slot. + /// Set the shared key for the given key slot. + /// + /// @param key Raw key bytes. + /// @param key_index Slot to store the key in. void setSharedKey(const std::vector& key, int key_index = 0); - /// Exports the shared key for a given key slot. + /// Export the shared key for a given key slot. + /// + /// @param key_index Slot to read. + /// @return The raw key bytes stored in `key_index`. std::vector exportSharedKey(int key_index = 0) const; - /// Ratchets the shared key at key_index and returns the newly derived key. + /// Ratchet the shared key at `key_index` and return the newly + /// derived key. + /// + /// @param key_index Slot to ratchet. + /// @return The new key bytes. std::vector ratchetSharedKey(int key_index = 0); - /// Sets a key for a specific participant identity. + /// Set a key for a specific participant identity. + /// + /// @param participant_identity Identity of the participant the + /// key applies to. + /// @param key Raw key bytes. + /// @param key_index Slot to store the key in. void setKey(const std::string& participant_identity, const std::vector& key, int key_index = 0); - /// Exports a participant-specific key. + /// Export a participant-specific key. + /// + /// @param participant_identity Identity of the participant. + /// @param key_index Slot to read. + /// @return The raw key bytes. std::vector exportKey(const std::string& participant_identity, int key_index = 0) const; - /// Ratchets a participant-specific key and returns the new key. + /// Ratchet a participant-specific key and return the new key. + /// + /// @param participant_identity Identity of the participant. + /// @param key_index Slot to ratchet. + /// @return The new key bytes. std::vector ratchetKey(const std::string& participant_identity, int key_index = 0); private: @@ -165,23 +212,46 @@ class LIVEKIT_API E2EEManager { KeyProviderOptions options_; }; + /// Per-participant frame cryptor. + /// + /// @note Thread-safety: Not thread-safe. Calls must be externally + /// serialized. class LIVEKIT_API FrameCryptor { public: + /// Construct a `FrameCryptor` for a given participant. + /// + /// @param room_handle Room handle that owns this cryptor. + /// @param participant_identity Identity of the participant. + /// @param key_index Active key index. + /// @param enabled Whether encryption/decryption is + /// initially enabled. FrameCryptor(std::uint64_t room_handle, std::string participant_identity, int key_index, bool enabled); + ~FrameCryptor() = default; FrameCryptor(const FrameCryptor&) = delete; FrameCryptor& operator=(const FrameCryptor&) = delete; FrameCryptor(FrameCryptor&&) noexcept = default; FrameCryptor& operator=(FrameCryptor&&) noexcept = default; + /// @return Identity of the participant this cryptor is bound to. const std::string& participantIdentity() const; + + /// @return The currently active key index. int keyIndex() const; + + /// @return Whether frame encryption/decryption is currently + /// enabled. bool enabled() const; - /// Enables or disables frame encryption/decryption for this participant. + /// Enable or disable frame encryption/decryption for this + /// participant. + /// + /// @param enabled `true` to enable, `false` to disable. void setEnabled(bool enabled); - /// Sets the active key index for this participant cryptor. + /// Set the active key index for this participant cryptor. + /// + /// @param key_index Slot to use as the active key. void setKeyIndex(int key_index); private: @@ -197,26 +267,37 @@ class LIVEKIT_API E2EEManager { E2EEManager(E2EEManager&&) noexcept = delete; E2EEManager& operator=(E2EEManager&&) noexcept = delete; - /// Returns whether E2EE is currently enabled for this room at runtime. + /// @return Whether E2EE is currently enabled for this room at + /// runtime. bool enabled() const; /// Enable or disable E2EE at runtime. /// - /// NOTE: - /// - Enabling E2EE without having compatible keys set across participants - /// will result in undecodable media (black video / silent audio). + /// @param enabled `true` to enable, `false` to disable. + /// + /// @note Enabling E2EE without having compatible keys set across + /// participants will result in undecodable media (black video / + /// silent audio). void setEnabled(bool enabled); - /// Returns the key provider if E2EE was configured for the room; otherwise - /// nullptr. + /// @return The key provider if E2EE was configured for the room; + /// otherwise `nullptr`. KeyProvider* keyProvider(); + + /// @return The key provider if E2EE was configured for the room; + /// otherwise `nullptr`. const KeyProvider* keyProvider() const; - /// Retrieves the current list of frame cryptors from the underlying runtime. + /// @return The current list of frame cryptors from the underlying + /// runtime. std::vector frameCryptors() const; protected: - /// Internal constructor used by Room when E2EEOptions are provided. + /// Internal constructor used by `Room` when `E2EEOptions` are + /// provided. + /// + /// @param room_handle Room handle to bind this manager to. + /// @param options Configured E2EE options. explicit E2EEManager(std::uint64_t room_handle, const E2EEOptions& options); friend class Room; diff --git a/include/livekit/export.h b/include/livekit/export.h index c7921fb1..5ab8c150 100644 --- a/include/livekit/export.h +++ b/include/livekit/export.h @@ -16,17 +16,20 @@ #pragma once -// LIVEKIT_API marks a symbol as part of the public ABI of liblivekit. -// -// On Unix, the SDK is built with -fvisibility=hidden / -fvisibility-inlines-hidden, -// so every symbol defaults to hidden. LIVEKIT_API re-exposes the symbol with -// default visibility. Consumers see no annotation (just a normal declaration). -// -// On Windows, the SDK is built without WINDOWS_EXPORT_ALL_SYMBOLS, so symbols -// must be explicitly tagged with __declspec(dllexport) when building the SDK -// and __declspec(dllimport) when consuming it. LIVEKIT_BUILDING_SDK is defined -// only when compiling the SDK itself (set in CMakeLists.txt). - +/// Marks a symbol as part of the public ABI of `liblivekit`. +/// +/// On Unix, the SDK is built with `-fvisibility=hidden` / +/// `-fvisibility-inlines-hidden`, so every symbol defaults to hidden. +/// `LIVEKIT_API` re-exposes the symbol with default visibility. Consumers +/// see no annotation (just a normal declaration). +/// +/// On Windows, the SDK is built without `WINDOWS_EXPORT_ALL_SYMBOLS`, so +/// symbols must be explicitly tagged with `__declspec(dllexport)` when +/// building the SDK and `__declspec(dllimport)` when consuming it. +/// `LIVEKIT_BUILDING_SDK` is defined only when compiling the SDK itself +/// (set in `CMakeLists.txt`). +/// +/// @note Thread-safety: N/A. Compile-time macro. #if defined(_WIN32) #if defined(LIVEKIT_BUILDING_SDK) #define LIVEKIT_API __declspec(dllexport) @@ -41,17 +44,18 @@ #endif #endif -// LIVEKIT_INTERNAL_API marks a symbol that is NOT part of the public ABI but -// must remain visible so that the in-tree test binaries (which link against -// the same shared library) can resolve it. -// -// External consumers must not rely on LIVEKIT_INTERNAL_API symbols; they may -// change or disappear without notice. -// -// On Windows, internal symbols are exported the same way as public ones -// because tests link via the standard import library; on Unix, hidden -// visibility is overridden for these specific symbols only. - +/// Marks a symbol that is NOT part of the public ABI but must remain +/// visible so that the in-tree test binaries (which link against the same +/// shared library) can resolve it. +/// +/// External consumers must not rely on `LIVEKIT_INTERNAL_API` symbols; they +/// may change or disappear without notice. +/// +/// On Windows, internal symbols are exported the same way as public ones +/// because tests link via the standard import library. On Unix, hidden +/// visibility is overridden for these specific symbols only. +/// +/// @note Thread-safety: N/A. Compile-time macro. #if defined(_WIN32) #define LIVEKIT_INTERNAL_API LIVEKIT_API #else diff --git a/include/livekit/ffi_handle.h b/include/livekit/ffi_handle.h index 840aa384..4d55d9f7 100644 --- a/include/livekit/ffi_handle.h +++ b/include/livekit/ffi_handle.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -22,42 +22,69 @@ namespace livekit { -/** - * @brief RAII wrapper for an FFI handle (uintptr_t) coming from Rust. - * - * Ensures that the handle is automatically released via - * livekit_ffi_drop_handle() when the object goes out of scope. - */ +/// RAII wrapper for an FFI handle (`uintptr_t`) coming from Rust. +/// +/// Ensures that the handle is automatically released via +/// `livekit_ffi_drop_handle()` when the object goes out of scope. +/// +/// @note Thread-safety: Not thread-safe. A single `FfiHandle` instance must +/// not be mutated (`reset`, `release`, move-assign) concurrently from +/// multiple threads. class LIVEKIT_API FfiHandle { public: + /// Construct an `FfiHandle` from a raw FFI handle value. + /// + /// @param h Raw handle obtained from Rust. `0` represents an invalid / + /// empty handle and disables the drop on destruction. explicit FfiHandle(uintptr_t h = 0) noexcept; + + /// Drop the underlying FFI handle (if valid). ~FfiHandle(); - // Non-copyable FfiHandle(const FfiHandle&) = delete; FfiHandle& operator=(const FfiHandle&) = delete; - // Movable + /// Move-construct from another `FfiHandle`, leaving the source empty. + /// + /// @param other Source handle to move from. FfiHandle(FfiHandle&& other) noexcept; + + /// Move-assign from another `FfiHandle`, dropping the current handle + /// (if any) and leaving the source empty. + /// + /// @param other Source handle to move from. FfiHandle& operator=(FfiHandle&& other) noexcept; - // Replace the current handle with a new one, dropping the old if needed + /// Replace the current handle with a new one, dropping the old one if + /// it was valid. + /// + /// @param new_handle Replacement FFI handle. `0` leaves the wrapper + /// empty after dropping the old handle. void reset(uintptr_t new_handle = 0) noexcept; - // Release ownership of the handle without dropping it + /// Release ownership of the handle without dropping it. + /// + /// @return The raw FFI handle that was previously owned by this wrapper. + /// The caller is now responsible for eventually dropping it. [[nodiscard]] uintptr_t release() noexcept; - // Whether the handle is valid (non-zero) + /// Whether the wrapped handle is valid (non-zero). + /// + /// @return `true` if `get() != 0`, `false` otherwise. [[nodiscard]] bool valid() const noexcept; - // Get the raw handle value + /// Access the raw FFI handle value. + /// + /// @return The underlying FFI handle, or `0` if empty. [[nodiscard]] uintptr_t get() const noexcept; - // Allow `if (handle)` syntax + /// Conversion to `bool`, allowing `if (handle)` style checks. + /// + /// @return Same as `valid()`. explicit operator bool() const noexcept { return valid(); } private: uintptr_t handle_{0}; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/livekit.h b/include/livekit/livekit.h index f7906c42..28935117 100644 --- a/include/livekit/livekit.h +++ b/include/livekit/livekit.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -44,27 +44,34 @@ namespace livekit { /// The log sink to use for SDK messages. enum class LogSink { - /// Log messages to the console. + /// Log messages to the console (stderr). kConsole = 0, - /// Log messages to a callback function. + /// Log messages to a callback function installed via `setLogCallback`. kCallback = 1, }; /// Initialize the LiveKit SDK. /// -/// This **must be the first LiveKit API called** in the process. -/// It configures global SDK state, including log routing. +/// This **must be the first LiveKit API called** in the process. It +/// configures global SDK state, including log routing. /// -/// @param level Minimum log level for SDK messages (default: Info). -/// Use setLogLevel() to change at runtime. -/// @param log_sink The log sink to use for SDK messages (default: Console). -/// @returns true if initialization happened on this call, false if it was -/// already initialized. +/// @param level Minimum log level for SDK messages. Use `setLogLevel()` +/// to change at runtime. +/// @param log_sink The log sink to use for SDK messages. +/// @return `true` if initialization happened on this call, +/// `false` if the SDK was already initialized. +/// +/// @note Thread-safety: Thread-safe. Multiple concurrent callers will +/// observe a single successful initialization; subsequent calls return +/// `false`. LIVEKIT_API bool initialize(const LogLevel& level = LogLevel::Info, const LogSink& log_sink = LogSink::kConsole); /// Shut down the LiveKit SDK. /// -/// After shutdown, you may call initialize() again. +/// After shutdown, `initialize()` may be called again. +/// +/// @note Thread-safety: Thread-safe. May be called once from any thread; +/// concurrent shutdowns are safe (only the first has effect). LIVEKIT_API void shutdown(); -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/local_audio_track.h b/include/livekit/local_audio_track.h index 8946f077..5324ef32 100644 --- a/include/livekit/local_audio_track.h +++ b/include/livekit/local_audio_track.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -32,61 +32,74 @@ class OwnedTrack; class AudioSource; -/** - * Represents a user-provided audio track sourced from the local device. - * - * `LocalAudioTrack` is used to publish microphone audio (or any custom - * audio source) to a LiveKit room. It wraps a platform-specific audio - * source and exposes simple controls such as `mute()` and `unmute()`. - * - * Typical usage: - * - * auto source = AudioSource::create(...); - * auto track = LocalAudioTrack::createLocalAudioTrack("mic", source); - * room->localParticipant()->publishTrack(track); - * - * Muting a local audio track stops transmitting audio to the room, but - * the underlying source may continue capturing depending on platform - * behavior. - * - * The track name provided during creation is visible to remote - * participants and can be used for debugging or UI display. - */ +/// A user-provided audio track sourced from the local device. +/// +/// `LocalAudioTrack` is used to publish microphone audio (or any custom +/// audio source) to a LiveKit room. It wraps a platform-specific audio +/// source and exposes simple controls such as `mute()` and `unmute()`. +/// +/// Typical usage: +/// ``` +/// auto source = AudioSource::create(...); +/// auto track = LocalAudioTrack::createLocalAudioTrack("mic", source); +/// room->localParticipant()->publishTrack(track); +/// ``` +/// +/// Muting a local audio track stops transmitting audio to the room, but +/// the underlying source may continue capturing depending on platform +/// behavior. The track name provided during creation is visible to remote +/// participants and can be used for debugging or UI display. +/// +/// @note Thread-safety: Not thread-safe. `LocalAudioTrack` is a thin +/// `sendRequest` wrapper with no internal synchronization. Mutating +/// operations such as `mute()` / `unmute()` / `setPublication()` must be +/// externally serialized. class LIVEKIT_API LocalAudioTrack : public Track { public: - /// Creates a new local audio track backed by the given `AudioSource`. + /// Create a new local audio track backed by the given `AudioSource`. /// - /// @param name Human-readable name for the track. This may appear to - /// remote participants and in analytics/debug logs. - /// @param source The audio source that produces PCM frames for this track. - /// The caller retains ownership and should use this source - /// directly for frame capture. + /// @param name Human-readable name for the track. This may appear + /// to remote participants and in analytics/debug logs. + /// @param source The audio source that produces PCM frames for this + /// track. The caller retains ownership and should use + /// this source directly for frame capture. + /// @return A shared pointer to the newly constructed + /// `LocalAudioTrack`. /// - /// @return A shared pointer to the newly constructed `LocalAudioTrack`. + /// @note Thread-safety: Thread-safe. Pure factory; constructs a fresh + /// instance with no shared mutable state. static std::shared_ptr createLocalAudioTrack(const std::string& name, const std::shared_ptr& source); - /// Mutes the audio track. + /// Mute the audio track. /// - /// A muted track stops sending audio to the room, but the track remains - /// published and can be unmuted later without renegotiation. + /// A muted track stops sending audio to the room, but the track + /// remains published and can be unmuted later without renegotiation. void mute(); - /// Unmutes the audio track and resumes sending audio to the room. + /// Unmute the audio track and resume sending audio to the room. void unmute(); - /// Returns a human-readable string representation of the track, - /// including its SID and name. Useful for debugging and logging. + /// @return Human-readable string representation of the track, + /// including its SID and name. Useful for debugging and + /// logging. std::string to_string() const; - /// Returns the publication that owns this track, or nullptr if the track is - /// not published. + /// @return The publication that owns this track, or `nullptr` if the + /// track is not yet published. std::shared_ptr publication() const noexcept { return local_publication_; } - /// 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 hence left for a future revision. + /// Associate this track with its `LocalTrackPublication`. + /// + /// 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, + /// hence left for a future revision. + /// + /// @param publication Publication to associate with this track. + /// + /// @note Thread-safety: Not thread-safe. Called by `LocalParticipant` + /// during publish. void setPublication(const std::shared_ptr& publication) noexcept override { local_publication_ = publication; } @@ -94,8 +107,8 @@ class LIVEKIT_API LocalAudioTrack : public Track { private: explicit LocalAudioTrack(FfiHandle handle, const proto::OwnedTrack& track); - /// The publication that owns this track. This is a nullptr until the track - /// is published, and then points to the publication that owns this track. + /// The publication that owns this track. `nullptr` until the track is + /// published, then points to the publication that owns this track. std::shared_ptr local_publication_; }; diff --git a/include/livekit/local_data_track.h b/include/livekit/local_data_track.h index 45e38611..4b5e93df 100644 --- a/include/livekit/local_data_track.h +++ b/include/livekit/local_data_track.h @@ -35,27 +35,29 @@ namespace proto { class OwnedLocalDataTrack; } -/** - * Represents a locally published data track. - * - * Unlike audio/video tracks, data tracks do not extend the Track base class. - * They use a separate publish/unpublish lifecycle and carry arbitrary binary - * frames instead of media. - * - * Created via LocalParticipant::publishDataTrack(). - * - * Typical usage: - * - * auto lp = room->localParticipant(); - * auto result = lp->publishDataTrack("sensor-data"); - * if (result) { - * auto dt = result.value(); - * DataTrackFrame frame; - * frame.payload = {0x01, 0x02, 0x03}; - * (void)dt->tryPush(frame); - * dt->unpublishDataTrack(); - * } - */ +/// A locally-published data track. +/// +/// Unlike audio/video tracks, data tracks do not extend the `Track` base +/// class. They use a separate publish/unpublish lifecycle and carry +/// arbitrary binary frames instead of media. Created via +/// `LocalParticipant::publishDataTrack()`. +/// +/// Typical usage: +/// ``` +/// auto lp = room->localParticipant(); +/// auto result = lp->publishDataTrack("sensor-data"); +/// if (result) { +/// auto dt = result.value(); +/// DataTrackFrame frame; +/// frame.payload = {0x01, 0x02, 0x03}; +/// (void)dt->tryPush(frame); +/// dt->unpublishDataTrack(); +/// } +/// ``` +/// +/// @note Thread-safety: Not thread-safe. `LocalDataTrack::tryPush` is a +/// thin `sendRequest` wrapper with no internal synchronization. Callers +/// must externally serialize `tryPush` and `unpublishDataTrack`. class LIVEKIT_API LocalDataTrack { public: ~LocalDataTrack() = default; @@ -63,34 +65,50 @@ class LIVEKIT_API LocalDataTrack { LocalDataTrack(const LocalDataTrack&) = delete; LocalDataTrack& operator=(const LocalDataTrack&) = delete; - /// Metadata about this data track. + /// @return Metadata snapshot taken at construction time. const DataTrackInfo& info() const noexcept { return info_; } - /** - * Try to push a frame to all subscribers of this track. - * - * @return success on delivery acceptance, or a typed error describing why - * the frame could not be queued. - */ + /// Try to push a frame to all subscribers of this track. + /// + /// @param frame The frame to push. Its payload is forwarded as-is to + /// subscribers. + /// @return Success on delivery acceptance, or a typed + /// `LocalDataTrackTryPushError` describing why the + /// frame could not be queued (back-pressure, track + /// unpublished, etc.). + /// + /// @note Thread-safety: Not thread-safe. Calls from multiple threads + /// must be externally serialized. Result tryPush(const DataTrackFrame& frame); - /** - * Try to push a frame to all subscribers of this track. - * - * @return success on delivery acceptance, or a typed error describing why - * the frame could not be queued. - */ + /// Try to push a frame to all subscribers of this track. + /// + /// Convenience overload that constructs the `DataTrackFrame` from its + /// component parts. + /// + /// @param payload Frame payload bytes (consumed via move). + /// @param user_timestamp Optional application-defined timestamp. + /// @return Success on delivery acceptance, or a typed + /// `LocalDataTrackTryPushError` describing why + /// the frame could not be queued. + /// + /// @note Thread-safety: Not thread-safe. Calls from multiple threads + /// must be externally serialized. Result tryPush(std::vector&& payload, std::optional user_timestamp = std::nullopt); - /// Whether the track is still published in the room. + /// @return `true` if the track is still published in the room. + /// + /// @note Thread-safety: Not thread-safe. bool isPublished() const; - /** - * Unpublish this data track from the room. - * - * After this call, tryPush() fails and the track cannot be re-published. - */ + /// Unpublish this data track from the room. + /// + /// After this call, `tryPush()` fails and the track cannot be + /// re-published. + /// + /// @note Thread-safety: Not thread-safe. Concurrent calls with + /// `tryPush()` are undefined behavior. void unpublishDataTrack(); private: @@ -100,10 +118,10 @@ class LIVEKIT_API LocalDataTrack { uintptr_t ffi_handle_id() const noexcept { return handle_.get(); } - /** RAII wrapper for the Rust-owned FFI resource. */ + /// RAII wrapper for the Rust-owned FFI resource. FfiHandle handle_; - /** Metadata snapshot taken at construction time. */ + /// Metadata snapshot taken at construction time. DataTrackInfo info_; }; diff --git a/include/livekit/local_participant.h b/include/livekit/local_participant.h index bcb94bd7..cef9a4cb 100644 --- a/include/livekit/local_participant.h +++ b/include/livekit/local_participant.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -43,229 +43,318 @@ class FfiClient; class Track; class LocalTrackPublication; +/// Context provided to RPC method handlers. +/// +/// Carries the incoming request identifier, caller identity, payload, +/// and the negotiated response timeout (in seconds). +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct RpcInvocationData { + /// Server-assigned request identifier. std::string request_id; + + /// Identity of the participant that issued the RPC call. std::string caller_identity; + + /// Request payload supplied by the caller. std::string payload; - double response_timeout_sec; // seconds + + /// Negotiated response timeout for this invocation, in seconds. + double response_timeout_sec; }; -/** - * Represents the local participant in a room. - * - * LocalParticipant, built on top of the participant.h base class. - */ +/// The local participant in a room. +/// +/// `LocalParticipant` is built on top of the `Participant` base class +/// and adds operations for publishing audio/video/data tracks, +/// performing RPC calls, and managing local subscriptions. +/// +/// @note Thread-safety: Not thread-safe. `LocalParticipant` mutating +/// operations (publish/unpublish, register/unregister RPC handlers, +/// etc.) must be externally serialized. The owning `Room` instance +/// already serializes calls coming from its FFI event loop; +/// applications calling these methods directly must avoid concurrent +/// invocations on the same instance. class LIVEKIT_API LocalParticipant : public Participant { public: + /// Map from publication SID to local publication. using PublicationMap = std::unordered_map>; + + /// Map from publication SID to a weak reference to the associated + /// `Track`. using TrackMap = std::unordered_map>; - /** - * Type of callback used to handle incoming RPC method invocations. - * - * The handler receives an RpcInvocationData describing the incoming call - * and may return an optional response payload. To signal an error to the - * remote caller, throw an RpcError; it will be serialized and forwarded. - * - * Returning std::nullopt means "no payload" and results in an empty - * response body being sent back to the caller. - */ + /// Type of callback used to handle incoming RPC method invocations. + /// + /// The handler receives an `RpcInvocationData` describing the + /// incoming call and may return an optional response payload. To + /// signal an error to the remote caller, throw an `RpcError`; it + /// will be serialized and forwarded. + /// + /// Returning `std::nullopt` means "no payload" and results in an + /// empty response body being sent back to the caller. using RpcHandler = std::function(const RpcInvocationData&)>; + /// Construct a `LocalParticipant` from FFI-supplied metadata. + /// + /// @param handle FFI handle owning the participant. + /// @param sid SFU-assigned SID. + /// @param name Display name. + /// @param identity Identity string used by the application. + /// @param metadata Application-defined metadata. + /// @param attributes Key/value attributes. + /// @param kind Participant kind. + /// @param reason Initial disconnect reason. LocalParticipant(FfiHandle handle, std::string sid, std::string name, std::string identity, std::string metadata, std::unordered_map attributes, ParticipantKind kind, DisconnectReason reason); - /** - * Track publications for this participant, keyed by publication SID. - * - * Built on each call from published local tracks (see \ref publishTrack). - * Expired track handles are removed from the internal map while building. - */ + /// Track publications for this participant, keyed by publication + /// SID. + /// + /// Built on each call from published local tracks (see + /// `publishTrack`). Expired track handles are removed from the + /// internal map while building. + /// + /// @return Snapshot of current publications. The map is owned by the + /// caller; the publication objects continue to be owned by + /// `LocalParticipant`. + /// + /// @note Thread-safety: Not thread-safe. Concurrent calls must be + /// externally synchronized. PublicationMap trackPublications() const; - /** - * Publish arbitrary data to the room. - * - * @param payload Raw bytes to send. - * @param reliable Whether to send reliably or not. - * @param destination_identities Optional list of participant identities. - * @param topic Optional topic string. - * - * Throws std::runtime_error if FFI reports an error (if you wire that up). - */ + /// Publish arbitrary data to the room. + /// + /// @param payload Raw bytes to send. + /// @param reliable Whether to send reliably or not. + /// @param destination_identities Optional list of participant + /// identities to deliver to (empty = + /// broadcast). + /// @param topic Optional topic string. + /// + /// @throws std::runtime_error if the FFI reports an error. + /// + /// @note Thread-safety: Not thread-safe. void publishData(const std::vector& payload, bool reliable = true, const std::vector& destination_identities = {}, const std::string& topic = {}); - /** - * Publish a SIP DTMF (phone keypad) tone into the room. - * - * Only meaningful when a SIP trunk is bridging a phone call into the - * room. See SipDtmfData for background on SIP and DTMF. - * - * @param code DTMF code (0-15). - * @param digit Human-readable digit string (e.g. "5", "#"). - */ + /// Publish a SIP DTMF (phone keypad) tone into the room. + /// + /// Only meaningful when a SIP trunk is bridging a phone call into the + /// room. See `SipDtmfData` for background on SIP and DTMF. + /// + /// @param code DTMF code (0-15). + /// @param digit Human-readable digit string (e.g. `"5"`, `"#"`). + /// + /// @note Thread-safety: Not thread-safe. void publishDtmf(int code, const std::string& digit); // ------------------------------------------------------------------------- // Metadata APIs (set metadata / name / attributes) // ------------------------------------------------------------------------- + /// Update the local participant's metadata string on the server. + /// + /// @param metadata New metadata to publish. + /// + /// @note Thread-safety: Not thread-safe. void setMetadata(const std::string& metadata); + + /// Update the local participant's display name on the server. + /// + /// @param name New display name. + /// + /// @note Thread-safety: Not thread-safe. void setName(const std::string& name); + + /// Update the local participant's attributes on the server. + /// + /// @param attributes Replacement attribute map. + /// + /// @note Thread-safety: Not thread-safe. void setAttributes(const std::unordered_map& attributes); - /** - * Set track subscription permissions for this participant. - * - * @param allow_all_participants If true, all participants may subscribe. - * @param participant_permissions Optional participant-specific permissions. - */ + /// Set track subscription permissions for this participant. + /// + /// @param allow_all_participants If `true`, all participants may + /// subscribe. + /// @param participant_permissions Optional participant-specific + /// permissions overriding the + /// default. + /// + /// @note Thread-safety: Not thread-safe. void setTrackSubscriptionPermissions(bool allow_all_participants, const std::vector& participant_permissions = {}); - /** - * Publish a local track to the room. - * - * Throws std::runtime_error on error (e.g. publish failure). - */ + /// Publish a local track to the room. + /// + /// @param track The track to publish. Must be non-null. + /// @param options Publish options (codec, simulcast, encoding, etc.). + /// + /// @throws std::runtime_error on error (e.g. publish failure). + /// + /// @note Thread-safety: Not thread-safe. void publishTrack(const std::shared_ptr& track, const TrackPublishOptions& options); - /** - * Create a \ref LocalVideoTrack backed by the given \ref VideoSource, - * publish it, and return the track. - * - * The caller retains ownership of \p source and should use it directly - * for frame capture on the video thread. - */ + /// Create a `LocalVideoTrack` backed by the given `VideoSource`, + /// publish it, and return the track. + /// + /// The caller retains ownership of `source` and should use it + /// directly for frame capture on the video thread. + /// + /// @param name Track name visible to remote participants. + /// @param source Video source feeding the new track. + /// @param track_source Logical track source (camera, screen-share, + /// etc.). + /// @return The published `LocalVideoTrack`. + /// + /// @note Thread-safety: Not thread-safe. std::shared_ptr publishVideoTrack(const std::string& name, const std::shared_ptr& source, TrackSource track_source); - /** - * Create a \ref LocalAudioTrack backed by the given \ref AudioSource, - * publish it, and return the track. - * - * The caller retains ownership of \p source and should use it directly - * for frame capture on the audio thread. - */ + /// Create a `LocalAudioTrack` backed by the given `AudioSource`, + /// publish it, and return the track. + /// + /// The caller retains ownership of `source` and should use it + /// directly for frame capture on the audio thread. + /// + /// @param name Track name visible to remote participants. + /// @param source Audio source feeding the new track. + /// @param track_source Logical track source (microphone, + /// screen-share audio, etc.). + /// @return The published `LocalAudioTrack`. + /// + /// @note Thread-safety: Not thread-safe. std::shared_ptr publishAudioTrack(const std::string& name, const std::shared_ptr& source, TrackSource track_source); - /** - * Unpublish a track from the room by SID. - * - * If the publication exists in the local map, it is removed. - */ + /// Unpublish a track from the room by SID. + /// + /// If the publication exists in the local map, it is removed. + /// + /// @param track_sid SID of the publication to unpublish. + /// + /// @note Thread-safety: Not thread-safe. void unpublishTrack(const std::string& track_sid); - /** - * Publish a data track to the room. - * - * Data tracks carry arbitrary binary frames and are independent of the - * audio/video track hierarchy. The returned LocalDataTrack can push - * frames via tryPush() and be unpublished via - * LocalDataTrack::unpublishDataTrack() or - * LocalParticipant::unpublishDataTrack(). - * - * @param name Unique track name visible to other participants. - * @return The published track on success, or a typed error describing why - * publication failed. - */ + /// Publish a data track to the room. + /// + /// Data tracks carry arbitrary binary frames and are independent of + /// the audio/video track hierarchy. The returned `LocalDataTrack` + /// can push frames via `tryPush()` and be unpublished via + /// `LocalDataTrack::unpublishDataTrack()` or + /// `LocalParticipant::unpublishDataTrack()`. + /// + /// @param name Unique track name visible to other participants. + /// @return The published track on success, or a typed + /// `PublishDataTrackError` describing why publication + /// failed. + /// + /// @note Thread-safety: Not thread-safe. Result, PublishDataTrackError> publishDataTrack(const std::string& name); - /** - * Unpublish a data track from the room. - * - * Delegates to LocalDataTrack::unpublishDataTrack(). After this call, - * tryPush() on the track will fail and the track cannot be re-published. - * - * @param track The data track to unpublish. Null is ignored. - */ + /// Unpublish a data track from the room. + /// + /// Delegates to `LocalDataTrack::unpublishDataTrack()`. After this + /// call, `tryPush()` on the track will fail and the track cannot be + /// re-published. + /// + /// @param track The data track to unpublish. `nullptr` is ignored. + /// + /// @note Thread-safety: Not thread-safe. void unpublishDataTrack(const std::shared_ptr& track); - /** - * Initiate an RPC call to a remote participant. - * - * @param destination_identity Identity of the destination participant. - * @param method Name of the RPC method to invoke. - * @param payload Request payload to send to the remote handler. - * @param response_timeout Optional timeout in seconds for receiving - * a response. If not set, the server default - * timeout (15 seconds) is used. - * - * @return The response payload returned by the remote handler. - * - * @throws RpcError If the remote side returns an RPC error, times out, - * or rejects the request. - * @throws std::runtime_error If the underlying FFI handle is invalid or - * the FFI call fails unexpectedly. - */ + /// Initiate an RPC call to a remote participant. + /// + /// @param destination_identity Identity of the destination + /// participant. + /// @param method Name of the RPC method to invoke. + /// @param payload Request payload to send to the + /// remote handler. + /// @param response_timeout Optional timeout in seconds for + /// receiving a response. If not set, + /// the server default timeout (15 + /// seconds) is used. + /// @return The response payload returned by the + /// remote handler. + /// + /// @throws RpcError If the remote side returns an RPC + /// error, times out, or rejects the + /// request. + /// @throws std::runtime_error If the underlying FFI handle is + /// invalid or the FFI call fails + /// unexpectedly. + /// + /// @note Thread-safety: Not thread-safe. Blocks the calling thread + /// until a response arrives or the timeout fires. std::string performRpc(const std::string& destination_identity, const std::string& method, const std::string& payload, const std::optional& response_timeout = std::nullopt); - /** - * Register a handler for an incoming RPC method. - * - * Once registered, the provided handler will be invoked whenever a remote - * participant calls the given method name on this LocalParticipant. - * - * @param method_name Name of the RPC method to handle. This must match - * the method name used by remote callers. - * @param handler Callback to execute when an invocation is received. - * The handler may return an optional response payload - * or throw an RpcError to signal failure. - * - * If a handler is already registered for the same method_name, it will be - * replaced by the new handler. - */ - + /// Register a handler for an incoming RPC method. + /// + /// Once registered, the provided handler will be invoked whenever a + /// remote participant calls the given method name on this + /// `LocalParticipant`. If a handler is already registered for the + /// same `method_name`, it will be replaced by the new handler. + /// + /// @param method_name Name of the RPC method to handle. This must + /// match the method name used by remote callers. + /// @param handler Callback to execute when an invocation is + /// received. The handler may return an optional + /// response payload or throw an `RpcError` to + /// signal failure. + /// + /// @note Thread-safety: Not thread-safe. void registerRpcMethod(const std::string& method_name, RpcHandler handler); - /** - * Unregister a previously registered RPC method handler. - * - * After this call, invocations for the given method_name will no longer - * be dispatched to a local handler and will instead result in an - * "unsupported method" error being returned to the caller. - * - * @param method_name Name of the RPC method to unregister. - * If no handler is registered for this name, the call - * is a no-op. - */ + /// Unregister a previously registered RPC method handler. + /// + /// After this call, invocations for the given `method_name` will no + /// longer be dispatched to a local handler and will instead result + /// in an "unsupported method" error being returned to the caller. + /// + /// @param method_name Name of the RPC method to unregister. If no + /// handler is registered for this name, the call + /// is a no-op. + /// + /// @note Thread-safety: Not thread-safe. void unregisterRpcMethod(const std::string& method_name); protected: - /** - * Shutdown the local participant, cleaning up all resources. - * - * This unregisters all RPC handlers and prepares the participant for - * destruction. Called by Room during its destruction sequence. - */ + /// Shutdown the local participant, cleaning up all resources. + /// + /// Unregisters all RPC handlers and prepares the participant for + /// destruction. Called by `Room` during its destruction sequence. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` during + /// destruction; must not race with other public methods. void shutdown(); - // Called by Room when an rpc_method_invocation event is received from the - // SFU. This is internal plumbing and not intended to be called directly by - // SDK users. + + // Called by Room when an rpc_method_invocation event is received from + // the SFU. This is internal plumbing and not intended to be called + // directly by SDK users. void handleRpcMethodInvocation(std::uint64_t invocation_id, const std::string& method, const std::string& request_id, const std::string& caller_identity, const std::string& payload, double response_timeout); + // Called by Room events like kTrackMuted. std::shared_ptr findTrackPublication(const std::string& sid) const override; friend class Room; private: - /// Publication SID → local track (\ref unpublishTrack clears the track’s - /// cached publication). \c mutable so \ref trackPublications() const can - /// prune expired \c weak_ptr entries. + /// Publication SID → local track. `unpublishTrack` clears the + /// track's cached publication. Declared `mutable` so + /// `trackPublications() const` can prune expired `weak_ptr` entries. mutable TrackMap published_tracks_by_sid_; std::unordered_map rpc_handlers_; - // Shared state for RPC invocation tracking. Using shared_ptr so the state - // can outlive the LocalParticipant if there are in-flight invocations when - // the participant is destroyed. + // Shared state for RPC invocation tracking. Using shared_ptr so the + // state can outlive the LocalParticipant if there are in-flight + // invocations when the participant is destroyed. struct RpcInvocationState { std::mutex mutex; std::condition_variable cv; diff --git a/include/livekit/local_track_publication.h b/include/livekit/local_track_publication.h index 50f5fd74..d587df4e 100644 --- a/include/livekit/local_track_publication.h +++ b/include/livekit/local_track_publication.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -25,10 +25,24 @@ namespace proto { class OwnedTrackPublication; } +/// Publication record for a locally-published track. +/// +/// `LocalTrackPublication` instances are constructed by `LocalParticipant` +/// when a publish succeeds and are returned to the application via +/// `LocalParticipant::trackPublications()` and +/// `RoomDelegate::onLocalTrackPublished`. +/// +/// @note Thread-safety: Not thread-safe. Inherits the same constraints as +/// `TrackPublication`. class LIVEKIT_API LocalTrackPublication : public TrackPublication { public: - /// Note, this LocalTrackPublication is constructed internally only; - /// safe to accept proto::OwnedTrackPublication. + /// Construct a `LocalTrackPublication` from an FFI-owned publication. + /// + /// Constructed internally only; safe to accept + /// `proto::OwnedTrackPublication`. + /// + /// @param owned Protobuf-encoded publication metadata received from + /// the FFI. explicit LocalTrackPublication(const proto::OwnedTrackPublication& owned); }; diff --git a/include/livekit/local_video_track.h b/include/livekit/local_video_track.h index 98b0b801..2c60fb16 100644 --- a/include/livekit/local_video_track.h +++ b/include/livekit/local_video_track.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -31,62 +31,76 @@ class OwnedTrack; class VideoSource; -/** - * Represents a user-provided video track sourced from the local device. - * - * `LocalVideoTrack` is used to publish camera video (or any custom - * video source) to a LiveKit room. It wraps a platform-specific video - * source and exposes simple controls such as `mute()` and `unmute()`. - * - * Typical usage: - * - * auto source = std::make_shared(1280, 720); - * auto track = LocalVideoTrack::createLocalVideoTrack("cam", source); - * room->localParticipant()->publishTrack(track); - * // Capture frames on the video thread via `source`, not via the track. - * - * Muting a local video track stops transmitting video to the room, but - * the underlying source may continue capturing depending on platform - * behavior. - * - * The track name provided during creation is visible to remote - * participants and can be used for debugging or UI display. - */ +/// A user-provided video track sourced from the local device. +/// +/// `LocalVideoTrack` is used to publish camera video (or any custom +/// video source) to a LiveKit room. It wraps a platform-specific video +/// source and exposes simple controls such as `mute()` and `unmute()`. +/// +/// Typical usage: +/// ``` +/// auto source = std::make_shared(1280, 720); +/// auto track = LocalVideoTrack::createLocalVideoTrack("cam", source); +/// room->localParticipant()->publishTrack(track); +/// // Capture frames on the video thread via `source`, not via the +/// // track. +/// ``` +/// +/// Muting a local video track stops transmitting video to the room, but +/// the underlying source may continue capturing depending on platform +/// behavior. The track name provided during creation is visible to remote +/// participants and can be used for debugging or UI display. +/// +/// @note Thread-safety: Not thread-safe. `LocalVideoTrack` is a thin +/// `sendRequest` wrapper with no internal synchronization. Mutating +/// operations such as `mute()` / `unmute()` / `setPublication()` must be +/// externally serialized. class LIVEKIT_API LocalVideoTrack : public Track { public: - /// Creates a new local video track backed by the given `VideoSource`. + /// Create a new local video track backed by the given `VideoSource`. /// - /// @param name Human-readable name for the track. This may appear to - /// remote participants and in analytics/debug logs. - /// @param source The video source that produces video frames for this track. - /// The caller retains ownership and should use this source - /// directly for frame capture. + /// @param name Human-readable name for the track. This may appear + /// to remote participants and in analytics/debug logs. + /// @param source The video source that produces video frames for + /// this track. The caller retains ownership and should + /// use this source directly for frame capture. + /// @return A shared pointer to the newly constructed + /// `LocalVideoTrack`. /// - /// @return A shared pointer to the newly constructed `LocalVideoTrack`. + /// @note Thread-safety: Thread-safe. Pure factory; constructs a fresh + /// instance with no shared mutable state. static std::shared_ptr createLocalVideoTrack(const std::string& name, const std::shared_ptr& source); - /// Mutes the video track. + /// Mute the video track. /// - /// A muted track stops sending video to the room, but the track remains - /// published and can be unmuted later without renegotiation. + /// A muted track stops sending video to the room, but the track + /// remains published and can be unmuted later without renegotiation. void mute(); - /// Unmutes the video track and resumes sending video to the room. + /// Unmute the video track and resume sending video to the room. void unmute(); - /// Returns a human-readable string representation of the track, - /// including its SID and name. Useful for debugging and logging. + /// @return Human-readable string representation of the track, + /// including its SID and name. Useful for debugging and + /// logging. std::string to_string() const; - /// Returns the publication that owns this track, or nullptr if the track is - /// not published. + /// @return The publication that owns this track, or `nullptr` if the + /// track is not yet published. std::shared_ptr publication() const noexcept { return local_publication_; } - /// 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 a API-breaking change hence left for a future revision. + /// Associate this track with its `LocalTrackPublication`. + /// + /// 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, + /// hence left for a future revision. + /// + /// @param publication Publication to associate with this track. + /// + /// @note Thread-safety: Not thread-safe. Called by `LocalParticipant` + /// during publish. void setPublication(const std::shared_ptr& publication) noexcept override { local_publication_ = publication; } @@ -94,9 +108,9 @@ class LIVEKIT_API LocalVideoTrack : public Track { private: explicit LocalVideoTrack(FfiHandle handle, const proto::OwnedTrack& track); - /// The publication that owns this track. This is a nullptr until the track - /// is published, and then points to the publication that owns this track. + /// The publication that owns this track. `nullptr` until the track is + /// published, then points to the publication that owns this track. std::shared_ptr local_publication_; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/logging.h b/include/livekit/logging.h index 4e5a145d..18a61e50 100644 --- a/include/livekit/logging.h +++ b/include/livekit/logging.h @@ -36,18 +36,27 @@ enum class LogLevel { /// Set the minimum log level for the SDK logger. /// -/// Messages below this level are discarded before reaching any sink -/// or callback. Thread-safe; may be called at any time after initialize(). +/// Messages below this level are discarded before reaching any sink or +/// callback. +/// +/// @param level New minimum log level. +/// +/// @note Thread-safety: Thread-safe. May be called at any time after +/// `initialize()`. LIVEKIT_API void setLogLevel(LogLevel level); /// Return the current minimum log level. +/// +/// @return The most recently configured `LogLevel`. +/// +/// @note Thread-safety: Thread-safe. LIVEKIT_API LogLevel getLogLevel(); /// Signature for a user-supplied log callback. /// -/// @param level Severity of the message. -/// @param logger_name Name of the originating logger (e.g. "livekit"). -/// @param message Formatted log message (no trailing newline). +/// @param level Severity of the message. +/// @param logger_name Name of the originating logger (e.g. `"livekit"`). +/// @param message Formatted log message (no trailing newline). /// /// The callback is invoked sequentially (never concurrently) from the /// thread that generated the log message. Implementations must not block @@ -56,8 +65,11 @@ using LogCallback = std::function attributes, ParticipantKind kind, DisconnectReason reason) : handle_(std::move(handle)), @@ -41,30 +65,97 @@ class LIVEKIT_API Participant { attributes_(std::move(attributes)), kind_(kind), reason_(reason) {} + virtual ~Participant() = default; - // Plain getters (caller ensures threading) + /// @return The SFU-assigned participant SID. const std::string& sid() const noexcept { return sid_; } + + /// @return Display name. const std::string& name() const noexcept { return name_; } + + /// @return Identity string used by the application. const std::string& identity() const noexcept { return identity_; } + + /// @return Application-defined metadata string. const std::string& metadata() const noexcept { return metadata_; } + + /// @return Map of key/value attributes attached to the participant. const std::unordered_map& attributes() const noexcept { return attributes_; } + + /// @return The participant kind (standard, agent, SIP, etc.). ParticipantKind kind() const noexcept { return kind_; } + + /// @return The most recent disconnect reason reported by the server. DisconnectReason disconnectReason() const noexcept { return reason_; } + /// @return The underlying FFI handle value. uintptr_t ffiHandleId() const noexcept { return handle_.get(); } - // Setters (caller ensures threading) + /// Update the cached display name. + /// + /// @param name New name (consumed via move). + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void set_name(std::string name) noexcept { name_ = std::move(name); } + + /// Update the cached metadata string. + /// + /// @param metadata New metadata (consumed via move). + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void set_metadata(std::string metadata) noexcept { metadata_ = std::move(metadata); } + + /// Replace the cached attribute map. + /// + /// @param attrs New attributes (consumed via move). + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void set_attributes(std::unordered_map attrs) noexcept { attributes_ = std::move(attrs); } + + /// Set a single attribute. + /// + /// @param key Attribute name. + /// @param value Attribute value. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void set_attribute(const std::string& key, const std::string& value) { attributes_[key] = value; } + + /// Remove a single attribute. + /// + /// @param key Attribute name to remove. No-op if absent. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void remove_attribute(const std::string& key) { attributes_.erase(key); } + + /// Update the cached participant kind. + /// + /// @param kind New kind. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void set_kind(ParticipantKind kind) noexcept { kind_ = kind; } + + /// Update the cached disconnect reason. + /// + /// @param reason New disconnect reason. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void set_disconnect_reason(DisconnectReason reason) noexcept { reason_ = reason; } protected: + /// Find a track publication by SID. + /// + /// @param sid Publication SID to look up. + /// @return The publication, or `nullptr` if not found. virtual std::shared_ptr findTrackPublication(const std::string& sid) const = 0; + friend class Room; private: @@ -75,4 +166,4 @@ class LIVEKIT_API Participant { DisconnectReason reason_; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/remote_audio_track.h b/include/livekit/remote_audio_track.h index 060b5825..9d322419 100644 --- a/include/livekit/remote_audio_track.h +++ b/include/livekit/remote_audio_track.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -30,28 +30,32 @@ class OwnedTrack; class AudioSource; -/** - * Represents an audio track published by a remote participant and - * subscribed to by the local participant. - * - * `RemoteAudioTrack` instances are created internally when the SDK receives a - * `kTrackSubscribed` event. Each instance is owned by its associated - * `RemoteParticipant` and delivered to the application via - * `TrackSubscribedEvent`. - * - * Applications generally interact with `RemoteAudioTrack` through events and - * `RemoteTrackPublication`, not through direct construction. - */ +/// An audio track published by a remote participant and subscribed to by +/// the local participant. +/// +/// `RemoteAudioTrack` instances are created internally when the SDK +/// receives a `kTrackSubscribed` event. Each instance is owned by its +/// associated `RemoteParticipant` and delivered to the application via +/// `TrackSubscribedEvent`. Applications generally interact with +/// `RemoteAudioTrack` through events and `RemoteTrackPublication`, not +/// through direct construction. +/// +/// @note Thread-safety: Not thread-safe. Inherits the same constraints as +/// `Track`. class LIVEKIT_API RemoteAudioTrack : public Track { public: - /// Constructs a `RemoteAudioTrack` from an internal protocol-level + /// Construct a `RemoteAudioTrack` from an internal protocol-level /// `OwnedTrack` description provided by the signaling/FFI layer. + /// /// This constructor is intended for internal SDK use only. + /// + /// @param track Protobuf-encoded owned track description. explicit RemoteAudioTrack(const proto::OwnedTrack& track); - /// Returns a concise, human-readable string summarizing the track, - /// including its SID and name. Useful for debugging and logging. + /// @return Concise human-readable string summarizing the track, + /// including its SID and name. Useful for debugging and + /// logging. std::string to_string() const; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/remote_data_track.h b/include/livekit/remote_data_track.h index 965893ca..28d2c67e 100644 --- a/include/livekit/remote_data_track.h +++ b/include/livekit/remote_data_track.h @@ -32,25 +32,29 @@ namespace proto { class OwnedRemoteDataTrack; } -/** - * Represents a data track published by a remote participant. - * - * Discovered via the DataTrackPublishedEvent room event. Unlike - * audio/video tracks, remote data tracks require an explicit subscribe() - * call to begin receiving frames. - * - * Typical usage: - * - * // In RoomDelegate::onDataTrackPublished callback: - * auto sub_result = remoteDataTrack->subscribe(); - * if (sub_result) { - * auto sub = sub_result.value(); - * DataTrackFrame frame; - * while (sub->read(frame)) { - * // process frame - * } - * } - */ +/// A data track published by a remote participant. +/// +/// Discovered via the `DataTrackPublishedEvent` room event. Unlike +/// audio/video tracks, remote data tracks require an explicit +/// `subscribe()` call to begin receiving frames. +/// +/// Typical usage: +/// ``` +/// // In RoomDelegate::onDataTrackPublished callback: +/// auto sub_result = remoteDataTrack->subscribe(); +/// if (sub_result) { +/// auto sub = sub_result.value(); +/// DataTrackFrame frame; +/// while (sub->read(frame)) { +/// // process frame +/// } +/// } +/// ``` +/// +/// @note Thread-safety: Not thread-safe. Mutating operations on a single +/// `RemoteDataTrack` instance must be externally serialized. +/// `DataTrackStream` instances returned by `subscribe()` are themselves +/// thread-safe (see `DataTrackStream`). class RemoteDataTrack { public: ~RemoteDataTrack() = default; @@ -58,26 +62,38 @@ class RemoteDataTrack { RemoteDataTrack(const RemoteDataTrack&) = delete; RemoteDataTrack& operator=(const RemoteDataTrack&) = delete; - /// Metadata about this data track. + /// @return Metadata snapshot taken at construction time. const DataTrackInfo& info() const noexcept { return info_; } - /// Identity of the remote participant who published this track. + /// @return Identity of the remote participant who published this + /// track. const std::string& publisherIdentity() const noexcept { return publisher_identity_; } - /// Whether the track is still published by the remote participant. + /// @return `true` if the track is still published by the remote + /// participant. + /// + /// @note Thread-safety: Not thread-safe. LIVEKIT_API bool isPublished() const; #ifdef LIVEKIT_TEST_ACCESS - /// Test-only accessor for exercising lower-level FFI subscription paths. + /// Test-only accessor for exercising lower-level FFI subscription + /// paths. + /// + /// @return The raw FFI handle id. uintptr_t testFfiHandleId() const noexcept { return ffi_handle_id(); } #endif - /** - * Subscribe to this remote data track. - * - * Returns a DataTrackStream that delivers frames via blocking - * read(). Destroy the stream to unsubscribe. - */ + /// Subscribe to this remote data track. + /// + /// @param options Subscription options (queue size, etc.). + /// @return A `DataTrackStream` that delivers frames via + /// blocking `read()`, or a typed + /// `SubscribeDataTrackError` describing why + /// subscription failed. Destroy the stream to + /// unsubscribe. + /// + /// @note Thread-safety: Not thread-safe. Must be externally + /// synchronized with other mutating operations on this track. LIVEKIT_API Result, SubscribeDataTrackError> subscribe( const DataTrackStream::Options& options = {}); @@ -87,13 +103,15 @@ class RemoteDataTrack { explicit RemoteDataTrack(const proto::OwnedRemoteDataTrack& owned); uintptr_t ffi_handle_id() const noexcept { return handle_.get(); } - /** RAII wrapper for the Rust-owned FFI resource. */ + + /// RAII wrapper for the Rust-owned FFI resource. FfiHandle handle_; - /** Metadata snapshot taken at construction time. */ + /// Metadata snapshot taken at construction time. DataTrackInfo info_; - /** Identity string of the remote participant who published this track. */ + /// Identity string of the remote participant who published this + /// track. std::string publisher_identity_; }; diff --git a/include/livekit/remote_participant.h b/include/livekit/remote_participant.h index 67f5e9a4..a6a16988 100644 --- a/include/livekit/remote_participant.h +++ b/include/livekit/remote_participant.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -27,25 +27,57 @@ namespace livekit { class RemoteTrackPublication; +/// A participant other than the local user, observed by the SDK. +/// +/// Instances are owned by `Room` and surfaced to the application via +/// `Room::remoteParticipant()` and `RoomDelegate` callbacks. +/// +/// @note Thread-safety: Not thread-safe. Inherits the same constraints +/// as `Participant`. Read-only getters may be called from arbitrary +/// threads only when no `Room` event handler is currently mutating the +/// participant. class LIVEKIT_API RemoteParticipant : public Participant { public: + /// Map of remote track publications keyed by publication SID. using PublicationMap = std::unordered_map>; + /// Construct a `RemoteParticipant` with FFI-supplied metadata. + /// + /// @param handle FFI handle owning the participant. + /// @param sid SFU-assigned SID. + /// @param name Display name. + /// @param identity Identity string used by the application. + /// @param metadata Application-defined metadata. + /// @param attributes Key/value attributes. + /// @param kind Participant kind. + /// @param reason Initial disconnect reason. RemoteParticipant(FfiHandle handle, std::string sid, std::string name, std::string identity, std::string metadata, std::unordered_map attributes, ParticipantKind kind, DisconnectReason reason); - // A dictionary of track publications associated with the participant. + /// @return Track publications associated with this participant. const PublicationMap& trackPublications() const noexcept { return track_publications_; } - // Optional: non-const access if you want to mutate in-place. + /// @return Mutable track publications map (intended for SDK + /// internals). + /// + /// @note Thread-safety: Not thread-safe. Intended for `Room` while + /// processing FFI events. PublicationMap& mutableTrackPublications() noexcept { return track_publications_; } + /// @return A concise, human-readable string summarizing the + /// participant (identity, SID, name). Useful for debugging + /// and logging. std::string to_string() const; protected: - // Called by Room events like kTrackMuted. This is internal plumbing and not - // intended to be called directly by SDK users. + /// Find a track publication by SID. + /// + /// Called by `Room` events like `kTrackMuted`. This is internal + /// plumbing and not intended to be called directly by SDK users. + /// + /// @param sid Publication SID to look up. + /// @return The publication, or `nullptr` if not found. std::shared_ptr findTrackPublication(const std::string& sid) const override; friend class Room; @@ -53,7 +85,15 @@ class LIVEKIT_API RemoteParticipant : public Participant { PublicationMap track_publications_; }; -// Convenience for logging / streaming +/// Stream a `RemoteParticipant`'s string representation to an output +/// stream. Convenience for logging. +/// +/// @param os Output stream to write to. +/// @param participant Participant to format. +/// @return A reference to `os` to allow chaining. +/// +/// @note Thread-safety: Not thread-safe. Concurrent mutation of +/// `participant` is undefined behavior. LIVEKIT_API std::ostream& operator<<(std::ostream& os, const RemoteParticipant& participant); } // namespace livekit diff --git a/include/livekit/remote_track_publication.h b/include/livekit/remote_track_publication.h index 2568a55d..2d888a19 100644 --- a/include/livekit/remote_track_publication.h +++ b/include/livekit/remote_track_publication.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -25,14 +25,36 @@ namespace proto { class OwnedTrackPublication; } +/// Publication record for a remotely-published track. +/// +/// `RemoteTrackPublication` instances are constructed by `Room` from +/// FFI events and are returned to the application via +/// `RemoteParticipant::trackPublications()` and +/// `RoomDelegate::onTrackPublished`. +/// +/// @note Thread-safety: Not thread-safe. Inherits the same constraints as +/// `TrackPublication`. class LIVEKIT_API RemoteTrackPublication : public TrackPublication { public: - /// Note, this RemoteTrackPublication is constructed internally only; - /// safe to accept proto::OwnedTrackPublication. + /// Construct a `RemoteTrackPublication` from an FFI-owned publication. + /// + /// Constructed internally only; safe to accept + /// `proto::OwnedTrackPublication`. + /// + /// @param owned Protobuf-encoded publication metadata received from + /// the FFI. explicit RemoteTrackPublication(const proto::OwnedTrackPublication& owned); + /// @return Whether the local client is currently subscribed to this + /// publication. bool subscribed() const noexcept { return subscribed_; } + /// Update the cached subscribed flag. + /// + /// @param subscribed `true` if the client is now subscribed. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void setSubscribed(bool subscribed); private: diff --git a/include/livekit/remote_video_track.h b/include/livekit/remote_video_track.h index 1df57220..93089c8c 100644 --- a/include/livekit/remote_video_track.h +++ b/include/livekit/remote_video_track.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -30,28 +30,32 @@ class OwnedTrack; class VideoSource; -/** - * Represents an video track published by a remote participant and - * subscribed to by the local participant. - * - * `RemoteVideoTrack` instances are created internally when the SDK receives a - * `kTrackSubscribed` event. Each instance is owned by its associated - * `RemoteParticipant` and delivered to the application via - * `TrackSubscribedEvent`. - * - * Applications generally interact with `RemoteVideoTrack` through events and - * `RemoteTrackPublication`, not through direct construction. - */ +/// A video track published by a remote participant and subscribed to by +/// the local participant. +/// +/// `RemoteVideoTrack` instances are created internally when the SDK +/// receives a `kTrackSubscribed` event. Each instance is owned by its +/// associated `RemoteParticipant` and delivered to the application via +/// `TrackSubscribedEvent`. Applications generally interact with +/// `RemoteVideoTrack` through events and `RemoteTrackPublication`, not +/// through direct construction. +/// +/// @note Thread-safety: Not thread-safe. Inherits the same constraints as +/// `Track`. class LIVEKIT_API RemoteVideoTrack : public Track { public: - /// Constructs a `RemoteVideoTrack` from an internal protocol-level + /// Construct a `RemoteVideoTrack` from an internal protocol-level /// `OwnedTrack` description provided by the signaling/FFI layer. + /// /// This constructor is intended for internal SDK use only. + /// + /// @param track Protobuf-encoded owned track description. explicit RemoteVideoTrack(const proto::OwnedTrack& track); - /// Returns a concise, human-readable string summarizing the track, - /// including its SID and name. Useful for debugging and logging. + /// @return Concise human-readable string summarizing the track, + /// including its SID and name. Useful for debugging and + /// logging. std::string to_string() const; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/result.h b/include/livekit/result.h index 5415ecaf..274e7096 100644 --- a/include/livekit/result.h +++ b/include/livekit/result.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -24,44 +24,61 @@ namespace livekit { -/** - * Lightweight success-or-error return type for non-exceptional API failures. - * - * This is intended for SDK operations where callers are expected to branch on - * success vs. failure, such as back-pressure or an unpublished track. - * - * `Result` stores either: - * - a success value of type `T`, or - * - an error value of type `E` - * - * Accessors validate their preconditions before returning. Calling `value()` - * on an error result, or `error()` on a success result, throws `std::logic_error`. - * Avoid this by checking `ok()` / `has_error()` / if (result) before calling `value()` or `error()`. - */ +/// Lightweight success-or-error return type for non-exceptional API failures. +/// +/// This is intended for SDK operations where callers are expected to branch +/// on success vs. failure, such as back-pressure or an unpublished track. +/// +/// `Result` stores either: +/// - a success value of type `T`, or +/// - an error value of type `E`. +/// +/// Accessors validate their preconditions before returning. Calling +/// `value()` on an error result, or `error()` on a success result, throws +/// `std::logic_error`. Avoid this by checking `ok()` / `has_error()` / +/// `if (result)` before calling `value()` or `error()`. +/// +/// @tparam T Success value type. +/// @tparam E Error value type. +/// +/// @note Thread-safety: Not thread-safe. A single `Result` instance must +/// not be mutated concurrently from multiple threads; concurrent +/// const-access is safe. template class [[nodiscard]] Result { public: /// Construct a successful result containing a value. + /// + /// @param value Success value to store. + /// @return A `Result` with `ok() == true`. template ::value>> static Result success(U&& value) { return Result(std::variant(std::in_place_index<0>, std::forward(value))); } /// Construct a failed result containing an error. + /// + /// @param error Error value to store. + /// @return A `Result` with `has_error() == true`. template ::value>> static Result failure(F&& error) { return Result(std::variant(std::in_place_index<1>, std::forward(error))); } - /// True when the result contains a success value. + /// @return `true` when the result contains a success value. bool ok() const noexcept { return storage_.index() == 0; } - /// True when the result contains an error. + + /// @return `true` when the result contains an error. bool has_error() const noexcept { return !ok(); } + /// Allows `if (result)` style success checks. + /// + /// @return Same as `ok()`. explicit operator bool() const noexcept { return ok(); } /// Access the success value. /// + /// @return Reference to the stored success value. /// @throws std::logic_error if `ok() == false`. T& value() & { if (!ok()) { @@ -72,6 +89,7 @@ class [[nodiscard]] Result { /// Access the success value. /// + /// @return Const reference to the stored success value. /// @throws std::logic_error if `ok() == false`. const T& value() const& { if (!ok()) { @@ -82,6 +100,7 @@ class [[nodiscard]] Result { /// Move the success value out. /// + /// @return Rvalue reference to the stored success value. /// @throws std::logic_error if `ok() == false`. T&& value() && { if (!ok()) { @@ -92,6 +111,7 @@ class [[nodiscard]] Result { /// Move the success value out. /// + /// @return Const rvalue reference to the stored success value. /// @throws std::logic_error if `ok() == false`. const T&& value() const&& { if (!ok()) { @@ -102,6 +122,7 @@ class [[nodiscard]] Result { /// Access the error value. /// + /// @return Reference to the stored error value. /// @throws std::logic_error if `has_error() == false`. E& error() & { if (!has_error()) { @@ -112,6 +133,7 @@ class [[nodiscard]] Result { /// Access the error value. /// + /// @return Const reference to the stored error value. /// @throws std::logic_error if `has_error() == false`. const E& error() const& { if (!has_error()) { @@ -122,6 +144,7 @@ class [[nodiscard]] Result { /// Move the error value out. /// + /// @return Rvalue reference to the stored error value. /// @throws std::logic_error if `has_error() == false`. E&& error() && { if (!has_error()) { @@ -132,6 +155,7 @@ class [[nodiscard]] Result { /// Move the error value out. /// + /// @return Const rvalue reference to the stored error value. /// @throws std::logic_error if `has_error() == false`. const E&& error() const&& { if (!has_error()) { @@ -146,32 +170,44 @@ class [[nodiscard]] Result { std::variant storage_; }; -/** - * `void` specialization for operations that only report success or failure. - * - * This keeps the same calling style as `Result` without forcing callers - * to invent a dummy success payload. - */ +/// `void` specialization for operations that only report success or failure. +/// +/// This keeps the same calling style as `Result` without forcing +/// callers to invent a dummy success payload. +/// +/// @tparam E Error value type. +/// +/// @note Thread-safety: Not thread-safe. Same constraints as the primary +/// template. template class [[nodiscard]] Result { public: /// Construct a successful result with no payload. + /// + /// @return A `Result` with `ok() == true`. static Result success() { return Result(std::nullopt); } /// Construct a failed result containing an error. + /// + /// @param error Error value to store. + /// @return A `Result` with `has_error() == true`. template ::value>> static Result failure(F&& error) { return Result(std::optional(std::forward(error))); } - /// True when the operation succeeded. + /// @return `true` when the operation succeeded. bool ok() const noexcept { return !error_.has_value(); } - /// True when the operation failed. + + /// @return `true` when the operation failed. bool has_error() const noexcept { return error_.has_value(); } + /// Allows `if (result)` style success checks. + /// + /// @return Same as `ok()`. explicit operator bool() const noexcept { return ok(); } - /// Validates success. Mirrors the `value()` API shape on the primary + /// Validate success. Mirrors the `value()` API shape on the primary /// template so generic code can use the same form for both. /// /// @throws std::logic_error if `ok() == false`. @@ -183,6 +219,7 @@ class [[nodiscard]] Result { /// Access the error value. /// + /// @return Reference to the stored error value. /// @throws std::logic_error if `has_error() == false`. E& error() & { if (!error_.has_value()) { @@ -193,6 +230,7 @@ class [[nodiscard]] Result { /// Access the error value. /// + /// @return Const reference to the stored error value. /// @throws std::logic_error if `has_error() == false`. const E& error() const& { if (!error_.has_value()) { @@ -203,6 +241,7 @@ class [[nodiscard]] Result { /// Move the error value out. /// + /// @return Rvalue reference to the stored error value. /// @throws std::logic_error if `has_error() == false`. E&& error() && { if (!error_.has_value()) { @@ -213,6 +252,7 @@ class [[nodiscard]] Result { /// Move the error value out. /// + /// @return Const rvalue reference to the stored error value. /// @throws std::logic_error if `has_error() == false`. const E&& error() const&& { if (!error_.has_value()) { diff --git a/include/livekit/room.h b/include/livekit/room.h index 46fed58a..129f39f1 100644 --- a/include/livekit/room.h +++ b/include/livekit/room.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -40,265 +40,408 @@ class E2EEManager; class LocalParticipant; class RemoteParticipant; -// Represents a single ICE server configuration. +/// A single ICE server configuration. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct IceServer { - // TURN/STUN server URL (e.g. "stun:stun.l.google.com:19302"). + /// TURN/STUN server URL (e.g. `"stun:stun.l.google.com:19302"`). std::string url; - // Optional username for TURN authentication. + /// Optional username for TURN authentication. std::string username; - // Optional credential (password) for TURN authentication. + /// Optional credential (password) for TURN authentication. std::string credential; }; -// WebRTC configuration (ICE, transport, etc.). +/// WebRTC configuration (ICE, transport, etc.). +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct RtcConfig { - // ICE transport type (e.g., ALL, RELAY). Maps to proto::IceTransportType. + /// ICE transport type (e.g., `ALL`, `RELAY`). Maps to + /// `proto::IceTransportType`. int ice_transport_type = 0; - // Continuous or single ICE gathering. Maps to - // proto::ContinualGatheringPolicy. + /// Continuous or single ICE gathering. Maps to + /// `proto::ContinualGatheringPolicy`. int continual_gathering_policy = 0; - // List of STUN/TURN servers for ICE candidate generation. + /// List of STUN/TURN servers for ICE candidate generation. std::vector ice_servers; }; -// Top-level room connection options. +/// Top-level room connection options. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct RoomOptions { - // If true (default), automatically subscribe to all remote tracks. - // This is CRITICAL. Without auto_subscribe, you will never receive: - // - `track_subscribed` events - // - remote audio/video frames + /// If `true` (default), automatically subscribe to all remote tracks. + /// + /// This is CRITICAL. Without `auto_subscribe`, you will never + /// receive: + /// - `track_subscribed` events + /// - remote audio/video frames bool auto_subscribe = true; - // Enable dynacast (server sends optimal layers depending on subscribers). + /// Enable dynacast (server sends optimal layers depending on + /// subscribers). bool dynacast = false; - // Enable single peer connection mode. When true, uses one RTCPeerConnection - // for both publishing and subscribing instead of two separate connections. - // Falls back to dual peer connection if the server doesn't support single PC. + /// Enable single peer connection mode. When `true`, uses one + /// `RTCPeerConnection` for both publishing and subscribing instead + /// of two separate connections. Falls back to dual peer connection + /// if the server doesn't support single PC. bool single_peer_connection = true; - // Optional WebRTC configuration (ICE policy, servers, etc.) + /// Optional WebRTC configuration (ICE policy, servers, etc.). std::optional rtc_config; - // Optional end-to-end encryption settings. + /// Optional end-to-end encryption settings. std::optional encryption; }; /// Represents a LiveKit room session. -/// A Room manages: +/// +/// A `Room` manages: /// - the connection to the LiveKit server /// - participant list (local + remote) /// - track publications -/// - server events forwarded to a RoomDelegate +/// - server events forwarded to a `RoomDelegate` +/// +/// @note Thread-safety: Thread-safe. An internal `std::mutex` protects +/// all mutable state. `RoomDelegate` callbacks are invoked outside the +/// lock and run on the FFI callback thread. Public methods may be +/// called concurrently from multiple application threads. class LIVEKIT_API Room { public: + /// Construct an unconnected `Room`. Room(); + + /// Destroy the `Room` and release any active resources. + /// + /// Stops the subscription thread dispatcher, unregisters the FFI + /// listener, and closes the connection if any. ~Room(); - /* Assign a RoomDelegate that receives room lifecycle callbacks. - * - * The delegate must remain valid for the lifetime of the Room or until a - * different delegate is assigned. The Room does not take ownership. - * Typical usage: - * class MyDelegate : public RoomDelegate { ... }; - * MyDelegate del; - * Room room; - * room.setDelegate(&del); - */ + /// Assign a `RoomDelegate` that receives room lifecycle callbacks. + /// + /// The delegate must remain valid for the lifetime of the `Room` or + /// until a different delegate is assigned. The `Room` does not take + /// ownership. + /// + /// Typical usage: + /// ``` + /// class MyDelegate : public RoomDelegate { ... }; + /// MyDelegate del; + /// Room room; + /// room.setDelegate(&del); + /// ``` + /// + /// @param delegate Delegate pointer, or `nullptr` to clear. + /// + /// @note Thread-safety: Thread-safe. Serialized with concurrent + /// callers via the internal mutex. void setDelegate(RoomDelegate* delegate); - /* Connect to a LiveKit room using the given URL and token, applying the - * supplied connection options. - * - * Parameters: - * url — WebSocket URL of the LiveKit server. - * token — Access token for authentication. - * options — Connection options controlling auto-subscribe, - * dynacast, E2EE, and WebRTC configuration. - * Behavior: - * - Registers an FFI event listener *before* sending the connect request. - * - Sends a proto::FfiRequest::Connect with the URL, token, - * and the provided RoomOptions. - * - Blocks until the FFI connect response arrives. - * - Initializes local participant and remote participants. - * - Emits room/participant/track events to the delegate. - * IMPORTANT: - * RoomOptions defaults auto_subscribe = true. - * Without auto_subscribe enabled, remote tracks will NOT be subscribed - * automatically, and no remote audio/video will ever arrive. - */ + /// Connect to a LiveKit room using the given URL and token, applying + /// the supplied connection options. + /// + /// Behavior: + /// - Registers an FFI event listener *before* sending the connect + /// request. + /// - Sends a `proto::FfiRequest::Connect` with the URL, token, and + /// the provided `RoomOptions`. + /// - Blocks until the FFI connect response arrives. + /// - Initializes local participant and remote participants. + /// - Emits room/participant/track events to the delegate. + /// + /// IMPORTANT: `RoomOptions` defaults `auto_subscribe = true`. Without + /// `auto_subscribe` enabled, remote tracks will NOT be subscribed + /// automatically, and no remote audio/video will ever arrive. + /// + /// @param url WebSocket URL of the LiveKit server. + /// @param token Access token for authentication. + /// @param options Connection options controlling auto-subscribe, + /// dynacast, E2EE, and WebRTC configuration. + /// @return `true` on success, `false` on failure. + /// + /// @note Thread-safety: Thread-safe. Blocks the calling thread until + /// the FFI connect response arrives. bool Connect(const std::string& url, const std::string& token, const RoomOptions& options); - // Accessors - - /* Retrieve static metadata about the room. - * This contains fields such as: - * - SID - * - room name - * - metadata - * - participant counts - * - creation timestamp - */ + /// Retrieve static metadata about the room. + /// + /// @return A snapshot containing fields such as SID, room name, + /// metadata, participant counts, and creation timestamp. + /// + /// @note Thread-safety: Thread-safe. RoomInfoData room_info() const; - /* Get the local participant. - * - * This object represents the current user, including: - * - published tracks (audio/video/screen) - * - identity, SID, metadata - * - publishing/unpublishing operations - * Return value: - * Non-null pointer after successful Connect(). - */ + /// Get the local participant. + /// + /// This object represents the current user, including: + /// - published tracks (audio/video/screen) + /// - identity, SID, metadata + /// - publishing/unpublishing operations + /// + /// @return Non-null pointer after successful `Connect()`. Returns + /// `nullptr` if not yet connected. + /// + /// @note Thread-safety: Thread-safe. LocalParticipant* localParticipant() const; - /* Look up a remote participant by identity. - * - * Parameters: - * identity — The participant’s identity string (not SID) - * Return value: - * Pointer to RemoteParticipant if present, otherwise nullptr. - * RemoteParticipant contains: - * - identity/name/metadata - * - track publications - * - callbacks for track subscribed/unsubscribed, muted/unmuted - */ + /// Look up a remote participant by identity. + /// + /// `RemoteParticipant` contains: + /// - identity/name/metadata + /// - track publications + /// - callbacks for track subscribed/unsubscribed, muted/unmuted + /// + /// @param identity The participant's identity string (not SID). + /// @return Pointer to `RemoteParticipant` if present, else + /// `nullptr`. + /// + /// @note Thread-safety: Thread-safe. RemoteParticipant* remoteParticipant(const std::string& identity) const; - /// Returns a snapshot of all current remote participants. + /// @return A snapshot of all current remote participants. + /// + /// @note Thread-safety: Thread-safe. std::vector> remoteParticipants() const; - /// Returns the current connection state of the room. + /// @return The current connection state of the room. + /// + /// @note Thread-safety: Thread-safe. 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, - * the handler is invoked with: - * - a shared_ptr for consuming the stream - * - the identity of the participant who sent the stream - * - * Notes: - * - Only one handler may be registered per topic. - * - If no handler is registered for a topic, incoming streams with that - * topic are ignored. - * - The handler is invoked on the Room event thread. The handler must - * not block; spawn a background thread if synchronous reading is - * required. - * - * Throws: - * std::runtime_error if a handler is already registered for the topic. - */ + /// Register a handler for incoming text streams on a specific topic. + /// + /// When a remote participant opens a text stream with the given + /// topic, the handler is invoked with: + /// - a `shared_ptr` for consuming the stream + /// - the identity of the participant who sent the stream + /// + /// Notes: + /// - Only one handler may be registered per topic. + /// - If no handler is registered for a topic, incoming streams with + /// that topic are ignored. + /// - The handler is invoked on the Room event thread. The handler + /// must not block; spawn a background thread if synchronous + /// reading is required. + /// + /// @param topic Topic to subscribe to. + /// @param handler Handler invoked when a stream with this topic + /// opens. + /// + /// @throws std::runtime_error if a handler is already registered for + /// the topic. + /// + /// @note Thread-safety: Thread-safe. void registerTextStreamHandler(const std::string& topic, TextStreamHandler handler); - /* Unregister the text stream handler for the given topic. - * - * If no handler exists for the topic, this function is a no-op. - */ + /// Unregister the text stream handler for the given topic. + /// + /// If no handler exists for the topic, this function is a no-op. + /// + /// @param topic Topic to unregister. + /// + /// @note Thread-safety: Thread-safe. void unregisterTextStreamHandler(const std::string& topic); - /* Register a handler for incoming byte streams on a specific topic. - * - * When a remote participant opens a byte stream with the given topic, - * the handler is invoked with: - * - a shared_ptr for consuming the stream - * - the identity of the participant who sent the stream - * - * Notes: - * - Only one handler may be registered per topic. - * - If no handler is registered for a topic, incoming streams with that - * topic are ignored. - * - The ByteStreamReader remains valid as long as the shared_ptr is held, - * preventing lifetime-related crashes when reading asynchronously. - * - * Throws: - * std::runtime_error if a handler is already registered for the topic. - */ + /// Register a handler for incoming byte streams on a specific topic. + /// + /// When a remote participant opens a byte stream with the given + /// topic, the handler is invoked with: + /// - a `shared_ptr` for consuming the stream + /// - the identity of the participant who sent the stream + /// + /// Notes: + /// - Only one handler may be registered per topic. + /// - If no handler is registered for a topic, incoming streams with + /// that topic are ignored. + /// - The `ByteStreamReader` remains valid as long as the + /// `shared_ptr` is held, preventing lifetime-related crashes when + /// reading asynchronously. + /// + /// @param topic Topic to subscribe to. + /// @param handler Handler invoked when a stream with this topic + /// opens. + /// + /// @throws std::runtime_error if a handler is already registered for + /// the topic. + /// + /// @note Thread-safety: Thread-safe. void registerByteStreamHandler(const std::string& topic, ByteStreamHandler handler); - /* Unregister the byte stream handler for the given topic. - * - * If no handler exists for the topic, this function is a no-op. - */ + /// Unregister the byte stream handler for the given topic. + /// + /// If no handler exists for the topic, this function is a no-op. + /// + /// @param topic Topic to unregister. + /// + /// @note Thread-safety: Thread-safe. void unregisterByteStreamHandler(const std::string& topic); - /** - * Returns the room's E2EE manager, or nullptr if E2EE was not enabled at - * connect time. - * - * Notes: - * - The manager is created after a successful Connect(). - * - If E2EE was not configured in RoomOptions, this will return nullptr. - */ + /// @return The room's E2EE manager, or `nullptr` if E2EE was not + /// enabled at connect time. + /// + /// Notes: + /// - The manager is created after a successful `Connect()`. + /// - If E2EE was not configured in `RoomOptions`, this will return + /// `nullptr`. + /// + /// @note Thread-safety: Thread-safe. E2EEManager* e2eeManager() const; // --------------------------------------------------------------- // Frame callbacks // --------------------------------------------------------------- - /** - * @brief Sets the audio frame callback via SubscriptionThreadDispatcher. - */ + /// Set the audio frame callback for a remote subscription, keyed by + /// `TrackSource`. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source to match. + /// @param callback Audio frame callback. Invoked on a + /// dedicated reader thread. + /// @param opts `AudioStream` options used when + /// creating the backing stream. + /// + /// @note Thread-safety: Thread-safe. void setOnAudioFrameCallback(const std::string& participant_identity, TrackSource source, AudioFrameCallback callback, const AudioStream::Options& opts = {}); - /** - * @brief Sets the audio frame callback via SubscriptionThreadDispatcher. - */ + /// Set the audio frame callback for a remote subscription, keyed by + /// track name. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to match. + /// @param callback Audio frame callback. Invoked on a + /// dedicated reader thread. + /// @param opts `AudioStream` options used when + /// creating the backing stream. + /// + /// @note Thread-safety: Thread-safe. void setOnAudioFrameCallback(const std::string& participant_identity, const std::string& track_name, AudioFrameCallback callback, const AudioStream::Options& opts = {}); - /** - * @brief Sets the video frame callback via SubscriptionThreadDispatcher. - */ + /// Set the video frame callback for a remote subscription, keyed by + /// `TrackSource`. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source to match. + /// @param callback Video frame callback. Invoked on a + /// dedicated reader thread. + /// @param opts `VideoStream` options used when + /// creating the backing stream. + /// + /// @note Thread-safety: Thread-safe. void setOnVideoFrameCallback(const std::string& participant_identity, TrackSource source, VideoFrameCallback callback, const VideoStream::Options& opts = {}); - /** - * @brief Sets the video frame callback via SubscriptionThreadDispatcher. - */ + /// Set the video frame callback for a remote subscription, keyed by + /// track name. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to match. + /// @param callback Video frame callback. Invoked on a + /// dedicated reader thread. + /// @param opts `VideoStream` options used when + /// creating the backing stream. + /// + /// @note Thread-safety: Thread-safe. void setOnVideoFrameCallback(const std::string& participant_identity, const std::string& track_name, VideoFrameCallback callback, const VideoStream::Options& opts = {}); - /** - * @brief Sets the video frame event callback via - * SubscriptionThreadDispatcher. - */ + /// Set the video frame event callback for a remote subscription + /// (includes optional packet-trailer metadata). + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to match. + /// @param callback Video frame event callback. Invoked + /// on a dedicated reader thread. + /// @param opts `VideoStream` options used when + /// creating the backing stream. + /// + /// @note Thread-safety: Thread-safe. void setOnVideoFrameEventCallback(const std::string& participant_identity, const std::string& track_name, VideoFrameEventCallback callback, const VideoStream::Options& opts = {}); - /** - * @brief Clears the audio frame callback via SubscriptionThreadDispatcher. - */ + /// Clear the audio frame callback for a remote subscription, keyed + /// by `TrackSource`. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source to clear. + /// + /// @note Thread-safety: Thread-safe. void clearOnAudioFrameCallback(const std::string& participant_identity, TrackSource source); - /** - * @brief Clears the audio frame callback via SubscriptionThreadDispatcher. - */ + + /// Clear the audio frame callback for a remote subscription, keyed + /// by track name. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to clear. + /// + /// @note Thread-safety: Thread-safe. void clearOnAudioFrameCallback(const std::string& participant_identity, const std::string& track_name); - /** - * @brief Clears the video frame callback via SubscriptionThreadDispatcher. - */ + /// Clear the video frame callback for a remote subscription, keyed + /// by `TrackSource`. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source to clear. + /// + /// @note Thread-safety: Thread-safe. void clearOnVideoFrameCallback(const std::string& participant_identity, TrackSource source); - /** - * @brief Clears the video frame callback via SubscriptionThreadDispatcher. - */ + /// Clear the video frame callback for a remote subscription, keyed + /// by track name. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to clear. + /// + /// @note Thread-safety: Thread-safe. void clearOnVideoFrameCallback(const std::string& participant_identity, const std::string& track_name); - /** - * @brief Adds a data frame callback via SubscriptionThreadDispatcher. - */ + /// Add a data frame callback for a remote data track. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Name of the remote data track. + /// @param callback Data frame callback. Invoked on a + /// dedicated reader thread. + /// @return Opaque id used to remove the + /// callback later. + /// + /// @note Thread-safety: Thread-safe. DataFrameCallbackId addOnDataFrameCallback(const std::string& participant_identity, const std::string& track_name, DataFrameCallback callback); - /** - * @brief Removes the data frame callback via SubscriptionThreadDispatcher. - */ + /// Remove a previously-registered data frame callback. + /// + /// Delegates to `SubscriptionThreadDispatcher`. + /// + /// @param id Identifier returned by `addOnDataFrameCallback()`. + /// + /// @note Thread-safety: Thread-safe. void removeOnDataFrameCallback(DataFrameCallbackId id); private: @@ -311,12 +454,10 @@ class LIVEKIT_API Room { std::shared_ptr room_handle_; std::unique_ptr local_participant_; std::unordered_map> remote_participants_; - // Data stream std::unordered_map text_stream_handlers_; std::unordered_map byte_stream_handlers_; std::unordered_map> text_stream_readers_; std::unordered_map> byte_stream_readers_; - // E2EE std::unique_ptr e2ee_manager_; std::shared_ptr subscription_thread_dispatcher_; diff --git a/include/livekit/room_delegate.h b/include/livekit/room_delegate.h index 79c484aa..cdef556e 100644 --- a/include/livekit/room_delegate.h +++ b/include/livekit/room_delegate.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -23,15 +23,20 @@ namespace livekit { class Room; -/** - * Interface for receiving room-level events. - * - * Implement this class and pass an instance to Room::setDelegate() - * to be notified about participants, tracks, data, and connection changes. - * - * All methods provide default no-op implementations so you can override - * only the callbacks you care about. - */ +/// Interface for receiving room-level events. +/// +/// Implement this class and pass an instance to `Room::setDelegate()` to +/// be notified about participants, tracks, data, and connection +/// changes. All methods provide default no-op implementations so you +/// can override only the callbacks you care about. +/// +/// @note Thread-safety: All `RoomDelegate` methods are invoked on the +/// FFI callback thread (the Rust-managed Tokio runtime thread that +/// delivers events to C++). Implementations MUST NOT block; spawn a +/// background thread for any synchronous work. Implementations are +/// called sequentially (never concurrently with themselves), but they +/// may run concurrently with application threads making other SDK +/// calls. class LIVEKIT_API RoomDelegate { public: virtual ~RoomDelegate() = default; @@ -40,257 +45,422 @@ class LIVEKIT_API RoomDelegate { // Participant lifecycle // ------------------------------------------------------------------ - /** - * Called when a new remote participant joins the room. - */ - virtual void onParticipantConnected(Room&, const ParticipantConnectedEvent&) {} - - /** - * Called when a remote participant leaves the room. - */ - virtual void onParticipantDisconnected(Room&, const ParticipantDisconnectedEvent&) {} + /// Called when a new remote participant joins the room. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the new participant. + virtual void onParticipantConnected(Room& room, const ParticipantConnectedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a remote participant leaves the room. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the departed participant. + virtual void onParticipantDisconnected(Room& room, const ParticipantDisconnectedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Local track publication events // ------------------------------------------------------------------ - /** - * Called when a local track is successfully published. - */ - virtual void onLocalTrackPublished(Room&, const LocalTrackPublishedEvent&) {} - - /** - * Called when a local track is unpublished. - */ - virtual void onLocalTrackUnpublished(Room&, const LocalTrackUnpublishedEvent&) {} - - /** - * Called when a local track gains its first subscriber. - */ - virtual void onLocalTrackSubscribed(Room&, const LocalTrackSubscribedEvent&) {} + /// Called when a local track is successfully published. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the published local track. + virtual void onLocalTrackPublished(Room& room, const LocalTrackPublishedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a local track is unpublished. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the unpublished local + /// track. + virtual void onLocalTrackUnpublished(Room& room, const LocalTrackUnpublishedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a local track gains its first subscriber. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the subscribed local track. + virtual void onLocalTrackSubscribed(Room& room, const LocalTrackSubscribedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Remote track publication/subscription // ------------------------------------------------------------------ - /** - * Called when a remote participant publishes a track. - */ - virtual void onTrackPublished(Room&, const TrackPublishedEvent&) {} - - /** - * Called when a remote participant unpublishes a track. - */ - virtual void onTrackUnpublished(Room&, const TrackUnpublishedEvent&) {} - - /** - * Called when a remote track is successfully subscribed. - */ - virtual void onTrackSubscribed(Room&, const TrackSubscribedEvent&) {} - - /** - * Called when a remote track is unsubscribed. - */ - virtual void onTrackUnsubscribed(Room&, const TrackUnsubscribedEvent&) {} - - /** - * Called when subscribing to a remote track fails. - */ - virtual void onTrackSubscriptionFailed(Room&, const TrackSubscriptionFailedEvent&) {} - - /** - * Called when a track is muted. - */ - virtual void onTrackMuted(Room&, const TrackMutedEvent&) {} - - /** - * Called when a track is unmuted. - */ - virtual void onTrackUnmuted(Room&, const TrackUnmutedEvent&) {} + /// Called when a remote participant publishes a track. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the published remote + /// track. + virtual void onTrackPublished(Room& room, const TrackPublishedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a remote participant unpublishes a track. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the unpublished remote + /// track. + virtual void onTrackUnpublished(Room& room, const TrackUnpublishedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a remote track is successfully subscribed. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the subscribed remote + /// track. + virtual void onTrackSubscribed(Room& room, const TrackSubscribedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a remote track is unsubscribed. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the unsubscribed remote + /// track. + virtual void onTrackUnsubscribed(Room& room, const TrackUnsubscribedEvent& event) { + (void)room; + (void)event; + } + + /// Called when subscribing to a remote track fails. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the failed subscription. + virtual void onTrackSubscriptionFailed(Room& room, const TrackSubscriptionFailedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a track is muted. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the muted track. + virtual void onTrackMuted(Room& room, const TrackMutedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a track is unmuted. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the unmuted track. + virtual void onTrackUnmuted(Room& room, const TrackUnmutedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Active speakers // ------------------------------------------------------------------ - /** - * Called when the list of active speakers changes. - */ - virtual void onActiveSpeakersChanged(Room&, const ActiveSpeakersChangedEvent&) {} + /// Called when the list of active speakers changes. + /// + /// @param room The room that fired the event. + /// @param event Event payload listing the current active speakers. + virtual void onActiveSpeakersChanged(Room& room, const ActiveSpeakersChangedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Room info / metadata // ------------------------------------------------------------------ - /** - * Called when the room's metadata changes. - */ - virtual void onRoomMetadataChanged(Room&, const RoomMetadataChangedEvent&) {} - - /** - * Called when the room SID changes (e.g., after migration). - */ - virtual void onRoomSidChanged(Room&, const RoomSidChangedEvent&) {} - - /** - * Called when any room info is updated. - */ - virtual void onRoomUpdated(Room&, const RoomUpdatedEvent&) {} - - /** - * Called when the participant is moved to another room. - */ - virtual void onRoomMoved(Room&, const RoomMovedEvent&) {} + /// Called when the room's metadata changes. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing old and new metadata. + virtual void onRoomMetadataChanged(Room& room, const RoomMetadataChangedEvent& event) { + (void)room; + (void)event; + } + + /// Called when the room SID changes (e.g., after migration). + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the new SID. + virtual void onRoomSidChanged(Room& room, const RoomSidChangedEvent& event) { + (void)room; + (void)event; + } + + /// Called when any room info is updated. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the new room info. + virtual void onRoomUpdated(Room& room, const RoomUpdatedEvent& event) { + (void)room; + (void)event; + } + + /// Called when the participant is moved to another room. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the new room info. + virtual void onRoomMoved(Room& room, const RoomMovedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Participant info changes // ------------------------------------------------------------------ - /** - * Called when a participant's metadata is updated. - */ - virtual void onParticipantMetadataChanged(Room&, const ParticipantMetadataChangedEvent&) {} - - /** - * Called when a participant's name is changed. - */ - virtual void onParticipantNameChanged(Room&, const ParticipantNameChangedEvent&) {} - - /** - * Called when a participant's attributes are updated. - */ - virtual void onParticipantAttributesChanged(Room&, const ParticipantAttributesChangedEvent&) {} - - /** - * Called when a participant's encryption status changes. - */ - virtual void onParticipantEncryptionStatusChanged(Room&, const ParticipantEncryptionStatusChangedEvent&) {} + /// Called when a participant's metadata is updated. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing old and new metadata. + virtual void onParticipantMetadataChanged(Room& room, const ParticipantMetadataChangedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a participant's name is changed. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing old and new names. + virtual void onParticipantNameChanged(Room& room, const ParticipantNameChangedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a participant's attributes are updated. + /// + /// @param room The room that fired the event. + /// @param event Event payload listing the attributes that changed. + virtual void onParticipantAttributesChanged(Room& room, const ParticipantAttributesChangedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a participant's encryption status changes. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the new encryption status. + virtual void onParticipantEncryptionStatusChanged(Room& room, const ParticipantEncryptionStatusChangedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Connection quality / state // ------------------------------------------------------------------ - /** - * Called when a participant's connection quality changes. - */ - virtual void onConnectionQualityChanged(Room&, const ConnectionQualityChangedEvent&) {} - - /** - * Called when the room's connection state changes. - */ - virtual void onConnectionStateChanged(Room&, const ConnectionStateChangedEvent&) {} - - /** - * Called when the room is disconnected. - */ - virtual void onDisconnected(Room&, const DisconnectedEvent&) {} - - /** - * Called before the SDK attempts to reconnect. - */ - virtual void onReconnecting(Room&, const ReconnectingEvent&) {} - - /** - * Called after the SDK successfully reconnects. - */ - virtual void onReconnected(Room&, const ReconnectedEvent&) {} + /// Called when a participant's connection quality changes. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the new connection quality. + virtual void onConnectionQualityChanged(Room& room, const ConnectionQualityChangedEvent& event) { + (void)room; + (void)event; + } + + /// Called when the room's connection state changes. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the new connection state. + virtual void onConnectionStateChanged(Room& room, const ConnectionStateChangedEvent& event) { + (void)room; + (void)event; + } + + /// Called when the room is disconnected. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the disconnect reason. + virtual void onDisconnected(Room& room, const DisconnectedEvent& event) { + (void)room; + (void)event; + } + + /// Called before the SDK attempts to reconnect. + /// + /// @param room The room that fired the event. + /// @param event Empty marker payload. + virtual void onReconnecting(Room& room, const ReconnectingEvent& event) { + (void)room; + (void)event; + } + + /// Called after the SDK successfully reconnects. + /// + /// @param room The room that fired the event. + /// @param event Empty marker payload. + virtual void onReconnected(Room& room, const ReconnectedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // E2EE // ------------------------------------------------------------------ - /** - * Called when a participant's end-to-end encryption state changes. - */ - virtual void onE2eeStateChanged(Room&, const E2eeStateChangedEvent&) {} + /// Called when a participant's end-to-end encryption state changes. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the new encryption state. + virtual void onE2eeStateChanged(Room& room, const E2eeStateChangedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // EOS // ------------------------------------------------------------------ - /** - * Called when the room reaches end-of-stream and will not emit further - * events. - */ - virtual void onRoomEos(Room&, const RoomEosEvent&) {} + /// Called when the room reaches end-of-stream and will not emit + /// further events. + /// + /// @param room The room that fired the event. + /// @param event Empty marker payload. + virtual void onRoomEos(Room& room, const RoomEosEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Data / transcription / chat // ------------------------------------------------------------------ - /** - * Called when a user data packet (non-SIP) is received. - */ - virtual void onUserPacketReceived(Room&, const UserDataPacketEvent&) {} - - /** - * Called when a SIP DTMF packet is received. - */ - virtual void onSipDtmfReceived(Room&, const SipDtmfReceivedEvent&) {} + /// Called when a user data packet (non-SIP) is received. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the received packet. + virtual void onUserPacketReceived(Room& room, const UserDataPacketEvent& event) { + (void)room; + (void)event; + } + + /// Called when a SIP DTMF packet is received. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the DTMF code and digit. + virtual void onSipDtmfReceived(Room& room, const SipDtmfReceivedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Data streams // ------------------------------------------------------------------ - /** - * Called when a data stream header is received. - */ - virtual void onDataStreamHeaderReceived(Room&, const DataStreamHeaderReceivedEvent&) {} - - /** - * Called when a data stream chunk is received. - */ - virtual void onDataStreamChunkReceived(Room&, const DataStreamChunkReceivedEvent&) {} - - /** - * Called when a data stream trailer is received. - */ - virtual void onDataStreamTrailerReceived(Room&, const DataStreamTrailerReceivedEvent&) {} - - /** - * Called when a data channel's buffered amount falls below its low threshold. - */ + /// Called when a data stream header is received. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the stream header. + virtual void onDataStreamHeaderReceived(Room& room, const DataStreamHeaderReceivedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a data stream chunk is received. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the chunk. + virtual void onDataStreamChunkReceived(Room& room, const DataStreamChunkReceivedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a data stream trailer is received. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the trailer. + virtual void onDataStreamTrailerReceived(Room& room, const DataStreamTrailerReceivedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a data channel's buffered amount falls below its low + /// threshold. + /// + /// @param room The room that fired the event. + /// @param event Event payload describing the channel and new + /// threshold. virtual void onDataChannelBufferedAmountLowThresholdChanged( - Room&, const DataChannelBufferedAmountLowThresholdChangedEvent&) {} + Room& room, const DataChannelBufferedAmountLowThresholdChangedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // High-level byte/text streams // ------------------------------------------------------------------ - /** - * Called when a high-level byte stream reader is opened. - */ - virtual void onByteStreamOpened(Room&, const ByteStreamOpenedEvent&) {} - - /** - * Called when a high-level text stream reader is opened. - */ - virtual void onTextStreamOpened(Room&, const TextStreamOpenedEvent&) {} + /// Called when a high-level byte stream reader is opened. + /// + /// @param room The room that fired the event. + /// @param event Event payload identifying the new reader. + virtual void onByteStreamOpened(Room& room, const ByteStreamOpenedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a high-level text stream reader is opened. + /// + /// @param room The room that fired the event. + /// @param event Event payload identifying the new reader. + virtual void onTextStreamOpened(Room& room, const TextStreamOpenedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Data tracks // ------------------------------------------------------------------ - /** - * Called when a remote participant publishes a data track. - * - * Data tracks are independent of the audio/video track hierarchy and - * require an explicit subscribe() call to start receiving frames. - */ - virtual void onDataTrackPublished(Room&, const DataTrackPublishedEvent&) {} - - /** - * Called when a remote participant unpublishes a data track. - */ - virtual void onDataTrackUnpublished(Room&, const DataTrackUnpublishedEvent&) {} + /// Called when a remote participant publishes a data track. + /// + /// Data tracks are independent of the audio/video track hierarchy + /// and require an explicit `subscribe()` call to start receiving + /// frames. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the new remote data track. + virtual void onDataTrackPublished(Room& room, const DataTrackPublishedEvent& event) { + (void)room; + (void)event; + } + + /// Called when a remote participant unpublishes a data track. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the SID of the removed + /// track. + virtual void onDataTrackUnpublished(Room& room, const DataTrackUnpublishedEvent& event) { + (void)room; + (void)event; + } // ------------------------------------------------------------------ // Participants snapshot // ------------------------------------------------------------------ - /** - * Called when a snapshot of participants has been updated. - */ - virtual void onParticipantsUpdated(Room&, const ParticipantsUpdatedEvent&) {} + /// Called when a snapshot of participants has been updated. + /// + /// @param room The room that fired the event. + /// @param event Event payload carrying the updated participants. + virtual void onParticipantsUpdated(Room& room, const ParticipantsUpdatedEvent& event) { + (void)room; + (void)event; + } }; } // namespace livekit diff --git a/include/livekit/room_event_types.h b/include/livekit/room_event_types.h index d30d925b..fe8c61ba 100644 --- a/include/livekit/room_event_types.h +++ b/include/livekit/room_event_types.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -37,9 +37,7 @@ class TrackPublication; enum class VideoCodec; enum class TrackSource; -/** - * Overall quality of a participant's connection. - */ +/// Overall quality of a participant's connection. enum class ConnectionQuality { Poor = 0, Good, @@ -47,31 +45,25 @@ enum class ConnectionQuality { Lost, }; -/** - * Current connection state of the room. - */ +/// Current connection state of the room. enum class ConnectionState { Disconnected = 0, Connected, Reconnecting, }; -/** - * Type of data packet delivery semantics. - * - * - Lossy: unordered, unreliable (e.g. for real-time updates). - * - Reliable: ordered, reliable (e.g. for critical messages). - */ +/// Type of data packet delivery semantics. +/// +/// - `Lossy`: unordered, unreliable (e.g. for real-time updates). +/// - `Reliable`: ordered, reliable (e.g. for critical messages). enum class DataPacketKind { Lossy, Reliable, }; -/** - * End-to-end encryption state for a participant. - * - * These values mirror the proto::EncryptionState enum. - */ +/// End-to-end encryption state for a participant. +/// +/// These values mirror the `proto::EncryptionState` enum. enum class EncryptionState { New = 0, Ok, @@ -82,11 +74,9 @@ enum class EncryptionState { InternalError, }; -/** - * Reason why a participant or room was disconnected. - * - * These values mirror the server-side DisconnectReason enum. - */ +/// Reason why a participant or room was disconnected. +/// +/// These values mirror the server-side `DisconnectReason` enum. enum class DisconnectReason { Unknown = 0, ClientInitiated, @@ -101,132 +91,137 @@ enum class DisconnectReason { RoomClosed, UserUnavailable, UserRejected, - SipTrunkFailure, ///< SIP (telephony) trunk connection failed + /// SIP (telephony) trunk connection failed. + SipTrunkFailure, ConnectionTimeout, MediaFailure }; -/** - * Application-level user data carried in a data packet. - */ +/// Application-level user data carried in a data packet. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct; concurrent +/// mutation is undefined behavior. struct UserPacketData { - /** Raw payload bytes. */ + /// Raw payload bytes. std::vector data; - /** Optional topic name associated with this payload. */ + /// Optional topic name associated with this payload. std::optional topic; }; -/** - * SIP (Session Initiation Protocol) DTMF payload carried via data packets. - * - * SIP is a signalling protocol used in VoIP telephony. LiveKit supports - * SIP trunking, which bridges traditional phone calls into LiveKit rooms. - * DTMF (Dual-Tone Multi-Frequency) tones are the signals generated when - * phone keypad buttons are pressed (0-9, *, #). This struct surfaces - * those tones so that applications handling SIP-bridged calls can react - * to caller input (e.g. IVR menu selection). - */ +/// SIP (Session Initiation Protocol) DTMF payload carried via data +/// packets. +/// +/// SIP is a signalling protocol used in VoIP telephony. LiveKit supports +/// SIP trunking, which bridges traditional phone calls into LiveKit +/// rooms. DTMF (Dual-Tone Multi-Frequency) tones are the signals +/// generated when phone keypad buttons are pressed (0-9, *, #). This +/// struct surfaces those tones so that applications handling SIP-bridged +/// calls can react to caller input (e.g. IVR menu selection). +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct SipDtmfData { - /** Numeric DTMF code (0-15, mapping to 0-9, *, #, A-D). */ + /// Numeric DTMF code (0-15, mapping to 0-9, *, #, A-D). std::uint32_t code = 0; - /** Human-readable digit representation (e.g. "1", "#"). */ + /// Human-readable digit representation (e.g. `"1"`, `"#"`). std::optional digit; }; -/** - * Snapshot of core room information. - */ +/// Snapshot of core room information. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct; consumers +/// typically receive an immutable snapshot from `Room::room_info()`. struct RoomInfoData { - /** Room SID, if known. */ + /// Room SID, if known. std::optional sid; - /** Room name. */ + /// Room name. std::string name; - /** Arbitrary application metadata associated with the room. */ + /// Arbitrary application metadata associated with the room. std::string metadata; - /** Low-watermark threshold for lossy data channel buffer. */ + /// Low-watermark threshold for lossy data channel buffer. std::uint64_t lossy_dc_buffered_amount_low_threshold = 0; - /** Low-watermark threshold for reliable data channel buffer. */ + /// Low-watermark threshold for reliable data channel buffer. std::uint64_t reliable_dc_buffered_amount_low_threshold = 0; - /** Time (seconds) to keep room open if no participants join. */ + /// Time (seconds) to keep room open if no participants join. std::uint32_t empty_timeout = 0; - /** Time (seconds) to keep room open after last standard participant leaves. - */ + /// Time (seconds) to keep room open after last standard participant + /// leaves. std::uint32_t departure_timeout = 0; - /** Maximum number of participants allowed in the room. */ + /// Maximum number of participants allowed in the room. std::uint32_t max_participants = 0; - /** Creation time of the room (ms since Unix epoch). */ + /// Creation time of the room (ms since Unix epoch). std::int64_t creation_time = 0; - /** Approximate number of participants (eventually consistent). */ + /// Approximate number of participants (eventually consistent). std::uint32_t num_participants = 0; - /** Approximate number of publishers (eventually consistent). */ + /// Approximate number of publishers (eventually consistent). std::uint32_t num_publishers = 0; - /** True if the room is currently being recorded. */ + /// True if the room is currently being recorded. bool active_recording = false; }; -/** - * Key/value pair for participant or room attributes. - */ +/// Key/value pair for participant or room attributes. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct AttributeEntry { - /** Attribute key. */ + /// Attribute key. std::string key; - /** Attribute value. */ + /// Attribute value. std::string value; AttributeEntry() = default; + /// Construct an `AttributeEntry` from key/value strings. + /// + /// @param k Attribute key. + /// @param v Attribute value. AttributeEntry(std::string k, std::string v) : key(std::move(k)), value(std::move(v)) {} }; -/** - * Header information for an incoming data stream. - * Represents proto_room.DataStream.Header in a C++-friendly form. - */ +/// Header information for an incoming data stream. +/// +/// Represents `proto_room.DataStream.Header` in a C++-friendly form. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct DataStreamHeaderData { - /** Unique stream identifier. */ + /// Unique stream identifier. std::string stream_id; - /** Timestamp (ms since Unix epoch). */ + /// Timestamp (ms since Unix epoch). std::int64_t timestamp = 0; - /** MIME type of the content (e.g. "application/json"). */ + /// MIME type of the content (e.g. `"application/json"`). std::string mime_type; - /** Application-defined topic name. */ + /// Application-defined topic name. std::string topic; - /** Optional total length in bytes, if known. */ + /// Optional total length in bytes, if known. std::optional total_length; - /** Custom attributes associated with this stream. */ + /// Custom attributes associated with this stream. std::map attributes; - /** - * Content type carried by this stream. - */ + /// Content type carried by this stream. enum class ContentType { None, Text, Byte, } content_type = ContentType::None; - /** - * Operation type for text streams. - */ + /// Operation type for text streams. enum class OperationType { Create = 0, Update = 1, @@ -234,121 +229,121 @@ struct DataStreamHeaderData { Reaction = 3, }; - /** Optional operation type, for text content. */ + /// Optional operation type, for text content. std::optional operation_type; - /** Optional version number for the text stream. */ + /// Optional version number for the text stream. std::optional version; - /** Optional ID of the stream this one replies to. */ + /// Optional ID of the stream this one replies to. std::optional reply_to_stream_id; - /** IDs of streams attached to this one. */ + /// IDs of streams attached to this one. std::vector attached_stream_ids; - /** True if this stream was generated (e.g. by AI). */ + /// True if this stream was generated (e.g. by AI). std::optional generated; - /** Optional filename for byte streams. */ + /// Optional filename for byte streams. std::optional name; }; -/** - * One chunk of a data stream’s payload. - */ +/// One chunk of a data stream's payload. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct DataStreamChunkData { - /** Stream identifier this chunk belongs to. */ + /// Stream identifier this chunk belongs to. std::string stream_id; - /** Zero-based index of this chunk. */ + /// Zero-based index of this chunk. std::uint64_t chunk_index = 0; - /** Raw chunk content. */ + /// Raw chunk content. std::vector content; - /** Optional version, mirroring header version if applicable. */ + /// Optional version, mirroring header version if applicable. std::optional version; - /** Optional initialization vector for encrypted payloads. */ + /// Optional initialization vector for encrypted payloads. std::vector iv; }; -/** - * Trailer metadata for a data stream, sent after all chunks. - */ +/// Trailer metadata for a data stream, sent after all chunks. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct DataStreamTrailerData { - /** Stream identifier. */ + /// Stream identifier. std::string stream_id; - /** Reason why the stream ended (empty if normal completion). */ + /// Reason why the stream ended (empty if normal completion). std::string reason; - /** Additional attributes describing the final state of the stream. */ + /// Additional attributes describing the final state of the stream. std::map attributes; }; -/** - * Video encoding configuration used when publishing a track. - */ +/// Video encoding configuration used when publishing a track. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct VideoEncodingOptions { - /** Maximum target bitrate in bps. */ + /// Maximum target bitrate in bps. std::uint64_t max_bitrate = 0; - /** Maximum frame rate in frames per second. */ + /// Maximum frame rate in frames per second. double max_framerate = 0.0; }; -/** - * Audio encoding configuration used when publishing a track. - */ +/// Audio encoding configuration used when publishing a track. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct AudioEncodingOptions { - /** Maximum target bitrate in bps. */ + /// Maximum target bitrate in bps. std::uint64_t max_bitrate = 0; }; -/** - * Optional RTP packet-trailer features for published video tracks. - */ +/// Optional RTP packet-trailer features for published video tracks. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct PacketTrailerFeatures { - /** Embed a user-supplied wall-clock timestamp. */ + /// Embed a user-supplied wall-clock timestamp. bool user_timestamp = false; - /** Embed a monotonically increasing frame identifier. */ + /// Embed a monotonically increasing frame identifier. bool frame_id = false; }; -/** - * Options for publishing a track to the room. - */ +/// Options for publishing a track to the room. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct TrackPublishOptions { - /** Optional video encoding parameters. */ + /// Optional video encoding parameters. std::optional video_encoding; - /** Optional audio encoding parameters. */ + /// Optional audio encoding parameters. std::optional audio_encoding; - /** Optional video codec to use. */ + /// Optional video codec to use. std::optional video_codec; - /** Enable or disable discontinuous transmission (DTX). */ + /// Enable or disable discontinuous transmission (DTX). std::optional dtx; - /** Enable or disable RED (redundant encoding). */ + /// Enable or disable RED (redundant encoding). std::optional red; - /** Enable or disable simulcast. */ + /// Enable or disable simulcast. std::optional simulcast; - /** Track source (camera, microphone, screen share, etc.). */ + /// Track source (camera, microphone, screen share, etc.). std::optional source; - /** Optional stream label/group for this track. */ + /// Optional stream label/group for this track. std::optional stream; - /** Enable pre-connect buffering for lower startup latency. */ + /// Enable pre-connect buffering for lower startup latency. std::optional preconnect_buffer; - /** Optional packet-trailer features to enable for published video. */ + /// Optional packet-trailer features to enable for published video. PacketTrailerFeatures packet_trailer_features{}; }; @@ -356,407 +351,331 @@ struct TrackPublishOptions { // Event structs – public representations of RoomEvent.* // --------------------------------------------------------- -/** - * Fired when a remote participant joins the room. - */ +/// Fired when a remote participant joins the room. struct ParticipantConnectedEvent { - /** The newly connected remote participant (owned by Room). */ + /// The newly connected remote participant (owned by `Room`). RemoteParticipant* participant = nullptr; }; -/** - * Fired when a remote participant leaves the room. - */ +/// Fired when a remote participant leaves the room. struct ParticipantDisconnectedEvent { - /** The participant that disconnected (owned by Room). */ + /// The participant that disconnected (owned by `Room`). RemoteParticipant* participant = nullptr; - /** Reason for the disconnect, if known. */ + /// Reason for the disconnect, if known. DisconnectReason reason = DisconnectReason::Unknown; }; -/** - * Fired when a local track is successfully published. - */ +/// Fired when a local track is successfully published. struct LocalTrackPublishedEvent { - /** Track publication for the local track. */ + /// Track publication for the local track. std::shared_ptr publication; - /** The published local track. */ + /// The published local track. std::shared_ptr track; }; -/** - * Fired when a local track is unpublished. - */ +/// Fired when a local track is unpublished. struct LocalTrackUnpublishedEvent { - /** Publication that was unpublished. */ + /// Publication that was unpublished. std::shared_ptr publication; }; -/** - * Fired when a local track gets its first subscriber. - */ +/// Fired when a local track gets its first subscriber. struct LocalTrackSubscribedEvent { - /** Subscribed local track. */ + /// Subscribed local track. std::shared_ptr track; }; -/** - * Fired when a remote participant publishes a track. - */ +/// Fired when a remote participant publishes a track. struct TrackPublishedEvent { - /** Remote track publication. */ + /// Remote track publication. std::shared_ptr publication; - /** Remote participant who owns this track (owned by Room). */ + /// Remote participant who owns this track (owned by `Room`). RemoteParticipant* participant = nullptr; }; -/** - * Fired when a remote participant unpublishes a track. - */ +/// Fired when a remote participant unpublishes a track. struct TrackUnpublishedEvent { - /** Remote track publication that was removed. */ + /// Remote track publication that was removed. std::shared_ptr publication; - /** Remote participant who owned this track (owned by Room). */ + /// Remote participant who owned this track (owned by `Room`). RemoteParticipant* participant = nullptr; }; -/** - * Fired when a remote track is successfully subscribed. - */ +/// Fired when a remote track is successfully subscribed. struct TrackSubscribedEvent { - /** Subscribed remote track. */ + /// Subscribed remote track. std::shared_ptr track; - /** Publication associated with the track. */ + /// Publication associated with the track. std::shared_ptr publication; - /** Remote participant who owns the track (owned by Room). */ + /// Remote participant who owns the track (owned by `Room`). RemoteParticipant* participant = nullptr; }; -/** - * Fired when a remote track is unsubscribed. - */ +/// Fired when a remote track is unsubscribed. struct TrackUnsubscribedEvent { - /** Track that was unsubscribed. */ + /// Track that was unsubscribed. std::shared_ptr track; - /** Publication associated with the track. */ + /// Publication associated with the track. std::shared_ptr publication; - /** Remote participant who owns the track (owned by Room). */ + /// Remote participant who owns the track (owned by `Room`). RemoteParticipant* participant = nullptr; }; -/** - * Fired when subscribing to a remote track fails. - */ +/// Fired when subscribing to a remote track fails. struct TrackSubscriptionFailedEvent { - /** Remote participant for which the subscription failed (owned by Room). */ + /// Remote participant for which the subscription failed (owned by + /// `Room`). RemoteParticipant* participant = nullptr; - /** SID of the track that failed to subscribe. */ + /// SID of the track that failed to subscribe. std::string track_sid; - /** Error message describing the failure. */ + /// Error message describing the failure. std::string error; }; -/** - * Fired when a track is muted. - */ +/// Fired when a track is muted. struct TrackMutedEvent { - /** Local or remote participant who owns the track (owned by Room). */ + /// Local or remote participant who owns the track (owned by `Room`). Participant* participant = nullptr; - /** Publication that was muted. */ + /// Publication that was muted. std::shared_ptr publication; }; -/** - * Fired when a track is unmuted. - */ +/// Fired when a track is unmuted. struct TrackUnmutedEvent { - /** Local or remote participant who owns the track (owned by Room). */ + /// Local or remote participant who owns the track (owned by `Room`). Participant* participant = nullptr; - /** Publication that was unmuted. */ + /// Publication that was unmuted. std::shared_ptr publication; }; -/** - * Fired when the list of active speakers changes. - */ +/// Fired when the list of active speakers changes. struct ActiveSpeakersChangedEvent { - /** Participants currently considered active speakers (owned by Room). */ + /// Participants currently considered active speakers (owned by `Room`). std::vector speakers; }; -/** - * Fired when room metadata is updated. - */ +/// Fired when room metadata is updated. struct RoomMetadataChangedEvent { - /** Previous metadata value. */ + /// Previous metadata value. std::string old_metadata; - /** New metadata value. */ + /// New metadata value. std::string new_metadata; }; -/** - * Fired when the room SID changes (e.g., after migration). - */ +/// Fired when the room SID changes (e.g., after migration). struct RoomSidChangedEvent { - /** New room SID. */ + /// New room SID. std::string sid; }; -/** - * Fired when a participant's metadata is updated. - */ +/// Fired when a participant's metadata is updated. struct ParticipantMetadataChangedEvent { - /** Participant whose metadata changed (owned by Room). */ + /// Participant whose metadata changed (owned by `Room`). Participant* participant = nullptr; - /** Old metadata value. */ + /// Old metadata value. std::string old_metadata; - /** New metadata value. */ + /// New metadata value. std::string new_metadata; }; -/** - * Fired when a participant's name changes. - */ +/// Fired when a participant's name changes. struct ParticipantNameChangedEvent { - /** Participant whose name changed (owned by Room). */ + /// Participant whose name changed (owned by `Room`). Participant* participant = nullptr; - /** Previous name. */ + /// Previous name. std::string old_name; - /** New name. */ + /// New name. std::string new_name; }; -/** - * Fired when a participant's attributes change. - */ +/// Fired when a participant's attributes change. struct ParticipantAttributesChangedEvent { - /** Participant whose attributes changed (owned by Room). */ + /// Participant whose attributes changed (owned by `Room`). Participant* participant = nullptr; - /** Set of attributes that changed (key/value pairs). */ + /// Set of attributes that changed (key/value pairs). std::vector changed_attributes; }; -/** - * Fired when a participant's encryption status changes. - */ +/// Fired when a participant's encryption status changes. struct ParticipantEncryptionStatusChangedEvent { - /** Participant whose encryption status changed (owned by Room). */ + /// Participant whose encryption status changed (owned by `Room`). Participant* participant = nullptr; - /** True if the participant is now fully encrypted. */ + /// True if the participant is now fully encrypted. bool is_encrypted = false; }; -/** - * Fired when a participant's connection quality estimate changes. - */ +/// Fired when a participant's connection quality estimate changes. struct ConnectionQualityChangedEvent { - /** Participant whose connection quality changed (owned by Room). */ + /// Participant whose connection quality changed (owned by `Room`). Participant* participant = nullptr; - /** New connection quality. */ + /// New connection quality. ConnectionQuality quality = ConnectionQuality::Good; }; -/** - * Fired when a user data packet (non-SIP) is received. - */ +/// Fired when a user data packet (non-SIP) is received. struct UserDataPacketEvent { - /** Payload data. */ + /// Payload data. std::vector data; - /** Delivery kind (reliable or lossy). */ + /// Delivery kind (reliable or lossy). DataPacketKind kind = DataPacketKind::Reliable; - /** Remote participant that sent this packet, or nullptr if server (owned by - * Room). */ + /// Remote participant that sent this packet, or `nullptr` if server + /// (owned by `Room`). RemoteParticipant* participant = nullptr; - /** Optional topic associated with this data (may be empty). */ + /// Optional topic associated with this data (may be empty). std::string topic; }; -/** - * Fired when a SIP DTMF packet is received. - */ +/// Fired when a SIP DTMF packet is received. struct SipDtmfReceivedEvent { - /** DTMF code. */ + /// DTMF code. int code = 0; - /** Human-readable DTMF digit. */ + /// Human-readable DTMF digit. std::string digit; - /** Remote participant that sent the DTMF (owned by Room). */ + /// Remote participant that sent the DTMF (owned by `Room`). RemoteParticipant* participant = nullptr; }; -/** - * Fired when the room's connection state changes. - */ +/// Fired when the room's connection state changes. struct ConnectionStateChangedEvent { - /** New connection state. */ + /// New connection state. ConnectionState state = ConnectionState::Disconnected; }; -/** - * Fired when the room is disconnected. - */ +/// Fired when the room is disconnected. struct DisconnectedEvent { - /** Reason for disconnect, if known. */ + /// Reason for disconnect, if known. DisconnectReason reason = DisconnectReason::Unknown; }; -/** - * Fired just before attempting to reconnect. - */ +/// Fired just before attempting to reconnect. struct ReconnectingEvent {}; -/** - * Fired after successfully reconnecting. - */ +/// Fired after successfully reconnecting. struct ReconnectedEvent {}; -/** - * Fired when the room has reached end-of-stream (no more events). - */ +/// Fired when the room has reached end-of-stream (no more events). struct RoomEosEvent {}; -/** - * Fired when a data stream header is received. - */ +/// Fired when a data stream header is received. struct DataStreamHeaderReceivedEvent { - /** Identity of the participant that sent the stream. */ + /// Identity of the participant that sent the stream. std::string participant_identity; - /** Parsed header data. */ + /// Parsed header data. DataStreamHeaderData header; }; -/** - * Fired when a data stream chunk is received. - */ +/// Fired when a data stream chunk is received. struct DataStreamChunkReceivedEvent { - /** Identity of the participant that sent the stream. */ + /// Identity of the participant that sent the stream. std::string participant_identity; - /** Chunk payload and metadata. */ + /// Chunk payload and metadata. DataStreamChunkData chunk; }; -/** - * Fired when a data stream trailer is received. - */ +/// Fired when a data stream trailer is received. struct DataStreamTrailerReceivedEvent { - /** Identity of the participant that sent the stream. */ + /// Identity of the participant that sent the stream. std::string participant_identity; - /** Trailer metadata describing the stream termination. */ + /// Trailer metadata describing the stream termination. DataStreamTrailerData trailer; }; -/** - * Fired when a data channel's buffered amount falls below its low threshold. - */ +/// Fired when a data channel's buffered amount falls below its low +/// threshold. struct DataChannelBufferedAmountLowThresholdChangedEvent { - /** Data channel kind (reliable or lossy). */ + /// Data channel kind (reliable or lossy). DataPacketKind kind = DataPacketKind::Reliable; - /** New threshold value in bytes. */ + /// New threshold value in bytes. std::uint64_t threshold = 0; }; -/** - * Fired when a high-level byte stream reader is opened. - */ +/// Fired when a high-level byte stream reader is opened. struct ByteStreamOpenedEvent { - /** Handle to the underlying byte stream reader. */ + /// Handle to the underlying byte stream reader. std::uint64_t reader_handle = 0; - /** Identity of the participant that opened the stream. */ + /// Identity of the participant that opened the stream. std::string participant_identity; }; -/** - * Fired when a high-level text stream reader is opened. - */ +/// Fired when a high-level text stream reader is opened. struct TextStreamOpenedEvent { - /** Handle to the underlying text stream reader. */ + /// Handle to the underlying text stream reader. std::uint64_t reader_handle = 0; - /** Identity of the participant that opened the stream. */ + /// Identity of the participant that opened the stream. std::string participant_identity; }; -/** - * Fired when the room's info is updated. - */ +/// Fired when the room's info is updated. struct RoomUpdatedEvent { - /** New room info snapshot. */ + /// New room info snapshot. RoomInfoData info; }; -/** - * Fired when the participant has been moved to another room. - */ +/// Fired when the participant has been moved to another room. struct RoomMovedEvent { - /** Info about the new room. */ + /// Info about the new room. RoomInfoData info; }; -/** - * Fired when a batch of participants has been updated. - */ +/// Fired when a batch of participants has been updated. struct ParticipantsUpdatedEvent { - /** Participants updated in this event (owned by Room). */ + /// Participants updated in this event (owned by `Room`). std::vector participants; }; -/** - * Fired when a participant's E2EE state changes. - */ +/// Fired when a participant's E2EE state changes. struct E2eeStateChangedEvent { - /** Local or remote participant whose state changed (owned by Room). */ + /// Local or remote participant whose state changed (owned by `Room`). Participant* participant = nullptr; - /** New encryption state. */ + /// New encryption state. EncryptionState state = EncryptionState::New; }; -/** - * Fired when a participant publishes a data track. - * - * Data tracks are independent of the audio/video track hierarchy. - * The application must call RemoteDataTrack::subscribe() to start - * receiving frames. - */ +/// Fired when a participant publishes a data track. +/// +/// Data tracks are independent of the audio/video track hierarchy. The +/// application must call `RemoteDataTrack::subscribe()` to start receiving +/// frames. struct DataTrackPublishedEvent { - /** The newly published remote data track. */ + /// The newly published remote data track. std::shared_ptr track; }; -/** - * Fired when a remote participant unpublishes a data track. - */ +/// Fired when a remote participant unpublishes a data track. struct DataTrackUnpublishedEvent { - /** SID of the track that was unpublished. */ + /// SID of the track that was unpublished. std::string sid; }; diff --git a/include/livekit/rpc_error.h b/include/livekit/rpc_error.h index 9e449644..f49d8705 100644 --- a/include/livekit/rpc_error.h +++ b/include/livekit/rpc_error.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -28,22 +28,23 @@ namespace proto { class RpcError; } -/** - * Specialized error type for RPC methods. - * - * Instances of this type, when thrown in a method handler, will have their - * `code`, `message`, and optional `data` serialized into a proto::RpcError - * and sent across the wire. The caller will receive an equivalent error - * on the other side. - * - * Built-in errors are included (codes 1400–1999) but developers may use - * arbitrary codes as well. - */ +/// Specialized error type for RPC methods. +/// +/// Instances of this type, when thrown in a method handler, will have +/// their `code`, `message`, and optional `data` serialized into a +/// `proto::RpcError` and sent across the wire. The caller will receive an +/// equivalent error on the other side. +/// +/// Built-in errors are included (codes 1400–1999) but developers may use +/// arbitrary codes as well. +/// +/// @note Thread-safety: Not thread-safe. A single `RpcError` instance is +/// immutable after construction and may be read concurrently; constructing +/// or moving an instance must not happen concurrently from multiple +/// threads. class LIVEKIT_API RpcError : public std::runtime_error { public: - /** - * Built-in error codes - */ + /// Built-in error codes for RPC failures. enum class ErrorCode : std::uint32_t { APPLICATION_ERROR = 1500, CONNECTION_TIMEOUT = 1501, @@ -59,55 +60,53 @@ class LIVEKIT_API RpcError : public std::runtime_error { UNSUPPORTED_VERSION = 1404, }; - /** - * Construct an RpcError with an explicit numeric code. - * - * @param code Error code value. Codes 1001–1999 are reserved for - * built-in errors (see ErrorCode). - * @param message Human-readable error message. - * @param data Optional extra data, e.g. JSON. Empty string means no data. - */ + /// Construct an `RpcError` with an explicit numeric code. + /// + /// @param code Error code value. Codes 1001–1999 are reserved for + /// built-in errors (see `ErrorCode`). + /// @param message Human-readable error message. + /// @param data Optional extra data, e.g. JSON. Empty string means + /// no data. RpcError(std::uint32_t code, std::string message, std::string data = {}); - /** - * Construct an RpcError from a built-in ErrorCode. - * - * @param code Built-in error code. - * @param message Human-readable error message. - * @param data Optional extra data, e.g. JSON. Empty string means no data. - */ + /// Construct an `RpcError` from a built-in `ErrorCode`. + /// + /// @param code Built-in error code. + /// @param message Human-readable error message. + /// @param data Optional extra data, e.g. JSON. Empty string means + /// no data. RpcError(ErrorCode code, std::string message, std::string data = {}); - /** - * Numeric error code. - * - * Codes 1001–1999 are reserved for built-in errors. For built-ins, this - * value matches the underlying ErrorCode enum value. - */ + /// Numeric error code. + /// + /// Codes 1001–1999 are reserved for built-in errors. For built-ins, + /// this value matches the underlying `ErrorCode` enum value. + /// + /// @return The numeric error code carried by this error. std::uint32_t code() const noexcept; - /** - * Human-readable error message. - */ + /// @return Human-readable error message. const std::string& message() const noexcept; - /** - * Optional extra data associated with the error (JSON recommended). - * May be an empty string if no data was provided. - */ + /// Optional extra data associated with the error (JSON recommended). + /// + /// @return The error's data payload, or an empty string if none was + /// provided. const std::string& data() const noexcept; - /** - * Create a built-in RpcError using a predefined ErrorCode and default - * message text. - * - * @param code Built-in error code. - * @param data Optional extra data payload (JSON recommended). - */ + /// Create a built-in `RpcError` using a predefined `ErrorCode` and + /// default message text. + /// + /// @param code Built-in error code. + /// @param data Optional extra data payload (JSON recommended). + /// @return A populated `RpcError` instance. + /// + /// @note Thread-safety: Thread-safe. Pure factory function with no + /// global state. static RpcError builtIn(ErrorCode code, const std::string& data = {}); protected: - // ----- Protected: only used by LocalParticipant (internal SDK code) ----- + // Protected: only used by LocalParticipant (internal SDK code) proto::RpcError toProto() const; static RpcError fromProto(const proto::RpcError& err); @@ -122,4 +121,4 @@ class LIVEKIT_API RpcError : public std::runtime_error { std::string data_; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/stats.h b/include/livekit/stats.h index 84674d8e..2c1b5476 100644 --- a/include/livekit/stats.h +++ b/include/livekit/stats.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -51,6 +51,7 @@ class CertificateStats; class StreamStats; } // namespace proto +/// Lifecycle state of an `RTCDataChannel` (mirror of the W3C WebRTC spec). enum class DataChannelState { Connecting, Open, @@ -59,6 +60,7 @@ enum class DataChannelState { Unknown, }; +/// Reason an outbound video stream's quality is being limited. enum class QualityLimitationReason { None, Cpu, @@ -66,12 +68,14 @@ enum class QualityLimitationReason { Other, }; +/// ICE role for a transport (`controlling` vs `controlled`). enum class IceRole { Unknown, Controlling, Controlled, }; +/// State of the DTLS transport associated with an RTP transport. enum class DtlsTransportState { New, Connecting, @@ -81,6 +85,7 @@ enum class DtlsTransportState { Unknown, }; +/// State of an ICE transport. enum class IceTransportState { New, Checking, @@ -92,12 +97,14 @@ enum class IceTransportState { Unknown, }; +/// DTLS role taken by this peer for the transport. enum class DtlsRole { Client, Server, Unknown, }; +/// Connectivity state of an ICE candidate pair. enum class IceCandidatePairState { Frozen, Waiting, @@ -107,6 +114,8 @@ enum class IceCandidatePairState { Unknown, }; +/// Type of an ICE candidate (host, server reflexive, peer reflexive, or +/// relay). enum class IceCandidateType { Host, Srflx, @@ -115,6 +124,7 @@ enum class IceCandidateType { Unknown, }; +/// Transport protocol used by an ICE server when relaying. enum class IceServerTransportProtocol { Udp, Tcp, @@ -122,6 +132,7 @@ enum class IceServerTransportProtocol { Unknown, }; +/// Type of a TCP ICE candidate (active, passive, or simultaneous-open). enum class IceTcpCandidateType { Active, Passive, @@ -132,12 +143,25 @@ enum class IceTcpCandidateType { // ---------------------- // Leaf stats types // ---------------------- - +// +// These mirror the W3C WebRTC `RTCStats` types one-to-one and are +// populated by the SDK from FFI events. Field semantics follow the +// WebRTC spec (https://www.w3.org/TR/webrtc-stats/) and are not +// re-documented here. +// +// @note Thread-safety: All of the leaf stats structs below are POD-like +// and are NOT thread-safe to mutate concurrently. They are typically +// observed as immutable snapshots and only mutated during construction +// from a `proto::*Stats` via `fromProto`. + +/// Common fields shared by every `RTCStats` dictionary (`id`, +/// `timestamp`). struct RtcStatsData { std::string id; std::int64_t timestamp_ms; }; +/// Codec-level statistics for a single payload type. struct CodecStats { std::uint32_t payload_type; std::string transport_id; @@ -147,6 +171,7 @@ struct CodecStats { std::string sdp_fmtp_line; }; +/// Common RTP stream identification fields. struct RtpStreamStats { std::uint32_t ssrc; std::string kind; @@ -154,12 +179,14 @@ struct RtpStreamStats { std::string codec_id; }; +/// Receive-side counters shared by inbound RTP streams. struct ReceivedRtpStreamStats { std::uint64_t packets_received; std::int64_t packets_lost; double jitter; }; +/// Inbound RTP stream statistics (decoded frames, jitter buffer, etc.). struct InboundRtpStreamStats { std::string track_identifier; std::string mid; @@ -216,11 +243,13 @@ struct InboundRtpStreamStats { std::uint32_t fec_ssrc; }; +/// Send-side counters shared by outbound RTP streams. struct SentRtpStreamStats { std::uint64_t packets_sent; std::uint64_t bytes_sent; }; +/// Outbound RTP stream statistics (encoder, bandwidth limitation, etc.). struct OutboundRtpStreamStats { std::string mid; std::string media_source_id; @@ -254,6 +283,8 @@ struct OutboundRtpStreamStats { std::string scalability_mode; }; +/// Remote-reported inbound stream stats (RTT, fraction lost) for an +/// outbound stream we send. struct RemoteInboundRtpStreamStats { std::string local_id; double round_trip_time; @@ -262,6 +293,8 @@ struct RemoteInboundRtpStreamStats { std::uint64_t round_trip_time_measurements; }; +/// Remote-reported outbound stream stats (RTT, reports sent) for an +/// inbound stream we receive. struct RemoteOutboundRtpStreamStats { std::string local_id; double remote_timestamp; @@ -271,11 +304,14 @@ struct RemoteOutboundRtpStreamStats { std::uint64_t round_trip_time_measurements; }; +/// Common media-source identification fields shared by audio/video source +/// stats. struct MediaSourceStats { std::string track_identifier; std::string kind; }; +/// Audio-source statistics (level, echo cancellation metrics, etc.). struct AudioSourceStats { double audio_level; double total_audio_energy; @@ -288,6 +324,7 @@ struct AudioSourceStats { std::uint64_t total_samples_captured; }; +/// Video-source statistics (resolution, frame count). struct VideoSourceStats { std::uint32_t width; std::uint32_t height; @@ -295,28 +332,33 @@ struct VideoSourceStats { double frames_per_second; }; -/** - * @brief Statistics for audio playout performance. - * - * Contains metrics about audio sample synthesis and playout timing, - * useful for monitoring audio quality and detecting issues like underruns. - */ +/// Statistics for audio playout performance. +/// +/// Contains metrics about audio sample synthesis and playout timing, +/// useful for monitoring audio quality and detecting issues like +/// underruns. struct AudioPlayoutStats { - std::string kind; ///< The type of media ("audio"). - double synthesized_samples_duration; ///< Duration of synthesized samples in - ///< seconds. - std::uint32_t synthesized_samples_events; ///< Number of synthesis events - ///< (e.g., concealment). - double total_samples_duration; ///< Total duration of all samples in seconds. - double total_playout_delay; ///< Cumulative playout delay in seconds. - std::uint64_t total_samples_count; ///< Total number of samples played out. + /// The type of media (always `"audio"`). + std::string kind; + /// Duration of synthesized samples in seconds. + double synthesized_samples_duration; + /// Number of synthesis events (e.g., concealment). + std::uint32_t synthesized_samples_events; + /// Total duration of all samples in seconds. + double total_samples_duration; + /// Cumulative playout delay in seconds. + double total_playout_delay; + /// Total number of samples played out. + std::uint64_t total_samples_count; }; +/// Peer-connection-level statistics (data channel open/close counts). struct PeerConnectionStats { std::uint32_t data_channels_opened; std::uint32_t data_channels_closed; }; +/// Per-data-channel statistics. struct DataChannelStats { std::string label; std::string protocol; @@ -328,6 +370,7 @@ struct DataChannelStats { std::uint64_t bytes_received; }; +/// Transport-level statistics for the underlying ICE/DTLS connection. struct TransportStats { std::uint64_t packets_sent; std::uint64_t packets_received; @@ -347,6 +390,8 @@ struct TransportStats { std::uint32_t selected_candidate_pair_changes; }; +/// Candidate pair statistics (RTT, available bandwidth, packet +/// counters). struct CandidatePairStats { std::string transport_id; std::string local_candidate_id; @@ -372,6 +417,7 @@ struct CandidatePairStats { std::uint64_t bytes_discarded_on_send; }; +/// Individual ICE candidate statistics (address, priority, type). struct IceCandidateStats { std::string transport_id; std::string address; @@ -388,6 +434,7 @@ struct IceCandidateStats { std::optional tcp_type; }; +/// DTLS certificate statistics (fingerprint, issuer chain). struct CertificateStats { std::string fingerprint; std::string fingerprint_algorithm; @@ -395,6 +442,7 @@ struct CertificateStats { std::string issuer_certificate_id; }; +/// Per-MediaStream identification stats. struct StreamStats { std::string id; std::string stream_identifier; @@ -404,11 +452,13 @@ struct StreamStats { // High-level RtcStats wrapper // ---------------------- +/// Codec stats wrapped with common `RtcStatsData` fields. struct RtcCodecStats { RtcStatsData rtc; CodecStats codec; }; +/// Inbound RTP stats wrapped with common stream/receive counters. struct RtcInboundRtpStats { RtcStatsData rtc; RtpStreamStats stream; @@ -416,6 +466,7 @@ struct RtcInboundRtpStats { InboundRtpStreamStats inbound; }; +/// Outbound RTP stats wrapped with common stream/send counters. struct RtcOutboundRtpStats { RtcStatsData rtc; RtpStreamStats stream; @@ -423,6 +474,8 @@ struct RtcOutboundRtpStats { OutboundRtpStreamStats outbound; }; +/// Remote-reported inbound RTP stats wrapped with common stream +/// counters. struct RtcRemoteInboundRtpStats { RtcStatsData rtc; RtpStreamStats stream; @@ -430,6 +483,8 @@ struct RtcRemoteInboundRtpStats { RemoteInboundRtpStreamStats remote_inbound; }; +/// Remote-reported outbound RTP stats wrapped with common stream +/// counters. struct RtcRemoteOutboundRtpStats { RtcStatsData rtc; RtpStreamStats stream; @@ -437,6 +492,7 @@ struct RtcRemoteOutboundRtpStats { RemoteOutboundRtpStreamStats remote_outbound; }; +/// Media-source stats (audio + video) wrapped with common identification. struct RtcMediaSourceStats { RtcStatsData rtc; MediaSourceStats source; @@ -444,46 +500,55 @@ struct RtcMediaSourceStats { VideoSourceStats video; }; +/// Audio-playout stats wrapped with common `RtcStatsData` fields. struct RtcMediaPlayoutStats { RtcStatsData rtc; AudioPlayoutStats audio_playout; }; +/// Peer-connection stats wrapped with common `RtcStatsData` fields. struct RtcPeerConnectionStats { RtcStatsData rtc; PeerConnectionStats pc; }; +/// Data-channel stats wrapped with common `RtcStatsData` fields. struct RtcDataChannelStats { RtcStatsData rtc; DataChannelStats dc; }; +/// Transport-level stats wrapped with common `RtcStatsData` fields. struct RtcTransportStats { RtcStatsData rtc; TransportStats transport; }; +/// Candidate-pair stats wrapped with common `RtcStatsData` fields. struct RtcCandidatePairStats { RtcStatsData rtc; CandidatePairStats candidate_pair; }; +/// Local ICE candidate stats wrapped with common `RtcStatsData` fields. struct RtcLocalCandidateStats { RtcStatsData rtc; IceCandidateStats candidate; }; +/// Remote ICE candidate stats wrapped with common `RtcStatsData` fields. struct RtcRemoteCandidateStats { RtcStatsData rtc; IceCandidateStats candidate; }; +/// Certificate stats wrapped with common `RtcStatsData` fields. struct RtcCertificateStats { RtcStatsData rtc; CertificateStats certificate; }; +/// MediaStream stats wrapped with common `RtcStatsData` fields. struct RtcStreamStats { RtcStatsData rtc; StreamStats stream; @@ -491,12 +556,15 @@ struct RtcStreamStats { // Deprecated Track omitted on purpose. +/// Tagged union of every supported `RtcStats` payload. using RtcStatsVariant = std::variant; +/// Top-level RTC stats entry produced by the SDK; `stats` holds the +/// concrete payload variant. struct RtcStats { RtcStatsVariant stats; }; @@ -504,33 +572,147 @@ struct RtcStats { // ---------------------- // fromProto declarations // ---------------------- - -LIVEKIT_API RtcStatsData fromProto(const proto::RtcStatsData&); - -LIVEKIT_API CodecStats fromProto(const proto::CodecStats&); -LIVEKIT_API RtpStreamStats fromProto(const proto::RtpStreamStats&); -LIVEKIT_API ReceivedRtpStreamStats fromProto(const proto::ReceivedRtpStreamStats&); -LIVEKIT_API InboundRtpStreamStats fromProto(const proto::InboundRtpStreamStats&); -LIVEKIT_API SentRtpStreamStats fromProto(const proto::SentRtpStreamStats&); -LIVEKIT_API OutboundRtpStreamStats fromProto(const proto::OutboundRtpStreamStats&); -LIVEKIT_API RemoteInboundRtpStreamStats fromProto(const proto::RemoteInboundRtpStreamStats&); -LIVEKIT_API RemoteOutboundRtpStreamStats fromProto(const proto::RemoteOutboundRtpStreamStats&); -LIVEKIT_API MediaSourceStats fromProto(const proto::MediaSourceStats&); -LIVEKIT_API AudioSourceStats fromProto(const proto::AudioSourceStats&); -LIVEKIT_API VideoSourceStats fromProto(const proto::VideoSourceStats&); -LIVEKIT_API AudioPlayoutStats fromProto(const proto::AudioPlayoutStats&); -LIVEKIT_API PeerConnectionStats fromProto(const proto::PeerConnectionStats&); -LIVEKIT_API DataChannelStats fromProto(const proto::DataChannelStats&); -LIVEKIT_API TransportStats fromProto(const proto::TransportStats&); -LIVEKIT_API CandidatePairStats fromProto(const proto::CandidatePairStats&); -LIVEKIT_API IceCandidateStats fromProto(const proto::IceCandidateStats&); -LIVEKIT_API CertificateStats fromProto(const proto::CertificateStats&); -LIVEKIT_API StreamStats fromProto(const proto::StreamStats&); - -// High-level: -LIVEKIT_API RtcStats fromProto(const proto::RtcStats&); - -// helper if you have repeated RtcStats in proto: -LIVEKIT_API std::vector fromProto(const std::vector&); +// +// Each of the functions below converts an FFI-side `proto::*Stats` +// message into the public C++ struct above. They are pure transformations +// over their input argument and hold no global state. +// +// @note Thread-safety: All `fromProto` overloads are thread-safe and may +// be called concurrently from multiple threads. + +/// Convert a `proto::RtcStatsData` into the public `RtcStatsData`. +/// +/// @param proto Protobuf-encoded stats common header. +/// @return Equivalent public struct. +LIVEKIT_API RtcStatsData fromProto(const proto::RtcStatsData& proto); + +/// Convert a `proto::CodecStats` into the public `CodecStats`. +/// +/// @param proto Protobuf-encoded codec stats. +/// @return Equivalent public struct. +LIVEKIT_API CodecStats fromProto(const proto::CodecStats& proto); + +/// Convert a `proto::RtpStreamStats` into the public `RtpStreamStats`. +/// +/// @param proto Protobuf-encoded RTP stream stats. +/// @return Equivalent public struct. +LIVEKIT_API RtpStreamStats fromProto(const proto::RtpStreamStats& proto); + +/// Convert a `proto::ReceivedRtpStreamStats` into the public form. +/// +/// @param proto Protobuf-encoded received RTP stream stats. +/// @return Equivalent public struct. +LIVEKIT_API ReceivedRtpStreamStats fromProto(const proto::ReceivedRtpStreamStats& proto); + +/// Convert a `proto::InboundRtpStreamStats` into the public form. +/// +/// @param proto Protobuf-encoded inbound RTP stream stats. +/// @return Equivalent public struct. +LIVEKIT_API InboundRtpStreamStats fromProto(const proto::InboundRtpStreamStats& proto); + +/// Convert a `proto::SentRtpStreamStats` into the public form. +/// +/// @param proto Protobuf-encoded sent RTP stream stats. +/// @return Equivalent public struct. +LIVEKIT_API SentRtpStreamStats fromProto(const proto::SentRtpStreamStats& proto); + +/// Convert a `proto::OutboundRtpStreamStats` into the public form. +/// +/// @param proto Protobuf-encoded outbound RTP stream stats. +/// @return Equivalent public struct. +LIVEKIT_API OutboundRtpStreamStats fromProto(const proto::OutboundRtpStreamStats& proto); + +/// Convert a `proto::RemoteInboundRtpStreamStats` into the public form. +/// +/// @param proto Protobuf-encoded remote inbound RTP stream stats. +/// @return Equivalent public struct. +LIVEKIT_API RemoteInboundRtpStreamStats fromProto(const proto::RemoteInboundRtpStreamStats& proto); + +/// Convert a `proto::RemoteOutboundRtpStreamStats` into the public form. +/// +/// @param proto Protobuf-encoded remote outbound RTP stream stats. +/// @return Equivalent public struct. +LIVEKIT_API RemoteOutboundRtpStreamStats fromProto(const proto::RemoteOutboundRtpStreamStats& proto); + +/// Convert a `proto::MediaSourceStats` into the public form. +/// +/// @param proto Protobuf-encoded media source stats. +/// @return Equivalent public struct. +LIVEKIT_API MediaSourceStats fromProto(const proto::MediaSourceStats& proto); + +/// Convert a `proto::AudioSourceStats` into the public form. +/// +/// @param proto Protobuf-encoded audio source stats. +/// @return Equivalent public struct. +LIVEKIT_API AudioSourceStats fromProto(const proto::AudioSourceStats& proto); + +/// Convert a `proto::VideoSourceStats` into the public form. +/// +/// @param proto Protobuf-encoded video source stats. +/// @return Equivalent public struct. +LIVEKIT_API VideoSourceStats fromProto(const proto::VideoSourceStats& proto); + +/// Convert a `proto::AudioPlayoutStats` into the public form. +/// +/// @param proto Protobuf-encoded audio playout stats. +/// @return Equivalent public struct. +LIVEKIT_API AudioPlayoutStats fromProto(const proto::AudioPlayoutStats& proto); + +/// Convert a `proto::PeerConnectionStats` into the public form. +/// +/// @param proto Protobuf-encoded peer connection stats. +/// @return Equivalent public struct. +LIVEKIT_API PeerConnectionStats fromProto(const proto::PeerConnectionStats& proto); + +/// Convert a `proto::DataChannelStats` into the public form. +/// +/// @param proto Protobuf-encoded data channel stats. +/// @return Equivalent public struct. +LIVEKIT_API DataChannelStats fromProto(const proto::DataChannelStats& proto); + +/// Convert a `proto::TransportStats` into the public form. +/// +/// @param proto Protobuf-encoded transport stats. +/// @return Equivalent public struct. +LIVEKIT_API TransportStats fromProto(const proto::TransportStats& proto); + +/// Convert a `proto::CandidatePairStats` into the public form. +/// +/// @param proto Protobuf-encoded candidate pair stats. +/// @return Equivalent public struct. +LIVEKIT_API CandidatePairStats fromProto(const proto::CandidatePairStats& proto); + +/// Convert a `proto::IceCandidateStats` into the public form. +/// +/// @param proto Protobuf-encoded ICE candidate stats. +/// @return Equivalent public struct. +LIVEKIT_API IceCandidateStats fromProto(const proto::IceCandidateStats& proto); + +/// Convert a `proto::CertificateStats` into the public form. +/// +/// @param proto Protobuf-encoded certificate stats. +/// @return Equivalent public struct. +LIVEKIT_API CertificateStats fromProto(const proto::CertificateStats& proto); + +/// Convert a `proto::StreamStats` into the public form. +/// +/// @param proto Protobuf-encoded media-stream stats. +/// @return Equivalent public struct. +LIVEKIT_API StreamStats fromProto(const proto::StreamStats& proto); + +/// Convert a `proto::RtcStats` (tagged union) into the public +/// `RtcStats`. +/// +/// @param proto Protobuf-encoded RTC stats entry. +/// @return Equivalent public struct. +LIVEKIT_API RtcStats fromProto(const proto::RtcStats& proto); + +/// Convert a vector of `proto::RtcStats` into a vector of public +/// `RtcStats`. +/// +/// @param proto_list Source vector of protobuf-encoded stats entries. +/// @return Vector of equivalent public structs, preserving +/// order. +LIVEKIT_API std::vector fromProto(const std::vector& proto_list); } // namespace livekit diff --git a/include/livekit/subscription_thread_dispatcher.h b/include/livekit/subscription_thread_dispatcher.h index d93e863e..a4b894a0 100644 --- a/include/livekit/subscription_thread_dispatcher.h +++ b/include/livekit/subscription_thread_dispatcher.h @@ -39,213 +39,227 @@ class Track; class VideoFrame; /// Callback type for incoming audio frames. +/// /// Invoked on a dedicated reader thread per (participant, source) pair. using AudioFrameCallback = std::function; /// Callback type for incoming video frames. +/// /// Invoked on a dedicated reader thread per (participant, source) pair. using VideoFrameCallback = std::function; /// Callback type for incoming video frame events. -/// Invoked on a dedicated reader thread per (participant, track_name) pair. +/// +/// Invoked on a dedicated reader thread per (participant, track_name) +/// pair. using VideoFrameEventCallback = std::function; /// Callback type for incoming data track frames. +/// /// Invoked on a dedicated reader thread per subscription. -/// @param payload Raw binary data received. -/// @param user_timestamp Optional application-defined timestamp from sender. +/// +/// @param payload Raw binary data received. +/// @param user_timestamp Optional application-defined timestamp from +/// the sender. using DataFrameCallback = std::function& payload, std::optional user_timestamp)>; -/// Opaque identifier returned by addOnDataFrameCallback, used to remove an -/// individual subscription via removeOnDataFrameCallback. +/// Opaque identifier returned by `addOnDataFrameCallback`, used to +/// remove an individual subscription via `removeOnDataFrameCallback`. using DataFrameCallbackId = std::uint64_t; -/** - * Owns subscription callback registration and per-subscription reader threads. - * - * `SubscriptionThreadDispatcher` is the low-level companion to \ref Room's - * remote track subscription flow. `Room` forwards user-facing callback - * registration requests here, and then calls \ref handleTrackSubscribed and - * \ref handleTrackUnsubscribed as room events arrive. - * - * For each registered `(participant identity, TrackSource)` pair, this class - * may create a dedicated \ref AudioStream or \ref VideoStream and a matching - * reader thread. That thread blocks on stream reads and invokes the - * registered callback with decoded frames. - * - * This type is intentionally independent from \ref RoomDelegate. High-level - * room events such as `RoomDelegate::onTrackSubscribed()` remain in \ref Room, - * while this dispatcher focuses only on callback registration, stream - * ownership, and reader-thread lifecycle. - * - * The design keeps track-type-specific startup isolated so additional track - * kinds can be added later without pushing more thread state back into - * \ref Room. - */ +/// Owns subscription callback registration and per-subscription reader +/// threads. +/// +/// `SubscriptionThreadDispatcher` is the low-level companion to `Room`'s +/// remote track subscription flow. `Room` forwards user-facing callback +/// registration requests here, and then calls `handleTrackSubscribed` +/// and `handleTrackUnsubscribed` as room events arrive. +/// +/// For each registered `(participant identity, TrackSource)` pair, this +/// class may create a dedicated `AudioStream` or `VideoStream` and a +/// matching reader thread. That thread blocks on stream reads and +/// invokes the registered callback with decoded frames. +/// +/// This type is intentionally independent from `RoomDelegate`. +/// High-level room events such as `RoomDelegate::onTrackSubscribed()` +/// remain in `Room`, while this dispatcher focuses only on callback +/// registration, stream ownership, and reader-thread lifecycle. +/// +/// The design keeps track-type-specific startup isolated so additional +/// track kinds can be added later without pushing more thread state +/// back into `Room`. +/// +/// @note Thread-safety: Thread-safe. An internal `std::mutex` protects +/// registrations and active readers. Thread joins happen outside the +/// lock so destructors and clear/stop calls do not deadlock with the +/// dispatcher's own reader threads. class LIVEKIT_API SubscriptionThreadDispatcher { public: - /// Constructs an empty dispatcher with no registered callbacks or readers. + /// Construct an empty dispatcher with no registered callbacks or + /// readers. SubscriptionThreadDispatcher(); - /// Stops all active readers and clears all registered callbacks. + /// Stop all active readers and clear all registered callbacks. ~SubscriptionThreadDispatcher(); - /** - * Register or replace an audio frame callback for a remote subscription. - * - * The callback is keyed by remote participant identity plus \p source. - * If the matching remote audio track is already subscribed, \ref Room may - * immediately call \ref handleTrackSubscribed to start a reader. - * - * @param participant_identity Identity of the remote participant. - * @param source Track source to match. - * @param callback Function invoked for each decoded audio frame. - * @param opts Options used when creating the backing - * \ref AudioStream. - */ + /// Register or replace an audio frame callback for a remote + /// subscription. + /// + /// The callback is keyed by remote participant identity plus + /// `source`. If the matching remote audio track is already + /// subscribed, `Room` may immediately call `handleTrackSubscribed` to + /// start a reader. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source to match. + /// @param callback Function invoked for each decoded + /// audio frame. + /// @param opts Options used when creating the + /// backing `AudioStream`. + /// + /// @note Thread-safety: Thread-safe. void setOnAudioFrameCallback(const std::string& participant_identity, TrackSource source, AudioFrameCallback callback, const AudioStream::Options& opts = {}); - /** - * Register or replace an audio frame callback for a remote subscription. - * - * The callback is keyed by remote participant identity plus \p track_name. - * If the matching remote audio track is already subscribed, \ref Room may - * immediately call \ref handleTrackSubscribed to start a reader. - * - * @param participant_identity Identity of the remote participant. - * @param track_name Track name to match. - * @param callback Function invoked for each decoded audio frame. - * @param opts Options used when creating the backing - * \ref AudioStream. - */ + /// Register or replace an audio frame callback for a remote + /// subscription, keyed by track name instead of source. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to match. + /// @param callback Function invoked for each decoded + /// audio frame. + /// @param opts Options used when creating the + /// backing `AudioStream`. + /// + /// @note Thread-safety: Thread-safe. void setOnAudioFrameCallback(const std::string& participant_identity, const std::string& track_name, AudioFrameCallback callback, const AudioStream::Options& opts = {}); - /** - * Register or replace a video frame callback for a remote subscription. - * - * The callback is keyed by remote participant identity plus \p source. - * If the matching remote video track is already subscribed, \ref Room may - * immediately call \ref handleTrackSubscribed to start a reader. - * - * @param participant_identity Identity of the remote participant. - * @param source Track source to match. - * @param callback Function invoked for each decoded video frame. - * @param opts Options used when creating the backing - * \ref VideoStream. - */ + /// Register or replace a video frame callback for a remote + /// subscription. + /// + /// The callback is keyed by remote participant identity plus + /// `source`. If the matching remote video track is already + /// subscribed, `Room` may immediately call `handleTrackSubscribed` to + /// start a reader. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source to match. + /// @param callback Function invoked for each decoded + /// video frame. + /// @param opts Options used when creating the + /// backing `VideoStream`. + /// + /// @note Thread-safety: Thread-safe. void setOnVideoFrameCallback(const std::string& participant_identity, TrackSource source, VideoFrameCallback callback, const VideoStream::Options& opts = {}); - /** - * Register or replace a video frame callback for a remote subscription. - * - * The callback is keyed by remote participant identity plus \p track_name. - * If the matching remote video track is already subscribed, \ref Room may - * immediately call \ref handleTrackSubscribed to start a reader. - * - * @param participant_identity Identity of the remote participant. - * @param track_name Track name to match. - * @param callback Function invoked for each decoded video frame. - * @param opts Options used when creating the backing - * \ref VideoStream. - */ + /// Register or replace a video frame callback for a remote + /// subscription, keyed by track name instead of source. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to match. + /// @param callback Function invoked for each decoded + /// video frame. + /// @param opts Options used when creating the + /// backing `VideoStream`. + /// + /// @note Thread-safety: Thread-safe. void setOnVideoFrameCallback(const std::string& participant_identity, const std::string& track_name, VideoFrameCallback callback, const VideoStream::Options& opts = {}); - /** - * Register or replace a rich video frame event callback for a remote - * subscription. - * - * The callback is keyed by remote participant identity plus \p track_name. - * If the matching remote video track is already subscribed, \ref Room may - * immediately call \ref handleTrackSubscribed to start a reader. - * - * @param participant_identity Identity of the remote participant. - * @param track_name Track name to match. - * @param callback Function invoked for each decoded video frame - * event, including optional metadata. - * @param opts Options used when creating the backing - * \ref VideoStream. - */ + /// Register or replace a rich video frame event callback for a remote + /// subscription. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to match. + /// @param callback Function invoked for each decoded + /// video frame event, including + /// optional metadata. + /// @param opts Options used when creating the + /// backing `VideoStream`. + /// + /// @note Thread-safety: Thread-safe. void setOnVideoFrameEventCallback(const std::string& participant_identity, const std::string& track_name, VideoFrameEventCallback callback, const VideoStream::Options& opts = {}); - /** - * Remove an audio callback registration and stop any active reader. - * - * If an audio reader thread is active for the given key, its stream is - * closed and the thread is joined before this call returns. - * - * @param participant_identity Identity of the remote participant. - * @param source Track source to clear. - */ + /// Remove an audio callback registration and stop any active reader. + /// + /// If an audio reader thread is active for the given key, its stream + /// is closed and the thread is joined before this call returns. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source to clear. + /// + /// @note Thread-safety: Thread-safe. void clearOnAudioFrameCallback(const std::string& participant_identity, TrackSource source); - /** - * Remove an audio callback registration and stop any active reader. - * - * If an audio reader thread is active for the given key, its stream is - * closed and the thread is joined before this call returns. - * - * @param participant_identity Identity of the remote participant. - * @param track_name Track name to clear. - */ + /// Remove an audio callback registration and stop any active reader, + /// keyed by track name. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to clear. + /// + /// @note Thread-safety: Thread-safe. void clearOnAudioFrameCallback(const std::string& participant_identity, const std::string& track_name); - /** - * Remove a video callback registration and stop any active reader. - * - * If a video reader thread is active for the given key, its stream is - * closed and the thread is joined before this call returns. - * - * @param participant_identity Identity of the remote participant. - * @param source Track source to clear. - */ + /// Remove a video callback registration and stop any active reader. + /// + /// If a video reader thread is active for the given key, its stream + /// is closed and the thread is joined before this call returns. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source to clear. + /// + /// @note Thread-safety: Thread-safe. void clearOnVideoFrameCallback(const std::string& participant_identity, TrackSource source); - /** - * Remove a video callback registration and stop any active reader. - * - * If a video reader thread is active for the given key, its stream is - * closed and the thread is joined before this call returns. - * - * @param participant_identity Identity of the remote participant. - * @param track_name Track name to clear. - */ + /// Remove a video callback registration and stop any active reader, + /// keyed by track name. + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Track name to clear. + /// + /// @note Thread-safety: Thread-safe. void clearOnVideoFrameCallback(const std::string& participant_identity, const std::string& track_name); - /** - * Start or restart reader dispatch for a newly subscribed remote track. - * - * \ref Room calls this after it has processed a track-subscription event and - * updated its publication state. If a matching callback registration exists, - * the dispatcher creates the appropriate stream type and launches a reader - * thread for the `(participant, source)` key. - * - * If no matching callback is registered, this is a no-op. - * - * @param participant_identity Identity of the remote participant. - * @param source Track source associated with the subscription. - * @param track Subscribed remote track to read from. - */ + /// Start or restart reader dispatch for a newly subscribed remote + /// track. + /// + /// `Room` calls this after it has processed a track-subscription + /// event and updated its publication state. If a matching callback + /// registration exists, the dispatcher creates the appropriate + /// stream type and launches a reader thread for the `(participant, + /// source)` key. If no matching callback is registered, this is a + /// no-op. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source associated with the + /// subscription. + /// @param track_name Track name associated with the + /// subscription. + /// @param track Subscribed remote track to read + /// from. + /// + /// @note Thread-safety: Thread-safe. Called by `Room`. void handleTrackSubscribed(const std::string& participant_identity, TrackSource source, const std::string& track_name, const std::shared_ptr& track); - /** - * Stop reader dispatch for an unsubscribed remote track. - * - * \ref Room calls this when a remote track is unsubscribed. Any active - * reader stream for the given `(participant, source)` key is closed and its - * thread is joined. Callback registration is preserved so future - * re-subscription can start dispatch again automatically. - * - * @param participant_identity Identity of the remote participant. - * @param source Track source associated with the subscription. - * @param track_name Track name associated with the subscription. - */ + /// Stop reader dispatch for an unsubscribed remote track. + /// + /// `Room` calls this when a remote track is unsubscribed. Any active + /// reader stream for the given `(participant, source)` key is closed + /// and its thread is joined. Callback registration is preserved so + /// future re-subscription can start dispatch again automatically. + /// + /// @param participant_identity Identity of the remote participant. + /// @param source Track source associated with the + /// subscription. + /// @param track_name Track name associated with the + /// subscription. + /// + /// @note Thread-safety: Thread-safe. Called by `Room`. void handleTrackUnsubscribed(const std::string& participant_identity, TrackSource source, const std::string& track_name); @@ -253,71 +267,75 @@ class LIVEKIT_API SubscriptionThreadDispatcher { // Data track callbacks // --------------------------------------------------------------- - /** - * Add a callback for data frames from a specific remote participant's - * data track. - * - * Multiple callbacks may be registered for the same (participant, - * track_name) pair; each one creates an independent FFI subscription. - * - * The callback fires on a dedicated background thread. If the remote - * data track has not yet been published, the callback is stored and - * auto-wired when the track appears (via handleDataTrackPublished). - * - * @param participant_identity Identity of the remote participant. - * @param track_name Name of the remote data track. - * @param callback Function to invoke per data frame. - * @return An opaque ID that can later be passed to - * removeOnDataFrameCallback() to tear down this subscription. - */ + /// Add a callback for data frames from a specific remote + /// participant's data track. + /// + /// Multiple callbacks may be registered for the same (participant, + /// track_name) pair; each one creates an independent FFI + /// subscription. The callback fires on a dedicated background + /// thread. If the remote data track has not yet been published, the + /// callback is stored and auto-wired when the track appears (via + /// `handleDataTrackPublished`). + /// + /// @param participant_identity Identity of the remote participant. + /// @param track_name Name of the remote data track. + /// @param callback Function to invoke per data frame. + /// @return An opaque ID that can later be + /// passed to `removeOnDataFrameCallback()` + /// to tear down this subscription. + /// + /// @note Thread-safety: Thread-safe. DataFrameCallbackId addOnDataFrameCallback(const std::string& participant_identity, const std::string& track_name, DataFrameCallback callback); - /** - * Remove a data frame callback previously registered via - * addOnDataFrameCallback(). Stops and joins the active reader thread - * for this subscription. - * No-op if the ID is not (or no longer) registered. - * - * @param id The identifier returned by addOnDataFrameCallback(). - */ + /// Remove a data frame callback previously registered via + /// `addOnDataFrameCallback()`. + /// + /// Stops and joins the active reader thread for this subscription. + /// No-op if the ID is not (or no longer) registered. + /// + /// @param id The identifier returned by `addOnDataFrameCallback()`. + /// + /// @note Thread-safety: Thread-safe. void removeOnDataFrameCallback(DataFrameCallbackId id); - /** - * Notify the dispatcher that a remote data track has been published. - * - * \ref Room calls this when it receives a kDataTrackPublished event. - * For every registered callback whose (participant, track_name) matches, - * a reader thread is launched. - * - * @param track The newly published remote data track. - */ + /// Notify the dispatcher that a remote data track has been published. + /// + /// `Room` calls this when it receives a `kDataTrackPublished` event. + /// For every registered callback whose (participant, track_name) + /// matches, a reader thread is launched. + /// + /// @param track The newly published remote data track. + /// + /// @note Thread-safety: Thread-safe. Called by `Room`. void handleDataTrackPublished(const std::shared_ptr& track); - /** - * Notify the dispatcher that a remote data track has been unpublished. - * - * \ref Room calls this when it receives a kDataTrackUnpublished event. - * Any active data reader threads for this track SID are closed and joined. - * - * @param sid The SID of the unpublished data track. - */ + /// Notify the dispatcher that a remote data track has been + /// unpublished. + /// + /// `Room` calls this when it receives a `kDataTrackUnpublished` + /// event. Any active data reader threads for this track SID are + /// closed and joined. + /// + /// @param sid The SID of the unpublished data track. + /// + /// @note Thread-safety: Thread-safe. Called by `Room`. void handleDataTrackUnpublished(const std::string& sid); - /** - * Stop all readers and clear all callback registrations. - * - * This is used during room teardown or EOS handling to ensure no reader - * thread survives beyond the lifetime of the owning \ref Room. - */ + /// Stop all readers and clear all callback registrations. + /// + /// Used during room teardown or EOS handling to ensure no reader + /// thread survives beyond the lifetime of the owning `Room`. + /// + /// @note Thread-safety: Thread-safe. void stopAll(); private: friend class SubscriptionThreadDispatcherTest; - /// Compound lookup key for callback dispatch: - /// either `(participant, source, "")` or `(participant, SOURCE_UNKNOWN, - /// track_name)`. + /// Compound lookup key for callback dispatch: either + /// `(participant, source, "")` or + /// `(participant, SOURCE_UNKNOWN, track_name)`. struct CallbackKey { std::string participant_identity; TrackSource source; @@ -328,7 +346,8 @@ class LIVEKIT_API SubscriptionThreadDispatcher { } }; - /// Hash function for \ref CallbackKey so it can be used in unordered maps. + /// Hash function for `CallbackKey` so it can be used in unordered + /// maps. struct CallbackKeyHash { std::size_t operator()(const CallbackKey& k) const { auto h1 = std::hash{}(k.participant_identity); @@ -338,14 +357,16 @@ class LIVEKIT_API SubscriptionThreadDispatcher { } }; - /// Active read-side resources for one audio/video subscription dispatch slot. + /// Active read-side resources for one audio/video subscription + /// dispatch slot. struct ActiveReader { std::shared_ptr audio_stream; std::shared_ptr video_stream; std::thread thread; }; - /// Compound lookup key for a remote participant identity and data track name. + /// Compound lookup key for a remote participant identity and data + /// track name. struct DataCallbackKey { std::string participant_identity; std::string track_name; @@ -355,7 +376,7 @@ class LIVEKIT_API SubscriptionThreadDispatcher { } }; - /// Hash function for \ref DataCallbackKey. + /// Hash function for `DataCallbackKey`. struct DataCallbackKeyHash { std::size_t operator()(const DataCallbackKey& k) const { auto h1 = std::hash{}(k.participant_identity); @@ -370,7 +391,8 @@ class LIVEKIT_API SubscriptionThreadDispatcher { DataFrameCallback callback; }; - /// Active read-side resources for one data track stream subscription. + /// Active read-side resources for one data track stream + /// subscription. struct ActiveDataReader { std::shared_ptr remote_track; std::mutex sub_mutex; @@ -378,67 +400,68 @@ class LIVEKIT_API SubscriptionThreadDispatcher { std::thread thread; }; - /// Stored audio callback registration plus stream-construction options. + /// Stored audio callback registration plus stream-construction + /// options. struct RegisteredAudioCallback { AudioFrameCallback callback; AudioStream::Options options; }; - /// Stored video callback registration plus stream-construction options. + /// Stored video callback registration plus stream-construction + /// options. struct RegisteredVideoCallback { VideoFrameCallback legacy_callback; VideoFrameEventCallback event_callback; VideoStream::Options options; }; - /// Remove and close the active reader for \p key, returning its thread. - /// - /// Must be called with \ref lock_ held. The returned thread, if joinable, - /// must be joined after releasing the lock. + /// Remove and close the active reader for `key`, returning its + /// thread. Must be called with `lock_` held. The returned thread, if + /// joinable, must be joined after releasing the lock. std::thread extractReaderThreadLocked(const CallbackKey& key); - /// Select the appropriate reader startup path for \p track. - /// - /// Must be called with \ref lock_ held. + /// Select the appropriate reader startup path for `track`. Must be + /// called with `lock_` held. std::thread startReaderLocked(const CallbackKey& key, const std::shared_ptr& track); - /// Start an audio reader thread for \p key using \p track. - /// - /// Must be called with \ref lock_ held. Any previous reader for the same key - /// is extracted and returned to the caller for joining outside the lock. + /// Start an audio reader thread for `key` using `track`. Must be + /// called with `lock_` held. Any previous reader for the same key + /// is extracted and returned to the caller for joining outside the + /// lock. std::thread startAudioReaderLocked(const CallbackKey& key, const std::shared_ptr& track, const AudioFrameCallback& cb, const AudioStream::Options& opts); - /// Start a video reader thread for \p key using \p track. - /// - /// Must be called with \ref lock_ held. Any previous reader for the same key - /// is extracted and returned to the caller for joining outside the lock. + /// Start a video reader thread for `key` using `track`. Must be + /// called with `lock_` held. Any previous reader for the same key + /// is extracted and returned to the caller for joining outside the + /// lock. std::thread startVideoReaderLocked(const CallbackKey& key, const std::shared_ptr& track, const RegisteredVideoCallback& callback); - /// Extract and close the data reader for a given callback ID, returning its - /// thread. Must be called with \ref lock_ held. + /// Extract and close the data reader for a given callback ID, + /// returning its thread. Must be called with `lock_` held. std::thread extractDataReaderThreadLocked(DataFrameCallbackId id); - /// Extract and close the data reader for a given (participant, track_name) - /// key, returning its thread. Must be called with \ref lock_ held. + /// Extract and close the data reader for a given (participant, + /// track_name) key, returning its thread. Must be called with + /// `lock_` held. std::thread extractDataReaderThreadLocked(const DataCallbackKey& key); - /// Start a data reader thread for the given callback ID, key, and track. - /// Must be called with \ref lock_ held. + /// Start a data reader thread for the given callback ID, key, and + /// track. Must be called with `lock_` held. std::thread startDataReaderLocked(DataFrameCallbackId id, const DataCallbackKey& key, const std::shared_ptr& track, const DataFrameCallback& cb); /// Protects callback registration maps and active reader state. mutable std::mutex lock_; - /// Registered audio frame callbacks keyed by \ref CallbackKey. + /// Registered audio frame callbacks keyed by `CallbackKey`. std::unordered_map audio_callbacks_; - /// Registered video frame callbacks keyed by \ref CallbackKey. + /// Registered video frame callbacks keyed by `CallbackKey`. std::unordered_map video_callbacks_; - /// Active stream/thread state keyed by \ref CallbackKey. + /// Active stream/thread state keyed by `CallbackKey`. std::unordered_map active_readers_; /// Next auto-increment ID for data frame callbacks. @@ -450,10 +473,12 @@ class LIVEKIT_API SubscriptionThreadDispatcher { /// Active data reader threads keyed by callback ID. std::unordered_map> active_data_readers_; - /// Currently published remote data tracks, keyed by (participant, name). + /// Currently published remote data tracks, keyed by (participant, + /// name). std::unordered_map, DataCallbackKeyHash> remote_data_tracks_; - /// Hard limit on concurrently active per-subscription reader threads. + /// Hard limit on concurrently active per-subscription reader + /// threads. static constexpr int kMaxActiveReaders = 20; }; diff --git a/include/livekit/tracing.h b/include/livekit/tracing.h index a630ffe6..d62c4a5c 100644 --- a/include/livekit/tracing.h +++ b/include/livekit/tracing.h @@ -23,35 +23,41 @@ namespace livekit { -/** - * Start tracing and write events to a file. - * - * Events are written to the file asynchronously by a background thread. - * The file is written in Chrome trace format (JSON), viewable in: - * - Chrome: chrome://tracing - * - Perfetto: https://ui.perfetto.dev - * - * @param trace_file_path Path to the output trace file (e.g., "trace.json") - * @param categories Categories to enable (empty = all categories). - * Supports wildcards: "livekit.*" matches all livekit - * categories. - * @return true if tracing was started, false if already running or file error - */ +/// Start tracing and write events to a file. +/// +/// Events are written to the file asynchronously by a background thread. +/// The file is written in Chrome trace format (JSON), viewable in: +/// - Chrome: `chrome://tracing` +/// - Perfetto: +/// +/// @param trace_file_path Path to the output trace file (e.g. +/// `"trace.json"`). +/// @param categories Categories to enable (empty = all categories). +/// Supports wildcards: `"livekit.*"` matches all +/// `livekit` categories. +/// @return `true` if tracing was started, `false` if it +/// was already running or the file could not be +/// opened. +/// +/// @note Thread-safety: Thread-safe. Calls from multiple threads are +/// serialized internally; only the first concurrent start has effect. LIVEKIT_API bool startTracing(const std::string& trace_file_path, const std::vector& categories = {}); -/** - * Stop tracing and flush remaining events to file. - * - * This blocks until all pending events are written and the file is closed. - * After stopping, the trace file is complete and ready for analysis. - */ +/// Stop tracing and flush remaining events to file. +/// +/// Blocks until all pending events are written and the file is closed. +/// After stopping, the trace file is complete and ready for analysis. +/// +/// @note Thread-safety: Thread-safe. Calls from multiple threads are +/// serialized internally; concurrent stops are safe (only the first has +/// effect). LIVEKIT_API void stopTracing(); -/** - * Check if tracing is currently active. - * - * @return true if tracing is running - */ +/// Check if tracing is currently active. +/// +/// @return `true` if tracing is running. +/// +/// @note Thread-safety: Thread-safe. LIVEKIT_API bool isTracingEnabled(); } // namespace livekit diff --git a/include/livekit/track.h b/include/livekit/track.h index 8051f0fd..ee146804 100644 --- a/include/livekit/track.h +++ b/include/livekit/track.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -32,12 +32,14 @@ namespace livekit { class LocalTrackPublication; +/// High-level media kind for a `Track`. enum class TrackKind { KIND_UNKNOWN = 0, KIND_AUDIO = 1, KIND_VIDEO = 2, }; +/// Logical source of a track (camera, microphone, screen share, etc.). enum class TrackSource { SOURCE_UNKNOWN = 0, SOURCE_CAMERA = 1, @@ -46,12 +48,14 @@ enum class TrackSource { SOURCE_SCREENSHARE_AUDIO = 4, }; +/// Server-side stream state for a track subscription. enum class StreamState { STATE_UNKNOWN = 0, STATE_ACTIVE = 1, STATE_PAUSED = 2, }; +/// Audio-track feature flags that may be advertised by the SDK. enum class AudioTrackFeature { TF_STEREO = 0, TF_NO_DTX = 1, @@ -62,63 +66,159 @@ enum class AudioTrackFeature { TF_PRECONNECT_BUFFER = 6, }; +/// Per-participant subscription permission entry used by +/// `LocalParticipant::setTrackSubscriptionPermissions`. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct ParticipantTrackPermission { + /// Identity of the participant the permission applies to. std::string participant_identity; + + /// If set, overrides the default "allow all" behavior for this + /// participant. std::optional allow_all; + + /// Specific track SIDs this participant is permitted to subscribe to + /// (only consulted when `allow_all` is not present or `false`). std::vector allowed_track_sids; }; // ============================================================ // Base Track // ============================================================ + +/// Base class for audio, video, and other media tracks. +/// +/// Tracks expose a stable read-only view of their metadata along with an +/// owned FFI handle that references the underlying Rust track object. +/// Concrete subclasses (`LocalAudioTrack`, `RemoteVideoTrack`, etc.) add +/// kind-specific behavior. +/// +/// @note Thread-safety: Not thread-safe. A single `Track` instance must +/// not be mutated concurrently from multiple threads. Read-only +/// accessors that return cached metadata may be called concurrently +/// while no mutator is active. class LIVEKIT_API Track { public: virtual ~Track() = default; - // Read-only properties + /// @return The SFU-assigned track identifier (SID). const std::string& sid() const noexcept { return sid_; } + + /// @return The publisher-assigned track name. const std::string& name() const noexcept { return name_; } + + /// @return The media kind (audio or video) of this track. TrackKind kind() const noexcept { return kind_; } + + /// @return The current server-side stream state for this track. StreamState stream_state() const noexcept { return state_; } + + /// @return Whether the track is currently muted. bool muted() const noexcept { return muted_; } + + /// @return `true` if this track was published by a remote participant, + /// `false` for local tracks. bool remote() const noexcept { return remote_; } - // Optional publication info + /// @return The publication source (camera / microphone / etc.) if + /// known. std::optional source() const noexcept { return source_; } + + /// @return Whether the publisher uses simulcast for this track, if + /// known. std::optional simulcasted() const noexcept { return simulcasted_; } + + /// @return The publication width in pixels (video tracks), if known. std::optional width() const noexcept { return width_; } + + /// @return The publication height in pixels (video tracks), if known. std::optional height() const noexcept { return height_; } + + /// @return The negotiated MIME type for the track, if known. // std::string can actually throw, suppressing for now to maintain API - // compatibility + // compatibility. // NOLINTNEXTLINE(bugprone-exception-escape) std::optional mime_type() const noexcept { return mime_type_; } - // Handle access + /// @return Whether the underlying FFI handle is valid. bool has_handle() const noexcept { return handle_.valid(); } + + /// @return The raw FFI handle id used in FFI requests. uintptr_t ffi_handle_id() const noexcept { return handle_.get(); } - // Async get stats + /// Asynchronously retrieve WebRTC statistics for this track. + /// + /// @return A `std::future` resolving to a vector of `RtcStats` once the + /// FFI call completes. The future may surface FFI errors via + /// exceptions stored on the future. + /// + /// @note Thread-safety: Thread-safe. The call dispatches the FFI + /// request immediately and may be issued from any thread; the returned + /// future is completed on the FFI callback thread. std::future> getStats() const; - /// After publishing a local track, associates the \ref LocalTrackPublication - /// with this track. Default implementation is a no-op (e.g. remote tracks). + /// After publishing a local track, associates the + /// `LocalTrackPublication` with this track. Default implementation is a + /// no-op (e.g. remote tracks). + /// + /// @param publication Publication to associate with this track. + /// + /// @note Thread-safety: Not thread-safe. Called by `LocalParticipant` + /// during publish; concurrent mutation is undefined behavior. virtual void setPublication(const std::shared_ptr& publication) noexcept { (void)publication; } - // Internal updates (called by Room) + /// Update the cached stream state. + /// + /// @param s New stream state value. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void setStreamState(StreamState s) noexcept { state_ = s; } + + /// Update the cached muted state. + /// + /// @param m `true` if the track is now muted. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void setMuted(bool m) noexcept { muted_ = m; } + + /// Update the cached track name. + /// + /// @param n New track name (consumed via move). + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void setName(std::string n) noexcept { name_ = std::move(n); } protected: + /// Construct a `Track` with a populated FFI handle and metadata. + /// + /// @param handle FFI handle owning this track's Rust counterpart. + /// @param sid SFU-assigned track identifier. + /// @param name Publisher-assigned track name. + /// @param kind Media kind. + /// @param state Initial stream state. + /// @param muted Whether the track starts muted. + /// @param remote `true` for tracks published by a remote participant. Track(FfiHandle handle, std::string sid, std::string name, TrackKind kind, StreamState state, bool muted, bool remote); + /// Populate the optional publication-derived fields after the track is + /// constructed. + /// + /// @param source Publication source (camera, microphone, etc.). + /// @param simulcasted Whether the publisher uses simulcast. + /// @param width Publication width in pixels (video tracks). + /// @param height Publication height in pixels (video tracks). + /// @param mime_type Negotiated MIME type. void setPublicationFields(std::optional source, std::optional simulcasted, std::optional width, std::optional height, std::optional mime_type); private: - FfiHandle handle_; // Owned + FfiHandle handle_; std::string sid_; std::string name_; @@ -134,4 +234,4 @@ class LIVEKIT_API Track { std::optional mime_type_; }; -} // namespace livekit \ No newline at end of file +} // namespace livekit diff --git a/include/livekit/track_publication.h b/include/livekit/track_publication.h index 7a8d9ead..72e37b5b 100644 --- a/include/livekit/track_publication.h +++ b/include/livekit/track_publication.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -32,12 +32,17 @@ class Track; class LocalTrack; class RemoteTrack; -/** - * C++ TrackPublication. - * - * Wraps the immutable publication info plus an FFI handle, and - * holds a weak reference to the associated Track (if any). - */ +/// Publication-side metadata for a track. +/// +/// Wraps the immutable publication info plus an FFI handle, and holds a +/// shared reference to the associated `Track` (if any). `LocalParticipant` +/// and `RemoteParticipant` expose their publications as subclasses of +/// this type. +/// +/// @note Thread-safety: Not thread-safe. A single `TrackPublication` +/// instance must not be mutated concurrently from multiple threads. +/// Read-only accessors that return cached metadata may be called +/// concurrently while no mutator is active. class LIVEKIT_API TrackPublication { public: virtual ~TrackPublication() = default; @@ -47,29 +52,81 @@ class LIVEKIT_API TrackPublication { TrackPublication(TrackPublication&&) noexcept = default; TrackPublication& operator=(TrackPublication&&) noexcept = default; - // Basic metadata + /// @return The SFU-assigned publication / track identifier (SID). const std::string& sid() const noexcept { return sid_; } + + /// @return The publisher-assigned track name. const std::string& name() const noexcept { return name_; } + + /// @return The media kind (audio or video) of the publication. TrackKind kind() const noexcept { return kind_; } + + /// @return The track source (camera, microphone, etc.). TrackSource source() const noexcept { return source_; } + + /// @return Whether the publisher uses simulcast for this track. bool simulcasted() const noexcept { return simulcasted_; } + + /// @return Publication width in pixels (video tracks), or `0` if + /// unknown. std::uint32_t width() const noexcept { return width_; } + + /// @return Publication height in pixels (video tracks), or `0` if + /// unknown. std::uint32_t height() const noexcept { return height_; } + + /// @return The negotiated MIME type for the publication. const std::string& mimeType() const noexcept { return mime_type_; } + + /// @return Whether the publication is currently muted. bool muted() const noexcept { return muted_; } + + /// Update the cached muted state. + /// + /// @param muted `true` if the publication should be reported as + /// muted. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events. void setMuted(bool muted) noexcept { muted_ = muted; } + /// @return The end-to-end encryption type advertised for this + /// publication. EncryptionType encryptionType() const noexcept { return encryption_type_; } + + /// @return Set of audio-track feature flags advertised by the publisher. const std::vector& audioFeatures() const noexcept { return audio_features_; } - /// Underlying FFI handle value. + /// @return The raw FFI handle value backing this publication. uintptr_t ffiHandleId() const noexcept { return handle_.get(); } - /// Associated Track (if attached). + /// @return The associated `Track`, or `nullptr` if no track is + /// attached. std::shared_ptr track() const noexcept { return track_; } + + /// Attach or replace the associated `Track`. + /// + /// @param track Track instance to associate with this publication. + /// + /// @note Thread-safety: Not thread-safe. Called by `Room` while + /// processing FFI events; concurrent mutation is undefined behavior. void setTrack(const std::shared_ptr& track) noexcept { track_ = track; } protected: + /// Construct a `TrackPublication` from FFI-supplied metadata. + /// + /// @param handle FFI handle owning the publication. + /// @param sid SFU-assigned publication SID. + /// @param name Publisher-assigned track name. + /// @param kind Media kind (audio or video). + /// @param source Track source (camera, microphone, etc.). + /// @param simulcasted Whether the publisher uses simulcast. + /// @param width Publication width in pixels (video tracks). + /// @param height Publication height in pixels (video tracks). + /// @param mime_type Negotiated MIME type. + /// @param muted Whether the publication starts muted. + /// @param encryption_type End-to-end encryption type. + /// @param audio_features Advertised audio-track features. TrackPublication(FfiHandle handle, std::string sid, std::string name, TrackKind kind, TrackSource source, bool simulcasted, std::uint32_t width, std::uint32_t height, std::string mime_type, bool muted, EncryptionType encryption_type, std::vector audio_features); diff --git a/include/livekit/video_frame.h b/include/livekit/video_frame.h index 4520aeed..8caf5fda 100644 --- a/include/livekit/video_frame.h +++ b/include/livekit/video_frame.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -25,31 +25,55 @@ namespace livekit { -// Mirror of WebRTC video buffer type +/// Pixel format for a video buffer (mirror of the WebRTC video buffer +/// types supported by the FFI). enum class VideoBufferType { RGBA = 0, ABGR, ARGB, BGRA, RGB24, I420, I420A, I422, I444, I010, NV12 }; +/// Describes the layout of a single image plane within a `VideoFrame`'s +/// backing buffer. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct VideoPlaneInfo { - std::uintptr_t data_ptr; // pointer to plane data (for FFI) - std::uint32_t stride; // bytes per row - std::uint32_t size; // plane size in bytes + /// Pointer to the plane's first byte (used for FFI calls). + std::uintptr_t data_ptr; + + /// Stride (bytes per row) for this plane. + std::uint32_t stride; + + /// Total size of this plane in bytes. + std::uint32_t size; }; namespace proto { class OwnedVideoBuffer; } -/** - * Public SDK representation of a video frame. - * - * - Owns its pixel buffer (std::vector). - * - Developers can allocate and fill frames in C++ and pass them to the SDK. - * - The SDK can expose the backing memory to Rust via data_ptr + layout for - * the duration of a blocking FFI call (similar to AudioFrame). - */ +/// Public SDK representation of a video frame. +/// +/// - Owns its pixel buffer (`std::vector`). +/// - Developers can allocate and fill frames in C++ and pass them to the +/// SDK. +/// - The SDK can expose the backing memory to Rust via `data_ptr` + +/// layout for the duration of a blocking FFI call (similar to +/// `AudioFrame`). +/// +/// @note Thread-safety: Not thread-safe. A single `VideoFrame` instance +/// must not be mutated concurrently from multiple threads; concurrent +/// const-access is safe. class LIVEKIT_API VideoFrame { public: + /// Construct an empty frame (zero dimensions, no buffer). VideoFrame(); + + /// Construct a frame from an existing pixel buffer. + /// + /// @param width Frame width in pixels. + /// @param height Frame height in pixels. + /// @param type Pixel format of `data`. + /// @param data Pixel data buffer (consumed via move). Must be sized + /// consistently with `width`, `height`, and `type`. VideoFrame(int width, int height, VideoBufferType type, std::vector data); + virtual ~VideoFrame() = default; VideoFrame(const VideoFrame&) = delete; @@ -57,60 +81,83 @@ class LIVEKIT_API VideoFrame { VideoFrame(VideoFrame&&) noexcept = default; VideoFrame& operator=(VideoFrame&&) noexcept = default; - /** - * Allocate a new frame with the correct buffer size for the given format. - * Data is zero-initialized. - */ + /// Allocate a new frame with the correct buffer size for the given + /// format. Data is zero-initialized. + /// + /// @param width Frame width in pixels. + /// @param height Frame height in pixels. + /// @param type Pixel format. + /// @return A new `VideoFrame` with a zeroed buffer of the right + /// size. + /// + /// @note Thread-safety: Thread-safe. Pure factory function. static VideoFrame create(int width, int height, VideoBufferType type); - // Basic properties + /// @return Frame width in pixels. int width() const noexcept { return width_; } + + /// @return Frame height in pixels. int height() const noexcept { return height_; } + + /// @return Pixel format of the frame's buffer. VideoBufferType type() const noexcept { return type_; } + /// @return Mutable pointer to the start of the pixel buffer. std::uint8_t* data() noexcept { return data_.data(); } + + /// @return Const pointer to the start of the pixel buffer. const std::uint8_t* data() const noexcept { return data_.data(); } + + /// @return Size of the pixel buffer in bytes. std::size_t dataSize() const noexcept { return data_.size(); } - /** - * Compute plane layout for this frame (Y/U/V, UV, etc.), in terms of - * pointers & sizes relative to this frame's backing buffer. - * - * For packed formats (ARGB, RGB24) this will be either 1 plane or empty. - */ + /// Compute plane layout for this frame (Y/U/V, UV, etc.), in terms of + /// pointers and sizes relative to this frame's backing buffer. + /// + /// For packed formats (`ARGB`, `RGB24`) this will be either 1 plane or + /// empty. + /// + /// @return One `VideoPlaneInfo` per plane in the frame. std::vector planeInfos() const; - /** - * Convert this frame into another pixel format. - * - * This uses the underlying FFI `video_convert` pipeline to transform the - * current frame into a new `VideoFrame` with the requested - * `dst` buffer type (e.g. ARGB → I420, BGRA → RGB24, etc.). - * - * @param dst Desired output format (see VideoBufferType). - * @param flip_y If true, the converted frame will be vertically flipped. - * - * @return A new VideoFrame containing the converted image data. - * - * Notes: - * - This function allocates a new buffer and copies pixel data; it does - * not modify the original frame. - * - This function performs a full CPU-based pixel conversion**. Depending - * on resolution and format, this may involve substantial computation - * (e.g., color-space transforms, planar repacking, vertical flipping). - * Avoid calling this inside tight real-time loops unless necessary. - * - Throws std::runtime_error if the FFI conversion fails or if the - * format combination is unsupported. - * - * Typical usage: - * VideoFrame i420 = frame.convert(VideoBufferType::I420); - */ + /// Convert this frame into another pixel format. + /// + /// Uses the underlying FFI `video_convert` pipeline to transform the + /// current frame into a new `VideoFrame` with the requested `dst` + /// buffer type (e.g. `ARGB` → `I420`, `BGRA` → `RGB24`, etc.). + /// + /// @param dst Desired output format. + /// @param flip_y If `true`, the converted frame is vertically + /// flipped. + /// + /// @return A new `VideoFrame` containing the converted image data. + /// + /// @throws std::runtime_error if the FFI conversion fails or if the + /// format combination is unsupported. + /// + /// Notes: + /// - This function allocates a new buffer and copies pixel data; it + /// does not modify the original frame. + /// - This function performs a full CPU-based pixel conversion. + /// Depending on resolution and format, this may involve substantial + /// computation (e.g., color-space transforms, planar repacking, + /// vertical flipping). Avoid calling this inside tight real-time + /// loops unless necessary. + /// + /// Typical usage: + /// ``` + /// VideoFrame i420 = frame.convert(VideoBufferType::I420); + /// ``` + /// + /// @note Thread-safety: Thread-safe with respect to other `VideoFrame` + /// instances. A single instance must not be mutated concurrently with + /// this call. VideoFrame convert(VideoBufferType dst, bool flip_y = false) const; protected: friend class VideoStream; - // Only internal classes (e.g., VideoStream) - // should construct frames directly from FFI buffers. + // Only internal classes (e.g., VideoStream) should construct frames + // directly from FFI buffers. static VideoFrame fromOwnedInfo(const proto::OwnedVideoBuffer& owned); private: diff --git a/include/livekit/video_source.h b/include/livekit/video_source.h index fa2f4378..5cba03d9 100644 --- a/include/livekit/video_source.h +++ b/include/livekit/video_source.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -26,11 +26,9 @@ namespace livekit { class VideoFrame; -/** - * Rotation of a video frame. - * - * Mirrors proto_video.VideoRotation but kept as a public SDK enum. - */ +/// Rotation of a video frame. +/// +/// Mirrors `proto_video.VideoRotation` but kept as a public SDK enum. enum class VideoRotation { VIDEO_ROTATION_0 = 0, VIDEO_ROTATION_90 = 90, @@ -38,43 +36,56 @@ enum class VideoRotation { VIDEO_ROTATION_270 = 270, }; -/** - * Optional packet-trailer metadata carried alongside a video frame. - * - * Each field is independently optional because the corresponding transport - * feature can be negotiated separately. - */ +/// Optional packet-trailer metadata carried alongside a video frame. +/// +/// Each field is independently optional because the corresponding +/// transport feature can be negotiated separately. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct VideoFrameMetadata { + /// User-supplied wall-clock timestamp in microseconds. std::optional user_timestamp_us; + + /// Monotonically increasing frame identifier supplied by the + /// application. std::optional frame_id; }; -/** - * Capture options for a single outbound video frame. - */ +/// Capture options for a single outbound video frame. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct VideoCaptureOptions { + /// Capture-time timestamp in microseconds (typically + /// `system_clock::now()`). std::int64_t timestamp_us = 0; + + /// Rotation applied to the frame at the source. VideoRotation rotation = VideoRotation::VIDEO_ROTATION_0; - // Populate meta data when you want to send user timestamps or frame IDs. + + /// Optional metadata to attach when packet-trailer features are + /// negotiated. std::optional metadata; }; -/** - * Represents a real-time video source that can accept frames from the - * application and feed them into the LiveKit core. - */ +/// A real-time video source that accepts frames from the application and +/// feeds them into the LiveKit core. +/// +/// @note Thread-safety: Not thread-safe. `VideoSource::captureFrame` is +/// **not** safe to call concurrently from multiple threads. Callers must +/// drive `captureFrame` from a single capture thread (typically a +/// camera / screen-capture thread). Other accessors may be read +/// concurrently while no `captureFrame` call is in flight. class LIVEKIT_API VideoSource { public: - /** - * Create a new native video source with a fixed resolution. - * - * @param width Width in pixels. - * @param height Height in pixels. - * - * Throws std::runtime_error if the FFI call fails or the response - * does not contain the expected new_video_source field. - */ + /// Create a new native video source with a fixed resolution. + /// + /// @param width Width in pixels. + /// @param height Height in pixels. + /// + /// @throws std::runtime_error if the FFI call fails or the response + /// does not contain the expected `new_video_source` field. VideoSource(int width, int height); + virtual ~VideoSource() = default; VideoSource(const VideoSource&) = delete; @@ -82,29 +93,39 @@ class LIVEKIT_API VideoSource { VideoSource(VideoSource&&) noexcept = default; VideoSource& operator=(VideoSource&&) noexcept = default; - /// Source resolution as declared at construction. + /// @return Source width as declared at construction, in pixels. int width() const noexcept { return width_; } + + /// @return Source height as declared at construction, in pixels. int height() const noexcept { return height_; } - /// Underlying FFI handle ID (0 if invalid). + /// @return Underlying FFI handle ID (`0` if invalid). std::uint64_t ffi_handle_id() const noexcept { return handle_.get(); } - /** - * Push a VideoFrame into the FFI video source. - * - * @param frame Video frame to send. - * @param options Timestamp, rotation, and optional metadata for this frame. - */ + /// Push a `VideoFrame` into the FFI video source. + /// + /// @param frame Video frame to send. + /// @param options Timestamp, rotation, and optional metadata for + /// this frame. + /// + /// @note Thread-safety: Not thread-safe. Concurrent `captureFrame` + /// calls from multiple threads are undefined behavior. void captureFrame(const VideoFrame& frame, const VideoCaptureOptions& options); - /** - * Backward-compatible convenience overload for timestamp + rotation only. - */ + /// Backward-compatible convenience overload for timestamp + rotation + /// only. + /// + /// @param frame Video frame to send. + /// @param timestamp_us Capture-time timestamp in microseconds. + /// @param rotation Rotation applied to the frame at the source. + /// + /// @note Thread-safety: Not thread-safe. Same constraints as the + /// primary overload. void captureFrame(const VideoFrame& frame, std::int64_t timestamp_us = 0, VideoRotation rotation = VideoRotation::VIDEO_ROTATION_0); private: - FfiHandle handle_; // owned FFI handle + FfiHandle handle_; int width_{0}; int height_{0}; }; diff --git a/include/livekit/video_stream.h b/include/livekit/video_stream.h index dcc11645..fdc9ceff 100644 --- a/include/livekit/video_stream.h +++ b/include/livekit/video_stream.h @@ -8,7 +8,7 @@ * 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, + * 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. @@ -33,15 +33,25 @@ namespace livekit { -// A single video frame event delivered by VideoStream::read(). +/// A single video frame event delivered by `VideoStream::read()`. +/// +/// @note Thread-safety: Not thread-safe. POD-like struct. struct VideoFrameEvent { + /// Decoded video frame. VideoFrame frame; - // WebRTC frame timestamp in microseconds. - // This may be translated onto WebRTC's internal capture-time timeline and - // should not be expected to match application-provided metadata such as - // VideoFrameMetadata::user_timestamp_us exactly. + + /// WebRTC frame timestamp in microseconds. + /// + /// This may be translated onto WebRTC's internal capture-time + /// timeline and should not be expected to match application-provided + /// metadata such as `VideoFrameMetadata::user_timestamp_us` exactly. std::int64_t timestamp_us; + + /// Rotation of the frame as reported by the sender. VideoRotation rotation; + + /// Optional metadata (user timestamp, frame ID) if the sender used + /// packet-trailer features. std::optional metadata; }; @@ -49,40 +59,64 @@ namespace proto { class FfiEvent; } -// 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); -// -// VideoFrameEvent ev; -// while (stream->read(ev)) { -// // ev.frame contains the decoded video buffer -// } -// -// stream->close(); // optional, called automatically in destructor -// +/// A pull-based stream of decoded video frames from a remote (or local) +/// LiveKit track. Similar to `AudioStream`, but for video. +/// +/// Typical usage: +/// ``` +/// VideoStream::Options opts; +/// auto stream = VideoStream::fromTrack(remoteVideoTrack, opts); +/// +/// VideoFrameEvent ev; +/// while (stream->read(ev)) { +/// // ev.frame contains the decoded video buffer +/// } +/// +/// stream->close(); // optional, called automatically in destructor +/// ``` +/// +/// @note Thread-safety: Thread-safe. An internal `std::mutex` + +/// `std::condition_variable` coordinate the FFI producer thread and the +/// consumer reader thread. Concurrent `read()` and `close()` from +/// different threads is safe. class LIVEKIT_API VideoStream { public: + /// Configuration options for `VideoStream` creation. + /// + /// @note Thread-safety: Not thread-safe. POD-like struct. struct Options { - // Maximum number of VideoFrameEvent items buffered in the internal queue. - // 0 means "unbounded" (the queue can grow without limit). - // - // With a non-zero capacity, the queue behaves like a ring-buffer: if it - // is full, the oldest frame is dropped when a new one arrives. + /// Maximum number of `VideoFrameEvent` items buffered in the + /// internal queue. `0` means "unbounded" (the queue can grow + /// without limit). + /// + /// With a non-zero capacity, the queue behaves like a ring-buffer: + /// if it is full, the oldest frame is dropped when a new one + /// arrives. std::size_t capacity{0}; - // Preferred pixel format for frames delivered by read(). The FFI layer - // converts into this format if supported (e.g., RGBA, BGRA, I420, ...). + /// Preferred pixel format for frames delivered by `read()`. The FFI + /// layer converts into this format if supported (e.g., `RGBA`, + /// `BGRA`, `I420`, ...). VideoBufferType format{VideoBufferType::RGBA}; }; - // Factory: create a VideoStream bound to a specific Track + /// Create a `VideoStream` bound to a specific `Track`. + /// + /// @param track Track to read video from. + /// @param options Stream configuration. + /// @return A new `VideoStream` instance. + /// + /// @note Thread-safety: Thread-safe. static std::shared_ptr fromTrack(const std::shared_ptr& track, const Options& options); - // Factory: create a VideoStream from a Participant + TrackSource + /// Create a `VideoStream` from a `Participant` + `TrackSource`. + /// + /// @param participant Participant whose track will be read. + /// @param track_source Source (camera, screen share, etc.). + /// @param options Stream configuration. + /// @return A new `VideoStream` instance. + /// + /// @note Thread-safety: Thread-safe. static std::shared_ptr fromParticipant(Participant& participant, TrackSource track_source, const Options& options); @@ -93,32 +127,38 @@ class LIVEKIT_API VideoStream { VideoStream(VideoStream&&) noexcept; VideoStream& operator=(VideoStream&&) noexcept; - /// Blocking read: waits until a VideoFrameEvent is available in the internal - /// queue, or the stream reaches EOS / is closed. + /// Blocking read: waits until a `VideoFrameEvent` is available in the + /// internal queue, or the stream reaches EOS / is closed. /// - /// \param out On success, filled with the next video frame event. - /// \return true if a frame was delivered; false if the stream ended - /// (end-of-stream or close()) and no more data is available. + /// @param out On success, filled with the next video frame event. + /// @return `true` if a frame was delivered; `false` if the stream + /// ended (end-of-stream or `close()`) and no more data is + /// available. + /// + /// @note Thread-safety: Thread-safe. Multiple consumer threads may + /// call `read()` concurrently; each delivered frame is observed by + /// exactly one caller. bool read(VideoFrameEvent& out); /// Signal that we are no longer interested in video frames. /// - /// This disposes the underlying FFI video stream, unregisters the listener - /// from FfiClient, marks the stream as closed, and wakes any blocking read(). - /// After calling close(), further calls to read() will return false. + /// Disposes the underlying FFI video stream, unregisters the listener + /// from `FfiClient`, marks the stream as closed, and wakes any + /// blocking `read()`. After calling `close()`, further calls to + /// `read()` will return `false`. + /// + /// @note Thread-safety: Thread-safe. May be called from any thread, + /// including concurrently with `read()`. void close(); private: VideoStream() = default; - // Internal init helpers, used by the factories void initFromTrack(const std::shared_ptr& track, const Options& options); void initFromParticipant(Participant& participant, TrackSource source, const Options& options); - // FFI event handler (registered with FfiClient) void onFfiEvent(const proto::FfiEvent& event); - // Queue helpers void pushFrame(VideoFrameEvent&& ev); void pushEos(); @@ -129,10 +169,8 @@ class LIVEKIT_API VideoStream { bool eof_{false}; bool closed_{false}; - // Underlying FFI handle for the video stream FfiHandle stream_handle_; - // Listener id registered on FfiClient std::int32_t listener_id_{0}; }; diff --git a/include/livekit/visibility.h b/include/livekit/visibility.h index 3f256e18..18d02918 100644 --- a/include/livekit/visibility.h +++ b/include/livekit/visibility.h @@ -16,18 +16,21 @@ #pragma once -// LIVEKIT_API marks a symbol as part of the public ABI of liblivekit. -// -// On Unix, the SDK is built with -fvisibility=hidden / -fvisibility-inlines-hidden, -// so every symbol defaults to hidden. LIVEKIT_API re-exposes the symbol with -// default visibility. Consumers also need default visibility so public RTTI -// stays unique across shared-library boundaries. -// -// On Windows, the SDK is built without WINDOWS_EXPORT_ALL_SYMBOLS, so symbols -// must be explicitly tagged with __declspec(dllexport) when building the SDK -// and __declspec(dllimport) when consuming it. LIVEKIT_BUILDING_SDK is defined -// only when compiling the SDK itself (set in CMakeLists.txt). - +/// Marks a symbol as part of the public ABI of `liblivekit`. +/// +/// On Unix, the SDK is built with `-fvisibility=hidden` / +/// `-fvisibility-inlines-hidden`, so every symbol defaults to hidden. +/// `LIVEKIT_API` re-exposes the symbol with default visibility. Consumers +/// also need default visibility so public RTTI stays unique across shared +/// library boundaries. +/// +/// On Windows, the SDK is built without `WINDOWS_EXPORT_ALL_SYMBOLS`, so +/// symbols must be explicitly tagged with `__declspec(dllexport)` when +/// building the SDK and `__declspec(dllimport)` when consuming it. +/// `LIVEKIT_BUILDING_SDK` is defined only when compiling the SDK itself +/// (set in `CMakeLists.txt`). +/// +/// @note Thread-safety: N/A. Compile-time macro. #if defined(_WIN32) #if defined(LIVEKIT_BUILDING_SDK) #define LIVEKIT_API __declspec(dllexport) @@ -38,17 +41,18 @@ #define LIVEKIT_API __attribute__((visibility("default"))) #endif -// LIVEKIT_INTERNAL_API marks a symbol that is NOT part of the public ABI but -// must remain visible so that the in-tree test binaries (which link against -// the same shared library) can resolve it. -// -// External consumers must not rely on LIVEKIT_INTERNAL_API symbols; they may -// change or disappear without notice. -// -// On Windows, internal symbols are exported the same way as public ones -// because tests link via the standard import library; on Unix, hidden -// visibility is overridden for these specific symbols only. - +/// Marks a symbol that is NOT part of the public ABI but must remain +/// visible so that the in-tree test binaries (which link against the same +/// shared library) can resolve it. +/// +/// External consumers must not rely on `LIVEKIT_INTERNAL_API` symbols; they +/// may change or disappear without notice. +/// +/// On Windows, internal symbols are exported the same way as public ones +/// because tests link via the standard import library. On Unix, hidden +/// visibility is overridden for these specific symbols only. +/// +/// @note Thread-safety: N/A. Compile-time macro. #if defined(_WIN32) #define LIVEKIT_INTERNAL_API LIVEKIT_API #else