diff --git a/CMakeLists.txt b/CMakeLists.txt index 473fd49..ec04b46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,7 @@ add_library(livekit include/livekit/audio_source.h include/livekit/audio_stream.h include/livekit/room.h + include/livekit/room_event_types.h include/livekit/room_delegate.h include/livekit/ffi_handle.h include/livekit/ffi_client.h diff --git a/examples/simple_room/main.cpp b/examples/simple_room/main.cpp index 9338e2f..5741cc5 100644 --- a/examples/simple_room/main.cpp +++ b/examples/simple_room/main.cpp @@ -156,8 +156,9 @@ class SimpleRoomDelegate : public livekit::RoomDelegate { void onParticipantConnected( livekit::Room & /*room*/, const livekit::ParticipantConnectedEvent &ev) override { - std::cout << "[Room] participant connected: identity=" << ev.identity - << " name=" << ev.name << "\n"; + std::cout << "[Room] participant connected: identity=" + << ev.participant->identity() + << " name=" << ev.participant->name() << "\n"; } void onTrackSubscribed(livekit::Room & /*room*/, @@ -172,19 +173,18 @@ class SimpleRoomDelegate : public livekit::RoomDelegate { << participant_identity << " track_sid=" << track_sid << " name=" << track_name; if (ev.track) { - std::cout << " kind=" << static_cast(ev.track->kind()) << "\n"; + std::cout << " kind=" << static_cast(ev.track->kind()); } if (ev.publication) { - std::cout << " source=" << static_cast(ev.publication->source()) - << "\n"; + std::cout << " source=" << static_cast(ev.publication->source()); } + std::cout << std::endl; // If this is a VIDEO track, create a VideoStream and attach to renderer if (ev.track && ev.track->kind() == TrackKind::KIND_VIDEO) { VideoStream::Options opts; opts.format = livekit::VideoBufferType::RGBA; auto video_stream = VideoStream::fromTrack(ev.track, opts); - std::cout << "after fromTrack " << std::endl; if (!video_stream) { std::cerr << "Failed to create VideoStream for track " << track_sid << "\n"; diff --git a/include/livekit/audio_frame.h b/include/livekit/audio_frame.h index 529d658..a7011c9 100644 --- a/include/livekit/audio_frame.h +++ b/include/livekit/audio_frame.h @@ -43,6 +43,7 @@ class AudioFrame { AudioFrame(std::vector data, int sample_rate, int num_channels, int samples_per_channel); AudioFrame(); // Default constructor + virtual ~AudioFrame() = default; /** * Create a new zero-initialized AudioFrame instance. diff --git a/include/livekit/audio_source.h b/include/livekit/audio_source.h index 8e52bbd..e3c864a 100644 --- a/include/livekit/audio_source.h +++ b/include/livekit/audio_source.h @@ -43,8 +43,7 @@ class AudioSource { * @param queue_size_ms Max buffer duration for the internal queue in ms. */ AudioSource(int sample_rate, int num_channels, int queue_size_ms = 1000); - - ~AudioSource(); + virtual ~AudioSource() = default; AudioSource(const AudioSource &) = delete; AudioSource &operator=(const AudioSource &) = delete; diff --git a/include/livekit/audio_stream.h b/include/livekit/audio_stream.h index 3616e8d..5baefc9 100644 --- a/include/livekit/audio_stream.h +++ b/include/livekit/audio_stream.h @@ -85,7 +85,7 @@ class AudioStream { TrackSource track_source, const Options &options); - ~AudioStream(); + virtual ~AudioStream(); /// No copy, assignment constructors. AudioStream(const AudioStream &) = delete; diff --git a/include/livekit/livekit.h b/include/livekit/livekit.h index 3eab63d..6bb4152 100644 --- a/include/livekit/livekit.h +++ b/include/livekit/livekit.h @@ -26,6 +26,7 @@ #include "remote_track_publication.h" #include "room.h" #include "room_delegate.h" +#include "room_event_types.h" #include "track_publication.h" #include "video_frame.h" #include "video_source.h" diff --git a/include/livekit/local_participant.h b/include/livekit/local_participant.h index 17dc9a0..3d9e36a 100644 --- a/include/livekit/local_participant.h +++ b/include/livekit/local_participant.h @@ -18,7 +18,7 @@ #include "livekit/ffi_handle.h" #include "livekit/participant.h" -#include "livekit/room_delegate.h" +#include "livekit/room_event_types.h" #include "livekit/rpc_error.h" #include @@ -203,6 +203,9 @@ class LocalParticipant : public Participant { 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: diff --git a/include/livekit/participant.h b/include/livekit/participant.h index 0cc7418..9b82b28 100644 --- a/include/livekit/participant.h +++ b/include/livekit/participant.h @@ -39,6 +39,7 @@ class Participant { name_(std::move(name)), identity_(std::move(identity)), metadata_(std::move(metadata)), attributes_(std::move(attributes)), kind_(kind), reason_(reason) {} + virtual ~Participant() = default; // Plain getters (caller ensures threading) const std::string &sid() const noexcept { return sid_; } @@ -72,6 +73,11 @@ class Participant { reason_ = reason; } +protected: + virtual std::shared_ptr + findTrackPublication(const std::string &sid) const = 0; + friend class Room; + private: FfiHandle handle_; std::string sid_, name_, identity_, metadata_; diff --git a/include/livekit/remote_participant.h b/include/livekit/remote_participant.h index d1c1579..cbd8aa3 100644 --- a/include/livekit/remote_participant.h +++ b/include/livekit/remote_participant.h @@ -28,7 +28,7 @@ class RemoteTrackPublication; class RemoteParticipant : public Participant { public: - using TrackPublicationMap = + using PublicationMap = std::unordered_map>; RemoteParticipant(FfiHandle handle, std::string sid, std::string name, @@ -37,20 +37,27 @@ class RemoteParticipant : public Participant { ParticipantKind kind, DisconnectReason reason); // A dictionary of track publications associated with the participant. - const TrackPublicationMap &track_publications() const noexcept { + const PublicationMap &trackPublications() const noexcept { return track_publications_; } // Optional: non-const access if you want to mutate in-place. - TrackPublicationMap &mutable_track_publications() noexcept { + PublicationMap &mutableTrackPublications() noexcept { return track_publications_; } // C++ equivalent of Python's __repr__ 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. + std::shared_ptr + findTrackPublication(const std::string &sid) const override; + friend class Room; + private: - TrackPublicationMap track_publications_; + PublicationMap track_publications_; }; // Convenience for logging / streaming diff --git a/include/livekit/room.h b/include/livekit/room.h index f2e21d4..27e6654 100644 --- a/include/livekit/room.h +++ b/include/livekit/room.h @@ -19,7 +19,7 @@ #include "livekit/ffi_client.h" #include "livekit/ffi_handle.h" -#include "livekit/room_delegate.h" +#include "livekit/room_event_types.h" #include #include @@ -185,6 +185,7 @@ class Room { std::unique_ptr local_participant_; std::unordered_map> remote_participants_; + ConnectionState connection_state_ = ConnectionState::Disconnected; void OnEvent(const proto::FfiEvent &event); }; diff --git a/include/livekit/room_delegate.h b/include/livekit/room_delegate.h index 0e73fae..04474a9 100644 --- a/include/livekit/room_delegate.h +++ b/include/livekit/room_delegate.h @@ -16,474 +16,284 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include "livekit/room_event_types.h" namespace livekit { class Room; -enum class VideoCodec; -enum class TrackSource; -class Track; -class RemoteTrackPublication; -class RemoteParticipant; - -enum class ConnectionQuality { - Poor, - Good, - Excellent, - Lost, -}; - -enum class ConnectionState { - Disconnected, - Connected, - Reconnecting, -}; - -enum class DataPacketKind { - Lossy, - Reliable, -}; - -enum class EncryptionState { - // mirror your proto enum values as needed - Unknown, - On, - Off, -}; - -enum class DisconnectReason { - Unknown = 0, - ClientInitiated, - DuplicateIdentity, - ServerShutdown, - ParticipantRemoved, - RoomDeleted, - StateMismatch, - JoinFailure, - Migration, - SignalClose, - RoomClosed, - UserUnavailable, - UserRejected, - SipTrunkFailure, - ConnectionTimeout, - MediaFailure -}; - -// --------------------------------------------------------- -// Basic data types corresponding to proto messages -// --------------------------------------------------------- - -struct ChatMessageData { - std::string id; - std::int64_t timestamp = 0; - std::string message; - std::optional edit_timestamp; - bool deleted = false; - bool generated = false; -}; - -struct UserPacketData { - std::vector data; - std::optional topic; // optional -}; - -struct SipDtmfData { - std::uint32_t code = 0; - std::optional digit; -}; - -struct RoomInfoData { - std::optional sid; - std::string name; - std::string metadata; - std::uint64_t lossy_dc_buffered_amount_low_threshold = 0; - std::uint64_t reliable_dc_buffered_amount_low_threshold = 0; - std::uint32_t empty_timeout = 0; - std::uint32_t departure_timeout = 0; - std::uint32_t max_participants = 0; - std::int64_t creation_time = 0; - std::uint32_t num_participants = 0; - std::uint32_t num_publishers = 0; - bool active_recording = false; -}; - -struct AttributeEntry { - std::string key; - std::string value; -}; - -struct DataStreamHeaderData { - std::string stream_id; - std::int64_t timestamp = 0; - std::string mime_type; - std::string topic; - std::optional total_length; - std::map attributes; - - // For content_header - enum class ContentType { - None, - Text, - Byte, - } content_type = ContentType::None; - - // TextHeader fields - enum class OperationType { - Create = 0, - Update = 1, - Delete = 2, - Reaction = 3, - }; - std::optional operation_type; - std::optional version; - std::optional reply_to_stream_id; - std::vector attached_stream_ids; - std::optional generated; - - // ByteHeader fields - std::optional name; -}; - -struct DataStreamChunkData { - std::string stream_id; - std::uint64_t chunk_index = 0; - std::vector content; - std::optional version; - std::vector iv; -}; - -struct DataStreamTrailerData { - std::string stream_id; - std::string reason; - std::map attributes; -}; - -// ------------- rooom.proto options ------------------------ - -struct VideoEncodingOptions { - std::uint64_t max_bitrate = 0; - double max_framerate = 0.0; -}; - -struct AudioEncodingOptions { - std::uint64_t max_bitrate = 0; -}; - -struct TrackPublishOptions { - std::optional video_encoding; - std::optional audio_encoding; - std::optional video_codec; - std::optional dtx; - std::optional red; - std::optional simulcast; - std::optional source; - std::optional stream; - std::optional preconnect_buffer; -}; - -// ------------- rooom.proto Transcription ------------------------ - -struct TranscriptionSegment { - std::string id; - std::string text; - std::uint64_t start_time = 0; - std::uint64_t end_time = 0; - bool final = false; - std::string language; -}; - -// --------------------------------------------------------- -// Event structs – “public” representations of RoomEvent.* -// --------------------------------------------------------- - -struct ParticipantConnectedEvent { - // Typically you’d also attach a handle / participant object - std::string identity; // from OwnedParticipant / ParticipantInfo - std::string metadata; - std::string name; -}; - -struct ParticipantDisconnectedEvent { - std::string participant_identity; - DisconnectReason reason = DisconnectReason::Unknown; -}; - -struct LocalTrackPublishedEvent { - std::string track_sid; -}; - -struct LocalTrackUnpublishedEvent { - std::string publication_sid; -}; - -struct LocalTrackSubscribedEvent { - std::string track_sid; -}; - -struct TrackPublishedEvent { - std::string participant_identity; - std::string publication_sid; - std::string track_name; - std::string track_kind; // or an enum if you have one - std::string track_source; // or enum -}; - -struct TrackUnpublishedEvent { - std::string participant_identity; - std::string publication_sid; -}; - -struct TrackSubscribedEvent { - std::shared_ptr track; - std::shared_ptr publication; - RemoteParticipant *participant = nullptr; -}; - -struct TrackUnsubscribedEvent { - std::string participant_identity; - std::string track_sid; -}; - -struct TrackSubscriptionFailedEvent { - std::string participant_identity; - std::string track_sid; - std::string error; -}; - -struct TrackMutedEvent { - std::string participant_identity; - std::string track_sid; -}; - -struct TrackUnmutedEvent { - std::string participant_identity; - std::string track_sid; -}; - -struct ActiveSpeakersChangedEvent { - std::vector participant_identities; -}; - -struct RoomMetadataChangedEvent { - std::string metadata; -}; - -struct RoomSidChangedEvent { - std::string sid; -}; - -struct ParticipantMetadataChangedEvent { - std::string participant_identity; - std::string metadata; -}; - -struct ParticipantNameChangedEvent { - std::string participant_identity; - std::string name; -}; - -struct ParticipantAttributesChangedEvent { - std::string participant_identity; - std::vector attributes; - std::vector changed_attributes; -}; - -struct ParticipantEncryptionStatusChangedEvent { - std::string participant_identity; - bool is_encrypted = false; -}; - -struct ConnectionQualityChangedEvent { - std::string participant_identity; - ConnectionQuality quality = ConnectionQuality::Good; -}; - -struct DataPacketReceivedEvent { - DataPacketKind kind = DataPacketKind::Reliable; - std::string participant_identity; // may be empty - std::optional user; - std::optional sip_dtmf; -}; - -struct Transcription { - std::optional participant_identity; - std::optional track_sid; - std::vector segments; -}; - -struct ConnectionStateChangedEvent { - ConnectionState state = ConnectionState::Disconnected; -}; - -struct DisconnectedEvent { - DisconnectReason reason = DisconnectReason::Unknown; -}; - -struct ReconnectingEvent {}; -struct ReconnectedEvent {}; - -struct RoomEosEvent {}; - -struct DataStreamHeaderReceivedEvent { - std::string participant_identity; - DataStreamHeaderData header; -}; - -struct DataStreamChunkReceivedEvent { - std::string participant_identity; - DataStreamChunkData chunk; -}; - -struct DataStreamTrailerReceivedEvent { - std::string participant_identity; - DataStreamTrailerData trailer; -}; - -struct DataChannelBufferedAmountLowThresholdChangedEvent { - DataPacketKind kind = DataPacketKind::Reliable; - std::uint64_t threshold = 0; -}; - -struct ByteStreamOpenedEvent { - std::uint64_t reader_handle = 0; // from OwnedByteStreamReader.handle - std::string participant_identity; -}; - -struct TextStreamOpenedEvent { - std::uint64_t reader_handle = 0; // from OwnedTextStreamReader.handle - std::string participant_identity; -}; - -struct RoomUpdatedEvent { - RoomInfoData info; -}; - -struct RoomMovedEvent { - RoomInfoData info; -}; - -struct ParticipantsUpdatedEvent { - // You can expand this into a richer participant struct later - std::vector participant_identities; -}; - -struct E2eeStateChangedEvent { - std::string participant_identity; - EncryptionState state = EncryptionState::Unknown; -}; - -struct ChatMessageReceivedEvent { - ChatMessageData message; - std::string participant_identity; -}; - -// --------------------------------------------------------- -// RoomDelegate interface – NO protobuf dependency -// --------------------------------------------------------- +/** + * 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. + */ class RoomDelegate { public: virtual ~RoomDelegate() = default; - // Optional: generic hook with no payload - virtual void onRoomEvent(Room & /*room*/) {} - - // Per-event callbacks. All default no-op so you can add more later - // without breaking existing user code. - + // ------------------------------------------------------------------ // 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 &) { } - // Local track publication + // ------------------------------------------------------------------ + // 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 &) {} + // ------------------------------------------------------------------ // 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 &) {} + // ------------------------------------------------------------------ // Active speakers + // ------------------------------------------------------------------ + + /** + * Called when the list of active speakers changes. + */ virtual void onActiveSpeakersChanged(Room &, const ActiveSpeakersChangedEvent &) {} + // ------------------------------------------------------------------ // 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 &) {} + // ------------------------------------------------------------------ // 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 &) {} + // ------------------------------------------------------------------ // 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 &) {} + // ------------------------------------------------------------------ // E2EE + // ------------------------------------------------------------------ + + /** + * Called when a participant's end-to-end encryption state changes. + */ virtual void onE2eeStateChanged(Room &, const E2eeStateChangedEvent &) {} + // ------------------------------------------------------------------ // EOS + // ------------------------------------------------------------------ + + /** + * Called when the room reaches end-of-stream and will not emit further + * events. + */ virtual void onRoomEos(Room &, const RoomEosEvent &) {} + // ------------------------------------------------------------------ // Data / transcription / chat - virtual void onDataPacketReceived(Room &, const DataPacketReceivedEvent &) {} - virtual void onTranscriptionReceived(Room &, const Transcription &) {} - virtual void onChatMessageReceived(Room &, const ChatMessageReceivedEvent &) { - } + // ------------------------------------------------------------------ + /** + * 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 &) {} + + // ------------------------------------------------------------------ // 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. + */ virtual void onDataChannelBufferedAmountLowThresholdChanged( Room &, const DataChannelBufferedAmountLowThresholdChangedEvent &) {} + // ------------------------------------------------------------------ // 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 &) {} + // ------------------------------------------------------------------ // Participants snapshot + // ------------------------------------------------------------------ + + /** + * Called when a snapshot of participants has been updated. + */ virtual void onParticipantsUpdated(Room &, const ParticipantsUpdatedEvent &) { } }; diff --git a/include/livekit/room_event_types.h b/include/livekit/room_event_types.h new file mode 100644 index 0000000..d70c060 --- /dev/null +++ b/include/livekit/room_event_types.h @@ -0,0 +1,759 @@ +/* + * Copyright 2025 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace livekit { + +// Forward declarations to avoid pulling in heavy headers. +class Track; +class Participant; +class RemoteParticipant; +class LocalTrackPublication; +class RemoteTrackPublication; +class TrackPublication; + +enum class VideoCodec; +enum class TrackSource; + +/** + * Overall quality of a participant's connection. + */ +enum class ConnectionQuality { + Poor = 0, + Good, + Excellent, + Lost, +}; + +/** + * 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). + */ +enum class DataPacketKind { + Lossy, + Reliable, +}; + +/** + * End-to-end encryption state for a participant. + * + * These values mirror the proto::EncryptionState enum. + */ +enum class EncryptionState { + New = 0, + Ok, + EncryptionFailed, + DecryptionFailed, + MissingKey, + KeyRatcheted, + InternalError, +}; + +/** + * Reason why a participant or room was disconnected. + * + * These values mirror the server-side DisconnectReason enum. + */ +enum class DisconnectReason { + Unknown = 0, + ClientInitiated, + DuplicateIdentity, + ServerShutdown, + ParticipantRemoved, + RoomDeleted, + StateMismatch, + JoinFailure, + Migration, + SignalClose, + RoomClosed, + UserUnavailable, + UserRejected, + SipTrunkFailure, + ConnectionTimeout, + MediaFailure +}; + +/** + * Application-level user data carried in a data packet. + */ +struct UserPacketData { + /** Raw payload bytes. */ + std::vector data; + + /** Optional topic name associated with this payload. */ + std::optional topic; +}; + +/** + * SIP DTMF payload carried via data packets. + */ +struct SipDtmfData { + /** DTMF code value. */ + std::uint32_t code = 0; + + /** Human-readable digit representation (e.g. "1", "#"). */ + std::optional digit; +}; + +/** + * Snapshot of core room information. + */ +struct RoomInfoData { + /** Room SID, if known. */ + std::optional sid; + + /** Room name. */ + std::string name; + + /** Arbitrary application metadata associated with the room. */ + std::string metadata; + + /** 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. */ + std::uint64_t reliable_dc_buffered_amount_low_threshold = 0; + + /** 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. + */ + std::uint32_t departure_timeout = 0; + + /** Maximum number of participants allowed in the room. */ + std::uint32_t max_participants = 0; + + /** Creation time of the room (ms since Unix epoch). */ + std::int64_t creation_time = 0; + + /** Approximate number of participants (eventually consistent). */ + std::uint32_t num_participants = 0; + + /** Approximate number of publishers (eventually consistent). */ + std::uint32_t num_publishers = 0; + + /** True if the room is currently being recorded. */ + bool active_recording = false; +}; + +/** + * Key/value pair for participant or room attributes. + */ +struct AttributeEntry { + /** Attribute key. */ + std::string key; + + /** Attribute value. */ + std::string value; + + AttributeEntry() = default; + + 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. + */ +struct DataStreamHeaderData { + /** Unique stream identifier. */ + std::string stream_id; + + /** Timestamp (ms since Unix epoch). */ + std::int64_t timestamp = 0; + + /** MIME type of the content (e.g. "application/json"). */ + std::string mime_type; + + /** Application-defined topic name. */ + std::string topic; + + /** Optional total length in bytes, if known. */ + std::optional total_length; + + /** Custom attributes associated with this stream. */ + std::map attributes; + + /** + * Content type carried by this stream. + */ + enum class ContentType { + None, + Text, + Byte, + } content_type = ContentType::None; + + /** + * Operation type for text streams. + */ + enum class OperationType { + Create = 0, + Update = 1, + Delete = 2, + Reaction = 3, + }; + + /** Optional operation type, for text content. */ + std::optional operation_type; + + /** Optional version number for the text stream. */ + std::optional version; + + /** Optional ID of the stream this one replies to. */ + std::optional reply_to_stream_id; + + /** IDs of streams attached to this one. */ + std::vector attached_stream_ids; + + /** True if this stream was generated (e.g. by AI). */ + std::optional generated; + + /** Optional filename for byte streams. */ + std::optional name; +}; + +/** + * One chunk of a data stream’s payload. + */ +struct DataStreamChunkData { + /** Stream identifier this chunk belongs to. */ + std::string stream_id; + + /** Zero-based index of this chunk. */ + std::uint64_t chunk_index = 0; + + /** Raw chunk content. */ + std::vector content; + + /** Optional version, mirroring header version if applicable. */ + std::optional version; + + /** Optional initialization vector for encrypted payloads. */ + std::vector iv; +}; + +/** + * Trailer metadata for a data stream, sent after all chunks. + */ +struct DataStreamTrailerData { + /** Stream identifier. */ + std::string stream_id; + + /** Reason why the stream ended (empty if normal completion). */ + std::string reason; + + /** Additional attributes describing the final state of the stream. */ + std::map attributes; +}; + +/** + * Video encoding configuration used when publishing a track. + */ +struct VideoEncodingOptions { + /** Maximum target bitrate in bps. */ + std::uint64_t max_bitrate = 0; + + /** Maximum frame rate in frames per second. */ + double max_framerate = 0.0; +}; + +/** + * Audio encoding configuration used when publishing a track. + */ +struct AudioEncodingOptions { + /** Maximum target bitrate in bps. */ + std::uint64_t max_bitrate = 0; +}; + +/** + * Options for publishing a track to the room. + */ +struct TrackPublishOptions { + /** Optional video encoding parameters. */ + std::optional video_encoding; + + /** Optional audio encoding parameters. */ + std::optional audio_encoding; + + /** Optional video codec to use. */ + std::optional video_codec; + + /** Enable or disable discontinuous transmission (DTX). */ + std::optional dtx; + + /** Enable or disable RED (redundant encoding). */ + std::optional red; + + /** Enable or disable simulcast. */ + std::optional simulcast; + + /** Track source (camera, microphone, screen share, etc.). */ + std::optional source; + + /** Optional stream label/group for this track. */ + std::optional stream; + + /** Enable pre-connect buffering for lower startup latency. */ + std::optional preconnect_buffer; +}; + +/** + * One transcription segment produced by speech recognition. + */ +struct TranscriptionSegment { + /** Segment identifier. */ + std::string id; + + /** Transcribed text. */ + std::string text; + + /** Start time (ms) relative to the beginning of the audio source. */ + std::uint64_t start_time = 0; + + /** End time (ms) relative to the beginning of the audio source. */ + std::uint64_t end_time = 0; + + /** True if this segment is final and will not be updated further. */ + bool final = false; + + /** Language code (e.g. "en-US"). */ + std::string language; +}; + +// --------------------------------------------------------- +// Event structs – public representations of RoomEvent.* +// --------------------------------------------------------- + +/** + * Fired when a remote participant joins the room. + */ +struct ParticipantConnectedEvent { + /** The newly connected remote participant (owned by Room). */ + RemoteParticipant *participant = nullptr; +}; + +/** + * Fired when a remote participant leaves the room. + */ +struct ParticipantDisconnectedEvent { + /** The participant that disconnected (owned by Room). */ + RemoteParticipant *participant = nullptr; + + /** Reason for the disconnect, if known. */ + DisconnectReason reason = DisconnectReason::Unknown; +}; + +/** + * Fired when a local track is successfully published. + */ +struct LocalTrackPublishedEvent { + /** Track publication for the local track. */ + std::shared_ptr publication; + + /** The published local track. */ + std::shared_ptr track; +}; + +/** + * Fired when a local track is unpublished. + */ +struct LocalTrackUnpublishedEvent { + /** Publication that was unpublished. */ + std::shared_ptr publication; +}; + +/** + * Fired when a local track gets its first subscriber. + */ +struct LocalTrackSubscribedEvent { + /** Subscribed local track. */ + std::shared_ptr track; +}; + +/** + * Fired when a remote participant publishes a track. + */ +struct TrackPublishedEvent { + /** Remote track publication. */ + std::shared_ptr publication; + + /** Remote participant who owns this track (owned by Room). */ + RemoteParticipant *participant = nullptr; +}; + +/** + * Fired when a remote participant unpublishes a track. + */ +struct TrackUnpublishedEvent { + /** Remote track publication that was removed. */ + std::shared_ptr publication; + + /** Remote participant who owned this track (owned by Room). */ + RemoteParticipant *participant = nullptr; +}; + +/** + * Fired when a remote track is successfully subscribed. + */ +struct TrackSubscribedEvent { + /** Subscribed remote track. */ + std::shared_ptr track; + + /** Publication associated with the track. */ + std::shared_ptr publication; + + /** Remote participant who owns the track (owned by Room). */ + RemoteParticipant *participant = nullptr; +}; + +/** + * Fired when a remote track is unsubscribed. + */ +struct TrackUnsubscribedEvent { + /** Track that was unsubscribed. */ + std::shared_ptr track; + + /** Publication associated with the track. */ + std::shared_ptr publication; + + /** Remote participant who owns the track (owned by Room). */ + RemoteParticipant *participant = nullptr; +}; + +/** + * Fired when subscribing to a remote track fails. + */ +struct TrackSubscriptionFailedEvent { + /** Remote participant for which the subscription failed (owned by Room). */ + RemoteParticipant *participant = nullptr; + + /** SID of the track that failed to subscribe. */ + std::string track_sid; + + /** Error message describing the failure. */ + std::string error; +}; + +/** + * Fired when a track is muted. + */ +struct TrackMutedEvent { + /** Local or remote participant who owns the track (owned by Room). */ + Participant *participant = nullptr; + + /** Publication that was muted. */ + std::shared_ptr publication; +}; + +/** + * Fired when a track is unmuted. + */ +struct TrackUnmutedEvent { + /** Local or remote participant who owns the track (owned by Room). */ + Participant *participant = nullptr; + + /** Publication that was unmuted. */ + std::shared_ptr publication; +}; + +/** + * Fired when the list of active speakers changes. + */ +struct ActiveSpeakersChangedEvent { + /** Participants currently considered active speakers (owned by Room). */ + std::vector speakers; +}; + +/** + * Fired when room metadata is updated. + */ +struct RoomMetadataChangedEvent { + /** Previous metadata value. */ + std::string old_metadata; + + /** New metadata value. */ + std::string new_metadata; +}; + +/** + * Fired when the room SID changes (e.g., after migration). + */ +struct RoomSidChangedEvent { + /** New room SID. */ + std::string sid; +}; + +/** + * Fired when a participant's metadata is updated. + */ +struct ParticipantMetadataChangedEvent { + /** Participant whose metadata changed (owned by Room). */ + Participant *participant = nullptr; + + /** Old metadata value. */ + std::string old_metadata; + + /** New metadata value. */ + std::string new_metadata; +}; + +/** + * Fired when a participant's name changes. + */ +struct ParticipantNameChangedEvent { + /** Participant whose name changed (owned by Room). */ + Participant *participant = nullptr; + + /** Previous name. */ + std::string old_name; + + /** New name. */ + std::string new_name; +}; + +/** + * Fired when a participant's attributes change. + */ +struct ParticipantAttributesChangedEvent { + /** Participant whose attributes changed (owned by Room). */ + Participant *participant = nullptr; + + /** Set of attributes that changed (key/value pairs). */ + std::vector changed_attributes; +}; + +/** + * Fired when a participant's encryption status changes. + */ +struct ParticipantEncryptionStatusChangedEvent { + /** Participant whose encryption status changed (owned by Room). */ + Participant *participant = nullptr; + + /** True if the participant is now fully encrypted. */ + bool is_encrypted = false; +}; + +/** + * Fired when a participant's connection quality estimate changes. + */ +struct ConnectionQualityChangedEvent { + /** Participant whose connection quality changed (owned by Room). */ + Participant *participant = nullptr; + + /** New connection quality. */ + ConnectionQuality quality = ConnectionQuality::Good; +}; + +/** + * Fired when a user data packet (non-SIP) is received. + */ +struct UserDataPacketEvent { + /** Payload data. */ + std::vector data; + + /** Delivery kind (reliable or lossy). */ + DataPacketKind kind = DataPacketKind::Reliable; + + /** 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). */ + std::string topic; +}; + +/** + * Fired when a SIP DTMF packet is received. + */ +struct SipDtmfReceivedEvent { + /** DTMF code. */ + int code = 0; + + /** Human-readable DTMF digit. */ + std::string digit; + + /** Remote participant that sent the DTMF (owned by Room). */ + RemoteParticipant *participant = nullptr; +}; + +/** + * One transcription unit with optional participant/track linkage. + */ +struct Transcription { + /** Optional identity of the participant who spoke. */ + std::optional participant_identity; + + /** Optional SID of the track associated with this transcription. */ + std::optional track_sid; + + /** Ordered segments that make up the transcription. */ + std::vector segments; +}; + +/** + * Fired when the room's connection state changes. + */ +struct ConnectionStateChangedEvent { + /** New connection state. */ + ConnectionState state = ConnectionState::Disconnected; +}; + +/** + * Fired when the room is disconnected. + */ +struct DisconnectedEvent { + /** Reason for disconnect, if known. */ + DisconnectReason reason = DisconnectReason::Unknown; +}; + +/** + * Fired just before attempting to reconnect. + */ +struct ReconnectingEvent {}; + +/** + * Fired after successfully reconnecting. + */ +struct ReconnectedEvent {}; + +/** + * Fired when the room has reached end-of-stream (no more events). + */ +struct RoomEosEvent {}; + +/** + * Fired when a data stream header is received. + */ +struct DataStreamHeaderReceivedEvent { + /** Identity of the participant that sent the stream. */ + std::string participant_identity; + + /** Parsed header data. */ + DataStreamHeaderData header; +}; + +/** + * Fired when a data stream chunk is received. + */ +struct DataStreamChunkReceivedEvent { + /** Identity of the participant that sent the stream. */ + std::string participant_identity; + + /** Chunk payload and metadata. */ + DataStreamChunkData chunk; +}; + +/** + * Fired when a data stream trailer is received. + */ +struct DataStreamTrailerReceivedEvent { + /** Identity of the participant that sent the stream. */ + std::string participant_identity; + + /** Trailer metadata describing the stream termination. */ + DataStreamTrailerData trailer; +}; + +/** + * Fired when a data channel's buffered amount falls below its low threshold. + */ +struct DataChannelBufferedAmountLowThresholdChangedEvent { + /** Data channel kind (reliable or lossy). */ + DataPacketKind kind = DataPacketKind::Reliable; + + /** New threshold value in bytes. */ + std::uint64_t threshold = 0; +}; + +/** + * Fired when a high-level byte stream reader is opened. + */ +struct ByteStreamOpenedEvent { + /** Handle to the underlying byte stream reader. */ + std::uint64_t reader_handle = 0; + + /** Identity of the participant that opened the stream. */ + std::string participant_identity; +}; + +/** + * Fired when a high-level text stream reader is opened. + */ +struct TextStreamOpenedEvent { + /** Handle to the underlying text stream reader. */ + std::uint64_t reader_handle = 0; + + /** Identity of the participant that opened the stream. */ + std::string participant_identity; +}; + +/** + * Fired when the room's info is updated. + */ +struct RoomUpdatedEvent { + /** New room info snapshot. */ + RoomInfoData info; +}; + +/** + * Fired when the participant has been moved to another room. + */ +struct RoomMovedEvent { + /** Info about the new room. */ + RoomInfoData info; +}; + +/** + * Fired when a batch of participants has been updated. + */ +struct ParticipantsUpdatedEvent { + /** Participants updated in this event (owned by Room). */ + std::vector participants; +}; + +/** + * Fired when a participant's E2EE state changes. + */ +struct E2eeStateChangedEvent { + /** Local or remote participant whose state changed (owned by Room). */ + Participant *participant = nullptr; + + /** New encryption state. */ + EncryptionState state = EncryptionState::New; +}; + +} // namespace livekit diff --git a/include/livekit/track_publication.h b/include/livekit/track_publication.h index 19b8055..b0365bf 100644 --- a/include/livekit/track_publication.h +++ b/include/livekit/track_publication.h @@ -62,6 +62,7 @@ class TrackPublication { std::uint32_t height() const noexcept { return height_; } const std::string &mimeType() const noexcept { return mime_type_; } bool muted() const noexcept { return muted_; } + void setMuted(bool muted) noexcept { muted_ = muted; } EncryptionType encryptionType() const noexcept { return encryption_type_; } const std::vector &audioFeatures() const noexcept { @@ -74,7 +75,6 @@ class TrackPublication { /// Associated Track (if attached). std::shared_ptr track() const noexcept { return track_; } void setTrack(const std::shared_ptr &track) noexcept { - std::cout << "track_ is null " << (track_.get() == nullptr) << std::endl; track_ = track; } diff --git a/include/livekit/video_frame.h b/include/livekit/video_frame.h index 878aaca..987f696 100644 --- a/include/livekit/video_frame.h +++ b/include/livekit/video_frame.h @@ -61,6 +61,7 @@ class LKVideoFrame { LKVideoFrame(); LKVideoFrame(int width, int height, VideoBufferType type, std::vector data); + virtual ~LKVideoFrame() = default; LKVideoFrame(const LKVideoFrame &) = delete; LKVideoFrame &operator=(const LKVideoFrame &) = delete; diff --git a/include/livekit/video_source.h b/include/livekit/video_source.h index 95b6750..0e8c352 100644 --- a/include/livekit/video_source.h +++ b/include/livekit/video_source.h @@ -52,9 +52,7 @@ class VideoSource { * does not contain the expected new_video_source field. */ VideoSource(int width, int height); - - // Owned FFI handle will be released by FfiHandle's destructor. - ~VideoSource() = default; + virtual ~VideoSource() = default; VideoSource(const VideoSource &) = delete; VideoSource &operator=(const VideoSource &) = delete; diff --git a/include/livekit/video_stream.h b/include/livekit/video_stream.h index fea3a93..f3f233b 100644 --- a/include/livekit/video_stream.h +++ b/include/livekit/video_stream.h @@ -81,7 +81,7 @@ class VideoStream { TrackSource track_source, const Options &options); - ~VideoStream(); + virtual ~VideoStream(); VideoStream(const VideoStream &) = delete; VideoStream &operator=(const VideoStream &) = delete; diff --git a/src/audio_source.cpp b/src/audio_source.cpp index 36fb226..b8cebc9 100644 --- a/src/audio_source.cpp +++ b/src/audio_source.cpp @@ -56,11 +56,6 @@ AudioSource::AudioSource(int sample_rate, int num_channels, int queue_size_ms) handle_ = FfiHandle(static_cast(source_info.handle().id())); } -AudioSource::~AudioSource() { - // Let FfiHandle::~FfiHandle() drop the native handle. - // If you later add an explicit "dispose" request, you can send it here. -} - double AudioSource::queuedDuration() const noexcept { if (last_capture_ == 0.0) { return 0.0; diff --git a/src/local_participant.cpp b/src/local_participant.cpp index 02f90d5..d717024 100644 --- a/src/local_participant.cpp +++ b/src/local_participant.cpp @@ -350,4 +350,13 @@ void LocalParticipant::handleRpcMethodInvocation( FfiClient::instance().sendRequest(req); } +std::shared_ptr +LocalParticipant::findTrackPublication(const std::string &sid) const { + auto it = track_publications_.find(sid); + if (it == track_publications_.end()) { + return nullptr; + } + return std::static_pointer_cast(it->second); +} + } // namespace livekit diff --git a/src/remote_participant.cpp b/src/remote_participant.cpp index 38def8e..a784887 100644 --- a/src/remote_participant.cpp +++ b/src/remote_participant.cpp @@ -20,6 +20,8 @@ #include #include +#include "livekit/remote_track_publication.h" + namespace livekit { RemoteParticipant::RemoteParticipant( @@ -45,4 +47,13 @@ std::ostream &operator<<(std::ostream &os, return os; } +std::shared_ptr +RemoteParticipant::findTrackPublication(const std::string &sid) const { + auto it = track_publications_.find(sid); + if (it == track_publications_.end()) { + return nullptr; + } + return std::static_pointer_cast(it->second); +} + } // namespace livekit diff --git a/src/room.cpp b/src/room.cpp index c20aac1..eaf82f0 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -25,6 +25,7 @@ #include "livekit/remote_track_publication.h" #include "livekit/remote_video_track.h" #include "livekit/room_delegate.h" +#include "livekit/room_event_types.h" #include "livekit/video_stream.h" #include "ffi.pb.h" @@ -45,7 +46,7 @@ using proto::FfiResponse; namespace { -std::unique_ptr +std::shared_ptr createRemoteParticipant(const proto::OwnedParticipant &owned) { const auto &pinfo = owned.info(); std::unordered_map attrs; @@ -56,7 +57,7 @@ createRemoteParticipant(const proto::OwnedParticipant &owned) { auto kind = livekit::fromProto(pinfo.kind()); auto reason = livekit::toDisconnectReason(pinfo.disconnect_reason()); livekit::FfiHandle handle(static_cast(owned.handle().id())); - return std::make_unique( + return std::make_shared( std::move(handle), pinfo.sid(), pinfo.name(), pinfo.identity(), pinfo.metadata(), std::move(attrs), kind, reason); } @@ -125,8 +126,8 @@ bool Room::Connect(const std::string &url, const std::string &token, for (const auto &owned_publication_info : pt.publications()) { auto publication = std::make_shared(owned_publication_info); - rp->mutable_track_publications().emplace(publication->sid(), - std::move(publication)); + rp->mutableTrackPublications().emplace(publication->sid(), + std::move(publication)); } remote_participants_.emplace(rp->identity(), std::move(rp)); @@ -204,33 +205,36 @@ void Room::OnEvent(const FfiEvent &event) { switch (event.message_case()) { case FfiEvent::kRoomEvent: { const proto::RoomEvent &re = event.room_event(); - - // Optional generic hook - delegate_snapshot->onRoomEvent(*this); - switch (re.message_case()) { case proto::RoomEvent::kParticipantConnected: { - auto ev = fromProto(re.participant_connected()); - std::cout << "kParticipantConnected " << std::endl; - // Create and register RemoteParticipant + std::shared_ptr new_participant; { std::lock_guard guard(lock_); - auto rp = createRemoteParticipant(re.participant_connected().info()); - remote_participants_.emplace(rp->identity(), std::move(rp)); + const auto &owned = re.participant_connected().info(); + // createRemoteParticipant takes proto::OwnedParticipant + new_participant = createRemoteParticipant(owned); + remote_participants_.emplace(new_participant->identity(), + new_participant); } - // TODO, use better public callback events + ParticipantConnectedEvent ev; + ev.participant = new_participant.get(); delegate_snapshot->onParticipantConnected(*this, ev); break; } case proto::RoomEvent::kParticipantDisconnected: { - auto ev = fromProto(re.participant_disconnected()); + std::shared_ptr removed; + DisconnectReason reason = DisconnectReason::Unknown; + { std::lock_guard guard(lock_); const auto &pd = re.participant_disconnected(); const std::string &identity = pd.participant_identity(); + reason = toDisconnectReason(pd.disconnect_reason()); + auto it = remote_participants_.find(identity); if (it != remote_participants_.end()) { + removed = it->second; remote_participants_.erase(it); } else { // We saw a disconnect event for a participant we don't track @@ -240,28 +244,86 @@ void Room::OnEvent(const FfiEvent &event) { << identity << std::endl; } } - // TODO, should we trigger onParticipantDisconnected if remote - // participants can't be found ? - delegate_snapshot->onParticipantDisconnected(*this, ev); + if (removed) { + ParticipantDisconnectedEvent ev; + ev.participant = removed.get(); + ev.reason = reason; + delegate_snapshot->onParticipantDisconnected(*this, ev); + } break; } case proto::RoomEvent::kLocalTrackPublished: { - auto ev = fromProto(re.local_track_published()); + LocalTrackPublishedEvent ev; + { + std::lock_guard guard(lock_); + if (!local_participant_) { + std::cerr << "kLocalTrackPublished: local_participant_ is nullptr" + << std::endl; + break; + } + const auto <p = re.local_track_published(); + const std::string &sid = ltp.track_sid(); + auto &pubs = local_participant_->trackPublications(); + auto it = pubs.find(sid); + if (it == pubs.end()) { + std::cerr << "local_track_published for unknown sid: " << sid + << std::endl; + break; + } + ev.publication = it->second; + ev.track = ev.publication ? ev.publication->track() : nullptr; + } delegate_snapshot->onLocalTrackPublished(*this, ev); break; } case proto::RoomEvent::kLocalTrackUnpublished: { - auto ev = fromProto(re.local_track_unpublished()); + LocalTrackUnpublishedEvent ev; + { + std::lock_guard guard(lock_); + if (!local_participant_) { + std::cerr << "kLocalTrackPublished: local_participant_ is nullptr" + << std::endl; + break; + } + const auto <u = re.local_track_unpublished(); + const std::string &pub_sid = ltu.publication_sid(); + auto &pubs = local_participant_->trackPublications(); + auto it = pubs.find(pub_sid); + if (it == pubs.end()) { + std::cerr << "local_track_unpublished for unknown publication sid: " + << pub_sid << std::endl; + break; + } + ev.publication = it->second; + } delegate_snapshot->onLocalTrackUnpublished(*this, ev); break; } case proto::RoomEvent::kLocalTrackSubscribed: { - auto ev = fromProto(re.local_track_subscribed()); + LocalTrackSubscribedEvent ev; + { + std::lock_guard guard(lock_); + if (!local_participant_) { + break; + } + const auto <s = re.local_track_subscribed(); + const std::string &sid = lts.track_sid(); + auto &pubs = local_participant_->trackPublications(); + auto it = pubs.find(sid); + if (it == pubs.end()) { + std::cerr << "local_track_subscribed for unknown sid: " << sid + << std::endl; + break; + } + auto publication = it->second; + ev.track = publication ? publication->track() : nullptr; + } + delegate_snapshot->onLocalTrackSubscribed(*this, ev); break; } case proto::RoomEvent::kTrackPublished: { - auto ev = fromProto(re.track_published()); + TrackPublishedEvent ev; { std::lock_guard guard(lock_); const auto &tp = re.track_published(); @@ -273,13 +335,14 @@ void Room::OnEvent(const FfiEvent &event) { auto rpublication = std::make_shared(owned_publication); // Store it on the participant, keyed by SID - rparticipant->mutable_track_publications().emplace( + rparticipant->mutableTrackPublications().emplace( rpublication->sid(), std::move(rpublication)); - + ev.participant = rparticipant; + ev.publication = rpublication; } else { // Optional: log if we get a track for an unknown participant std::cerr << "track_published for unknown participant: " << identity - << "\n"; + << std::endl; // Don't emit the break; } @@ -288,7 +351,31 @@ void Room::OnEvent(const FfiEvent &event) { break; } case proto::RoomEvent::kTrackUnpublished: { - auto ev = fromProto(re.track_unpublished()); + TrackUnpublishedEvent ev; + { + std::lock_guard guard(lock_); + const auto &tu = re.track_unpublished(); + const std::string &identity = tu.participant_identity(); + const std::string &pub_sid = tu.publication_sid(); + auto pit = remote_participants_.find(identity); + if (pit == remote_participants_.end()) { + std::cerr << "track_unpublished for unknown participant: " << identity + << std::endl; + break; + } + RemoteParticipant *rparticipant = pit->second.get(); + auto &pubs = rparticipant->mutableTrackPublications(); + auto it = pubs.find(pub_sid); + if (it == pubs.end()) { + std::cerr << "track_unpublished for unknown publication sid " + << pub_sid << " (participant " << identity << ")\n"; + break; + } + ev.participant = rparticipant; + ev.publication = it->second; + pubs.erase(it); + } + delegate_snapshot->onTrackUnpublished(*this, ev); break; } @@ -311,7 +398,7 @@ void Room::OnEvent(const FfiEvent &event) { } rparticipant = pit->second.get(); // Find existing publication by track SID (from track_published) - auto &pubs = rparticipant->mutable_track_publications(); + auto &pubs = rparticipant->mutableTrackPublications(); auto pubIt = pubs.find(track_info.sid()); if (pubIt == pubs.end()) { std::cerr << "track_subscribed for unknown publication sid " @@ -331,13 +418,9 @@ void Room::OnEvent(const FfiEvent &event) { << track_info.kind() << "\n"; break; } - std::cout << "before setTrack " << std::endl; - // Attach to publication, mark subscribed rpublication->setTrack(remote_track); - std::cout << "setTrack " << std::endl; rpublication->setSubscribed(true); - std::cout << "setSubscribed " << std::endl; } // Emit remote track_subscribed-style callback @@ -345,114 +428,448 @@ void Room::OnEvent(const FfiEvent &event) { ev.track = remote_track; ev.publication = rpublication; ev.participant = rparticipant; - std::cout << "onTrackSubscribed " << std::endl; delegate_snapshot->onTrackSubscribed(*this, ev); - std::cout << "after onTrackSubscribed " << std::endl; break; } case proto::RoomEvent::kTrackUnsubscribed: { - auto ev = fromProto(re.track_unsubscribed()); + TrackUnsubscribedEvent ev; + { + std::lock_guard guard(lock_); + const auto &tu = re.track_unsubscribed(); + const std::string &identity = tu.participant_identity(); + const std::string &track_sid = tu.track_sid(); + auto pit = remote_participants_.find(identity); + if (pit == remote_participants_.end()) { + std::cerr << "track_unsubscribed for unknown participant: " + << identity << "\n"; + break; + } + RemoteParticipant *rparticipant = pit->second.get(); + auto &pubs = rparticipant->mutableTrackPublications(); + auto pubIt = pubs.find(track_sid); + if (pubIt == pubs.end()) { + std::cerr << "track_unsubscribed for unknown publication sid " + << track_sid << " (participant " << identity << ")\n"; + break; + } + auto publication = pubIt->second; + auto track = publication->track(); + publication->setTrack(nullptr); + publication->setSubscribed(false); + ev.participant = rparticipant; + ev.publication = publication; + ev.track = track; + } + delegate_snapshot->onTrackUnsubscribed(*this, ev); break; } case proto::RoomEvent::kTrackSubscriptionFailed: { - auto ev = fromProto(re.track_subscription_failed()); + TrackSubscriptionFailedEvent ev; + { + std::lock_guard guard(lock_); + const auto &tsf = re.track_subscription_failed(); + const std::string &identity = tsf.participant_identity(); + auto pit = remote_participants_.find(identity); + if (pit == remote_participants_.end()) { + std::cerr << "track_subscription_failed for unknown participant: " + << identity << "\n"; + break; + } + ev.participant = pit->second.get(); + ev.track_sid = tsf.track_sid(); + ev.error = tsf.error(); + } delegate_snapshot->onTrackSubscriptionFailed(*this, ev); break; } case proto::RoomEvent::kTrackMuted: { - auto ev = fromProto(re.track_muted()); - delegate_snapshot->onTrackMuted(*this, ev); + TrackMutedEvent ev; + bool success = false; + { + std::lock_guard guard(lock_); + const auto &tm = re.track_muted(); + const std::string &identity = tm.participant_identity(); + const std::string &sid = tm.track_sid(); + Participant *participant = nullptr; + if (local_participant_ && local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto pit = remote_participants_.find(identity); + if (pit != remote_participants_.end()) { + participant = pit->second.get(); + } + } + if (!participant) { + std::cerr << "track_muted for unknown participant: " << identity + << "\n"; + break; + } + auto pub = participant->findTrackPublication(sid); + if (!pub) { + std::cerr << "track_muted for unknown track sid: " << sid + << std::endl; + } else { + pub->setMuted(true); + if (auto t = pub->track()) { + t->setMuted(true); + } + ev.participant = participant; + ev.publication = pub; + success = true; + } + } + if (success) { + delegate_snapshot->onTrackMuted(*this, ev); + } break; } case proto::RoomEvent::kTrackUnmuted: { - auto ev = fromProto(re.track_unmuted()); - delegate_snapshot->onTrackUnmuted(*this, ev); + TrackUnmutedEvent ev; + bool success = false; + { + std::lock_guard guard(lock_); + const auto &tu = re.track_unmuted(); + const std::string &identity = tu.participant_identity(); + const std::string &sid = tu.track_sid(); + Participant *participant = nullptr; + if (local_participant_ && local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto pit = remote_participants_.find(identity); + if (pit != remote_participants_.end()) { + participant = pit->second.get(); + } + } + if (!participant) { + std::cerr << "track_unmuted for unknown participant: " << identity + << "\n"; + break; + } + + auto pub = participant->findTrackPublication(sid); + if (!pub) { + std::cerr << "track_muted for unknown track sid: " << sid + << std::endl; + } else { + pub->setMuted(false); + if (auto t = pub->track()) { + t->setMuted(false); + } + ev.participant = participant; + ev.publication = pub; + success = true; + } + + ev.participant = participant; + ev.publication = pub; + } + + if (success) { + delegate_snapshot->onTrackUnmuted(*this, ev); + } break; } case proto::RoomEvent::kActiveSpeakersChanged: { - auto ev = fromProto(re.active_speakers_changed()); + ActiveSpeakersChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto &asc = re.active_speakers_changed(); + for (const auto &identity : asc.participant_identities()) { + Participant *participant = nullptr; + if (local_participant_ && + local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto pit = remote_participants_.find(identity); + if (pit != remote_participants_.end()) { + participant = pit->second.get(); + } + } + if (participant) { + ev.speakers.push_back(participant); + } + } + } delegate_snapshot->onActiveSpeakersChanged(*this, ev); break; } case proto::RoomEvent::kRoomMetadataChanged: { - auto ev = fromProto(re.room_metadata_changed()); + RoomMetadataChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto old_metadata = room_info_.metadata; + room_info_.metadata = re.room_metadata_changed().metadata(); + ev.old_metadata = old_metadata; + ev.new_metadata = room_info_.metadata; + } delegate_snapshot->onRoomMetadataChanged(*this, ev); break; } case proto::RoomEvent::kRoomSidChanged: { - auto ev = fromProto(re.room_sid_changed()); + RoomSidChangedEvent ev; + { + std::lock_guard guard(lock_); + room_info_.sid = re.room_sid_changed().sid(); + ev.sid = room_info_.sid.value_or(std::string{}); + } delegate_snapshot->onRoomSidChanged(*this, ev); break; } case proto::RoomEvent::kParticipantMetadataChanged: { - auto ev = fromProto(re.participant_metadata_changed()); + ParticipantMetadataChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto &pm = re.participant_metadata_changed(); + const std::string &identity = pm.participant_identity(); + Participant *participant = nullptr; + if (local_participant_ && local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto it = remote_participants_.find(identity); + if (it != remote_participants_.end()) { + participant = it->second.get(); + } + } + if (!participant) { + std::cerr << "participant_metadata_changed for unknown participant: " + << identity << "\n"; + break; + } + std::string old_metadata = participant->metadata(); + participant->set_metadata(pm.metadata()); + ev.participant = participant; + ev.old_metadata = old_metadata; + ev.new_metadata = participant->metadata(); + } + delegate_snapshot->onParticipantMetadataChanged(*this, ev); break; } case proto::RoomEvent::kParticipantNameChanged: { - auto ev = fromProto(re.participant_name_changed()); + ParticipantNameChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto &pn = re.participant_name_changed(); + const std::string &identity = pn.participant_identity(); + Participant *participant = nullptr; + if (local_participant_ && local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto it = remote_participants_.find(identity); + if (it != remote_participants_.end()) { + participant = it->second.get(); + } + } + if (!participant) { + std::cerr << "participant_name_changed for unknown participant: " + << identity << "\n"; + break; + } + std::string old_name = participant->name(); + participant->set_name(pn.name()); + ev.participant = participant; + ev.old_name = old_name; + ev.new_name = participant->name(); + } delegate_snapshot->onParticipantNameChanged(*this, ev); break; } case proto::RoomEvent::kParticipantAttributesChanged: { - auto ev = fromProto(re.participant_attributes_changed()); + ParticipantAttributesChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto &pa = re.participant_attributes_changed(); + const std::string &identity = pa.participant_identity(); + Participant *participant = nullptr; + if (local_participant_ && local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto it = remote_participants_.find(identity); + if (it != remote_participants_.end()) { + participant = it->second.get(); + } + } + if (!participant) { + std::cerr + << "participant_attributes_changed for unknown participant: " + << identity << "\n"; + break; + } + // Build full attributes map + std::unordered_map attrs; + for (const auto &entry : pa.attributes()) { + attrs.emplace(entry.key(), entry.value()); + } + participant->set_attributes(attrs); + + // Build changed_attributes map + for (const auto &entry : pa.changed_attributes()) { + ev.changed_attributes.emplace_back(entry.key(), entry.value()); + } + ev.participant = participant; + } delegate_snapshot->onParticipantAttributesChanged(*this, ev); break; } case proto::RoomEvent::kParticipantEncryptionStatusChanged: { - auto ev = fromProto(re.participant_encryption_status_changed()); + ParticipantEncryptionStatusChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto &pe = re.participant_encryption_status_changed(); + const std::string &identity = pe.participant_identity(); + Participant *participant = nullptr; + if (local_participant_ && local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto it = remote_participants_.find(identity); + if (it != remote_participants_.end()) { + participant = it->second.get(); + } + } + if (!participant) { + std::cerr << "participant_encryption_status_changed for unknown " + "participant: " + << identity << "\n"; + break; + } + ev.participant = participant; + ev.is_encrypted = pe.is_encrypted(); + } + delegate_snapshot->onParticipantEncryptionStatusChanged(*this, ev); break; } case proto::RoomEvent::kConnectionQualityChanged: { - auto ev = fromProto(re.connection_quality_changed()); + ConnectionQualityChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto &cq = re.connection_quality_changed(); + const std::string &identity = cq.participant_identity(); + Participant *participant = nullptr; + if (local_participant_ && local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto it = remote_participants_.find(identity); + if (it != remote_participants_.end()) { + participant = it->second.get(); + } + } + if (!participant) { + std::cerr << "connection_quality_changed for unknown participant: " + << identity << "\n"; + break; + } + ev.participant = participant; + ev.quality = static_cast(cq.quality()); + } + delegate_snapshot->onConnectionQualityChanged(*this, ev); break; } + + // ------------------------------------------------------------------------ + // Transcription + // ------------------------------------------------------------------------ + + case proto::RoomEvent::kTranscriptionReceived: { + // Deprecated event, do nothing. + break; + } + + // ------------------------------------------------------------------------ + // Data packets: user vs SIP DTMF + // ------------------------------------------------------------------------ + case proto::RoomEvent::kDataPacketReceived: { + const auto &dp = re.data_packet_received(); + RemoteParticipant *rp = nullptr; + { + std::lock_guard guard(lock_); + auto it = remote_participants_.find(dp.participant_identity()); + if (it != remote_participants_.end()) { + rp = it->second.get(); + } + } + const auto which_val = dp.value_case(); + if (which_val == proto::DataPacketReceived::kUser) { + UserDataPacketEvent ev = userDataPacketFromProto(dp, rp); + delegate_snapshot->onUserPacketReceived(*this, ev); + } else if (which_val == proto::DataPacketReceived::kSipDtmf) { + SipDtmfReceivedEvent ev = sipDtmfFromProto(dp, rp); + delegate_snapshot->onSipDtmfReceived(*this, ev); + } + break; + } + + // ------------------------------------------------------------------------ + // E2EE state + // ------------------------------------------------------------------------ + case proto::RoomEvent::kE2EeStateChanged: { + E2eeStateChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto &es = re.e2ee_state_changed(); + const std::string &identity = es.participant_identity(); + Participant *participant = nullptr; + if (local_participant_ && local_participant_->identity() == identity) { + participant = local_participant_.get(); + } else { + auto it = remote_participants_.find(identity); + if (it != remote_participants_.end()) { + participant = it->second.get(); + } + } + if (!participant) { + std::cerr << "e2ee_state_changed for unknown participant: " + << identity << std::endl; + break; + } + + ev.participant = participant; + ev.state = static_cast(es.state()); + } + delegate_snapshot->onE2eeStateChanged(*this, ev); + break; + } + + // ------------------------------------------------------------------------ + // Connection state / lifecycle + // ------------------------------------------------------------------------ + case proto::RoomEvent::kConnectionStateChanged: { - auto ev = fromProto(re.connection_state_changed()); + ConnectionStateChangedEvent ev; + { + std::lock_guard guard(lock_); + const auto &cs = re.connection_state_changed(); + connection_state_ = static_cast(cs.state()); + ev.state = connection_state_; + } delegate_snapshot->onConnectionStateChanged(*this, ev); break; } case proto::RoomEvent::kDisconnected: { - auto ev = fromProto(re.disconnected()); + DisconnectedEvent ev; + ev.reason = toDisconnectReason(re.disconnected().reason()); delegate_snapshot->onDisconnected(*this, ev); break; } case proto::RoomEvent::kReconnecting: { - auto ev = fromProto(re.reconnecting()); + ReconnectingEvent ev; delegate_snapshot->onReconnecting(*this, ev); break; } case proto::RoomEvent::kReconnected: { - auto ev = fromProto(re.reconnected()); + ReconnectedEvent ev; delegate_snapshot->onReconnected(*this, ev); break; } - case proto::RoomEvent::kE2EeStateChanged: { - auto ev = fromProto(re.e2ee_state_changed()); - delegate_snapshot->onE2eeStateChanged(*this, ev); - break; - } case proto::RoomEvent::kEos: { - auto ev = fromProto(re.eos()); + RoomEosEvent ev; delegate_snapshot->onRoomEos(*this, ev); break; } - case proto::RoomEvent::kDataPacketReceived: { - auto ev = fromProto(re.data_packet_received()); - delegate_snapshot->onDataPacketReceived(*this, ev); - break; - } - case proto::RoomEvent::kTranscriptionReceived: { - auto ev = fromProto(re.transcription_received()); - delegate_snapshot->onTranscriptionReceived(*this, ev); - break; - } case proto::RoomEvent::kChatMessage: { - auto ev = fromProto(re.chat_message()); - delegate_snapshot->onChatMessageReceived(*this, ev); + // Deprecated event, do nothing. break; } case proto::RoomEvent::kStreamHeaderReceived: { @@ -497,36 +914,33 @@ void Room::OnEvent(const FfiEvent &event) { break; } case proto::RoomEvent::kParticipantsUpdated: { - auto ev = fromProto(re.participants_updated()); + ParticipantsUpdatedEvent ev; { std::lock_guard guard(lock_); const auto &pu = re.participants_updated(); for (const auto &info : pu.participants()) { const std::string &identity = info.identity(); Participant *participant = nullptr; - // First, check local participant. + if (local_participant_ && identity == local_participant_->identity()) { participant = local_participant_.get(); } else { - // Otherwise, look for a remote participant. auto it = remote_participants_.find(identity); if (it != remote_participants_.end()) { participant = it->second.get(); } } - if (!participant) { - // Participant might not exist yet; ignore for now. std::cerr << "Room::RoomEvent::kParticipantsUpdated participant " "does not exist: " << identity << std::endl; continue; } - // Update basic fields participant->set_name(info.name()); participant->set_metadata(info.metadata()); + std::unordered_map attrs; attrs.reserve(info.attributes_size()); for (const auto &kv : info.attributes()) { @@ -536,6 +950,8 @@ void Room::OnEvent(const FfiEvent &event) { participant->set_kind(fromProto(info.kind())); participant->set_disconnect_reason( toDisconnectReason(info.disconnect_reason())); + + ev.participants.push_back(participant); } } delegate_snapshot->onParticipantsUpdated(*this, ev); diff --git a/src/room_proto_converter.cpp b/src/room_proto_converter.cpp index 7a6b1c1..9da3d24 100644 --- a/src/room_proto_converter.cpp +++ b/src/room_proto_converter.cpp @@ -62,11 +62,6 @@ DataPacketKind toDataPacketKind(proto::DataPacketKind in) { } } -EncryptionState toEncryptionState(proto::EncryptionState /*in*/) { - // TODO: fill out once you have the proto::EncryptionState enum - return EncryptionState::Unknown; -} - DisconnectReason toDisconnectReason(proto::DisconnectReason /*in*/) { // TODO: map each proto::DisconnectReason to your DisconnectReason enum return DisconnectReason::Unknown; @@ -74,23 +69,6 @@ DisconnectReason toDisconnectReason(proto::DisconnectReason /*in*/) { // --------- basic helper conversions --------- -ChatMessageData fromProto(const proto::ChatMessage &in) { - ChatMessageData out; - out.id = in.id(); - out.timestamp = in.timestamp(); - out.message = in.message(); - if (in.has_edit_timestamp()) { - out.edit_timestamp = in.edit_timestamp(); - } - if (in.has_deleted()) { - out.deleted = in.deleted(); - } - if (in.has_generated()) { - out.generated = in.generated(); - } - return out; -} - UserPacketData fromProto(const proto::UserPacket &in) { UserPacketData out; // TODO, double check following code is safe @@ -216,174 +194,12 @@ DataStreamTrailerData fromProto(const proto::DataStream_Trailer &in) { // --------- event conversions --------- -ParticipantConnectedEvent fromProto(const proto::ParticipantConnected &in) { - ParticipantConnectedEvent ev; - const auto &pinfo = in.info().info(); - ev.identity = pinfo.identity(); - ev.name = pinfo.name(); - ev.metadata = pinfo.metadata(); - return ev; -} - -ParticipantDisconnectedEvent -fromProto(const proto::ParticipantDisconnected &in) { - ParticipantDisconnectedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.reason = toDisconnectReason(in.disconnect_reason()); - return ev; -} - -LocalTrackPublishedEvent fromProto(const proto::LocalTrackPublished &in) { - LocalTrackPublishedEvent ev; - ev.track_sid = in.track_sid(); - return ev; -} - -LocalTrackUnpublishedEvent fromProto(const proto::LocalTrackUnpublished &in) { - LocalTrackUnpublishedEvent ev; - ev.publication_sid = in.publication_sid(); - return ev; -} - -LocalTrackSubscribedEvent fromProto(const proto::LocalTrackSubscribed &in) { - LocalTrackSubscribedEvent ev; - ev.track_sid = in.track_sid(); - return ev; -} - -TrackPublishedEvent fromProto(const proto::TrackPublished &in) { - TrackPublishedEvent ev; - ev.participant_identity = in.participant_identity(); - // OwnedTrackPublication publication = 2; - // TODO: map publication info once you inspect OwnedTrackPublication - // ev.publication_sid = in.publication().info().sid(); - // ev.track_name = in.publication().info().name(); - // ev.track_kind = ...; - // ev.track_source = ...; - return ev; -} - -TrackUnpublishedEvent fromProto(const proto::TrackUnpublished &in) { - TrackUnpublishedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.publication_sid = in.publication_sid(); - return ev; -} - -TrackUnsubscribedEvent fromProto(const proto::TrackUnsubscribed &in) { - TrackUnsubscribedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.track_sid = in.track_sid(); - return ev; -} - -TrackSubscriptionFailedEvent -fromProto(const proto::TrackSubscriptionFailed &in) { - TrackSubscriptionFailedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.track_sid = in.track_sid(); - ev.error = in.error(); - return ev; -} - -TrackMutedEvent fromProto(const proto::TrackMuted &in) { - TrackMutedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.track_sid = in.track_sid(); - return ev; -} - -TrackUnmutedEvent fromProto(const proto::TrackUnmuted &in) { - TrackUnmutedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.track_sid = in.track_sid(); - return ev; -} - -ActiveSpeakersChangedEvent fromProto(const proto::ActiveSpeakersChanged &in) { - ActiveSpeakersChangedEvent ev; - for (const auto &id : in.participant_identities()) { - ev.participant_identities.push_back(id); - } - return ev; -} - -RoomMetadataChangedEvent fromProto(const proto::RoomMetadataChanged &in) { - RoomMetadataChangedEvent ev; - ev.metadata = in.metadata(); - return ev; -} - RoomSidChangedEvent fromProto(const proto::RoomSidChanged &in) { RoomSidChangedEvent ev; ev.sid = in.sid(); return ev; } -ParticipantMetadataChangedEvent -fromProto(const proto::ParticipantMetadataChanged &in) { - ParticipantMetadataChangedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.metadata = in.metadata(); - return ev; -} - -ParticipantNameChangedEvent fromProto(const proto::ParticipantNameChanged &in) { - ParticipantNameChangedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.name = in.name(); - return ev; -} - -ParticipantAttributesChangedEvent -fromProto(const proto::ParticipantAttributesChanged &in) { - ParticipantAttributesChangedEvent ev; - ev.participant_identity = in.participant_identity(); - for (const auto &a : in.attributes()) { - ev.attributes.push_back(fromProto(a)); - } - for (const auto &a : in.changed_attributes()) { - ev.changed_attributes.push_back(fromProto(a)); - } - return ev; -} - -ParticipantEncryptionStatusChangedEvent -fromProto(const proto::ParticipantEncryptionStatusChanged &in) { - ParticipantEncryptionStatusChangedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.is_encrypted = in.is_encrypted(); - return ev; -} - -ConnectionQualityChangedEvent -fromProto(const proto::ConnectionQualityChanged &in) { - ConnectionQualityChangedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.quality = toConnectionQuality(in.quality()); - return ev; -} - -DataPacketReceivedEvent fromProto(const proto::DataPacketReceived &in) { - DataPacketReceivedEvent ev; - ev.kind = toDataPacketKind(in.kind()); - ev.participant_identity = in.participant_identity(); - - switch (in.value_case()) { - case proto::DataPacketReceived::kUser: - ev.user = fromProto(in.user()); - break; - case proto::DataPacketReceived::kSipDtmf: - ev.sip_dtmf = fromProto(in.sip_dtmf()); - break; - case proto::DataPacketReceived::VALUE_NOT_SET: - default: - break; - } - - return ev; -} - ConnectionStateChangedEvent fromProto(const proto::ConnectionStateChanged &in) { ConnectionStateChangedEvent ev; ev.state = toConnectionState(in.state()); @@ -466,30 +282,6 @@ RoomMovedEvent roomMovedFromProto(const proto::RoomInfo &in) { return ev; } -ParticipantsUpdatedEvent fromProto(const proto::ParticipantsUpdated &in) { - ParticipantsUpdatedEvent ev; - // We only know that it has ParticipantInfo participants = 1; - // TODO: fill real identities once you inspect proto::ParticipantInfo - for (const auto &p : in.participants()) { - ev.participant_identities.push_back(p.identity()); - } - return ev; -} - -E2eeStateChangedEvent fromProto(const proto::E2eeStateChanged &in) { - E2eeStateChangedEvent ev; - ev.participant_identity = in.participant_identity(); - ev.state = toEncryptionState(in.state()); - return ev; -} - -ChatMessageReceivedEvent fromProto(const proto::ChatMessageReceived &in) { - ChatMessageReceivedEvent ev; - ev.message = fromProto(in.message()); - ev.participant_identity = in.participant_identity(); - return ev; -} - // ---------------- Room Options ---------------- proto::AudioEncoding toProto(const AudioEncodingOptions &in) { @@ -604,34 +396,34 @@ TranscriptionSegment fromProto(const proto::TranscriptionSegment &in) { return out; } -proto::TranscriptionReceived toProto(const Transcription &in) { - proto::TranscriptionReceived msg; - if (in.participant_identity) { - msg.set_participant_identity(*in.participant_identity); - } - if (in.track_sid) { - msg.set_track_sid(*in.track_sid); - } - for (const auto &seg : in.segments) { - auto *pseg = msg.add_segments(); - pseg->CopyFrom(toProto(seg)); +UserDataPacketEvent userDataPacketFromProto(const proto::DataPacketReceived &in, + RemoteParticipant *participant) { + UserDataPacketEvent ev; + ev.kind = static_cast(in.kind()); + ev.participant = participant; + ev.topic = in.user().topic(); + + // Copy bytes + const auto &owned = in.user().data(); + const auto &info = owned.data(); + if (info.data_ptr() != 0 && info.data_len() > 0) { + auto ptr = reinterpret_cast(info.data_ptr()); + auto len = static_cast(info.data_len()); + ev.data.assign(ptr, ptr + len); + } else { + ev.data.clear(); } - return msg; + + return ev; } -Transcription fromProto(const proto::TranscriptionReceived &in) { - Transcription out; - if (in.has_participant_identity()) { - out.participant_identity = in.participant_identity(); - } - if (in.has_track_sid()) { - out.track_sid = in.track_sid(); - } - out.segments.reserve(in.segments_size()); - for (const auto &pseg : in.segments()) { - out.segments.push_back(fromProto(pseg)); - } - return out; +SipDtmfReceivedEvent sipDtmfFromProto(const proto::DataPacketReceived &in, + RemoteParticipant *participant) { + SipDtmfReceivedEvent ev; + ev.participant = participant; + ev.code = in.sip_dtmf().code(); + ev.digit = in.sip_dtmf().digit(); + return ev; } } // namespace livekit diff --git a/src/room_proto_converter.h b/src/room_proto_converter.h index 127a70c..0538207 100644 --- a/src/room_proto_converter.h +++ b/src/room_proto_converter.h @@ -16,7 +16,7 @@ #pragma once -#include "livekit/room_delegate.h" +#include "livekit/room_event_types.h" #include "room.pb.h" #include @@ -24,20 +24,18 @@ namespace livekit { enum class RpcErrorCode; +class RemoteParticipant; // --------- basic helper conversions --------- ConnectionQuality toConnectionQuality(proto::ConnectionQuality in); ConnectionState toConnectionState(proto::ConnectionState in); DataPacketKind toDataPacketKind(proto::DataPacketKind in); -EncryptionState toEncryptionState(proto::EncryptionState in); DisconnectReason toDisconnectReason(proto::DisconnectReason in); -ChatMessageData fromProto(const proto::ChatMessage &in); UserPacketData fromProto(const proto::UserPacket &in); SipDtmfData fromProto(const proto::SipDTMF &in); RoomInfoData fromProto(const proto::RoomInfo &in); -AttributeEntry fromProto(const proto::AttributesEntry &in); DataStreamHeaderData fromProto(const proto::DataStream_Header &in); DataStreamChunkData fromProto(const proto::DataStream_Chunk &in); @@ -45,40 +43,8 @@ DataStreamTrailerData fromProto(const proto::DataStream_Trailer &in); // --------- event conversions (RoomEvent.oneof message) --------- -ParticipantConnectedEvent fromProto(const proto::ParticipantConnected &in); -ParticipantDisconnectedEvent -fromProto(const proto::ParticipantDisconnected &in); - -LocalTrackPublishedEvent fromProto(const proto::LocalTrackPublished &in); -LocalTrackUnpublishedEvent fromProto(const proto::LocalTrackUnpublished &in); -LocalTrackSubscribedEvent fromProto(const proto::LocalTrackSubscribed &in); - -TrackPublishedEvent fromProto(const proto::TrackPublished &in); -TrackUnpublishedEvent fromProto(const proto::TrackUnpublished &in); -TrackUnsubscribedEvent fromProto(const proto::TrackUnsubscribed &in); -TrackSubscriptionFailedEvent -fromProto(const proto::TrackSubscriptionFailed &in); -TrackMutedEvent fromProto(const proto::TrackMuted &in); -TrackUnmutedEvent fromProto(const proto::TrackUnmuted &in); - -ActiveSpeakersChangedEvent fromProto(const proto::ActiveSpeakersChanged &in); - -RoomMetadataChangedEvent fromProto(const proto::RoomMetadataChanged &in); RoomSidChangedEvent fromProto(const proto::RoomSidChanged &in); -ParticipantMetadataChangedEvent -fromProto(const proto::ParticipantMetadataChanged &in); -ParticipantNameChangedEvent fromProto(const proto::ParticipantNameChanged &in); -ParticipantAttributesChangedEvent -fromProto(const proto::ParticipantAttributesChanged &in); -ParticipantEncryptionStatusChangedEvent -fromProto(const proto::ParticipantEncryptionStatusChanged &in); - -ConnectionQualityChangedEvent -fromProto(const proto::ConnectionQualityChanged &in); - -DataPacketReceivedEvent fromProto(const proto::DataPacketReceived &in); - ConnectionStateChangedEvent fromProto(const proto::ConnectionStateChanged &in); DisconnectedEvent fromProto(const proto::Disconnected &in); ReconnectingEvent fromProto(const proto::Reconnecting &in); @@ -102,10 +68,6 @@ RoomUpdatedEvent roomUpdatedFromProto(const proto::RoomInfo &in); // room_updated RoomMovedEvent roomMovedFromProto(const proto::RoomInfo &in); // moved -ParticipantsUpdatedEvent fromProto(const proto::ParticipantsUpdated &in); -E2eeStateChangedEvent fromProto(const proto::E2eeStateChanged &in); -ChatMessageReceivedEvent fromProto(const proto::ChatMessageReceived &in); - // --------- room options conversions --------- proto::AudioEncoding toProto(const AudioEncodingOptions &in); @@ -122,7 +84,12 @@ TrackPublishOptions fromProto(const proto::TrackPublishOptions &in); proto::TranscriptionSegment toProto(const TranscriptionSegment &in); TranscriptionSegment fromProto(const proto::TranscriptionSegment &in); -proto::TranscriptionReceived toProto(const Transcription &in); -Transcription fromProto(const proto::TranscriptionReceived &in); +// --------- room Data Packet conversions --------- + +UserDataPacketEvent userDataPacketFromProto(const proto::DataPacketReceived &in, + RemoteParticipant *participant); + +SipDtmfReceivedEvent sipDtmfFromProto(const proto::DataPacketReceived &in, + RemoteParticipant *participant); } // namespace livekit diff --git a/src/video_stream.cpp b/src/video_stream.cpp index 7847920..cb6d447 100644 --- a/src/video_stream.cpp +++ b/src/video_stream.cpp @@ -86,7 +86,6 @@ bool VideoStream::read(VideoFrameEvent &out) { } void VideoStream::close() { - std::cout << "VideoSream::close() \n"; { std::lock_guard lock(mutex_); if (closed_) { @@ -211,17 +210,14 @@ void VideoStream::pushFrame(VideoFrameEvent &&ev) { } void VideoStream::pushEos() { - std::cout << "pushEos 1" << std::endl; { std::lock_guard lock(mutex_); if (eof_) { - std::cout << "pushEos 2" << std::endl; return; } eof_ = true; } cv_.notify_all(); - std::cout << "pushEos 3" << std::endl; } } // namespace livekit