From 10661dea72d0c234c9a631aa02afd63f5beb6a90 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 15 May 2026 14:16:21 +0530 Subject: [PATCH 01/63] chore: add endpoint for reportClientCallEvent --- .../video/generated/apis/ProductvideoApi.kt | 17 +- .../generated/infrastructure/Serializer.kt | 6 +- .../video/generated/models/CallEndedEvent.kt | 3 + .../generated/models/CallLevelEventPayload.kt | 53 ++++ .../generated/models/CallReactionEvent.kt | 2 +- .../models/CallStatsParticipantCounts.kt | 18 ++ .../models/CallStatsParticipantSession.kt | 12 + .../models/CallStatsReportReadyEvent.kt | 11 +- .../models/CallStatsSessionResponse.kt | 62 +++++ .../models/ChatPreferencesResponse.kt | 62 +++++ ...{Coordinates.kt => CoordinatesResponse.kt} | 4 +- .../video/generated/models/Credentials.kt | 2 +- .../models/FeedsPreferencesResponse.kt | 6 + .../{ICEServer.kt => ICEServerResponse.kt} | 4 +- .../IndividualRecordingSettingsRequest.kt | 5 +- .../IndividualRecordingSettingsResponse.kt | 5 +- .../video/generated/models/JoinCallRequest.kt | 14 +- .../models/LocalCallAcceptedPostEvent.kt | 59 ---- .../models/LocalCallRejectedPostEvent.kt | 62 ----- .../{Location.kt => LocationResponse.kt} | 4 +- .../models/PushPreferencesResponse.kt | 3 + ...ueryCallSessionParticipantStatsResponse.kt | 5 +- ...ngs.kt => QueryCallSessionStatsRequest.kt} | 20 +- .../models/QueryCallSessionStatsResponse.kt | 53 ++++ .../models/RawRecordingSettingsRequest.kt | 5 +- .../models/RawRecordingSettingsResponse.kt | 5 +- .../video/generated/models/ReadReceipts.kt | 44 --- .../models/ReportClientCallEventRequest.kt | 263 ++++++++++++++++++ ...ts.kt => ReportClientCallEventResponse.kt} | 8 +- .../generated/models/ResolveSipAuthRequest.kt | 53 ++++ .../models/ResolveSipAuthResponse.kt | 56 ++++ .../models/ResolveSipInboundRequest.kt | 9 +- .../generated/models/SFULocationResponse.kt | 4 +- ...SIPChallenge.kt => SIPChallengeRequest.kt} | 4 +- .../generated/models/SIPTrunkResponse.kt | 3 + ...Request.kt => SendVideoReactionRequest.kt} | 2 +- ...sponse.kt => SendVideoReactionResponse.kt} | 4 +- .../generated/models/SipInboundCredentials.kt | 3 + .../generated/models/SortParamRequest.kt | 38 +-- .../generated/models/TypingIndicators.kt | 44 --- .../video/generated/models/UserBannedEvent.kt | 50 ++-- .../generated/models/UserDeactivatedEvent.kt | 16 +- .../generated/models/UserDeletedEvent.kt | 81 ++++++ ...edEvent.kt => UserPresenceChangedEvent.kt} | 20 +- .../generated/models/UserReactivatedEvent.kt | 15 +- .../{User.kt => UserResponseCommonFields.kt} | 46 ++- .../generated/models/UserUnbannedEvent.kt | 90 ++++++ .../video/generated/models/VideoEvent.kt | 4 +- ...onResponse.kt => VideoReactionResponse.kt} | 2 +- 49 files changed, 1006 insertions(+), 355 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallLevelEventPayload.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsSessionResponse.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ChatPreferencesResponse.kt rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{Coordinates.kt => CoordinatesResponse.kt} (95%) rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{ICEServer.kt => ICEServerResponse.kt} (94%) delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallAcceptedPostEvent.kt delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallRejectedPostEvent.kt rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{Location.kt => LocationResponse.kt} (95%) rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{PrivacySettings.kt => QueryCallSessionStatsRequest.kt} (69%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionStatsResponse.kt delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReadReceipts.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{DeliveryReceipts.kt => ReportClientCallEventResponse.kt} (87%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipAuthRequest.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipAuthResponse.kt rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{SIPChallenge.kt => SIPChallengeRequest.kt} (96%) rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{SendReactionRequest.kt => SendVideoReactionRequest.kt} (97%) rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{SendReactionResponse.kt => SendVideoReactionResponse.kt} (91%) delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/TypingIndicators.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserDeletedEvent.kt rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{UserMutedEvent.kt => UserPresenceChangedEvent.kt} (76%) rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{User.kt => UserResponseCommonFields.kt} (75%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserUnbannedEvent.kt rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{ReactionResponse.kt => VideoReactionResponse.kt} (97%) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt index 252b8703282..b5b9be2fbd7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt @@ -35,6 +35,15 @@ import retrofit2.http.PUT interface ProductvideoApi { + /** + * Report client-side call event + * Reports a single client-side join-lifecycle event (initiated or completed) for one of CoordinatorJoin, WSJoin, or PeerConnectionConnect. Pairs are correlated by event_session_id. + */ + @POST("/video/call/client_event") + suspend fun reportClientCallEvent( + @Body reportClientCallEventRequest: io.getstream.android.video.generated.models.ReportClientCallEventRequest + ): io.getstream.android.video.generated.models.ReportClientCallEventResponse + /** * Query call members * Query call members with filter query @@ -332,8 +341,8 @@ interface ProductvideoApi { suspend fun sendVideoReaction( @Path("type") type: kotlin.String, @Path("id") id: kotlin.String , - @Body sendReactionRequest: io.getstream.android.video.generated.models.SendReactionRequest - ): io.getstream.android.video.generated.models.SendReactionResponse + @Body sendVideoReactionRequest: io.getstream.android.video.generated.models.SendVideoReactionRequest + ): io.getstream.android.video.generated.models.SendVideoReactionResponse /** * List recordings @@ -716,7 +725,7 @@ interface ProductvideoApi { @Path("session") session: kotlin.String, @Path("filename") filename: kotlin.String ): io.getstream.android.video.generated.models.DeleteTranscriptionResponse - + /** * Map call participants by location * @@ -850,7 +859,7 @@ interface ProductvideoApi { @GET("/video/longpoll") suspend fun videoConnect( ) - + /** * Resolve SIP Inbound Routing * Resolve SIP inbound routing based on trunk number, caller number, and challenge authentication diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/infrastructure/Serializer.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/infrastructure/Serializer.kt index f611659ac97..1e3c2c9a0cf 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/infrastructure/Serializer.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/infrastructure/Serializer.kt @@ -52,7 +52,11 @@ object Serializer { .add(io.getstream.android.video.generated.models.RawRecordingSettingsResponse.Mode.ModeAdapter()) .add(io.getstream.android.video.generated.models.RecordSettingsRequest.Mode.ModeAdapter()) .add(io.getstream.android.video.generated.models.RecordSettingsRequest.Quality.QualityAdapter()) - .add(io.getstream.android.video.generated.models.SortParamRequest.Type.TypeAdapter()) + .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.EventType.EventTypeAdapter()) + .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.Stage.StageAdapter()) + .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.IceState.IceStateAdapter()) + .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.Outcome.OutcomeAdapter()) + .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.PeerConnection.PeerConnectionAdapter()) .add(io.getstream.android.video.generated.models.StartClosedCaptionsRequest.Language.LanguageAdapter()) .add(io.getstream.android.video.generated.models.StartTranscriptionRequest.Language.LanguageAdapter()) .add(io.getstream.android.video.generated.models.TranscriptionSettingsRequest.ClosedCaptionMode.ClosedCaptionModeAdapter()) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallEndedEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallEndedEvent.kt index c05fb192070..45d5947e2e5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallEndedEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallEndedEvent.kt @@ -54,6 +54,9 @@ data class CallEndedEvent ( @Json(name = "reason") val reason: kotlin.String? = null, + @Json(name = "members") + val members: kotlin.collections.List? = emptyList(), + @Json(name = "user") val user: io.getstream.android.video.generated.models.UserResponse? = null ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallLevelEventPayload.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallLevelEventPayload.kt new file mode 100644 index 00000000000..c75660eb9bc --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallLevelEventPayload.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * + */ + +data class CallLevelEventPayload ( + @Json(name = "event_type") + val eventType: kotlin.String, + + @Json(name = "timestamp") + val timestamp: kotlin.Int, + + @Json(name = "user_id") + val userId: kotlin.String, + + @Json(name = "payload") + val payload: kotlin.collections.Map? = emptyMap() +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt index e78e8db37df..64ae74ea945 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt @@ -46,7 +46,7 @@ data class CallReactionEvent ( val createdAt: org.threeten.bp.OffsetDateTime, @Json(name = "reaction") - val reaction: io.getstream.android.video.generated.models.ReactionResponse, + val reaction: io.getstream.android.video.generated.models.VideoReactionResponse, @Json(name = "type") val type: kotlin.String diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsParticipantCounts.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsParticipantCounts.kt index 8b4d9b2bb16..2438afbbb47 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsParticipantCounts.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsParticipantCounts.kt @@ -57,6 +57,24 @@ data class CallStatsParticipantCounts ( @Json(name = "sessions") val sessions: kotlin.Int, + @Json(name = "sfus_used") + val sfusUsed: kotlin.Int, + + @Json(name = "average_jitter_ms") + val averageJitterMs: kotlin.Int? = null, + + @Json(name = "average_latency_ms") + val averageLatencyMs: kotlin.Int? = null, + + @Json(name = "call_event_count") + val callEventCount: kotlin.Int? = null, + + @Json(name = "cq_score") + val cqScore: kotlin.Int? = null, + + @Json(name = "max_freezes_duration_ms") + val maxFreezesDurationMs: kotlin.Int? = null, + @Json(name = "total_participant_duration") val totalParticipantDuration: kotlin.Int? = null ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsParticipantSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsParticipantSession.kt index ce8e49588a9..822681ac48c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsParticipantSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsParticipantSession.kt @@ -69,6 +69,18 @@ data class CallStatsParticipantSession ( @Json(name = "ended_at") val endedAt: org.threeten.bp.OffsetDateTime? = null, + @Json(name = "freezes_duration_ms") + val freezesDurationMs: kotlin.Int? = null, + + @Json(name = "ingress") + val ingress: kotlin.String? = null, + + @Json(name = "jitter_ms") + val jitterMs: kotlin.Int? = null, + + @Json(name = "latency_ms") + val latencyMs: kotlin.Int? = null, + @Json(name = "os") val os: kotlin.String? = null, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsReportReadyEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsReportReadyEvent.kt index c002187193c..aa097fce3b8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsReportReadyEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsReportReadyEvent.kt @@ -48,8 +48,17 @@ data class CallStatsReportReadyEvent ( @Json(name = "session_id") val sessionId: kotlin.String, + @Json(name = "counts") + val counts: io.getstream.android.video.generated.models.CallStatsParticipantCounts, + @Json(name = "type") - val type: kotlin.String + val type: kotlin.String, + + @Json(name = "is_trimmed") + val isTrimmed: kotlin.Boolean? = null, + + @Json(name = "participants_overview") + val participantsOverview: kotlin.collections.List? = emptyList() ) : io.getstream.android.video.generated.models.VideoEvent(), io.getstream.android.video.generated.models.WSCallEvent { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsSessionResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsSessionResponse.kt new file mode 100644 index 00000000000..6f47076e4be --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsSessionResponse.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * + */ + +data class CallStatsSessionResponse ( + @Json(name = "call_id") + val callId: kotlin.String, + + @Json(name = "call_session_id") + val callSessionId: kotlin.String, + + @Json(name = "call_type") + val callType: kotlin.String, + + @Json(name = "generated_at") + val generatedAt: org.threeten.bp.OffsetDateTime, + + @Json(name = "counts") + val counts: io.getstream.android.video.generated.models.CallStatsParticipantCounts, + + @Json(name = "call_ended_at") + val callEndedAt: org.threeten.bp.OffsetDateTime? = null, + + @Json(name = "call_started_at") + val callStartedAt: org.threeten.bp.OffsetDateTime? = null +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ChatPreferencesResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ChatPreferencesResponse.kt new file mode 100644 index 00000000000..9941dab2a37 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ChatPreferencesResponse.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * + */ + +data class ChatPreferencesResponse ( + @Json(name = "channel_mentions") + val channelMentions: kotlin.String? = null, + + @Json(name = "default_preference") + val defaultPreference: kotlin.String? = null, + + @Json(name = "direct_mentions") + val directMentions: kotlin.String? = null, + + @Json(name = "group_mentions") + val groupMentions: kotlin.String? = null, + + @Json(name = "here_mentions") + val hereMentions: kotlin.String? = null, + + @Json(name = "role_mentions") + val roleMentions: kotlin.String? = null, + + @Json(name = "thread_replies") + val threadReplies: kotlin.String? = null +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Coordinates.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CoordinatesResponse.kt similarity index 95% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Coordinates.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CoordinatesResponse.kt index 9fcd2acba77..dbbb8d63edb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Coordinates.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CoordinatesResponse.kt @@ -35,10 +35,10 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * Geographic coordinates */ -data class Coordinates ( +data class CoordinatesResponse ( @Json(name = "latitude") val latitude: kotlin.Float, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Credentials.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Credentials.kt index 21d515cf14a..1ccb7b7e8a3 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Credentials.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Credentials.kt @@ -43,7 +43,7 @@ data class Credentials ( val token: kotlin.String, @Json(name = "ice_servers") - val iceServers: kotlin.collections.List = emptyList(), + val iceServers: kotlin.collections.List = emptyList(), @Json(name = "server") val server: io.getstream.android.video.generated.models.SFUResponse diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/FeedsPreferencesResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/FeedsPreferencesResponse.kt index 843633138c6..a7959eb54fa 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/FeedsPreferencesResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/FeedsPreferencesResponse.kt @@ -42,9 +42,15 @@ data class FeedsPreferencesResponse ( @Json(name = "comment") val comment: kotlin.String? = null, + @Json(name = "comment_mention") + val commentMention: kotlin.String? = null, + @Json(name = "comment_reaction") val commentReaction: kotlin.String? = null, + @Json(name = "comment_reply") + val commentReply: kotlin.String? = null, + @Json(name = "follow") val follow: kotlin.String? = null, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServer.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServerResponse.kt similarity index 94% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServer.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServerResponse.kt index 00d727d69ac..734f4dc1329 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServer.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServerResponse.kt @@ -35,10 +35,10 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * ICE server configuration for WebRTC connections */ -data class ICEServer ( +data class ICEServerResponse ( @Json(name = "password") val password: kotlin.String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/IndividualRecordingSettingsRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/IndividualRecordingSettingsRequest.kt index 1356d5e2a3b..c4bed824528 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/IndividualRecordingSettingsRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/IndividualRecordingSettingsRequest.kt @@ -40,7 +40,10 @@ import com.squareup.moshi.ToJson data class IndividualRecordingSettingsRequest ( @Json(name = "mode") - val mode: Mode + val mode: Mode, + + @Json(name = "output_types") + val outputTypes: kotlin.collections.List? = emptyList() ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/IndividualRecordingSettingsResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/IndividualRecordingSettingsResponse.kt index e08e63ea85d..eef0586d67b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/IndividualRecordingSettingsResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/IndividualRecordingSettingsResponse.kt @@ -40,7 +40,10 @@ import com.squareup.moshi.ToJson data class IndividualRecordingSettingsResponse ( @Json(name = "mode") - val mode: Mode + val mode: Mode, + + @Json(name = "output_types") + val outputTypes: kotlin.collections.List? = emptyList() ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/JoinCallRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/JoinCallRequest.kt index ef10fba56c0..6847ae63337 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/JoinCallRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/JoinCallRequest.kt @@ -45,15 +45,15 @@ data class JoinCallRequest ( @Json(name = "create") val create: kotlin.Boolean? = null, + @Json(name = "hint_high_scale_livestream_publisher") + val hintHighScaleLivestreamPublisher: kotlin.Boolean? = null, + @Json(name = "members_limit") val membersLimit: kotlin.Int? = null, @Json(name = "migrating_from") val migratingFrom: kotlin.String? = null, - @Json(name = "migrating_from_list") - val migratingFromList: kotlin.collections.List? = null, - @Json(name = "notify") val notify: kotlin.Boolean? = null, @@ -63,9 +63,9 @@ data class JoinCallRequest ( @Json(name = "video") val video: kotlin.Boolean? = null, - @Json(name = "data") - val data: io.getstream.android.video.generated.models.CallRequest? = null, + @Json(name = "migrating_from_list") + val migratingFromList: kotlin.collections.List? = emptyList(), - @Json(name = "hint_high_scale_livestream_publisher") - val hintHighScaleLivestreamPublisher: kotlin.Boolean? = null + @Json(name = "data") + val data: io.getstream.android.video.generated.models.CallRequest? = null ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallAcceptedPostEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallAcceptedPostEvent.kt deleted file mode 100644 index 240057076e8..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallAcceptedPostEvent.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress( - "ArrayInDataClass", - "EnumEntryName", - "RemoveRedundantQualifierName", - "UnusedImport" -) - -package io.getstream.android.video.generated.models - -import com.squareup.moshi.Json -import org.threeten.bp.OffsetDateTime - -/** - * This event is sent after [CallAcceptedEvent] is consumed in [io.getstream.video.android.core.CallState] - */ - -internal data class LocalCallAcceptedPostEvent ( - @Json(name = "call_cid") - val callCid: String, - - @Json(name = "created_at") - val createdAt: OffsetDateTime, - - @Json(name = "call") - val call: CallResponse, - - @Json(name = "user") - val user: UserResponse, - - @Json(name = "type") - val type: String -) -: VideoEvent(), WSCallEvent -{ - - override fun getEventType(): String { - return type - } - - override fun getCallCID(): String { - return callCid - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallRejectedPostEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallRejectedPostEvent.kt deleted file mode 100644 index 953db4ca942..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallRejectedPostEvent.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress( - "ArrayInDataClass", - "EnumEntryName", - "RemoveRedundantQualifierName", - "UnusedImport" -) - -package io.getstream.android.video.generated.models - -import com.squareup.moshi.Json -import org.threeten.bp.OffsetDateTime - -/** - * This event is sent after [CallRejectedEvent] is consumed in [io.getstream.video.android.core.CallState] - */ - -internal data class LocalCallRejectedPostEvent ( - @Json(name = "call_cid") - val callCid: String, - - @Json(name = "created_at") - val createdAt: OffsetDateTime, - - @Json(name = "call") - val call: CallResponse, - - @Json(name = "user") - val user: UserResponse, - - @Json(name = "type") - val type: String, - - @Json(name = "reason") - val reason: String? = null -) -: VideoEvent(), WSCallEvent -{ - - override fun getEventType(): String { - return type - } - - override fun getCallCID(): String { - return callCid - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Location.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocationResponse.kt similarity index 95% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Location.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocationResponse.kt index 04d8c09a45a..59506265fea 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Location.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocationResponse.kt @@ -35,10 +35,10 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * Geographic location metadata */ -data class Location ( +data class LocationResponse ( @Json(name = "continent_code") val continentCode: kotlin.String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/PushPreferencesResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/PushPreferencesResponse.kt index 3f077dcd089..f917dbaa12f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/PushPreferencesResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/PushPreferencesResponse.kt @@ -51,6 +51,9 @@ data class PushPreferencesResponse ( @Json(name = "feeds_level") val feedsLevel: kotlin.String? = null, + @Json(name = "chat_preferences") + val chatPreferences: io.getstream.android.video.generated.models.ChatPreferencesResponse? = null, + @Json(name = "feeds_preferences") val feedsPreferences: io.getstream.android.video.generated.models.FeedsPreferencesResponse? = null ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse.kt index d80a7b2be92..caf03241048 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse.kt @@ -70,5 +70,8 @@ data class QueryCallSessionParticipantStatsResponse ( val prev: kotlin.String? = null, @Json(name = "tmp_data_source") - val tmpDataSource: kotlin.String? = null + val tmpDataSource: kotlin.String? = null, + + @Json(name = "call_events") + val callEvents: kotlin.collections.List? = emptyList() ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/PrivacySettings.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionStatsRequest.kt similarity index 69% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/PrivacySettings.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionStatsRequest.kt index 44f611ec9ac..f663ebc93ad 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/PrivacySettings.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionStatsRequest.kt @@ -38,13 +38,19 @@ import com.squareup.moshi.ToJson * */ -data class PrivacySettings ( - @Json(name = "delivery_receipts") - val deliveryReceipts: io.getstream.android.video.generated.models.DeliveryReceipts? = null, +data class QueryCallSessionStatsRequest ( + @Json(name = "limit") + val limit: kotlin.Int? = null, - @Json(name = "read_receipts") - val readReceipts: io.getstream.android.video.generated.models.ReadReceipts? = null, + @Json(name = "next") + val next: kotlin.String? = null, - @Json(name = "typing_indicators") - val typingIndicators: io.getstream.android.video.generated.models.TypingIndicators? = null + @Json(name = "prev") + val prev: kotlin.String? = null, + + @Json(name = "sort") + val sort: kotlin.collections.List? = emptyList(), + + @Json(name = "filter_conditions") + val filterConditions: kotlin.collections.Map? = emptyMap() ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionStatsResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionStatsResponse.kt new file mode 100644 index 00000000000..4a6184828c8 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/QueryCallSessionStatsResponse.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * Basic response information + */ + +data class QueryCallSessionStatsResponse ( + @Json(name = "duration") + val duration: kotlin.String, + + @Json(name = "call_stats") + val callStats: kotlin.collections.List = emptyList(), + + @Json(name = "next") + val next: kotlin.String? = null, + + @Json(name = "prev") + val prev: kotlin.String? = null +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/RawRecordingSettingsRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/RawRecordingSettingsRequest.kt index b09284cea3a..ea05930e912 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/RawRecordingSettingsRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/RawRecordingSettingsRequest.kt @@ -40,7 +40,10 @@ import com.squareup.moshi.ToJson data class RawRecordingSettingsRequest ( @Json(name = "mode") - val mode: Mode + val mode: Mode, + + @Json(name = "audio_only") + val audioOnly: kotlin.Boolean? = null ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/RawRecordingSettingsResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/RawRecordingSettingsResponse.kt index a2c7bbf04d9..8787ee437f3 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/RawRecordingSettingsResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/RawRecordingSettingsResponse.kt @@ -40,7 +40,10 @@ import com.squareup.moshi.ToJson data class RawRecordingSettingsResponse ( @Json(name = "mode") - val mode: Mode + val mode: Mode, + + @Json(name = "audio_only") + val audioOnly: kotlin.Boolean? = null ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReadReceipts.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReadReceipts.kt deleted file mode 100644 index efa0da4108d..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReadReceipts.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress( - "ArrayInDataClass", - "EnumEntryName", - "RemoveRedundantQualifierName", - "UnusedImport" -) - -package io.getstream.android.video.generated.models - -import kotlin.collections.List -import kotlin.collections.Map -import kotlin.collections.* -import kotlin.io.* -import com.squareup.moshi.FromJson -import com.squareup.moshi.Json -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson - -/** - * - */ - -data class ReadReceipts ( - @Json(name = "enabled") - val enabled: kotlin.Boolean -) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt new file mode 100644 index 00000000000..cf2591c4920 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * Reports a single client-side join-lifecycle event (initiated or completed) for one of CoordinatorJoin, WSJoin, or PeerConnectionConnect. Initiation and completion of a stage attempt share the same event_session_id. + */ + +data class ReportClientCallEventRequest ( + @Json(name = "event_session_id") + val eventSessionId: kotlin.String, + + @Json(name = "event_type") + val eventType: EventType, + + @Json(name = "id") + val id: kotlin.String, + + @Json(name = "sdk_version") + val sdkVersion: kotlin.String, + + @Json(name = "stage") + val stage: Stage, + + @Json(name = "timestamp") + val timestamp: org.threeten.bp.OffsetDateTime, + + @Json(name = "type") + val type: kotlin.String, + + @Json(name = "user_agent") + val userAgent: kotlin.String, + + @Json(name = "user_id") + val userId: kotlin.String, + + @Json(name = "call_session_id") + val callSessionId: kotlin.String? = null, + + @Json(name = "ice_state") + val iceState: IceState? = null, + + @Json(name = "outcome") + val outcome: Outcome? = null, + + @Json(name = "peer_connection") + val peerConnection: PeerConnection? = null, + + @Json(name = "previously_connected_timestamp") + val previouslyConnectedTimestamp: org.threeten.bp.OffsetDateTime? = null, + + @Json(name = "retry_count_attempt") + val retryCountAttempt: kotlin.Int? = null, + + @Json(name = "retry_failure_code") + val retryFailureCode: kotlin.String? = null, + + @Json(name = "retry_failure_reason") + val retryFailureReason: kotlin.String? = null, + + @Json(name = "sfu_id") + val sfuId: kotlin.String? = null, + + @Json(name = "user_session_id") + val userSessionId: kotlin.String? = null, + + @Json(name = "was_previously_connected") + val wasPreviouslyConnected: kotlin.Boolean? = null +) +{ + + /** + * EventType Enum + */ + sealed class EventType(val value: kotlin.String) { + override fun toString(): String = value + + companion object { + fun fromString(s: kotlin.String): EventType = when (s) { + "completed" -> Completed + "initiated" -> Initiated + else -> Unknown(s) + } + } + object Completed : EventType("completed") + object Initiated : EventType("initiated") + data class Unknown(val unknownValue: kotlin.String) : EventType(unknownValue) + + + class EventTypeAdapter : JsonAdapter() { + @FromJson + override fun fromJson(reader: JsonReader): EventType? { + val s = reader.nextString() ?: return null + return EventType.fromString(s) + } + + @ToJson + override fun toJson(writer: JsonWriter, value: EventType?) { + writer.value(value?.value) + } + } + } + /** + * Stage Enum + */ + sealed class Stage(val value: kotlin.String) { + override fun toString(): String = value + + companion object { + fun fromString(s: kotlin.String): Stage = when (s) { + "CoordinatorJoin" -> CoordinatorJoin + "PeerConnectionConnect" -> PeerConnectionConnect + "WSJoin" -> WSJoin + else -> Unknown(s) + } + } + object CoordinatorJoin : Stage("CoordinatorJoin") + object PeerConnectionConnect : Stage("PeerConnectionConnect") + object WSJoin : Stage("WSJoin") + data class Unknown(val unknownValue: kotlin.String) : Stage(unknownValue) + + + class StageAdapter : JsonAdapter() { + @FromJson + override fun fromJson(reader: JsonReader): Stage? { + val s = reader.nextString() ?: return null + return Stage.fromString(s) + } + + @ToJson + override fun toJson(writer: JsonWriter, value: Stage?) { + writer.value(value?.value) + } + } + } + /** + * IceState Enum + */ + sealed class IceState(val value: kotlin.String) { + override fun toString(): String = value + + companion object { + fun fromString(s: kotlin.String): IceState = when (s) { + "CONNECTED" -> CONNECTED + "FAILED" -> FAILED + "NOT_CONNECTED" -> NOTCONNECTED + else -> Unknown(s) + } + } + object CONNECTED : IceState("CONNECTED") + object FAILED : IceState("FAILED") + object NOTCONNECTED : IceState("NOT_CONNECTED") + data class Unknown(val unknownValue: kotlin.String) : IceState(unknownValue) + + + class IceStateAdapter : JsonAdapter() { + @FromJson + override fun fromJson(reader: JsonReader): IceState? { + val s = reader.nextString() ?: return null + return IceState.fromString(s) + } + + @ToJson + override fun toJson(writer: JsonWriter, value: IceState?) { + writer.value(value?.value) + } + } + } + /** + * Outcome Enum + */ + sealed class Outcome(val value: kotlin.String) { + override fun toString(): String = value + + companion object { + fun fromString(s: kotlin.String): Outcome = when (s) { + "failure" -> Failure + "success" -> Success + else -> Unknown(s) + } + } + object Failure : Outcome("failure") + object Success : Outcome("success") + data class Unknown(val unknownValue: kotlin.String) : Outcome(unknownValue) + + + class OutcomeAdapter : JsonAdapter() { + @FromJson + override fun fromJson(reader: JsonReader): Outcome? { + val s = reader.nextString() ?: return null + return Outcome.fromString(s) + } + + @ToJson + override fun toJson(writer: JsonWriter, value: Outcome?) { + writer.value(value?.value) + } + } + } + /** + * PeerConnection Enum + */ + sealed class PeerConnection(val value: kotlin.String) { + override fun toString(): String = value + + companion object { + fun fromString(s: kotlin.String): PeerConnection = when (s) { + "publish" -> Publish + "subscribe" -> Subscribe + else -> Unknown(s) + } + } + object Publish : PeerConnection("publish") + object Subscribe : PeerConnection("subscribe") + data class Unknown(val unknownValue: kotlin.String) : PeerConnection(unknownValue) + + + class PeerConnectionAdapter : JsonAdapter() { + @FromJson + override fun fromJson(reader: JsonReader): PeerConnection? { + val s = reader.nextString() ?: return null + return PeerConnection.fromString(s) + } + + @ToJson + override fun toJson(writer: JsonWriter, value: PeerConnection?) { + writer.value(value?.value) + } + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/DeliveryReceipts.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventResponse.kt similarity index 87% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/DeliveryReceipts.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventResponse.kt index 1896c2be46f..d148e380e6e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/DeliveryReceipts.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventResponse.kt @@ -35,10 +35,10 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * Response for reporting a client-side call event */ -data class DeliveryReceipts ( - @Json(name = "enabled") - val enabled: kotlin.Boolean +data class ReportClientCallEventResponse ( + @Json(name = "duration") + val duration: kotlin.String ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipAuthRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipAuthRequest.kt new file mode 100644 index 00000000000..9a4edebad10 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipAuthRequest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * Request to determine SIP trunk authentication requirements + */ + +data class ResolveSipAuthRequest ( + @Json(name = "sip_caller_number") + val sipCallerNumber: kotlin.String, + + @Json(name = "sip_trunk_number") + val sipTrunkNumber: kotlin.String, + + @Json(name = "from_host") + val fromHost: kotlin.String? = null, + + @Json(name = "source_ip") + val sourceIp: kotlin.String? = null +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipAuthResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipAuthResponse.kt new file mode 100644 index 00000000000..3719bfff87b --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipAuthResponse.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * Response containing the pre-authentication decision for a SIP trunk + */ + +data class ResolveSipAuthResponse ( + @Json(name = "auth_result") + val authResult: kotlin.String, + + @Json(name = "duration") + val duration: kotlin.String, + + @Json(name = "password") + val password: kotlin.String? = null, + + @Json(name = "trunk_id") + val trunkId: kotlin.String? = null, + + @Json(name = "username") + val username: kotlin.String? = null +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipInboundRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipInboundRequest.kt index 89dd1902e9b..0492f2ebb06 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipInboundRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ResolveSipInboundRequest.kt @@ -45,12 +45,15 @@ data class ResolveSipInboundRequest ( @Json(name = "sip_trunk_number") val sipTrunkNumber: kotlin.String, - @Json(name = "challenge") - val challenge: io.getstream.android.video.generated.models.SIPChallenge, - @Json(name = "routing_number") val routingNumber: kotlin.String? = null, + @Json(name = "trunk_id") + val trunkId: kotlin.String? = null, + + @Json(name = "challenge") + val challenge: io.getstream.android.video.generated.models.SIPChallengeRequest? = null, + @Json(name = "sip_headers") val sipHeaders: kotlin.collections.Map? = emptyMap() ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SFULocationResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SFULocationResponse.kt index 87aac384f26..bebb6398910 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SFULocationResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SFULocationResponse.kt @@ -46,10 +46,10 @@ data class SFULocationResponse ( val id: kotlin.String, @Json(name = "coordinates") - val coordinates: io.getstream.android.video.generated.models.Coordinates, + val coordinates: io.getstream.android.video.generated.models.CoordinatesResponse, @Json(name = "location") - val location: io.getstream.android.video.generated.models.Location, + val location: io.getstream.android.video.generated.models.LocationResponse, @Json(name = "count") val count: kotlin.Int? = null diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPChallenge.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPChallengeRequest.kt similarity index 96% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPChallenge.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPChallengeRequest.kt index e6ee0932acc..7522eefb8da 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPChallenge.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPChallengeRequest.kt @@ -35,10 +35,10 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * SIP digest challenge authentication data */ -data class SIPChallenge ( +data class SIPChallengeRequest ( @Json(name = "a1") val a1: kotlin.String? = null, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPTrunkResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPTrunkResponse.kt index 443059f75ac..7cec3dd240b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPTrunkResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SIPTrunkResponse.kt @@ -60,6 +60,9 @@ data class SIPTrunkResponse ( @Json(name = "username") val username: kotlin.String, + @Json(name = "allowed_ips") + val allowedIps: kotlin.collections.List = emptyList(), + @Json(name = "numbers") val numbers: kotlin.collections.List = emptyList() ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionRequest.kt similarity index 97% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionRequest.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionRequest.kt index ee446a9f3ee..de4a51c171c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionRequest.kt @@ -38,7 +38,7 @@ import com.squareup.moshi.ToJson * */ -data class SendReactionRequest ( +data class SendVideoReactionRequest ( @Json(name = "type") val type: kotlin.String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionResponse.kt similarity index 91% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionResponse.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionResponse.kt index da2aeccd621..c17b8e097f6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionResponse.kt @@ -38,10 +38,10 @@ import com.squareup.moshi.ToJson * Basic response information */ -data class SendReactionResponse ( +data class SendVideoReactionResponse ( @Json(name = "duration") val duration: kotlin.String, @Json(name = "reaction") - val reaction: io.getstream.android.video.generated.models.ReactionResponse + val reaction: io.getstream.android.video.generated.models.VideoReactionResponse ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SipInboundCredentials.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SipInboundCredentials.kt index 72e16aaa232..6806b56102f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SipInboundCredentials.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SipInboundCredentials.kt @@ -39,6 +39,9 @@ import com.squareup.moshi.ToJson */ data class SipInboundCredentials ( + @Json(name = "api_key") + val apiKey: kotlin.String, + @Json(name = "call_id") val callId: kotlin.String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SortParamRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SortParamRequest.kt index de682733e84..2ec5e310295 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SortParamRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SortParamRequest.kt @@ -46,41 +46,5 @@ data class SortParamRequest ( val field: kotlin.String? = null, @Json(name = "type") - val type: Type? = null + val type: kotlin.String? = null ) -{ - - /** - * Type Enum - */ - sealed class Type(val value: kotlin.String) { - override fun toString(): String = value - - companion object { - fun fromString(s: kotlin.String): Type = when (s) { - "boolean" -> Boolean - "" -> Empty - "number" -> Number - else -> Unknown(s) - } - } - object Boolean : Type("boolean") - object Empty : Type("") - object Number : Type("number") - data class Unknown(val unknownValue: kotlin.String) : Type(unknownValue) - - - class TypeAdapter : JsonAdapter() { - @FromJson - override fun fromJson(reader: JsonReader): Type? { - val s = reader.nextString() ?: return null - return Type.fromString(s) - } - - @ToJson - override fun toJson(writer: JsonWriter, value: Type?) { - writer.value(value?.value) - } - } - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/TypingIndicators.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/TypingIndicators.kt deleted file mode 100644 index 2dd76e1094e..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/TypingIndicators.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress( - "ArrayInDataClass", - "EnumEntryName", - "RemoveRedundantQualifierName", - "UnusedImport" -) - -package io.getstream.android.video.generated.models - -import kotlin.collections.List -import kotlin.collections.Map -import kotlin.collections.* -import kotlin.io.* -import com.squareup.moshi.FromJson -import com.squareup.moshi.Json -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson - -/** - * - */ - -data class TypingIndicators ( - @Json(name = "enabled") - val enabled: kotlin.Boolean -) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserBannedEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserBannedEvent.kt index e98e0e668b2..008505dc948 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserBannedEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserBannedEvent.kt @@ -35,42 +35,60 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * This event is sent when a user gets banned. The event contains information about the user that was banned. */ data class UserBannedEvent ( - @Json(name = "channel_id") - val channelId: kotlin.String, - - @Json(name = "channel_type") - val channelType: kotlin.String, - - @Json(name = "cid") - val cid: kotlin.String, - @Json(name = "created_at") val createdAt: org.threeten.bp.OffsetDateTime, - @Json(name = "shadow") - val shadow: kotlin.Boolean, + @Json(name = "custom") + val custom: kotlin.collections.Map = emptyMap(), - @Json(name = "created_by") - val createdBy: io.getstream.android.video.generated.models.User, + @Json(name = "user") + val user: io.getstream.android.video.generated.models.UserResponseCommonFields, @Json(name = "type") val type: kotlin.String, + @Json(name = "channel_id") + val channelId: kotlin.String? = null, + + @Json(name = "channel_member_count") + val channelMemberCount: kotlin.Int? = null, + + @Json(name = "channel_message_count") + val channelMessageCount: kotlin.Int? = null, + + @Json(name = "channel_type") + val channelType: kotlin.String? = null, + + @Json(name = "cid") + val cid: kotlin.String? = null, + @Json(name = "expiration") val expiration: org.threeten.bp.OffsetDateTime? = null, @Json(name = "reason") val reason: kotlin.String? = null, + @Json(name = "received_at") + val receivedAt: org.threeten.bp.OffsetDateTime? = null, + + @Json(name = "shadow") + val shadow: kotlin.Boolean? = null, + @Json(name = "team") val team: kotlin.String? = null, - @Json(name = "user") - val user: io.getstream.android.video.generated.models.User? = null + @Json(name = "total_bans") + val totalBans: kotlin.Int? = null, + + @Json(name = "channel_custom") + val channelCustom: kotlin.collections.Map? = emptyMap(), + + @Json(name = "created_by") + val createdBy: io.getstream.android.video.generated.models.UserResponseCommonFields? = null ) : io.getstream.android.video.generated.models.VideoEvent() { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserDeactivatedEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserDeactivatedEvent.kt index 3c8c12a9cb2..9ca1f481ccd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserDeactivatedEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserDeactivatedEvent.kt @@ -35,21 +35,27 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * This event is sent when a user gets deactivated. The event contains information about the user that was deactivated. */ data class UserDeactivatedEvent ( @Json(name = "created_at") val createdAt: org.threeten.bp.OffsetDateTime, - @Json(name = "created_by") - val createdBy: io.getstream.android.video.generated.models.User, + @Json(name = "custom") + val custom: kotlin.collections.Map = emptyMap(), + + @Json(name = "user") + val user: io.getstream.android.video.generated.models.UserResponseCommonFields, @Json(name = "type") val type: kotlin.String, - @Json(name = "user") - val user: io.getstream.android.video.generated.models.User? = null + @Json(name = "received_at") + val receivedAt: org.threeten.bp.OffsetDateTime? = null, + + @Json(name = "created_by") + val createdBy: io.getstream.android.video.generated.models.UserResponseCommonFields? = null ) : io.getstream.android.video.generated.models.VideoEvent() { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserDeletedEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserDeletedEvent.kt new file mode 100644 index 00000000000..77da9110465 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserDeletedEvent.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * This event is sent when a user gets deleted. The event contains information about the user that was deleted and the deletion options that were used. + */ + +data class UserDeletedEvent ( + @Json(name = "created_at") + val createdAt: org.threeten.bp.OffsetDateTime, + + @Json(name = "delete_conversation") + val deleteConversation: kotlin.String, + + @Json(name = "delete_conversation_channels") + val deleteConversationChannels: kotlin.Boolean, + + @Json(name = "delete_messages") + val deleteMessages: kotlin.String, + + @Json(name = "delete_user") + val deleteUser: kotlin.String, + + @Json(name = "hard_delete") + val hardDelete: kotlin.Boolean, + + @Json(name = "mark_messages_deleted") + val markMessagesDeleted: kotlin.Boolean, + + @Json(name = "custom") + val custom: kotlin.collections.Map = emptyMap(), + + @Json(name = "user") + val user: io.getstream.android.video.generated.models.UserResponseCommonFields, + + @Json(name = "type") + val type: kotlin.String, + + @Json(name = "received_at") + val receivedAt: org.threeten.bp.OffsetDateTime? = null +) +: io.getstream.android.video.generated.models.VideoEvent() +{ + + override fun getEventType(): kotlin.String { + return type + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserMutedEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserPresenceChangedEvent.kt similarity index 76% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserMutedEvent.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserPresenceChangedEvent.kt index 19d35c49d9b..8d97219f8c5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserMutedEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserPresenceChangedEvent.kt @@ -35,24 +35,24 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * This event is sent when the presence of a user changes. The event contains information about the user whose presence changed. */ -data class UserMutedEvent ( +data class UserPresenceChangedEvent ( @Json(name = "created_at") val createdAt: org.threeten.bp.OffsetDateTime, - @Json(name = "type") - val type: kotlin.String, + @Json(name = "custom") + val custom: kotlin.collections.Map = emptyMap(), - @Json(name = "target_user") - val targetUser: kotlin.String? = null, + @Json(name = "user") + val user: io.getstream.android.video.generated.models.UserResponseCommonFields, - @Json(name = "target_users") - val targetUsers: kotlin.collections.List? = emptyList(), + @Json(name = "type") + val type: kotlin.String, - @Json(name = "user") - val user: io.getstream.android.video.generated.models.User? = null + @Json(name = "received_at") + val receivedAt: org.threeten.bp.OffsetDateTime? = null ) : io.getstream.android.video.generated.models.VideoEvent() { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserReactivatedEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserReactivatedEvent.kt index 09930a30f81..b71868f47a9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserReactivatedEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserReactivatedEvent.kt @@ -35,18 +35,27 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * This event is sent when a user gets reactivated. The event contains information about the user that was reactivated. */ data class UserReactivatedEvent ( @Json(name = "created_at") val createdAt: org.threeten.bp.OffsetDateTime, + @Json(name = "custom") + val custom: kotlin.collections.Map = emptyMap(), + + @Json(name = "user") + val user: io.getstream.android.video.generated.models.UserResponseCommonFields, + @Json(name = "type") val type: kotlin.String, - @Json(name = "user") - val user: io.getstream.android.video.generated.models.User? = null + @Json(name = "received_at") + val receivedAt: org.threeten.bp.OffsetDateTime? = null, + + @Json(name = "created_by") + val createdBy: io.getstream.android.video.generated.models.UserResponseCommonFields? = null ) : io.getstream.android.video.generated.models.VideoEvent() { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/User.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserResponseCommonFields.kt similarity index 75% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/User.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserResponseCommonFields.kt index e1d3012223c..9ea07b69851 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/User.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserResponseCommonFields.kt @@ -38,58 +38,52 @@ import com.squareup.moshi.ToJson * */ -data class User ( - @Json(name = "banned") - val banned: kotlin.Boolean, +data class UserResponseCommonFields ( + @Json(name = "created_at") + val createdAt: org.threeten.bp.OffsetDateTime, @Json(name = "id") val id: kotlin.String, - @Json(name = "online") - val online: kotlin.Boolean, + @Json(name = "language") + val language: kotlin.String, @Json(name = "role") val role: kotlin.String, + @Json(name = "updated_at") + val updatedAt: org.threeten.bp.OffsetDateTime, + + @Json(name = "blocked_user_ids") + val blockedUserIds: kotlin.collections.List = emptyList(), + + @Json(name = "teams") + val teams: kotlin.collections.List = emptyList(), + @Json(name = "custom") val custom: kotlin.collections.Map = emptyMap(), - @Json(name = "teams_role") - val teamsRole: kotlin.collections.Map = emptyMap(), - @Json(name = "avg_response_time") val avgResponseTime: kotlin.Int? = null, - @Json(name = "ban_expires") - val banExpires: org.threeten.bp.OffsetDateTime? = null, - - @Json(name = "created_at") - val createdAt: org.threeten.bp.OffsetDateTime? = null, - @Json(name = "deactivated_at") val deactivatedAt: org.threeten.bp.OffsetDateTime? = null, @Json(name = "deleted_at") val deletedAt: org.threeten.bp.OffsetDateTime? = null, - @Json(name = "invisible") - val invisible: kotlin.Boolean? = null, - - @Json(name = "language") - val language: kotlin.String? = null, + @Json(name = "image") + val image: kotlin.String? = null, @Json(name = "last_active") val lastActive: org.threeten.bp.OffsetDateTime? = null, - @Json(name = "last_engaged_at") - val lastEngagedAt: org.threeten.bp.OffsetDateTime? = null, + @Json(name = "name") + val name: kotlin.String? = null, @Json(name = "revoke_tokens_issued_before") val revokeTokensIssuedBefore: org.threeten.bp.OffsetDateTime? = null, - @Json(name = "updated_at") - val updatedAt: org.threeten.bp.OffsetDateTime? = null, - - @Json(name = "teams") - val teams: kotlin.collections.List? = emptyList() + @Json(name = "teams_role") + val teamsRole: kotlin.collections.Map? = emptyMap() ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserUnbannedEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserUnbannedEvent.kt new file mode 100644 index 00000000000..c958db7e299 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/UserUnbannedEvent.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * This event is sent when a user gets unbanned. The event contains information about the user that was unbanned. + */ + +data class UserUnbannedEvent ( + @Json(name = "created_at") + val createdAt: org.threeten.bp.OffsetDateTime, + + @Json(name = "custom") + val custom: kotlin.collections.Map = emptyMap(), + + @Json(name = "user") + val user: io.getstream.android.video.generated.models.UserResponseCommonFields, + + @Json(name = "type") + val type: kotlin.String, + + @Json(name = "channel_id") + val channelId: kotlin.String? = null, + + @Json(name = "channel_member_count") + val channelMemberCount: kotlin.Int? = null, + + @Json(name = "channel_message_count") + val channelMessageCount: kotlin.Int? = null, + + @Json(name = "channel_type") + val channelType: kotlin.String? = null, + + @Json(name = "cid") + val cid: kotlin.String? = null, + + @Json(name = "received_at") + val receivedAt: org.threeten.bp.OffsetDateTime? = null, + + @Json(name = "shadow") + val shadow: kotlin.Boolean? = null, + + @Json(name = "team") + val team: kotlin.String? = null, + + @Json(name = "channel_custom") + val channelCustom: kotlin.collections.Map? = emptyMap(), + + @Json(name = "created_by") + val createdBy: io.getstream.android.video.generated.models.UserResponseCommonFields? = null +) +: io.getstream.android.video.generated.models.VideoEvent() +{ + + override fun getEventType(): kotlin.String { + return type + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoEvent.kt index 7d31a4f07fc..90432fef7b5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoEvent.kt @@ -129,8 +129,10 @@ class VideoEventAdapter : JsonAdapter() { "ingress.stopped" -> io.getstream.android.video.generated.models.IngressStoppedEvent::class.java "user.banned" -> io.getstream.android.video.generated.models.UserBannedEvent::class.java "user.deactivated" -> io.getstream.android.video.generated.models.UserDeactivatedEvent::class.java - "user.muted" -> io.getstream.android.video.generated.models.UserMutedEvent::class.java + "user.deleted" -> io.getstream.android.video.generated.models.UserDeletedEvent::class.java + "user.presence.changed" -> io.getstream.android.video.generated.models.UserPresenceChangedEvent::class.java "user.reactivated" -> io.getstream.android.video.generated.models.UserReactivatedEvent::class.java + "user.unbanned" -> io.getstream.android.video.generated.models.UserUnbannedEvent::class.java "user.updated" -> io.getstream.android.video.generated.models.UserUpdatedEvent::class.java else -> UnsupportedVideoEvent::class.java } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReactionResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoReactionResponse.kt similarity index 97% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReactionResponse.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoReactionResponse.kt index 284d667f953..b482d749794 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReactionResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoReactionResponse.kt @@ -38,7 +38,7 @@ import com.squareup.moshi.ToJson * */ -data class ReactionResponse ( +data class VideoReactionResponse ( @Json(name = "type") val type: kotlin.String, From cf5c34c32c65642ea5b4e06ff09a628e65e50896 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 15 May 2026 16:00:23 +0530 Subject: [PATCH 02/63] chore: revert deleted files and unnecessary renames --- .../video/generated/apis/ProductvideoApi.kt | 5 +- .../generated/models/CallReactionEvent.kt | 11 +--- .../video/generated/models/Credentials.kt | 2 +- .../{ICEServerResponse.kt => ICEServer.kt} | 2 +- .../models/LocalCallAcceptedPostEvent.kt | 59 ++++++++++++++++++ .../models/LocalCallRejectedPostEvent.kt | 62 +++++++++++++++++++ ...eactionResponse.kt => ReactionResponse.kt} | 2 +- ...ctionRequest.kt => SendReactionRequest.kt} | 2 +- ...ionResponse.kt => SendReactionResponse.kt} | 4 +- 9 files changed, 130 insertions(+), 19 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{ICEServerResponse.kt => ICEServer.kt} (97%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallAcceptedPostEvent.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallRejectedPostEvent.kt rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{VideoReactionResponse.kt => ReactionResponse.kt} (97%) rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{SendVideoReactionRequest.kt => SendReactionRequest.kt} (97%) rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{SendVideoReactionResponse.kt => SendReactionResponse.kt} (91%) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt index b5b9be2fbd7..d72ca11abc3 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt @@ -31,7 +31,6 @@ import retrofit2.http.PATCH import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query -import retrofit2.http.PUT interface ProductvideoApi { @@ -341,8 +340,8 @@ interface ProductvideoApi { suspend fun sendVideoReaction( @Path("type") type: kotlin.String, @Path("id") id: kotlin.String , - @Body sendVideoReactionRequest: io.getstream.android.video.generated.models.SendVideoReactionRequest - ): io.getstream.android.video.generated.models.SendVideoReactionResponse + @Body sendReactionRequest: io.getstream.android.video.generated.models.SendReactionRequest + ): io.getstream.android.video.generated.models.SendReactionResponse /** * List recordings diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt index 64ae74ea945..9993f14c172 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt @@ -23,16 +23,7 @@ package io.getstream.android.video.generated.models -import kotlin.collections.List -import kotlin.collections.Map -import kotlin.collections.* -import kotlin.io.* -import com.squareup.moshi.FromJson import com.squareup.moshi.Json -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson /** * This event is sent when a reaction is sent in a call, clients should use this to show the reaction in the call screen @@ -46,7 +37,7 @@ data class CallReactionEvent ( val createdAt: org.threeten.bp.OffsetDateTime, @Json(name = "reaction") - val reaction: io.getstream.android.video.generated.models.VideoReactionResponse, + val reaction: io.getstream.android.video.generated.models.ReactionResponse, @Json(name = "type") val type: kotlin.String diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Credentials.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Credentials.kt index 1ccb7b7e8a3..21d515cf14a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Credentials.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/Credentials.kt @@ -43,7 +43,7 @@ data class Credentials ( val token: kotlin.String, @Json(name = "ice_servers") - val iceServers: kotlin.collections.List = emptyList(), + val iceServers: kotlin.collections.List = emptyList(), @Json(name = "server") val server: io.getstream.android.video.generated.models.SFUResponse diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServerResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServer.kt similarity index 97% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServerResponse.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServer.kt index 734f4dc1329..121d977e229 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServerResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ICEServer.kt @@ -38,7 +38,7 @@ import com.squareup.moshi.ToJson * ICE server configuration for WebRTC connections */ -data class ICEServerResponse ( +data class ICEServer ( @Json(name = "password") val password: kotlin.String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallAcceptedPostEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallAcceptedPostEvent.kt new file mode 100644 index 00000000000..240057076e8 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallAcceptedPostEvent.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import com.squareup.moshi.Json +import org.threeten.bp.OffsetDateTime + +/** + * This event is sent after [CallAcceptedEvent] is consumed in [io.getstream.video.android.core.CallState] + */ + +internal data class LocalCallAcceptedPostEvent ( + @Json(name = "call_cid") + val callCid: String, + + @Json(name = "created_at") + val createdAt: OffsetDateTime, + + @Json(name = "call") + val call: CallResponse, + + @Json(name = "user") + val user: UserResponse, + + @Json(name = "type") + val type: String +) +: VideoEvent(), WSCallEvent +{ + + override fun getEventType(): String { + return type + } + + override fun getCallCID(): String { + return callCid + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallRejectedPostEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallRejectedPostEvent.kt new file mode 100644 index 00000000000..953db4ca942 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/LocalCallRejectedPostEvent.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import com.squareup.moshi.Json +import org.threeten.bp.OffsetDateTime + +/** + * This event is sent after [CallRejectedEvent] is consumed in [io.getstream.video.android.core.CallState] + */ + +internal data class LocalCallRejectedPostEvent ( + @Json(name = "call_cid") + val callCid: String, + + @Json(name = "created_at") + val createdAt: OffsetDateTime, + + @Json(name = "call") + val call: CallResponse, + + @Json(name = "user") + val user: UserResponse, + + @Json(name = "type") + val type: String, + + @Json(name = "reason") + val reason: String? = null +) +: VideoEvent(), WSCallEvent +{ + + override fun getEventType(): String { + return type + } + + override fun getCallCID(): String { + return callCid + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoReactionResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReactionResponse.kt similarity index 97% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoReactionResponse.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReactionResponse.kt index b482d749794..284d667f953 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/VideoReactionResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReactionResponse.kt @@ -38,7 +38,7 @@ import com.squareup.moshi.ToJson * */ -data class VideoReactionResponse ( +data class ReactionResponse ( @Json(name = "type") val type: kotlin.String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionRequest.kt similarity index 97% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionRequest.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionRequest.kt index de4a51c171c..ee446a9f3ee 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionRequest.kt @@ -38,7 +38,7 @@ import com.squareup.moshi.ToJson * */ -data class SendVideoReactionRequest ( +data class SendReactionRequest ( @Json(name = "type") val type: kotlin.String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionResponse.kt similarity index 91% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionResponse.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionResponse.kt index c17b8e097f6..da2aeccd621 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendVideoReactionResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/SendReactionResponse.kt @@ -38,10 +38,10 @@ import com.squareup.moshi.ToJson * Basic response information */ -data class SendVideoReactionResponse ( +data class SendReactionResponse ( @Json(name = "duration") val duration: kotlin.String, @Json(name = "reaction") - val reaction: io.getstream.android.video.generated.models.VideoReactionResponse + val reaction: io.getstream.android.video.generated.models.ReactionResponse ) From 9aa48111d6cfca144ea179ec6f2f373ecad2dfe1 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 15 May 2026 18:23:58 +0530 Subject: [PATCH 03/63] chore: update api file --- .../api/stream-video-android-core.api | 1153 ++++++++++++----- 1 file changed, 805 insertions(+), 348 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 1abcb7fad1a..b67d132be92 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -56,6 +56,7 @@ public abstract interface class io/getstream/android/video/generated/apis/Produc public static synthetic fun queryCalls$default (Lio/getstream/android/video/generated/apis/ProductvideoApi;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public abstract fun rejectCall (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/RejectCallRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun rejectCall (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun reportClientCallEvent (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun requestPermission (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/RequestPermissionRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun resolveSipInbound (Lio/getstream/android/video/generated/models/ResolveSipInboundRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun ringCall (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/RingCallRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -667,22 +668,24 @@ public final class io/getstream/android/video/generated/models/CallDurationRepor } public final class io/getstream/android/video/generated/models/CallEndedEvent : io/getstream/android/video/generated/models/VideoEvent, io/getstream/android/video/generated/models/WSCallEvent { - public fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/UserResponse;)V - public synthetic fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/UserResponse;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/UserResponse;)V + public synthetic fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/UserResponse;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lorg/threeten/bp/OffsetDateTime; public final fun component3 ()Lio/getstream/android/video/generated/models/CallResponse; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Lio/getstream/android/video/generated/models/UserResponse; - public final fun copy (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/UserResponse;)Lio/getstream/android/video/generated/models/CallEndedEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallEndedEvent;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/UserResponse;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallEndedEvent; + public final fun component6 ()Ljava/util/List; + public final fun component7 ()Lio/getstream/android/video/generated/models/UserResponse; + public final fun copy (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/UserResponse;)Lio/getstream/android/video/generated/models/CallEndedEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallEndedEvent;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/UserResponse;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallEndedEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCall ()Lio/getstream/android/video/generated/models/CallResponse; public fun getCallCID ()Ljava/lang/String; public final fun getCallCid ()Ljava/lang/String; public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; public fun getEventType ()Ljava/lang/String; + public final fun getMembers ()Ljava/util/List; public final fun getReason ()Ljava/lang/String; public final fun getType ()Ljava/lang/String; public final fun getUser ()Lio/getstream/android/video/generated/models/UserResponse; @@ -853,6 +856,24 @@ public final class io/getstream/android/video/generated/models/CallIngressRespon public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/CallLevelEventPayload { + public fun (Ljava/lang/String;ILjava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;ILjava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()I + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;ILjava/lang/String;Ljava/util/Map;)Lio/getstream/android/video/generated/models/CallLevelEventPayload; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallLevelEventPayload;Ljava/lang/String;ILjava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallLevelEventPayload; + public fun equals (Ljava/lang/Object;)Z + public final fun getEventType ()Ljava/lang/String; + public final fun getPayload ()Ljava/util/Map; + public final fun getTimestamp ()I + public final fun getUserId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/CallLiveStartedEvent : io/getstream/android/video/generated/models/VideoEvent, io/getstream/android/video/generated/models/WSCallEvent { public fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallResponse;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; @@ -2026,43 +2047,59 @@ public final class io/getstream/android/video/generated/models/CallStatsParticip } public final class io/getstream/android/video/generated/models/CallStatsParticipantCounts { - public fun (IIIIIILjava/lang/Integer;)V - public synthetic fun (IIIIIILjava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (IIIIIIILjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)V + public synthetic fun (IIIIIIILjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()I + public final fun component10 ()Ljava/lang/Integer; + public final fun component11 ()Ljava/lang/Integer; + public final fun component12 ()Ljava/lang/Integer; + public final fun component13 ()Ljava/lang/Integer; public final fun component2 ()I public final fun component3 ()I public final fun component4 ()I public final fun component5 ()I public final fun component6 ()I - public final fun component7 ()Ljava/lang/Integer; - public final fun copy (IIIIIILjava/lang/Integer;)Lio/getstream/android/video/generated/models/CallStatsParticipantCounts; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;IIIIIILjava/lang/Integer;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsParticipantCounts; + public final fun component7 ()I + public final fun component8 ()Ljava/lang/Integer; + public final fun component9 ()Ljava/lang/Integer; + public final fun copy (IIIIIIILjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)Lio/getstream/android/video/generated/models/CallStatsParticipantCounts; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;IIIIIIILjava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsParticipantCounts; public fun equals (Ljava/lang/Object;)Z + public final fun getAverageJitterMs ()Ljava/lang/Integer; + public final fun getAverageLatencyMs ()Ljava/lang/Integer; + public final fun getCallEventCount ()Ljava/lang/Integer; + public final fun getCqScore ()Ljava/lang/Integer; public final fun getLiveSessions ()I + public final fun getMaxFreezesDurationMs ()Ljava/lang/Integer; public final fun getParticipants ()I public final fun getPeakConcurrentSessions ()I public final fun getPeakConcurrentUsers ()I public final fun getPublishers ()I public final fun getSessions ()I + public final fun getSfusUsed ()I public final fun getTotalParticipantDuration ()Ljava/lang/Integer; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class io/getstream/android/video/generated/models/CallStatsParticipantSession { - public fun (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsLocation;)V - public synthetic fun (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsLocation;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsLocation;)V + public synthetic fun (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsLocation;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z public final fun component10 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component11 ()Ljava/lang/String; + public final fun component11 ()Ljava/lang/Integer; public final fun component12 ()Ljava/lang/String; - public final fun component13 ()Ljava/lang/String; - public final fun component14 ()Ljava/lang/String; - public final fun component15 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component13 ()Ljava/lang/Integer; + public final fun component14 ()Ljava/lang/Integer; + public final fun component15 ()Ljava/lang/String; public final fun component16 ()Ljava/lang/String; public final fun component17 ()Ljava/lang/String; - public final fun component18 ()Lio/getstream/android/video/generated/models/CallStatsLocation; + public final fun component18 ()Ljava/lang/String; + public final fun component19 ()Lorg/threeten/bp/OffsetDateTime; public final fun component2 ()Ljava/lang/String; + public final fun component20 ()Ljava/lang/String; + public final fun component21 ()Ljava/lang/String; + public final fun component22 ()Lio/getstream/android/video/generated/models/CallStatsLocation; public final fun component3 ()Lio/getstream/android/video/generated/models/PublishedTrackFlags; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -2070,8 +2107,8 @@ public final class io/getstream/android/video/generated/models/CallStatsParticip public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/Float; - public final fun copy (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsLocation;)Lio/getstream/android/video/generated/models/CallStatsParticipantSession; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallStatsParticipantSession;ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsLocation;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsParticipantSession; + public final fun copy (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsLocation;)Lio/getstream/android/video/generated/models/CallStatsParticipantSession; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallStatsParticipantSession;ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsLocation;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsParticipantSession; public fun equals (Ljava/lang/Object;)Z public final fun getBrowser ()Ljava/lang/String; public final fun getBrowserVersion ()Ljava/lang/String; @@ -2080,6 +2117,10 @@ public final class io/getstream/android/video/generated/models/CallStatsParticip public final fun getCurrentSfu ()Ljava/lang/String; public final fun getDistanceToSfuKilometers ()Ljava/lang/Float; public final fun getEndedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getFreezesDurationMs ()Ljava/lang/Integer; + public final fun getIngress ()Ljava/lang/String; + public final fun getJitterMs ()Ljava/lang/Integer; + public final fun getLatencyMs ()Ljava/lang/Integer; public final fun getLocation ()Lio/getstream/android/video/generated/models/CallStatsLocation; public final fun getOs ()Ljava/lang/String; public final fun getPublishedTracks ()Lio/getstream/android/video/generated/models/PublishedTrackFlags; @@ -2096,21 +2137,28 @@ public final class io/getstream/android/video/generated/models/CallStatsParticip } public final class io/getstream/android/video/generated/models/CallStatsReportReadyEvent : io/getstream/android/video/generated/models/VideoEvent, io/getstream/android/video/generated/models/WSCallEvent { - public fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;)V + public fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lorg/threeten/bp/OffsetDateTime; public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/CallStatsReportReadyEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallStatsReportReadyEvent;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsReportReadyEvent; + public final fun component4 ()Lio/getstream/android/video/generated/models/CallStatsParticipantCounts; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Boolean; + public final fun component7 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;)Lio/getstream/android/video/generated/models/CallStatsReportReadyEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallStatsReportReadyEvent;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsReportReadyEvent; public fun equals (Ljava/lang/Object;)Z public fun getCallCID ()Ljava/lang/String; public final fun getCallCid ()Ljava/lang/String; + public final fun getCounts ()Lio/getstream/android/video/generated/models/CallStatsParticipantCounts; public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; public fun getEventType ()Ljava/lang/String; + public final fun getParticipantsOverview ()Ljava/util/List; public final fun getSessionId ()Ljava/lang/String; public final fun getType ()Ljava/lang/String; public fun hashCode ()I + public final fun isTrimmed ()Ljava/lang/Boolean; public fun toString ()Ljava/lang/String; } @@ -2140,6 +2188,30 @@ public final class io/getstream/android/video/generated/models/CallStatsReportSu public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/CallStatsSessionResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component5 ()Lio/getstream/android/video/generated/models/CallStatsParticipantCounts; + public final fun component6 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component7 ()Lorg/threeten/bp/OffsetDateTime; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;)Lio/getstream/android/video/generated/models/CallStatsSessionResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallStatsSessionResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsSessionResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getCallEndedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getCallId ()Ljava/lang/String; + public final fun getCallSessionId ()Ljava/lang/String; + public final fun getCallStartedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getCallType ()Ljava/lang/String; + public final fun getCounts ()Lio/getstream/android/video/generated/models/CallStatsParticipantCounts; + public final fun getGeneratedAt ()Lorg/threeten/bp/OffsetDateTime; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/CallTranscription { public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;)V public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; @@ -2355,6 +2427,31 @@ public final class io/getstream/android/video/generated/models/ChatActivityStats public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/ChatPreferencesResponse { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/ChatPreferencesResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ChatPreferencesResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ChatPreferencesResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getChannelMentions ()Ljava/lang/String; + public final fun getDefaultPreference ()Ljava/lang/String; + public final fun getDirectMentions ()Ljava/lang/String; + public final fun getGroupMentions ()Ljava/lang/String; + public final fun getHereMentions ()Ljava/lang/String; + public final fun getRoleMentions ()Ljava/lang/String; + public final fun getThreadReplies ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/ClosedCaptionEvent : io/getstream/android/video/generated/models/VideoEvent, io/getstream/android/video/generated/models/WSCallEvent { public fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallClosedCaption;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; @@ -2476,12 +2573,12 @@ public final class io/getstream/android/video/generated/models/ConnectionErrorEv public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/Coordinates { +public final class io/getstream/android/video/generated/models/CoordinatesResponse { public fun (FF)V public final fun component1 ()F public final fun component2 ()F - public final fun copy (FF)Lio/getstream/android/video/generated/models/Coordinates; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/Coordinates;FFILjava/lang/Object;)Lio/getstream/android/video/generated/models/Coordinates; + public final fun copy (FF)Lio/getstream/android/video/generated/models/CoordinatesResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CoordinatesResponse;FFILjava/lang/Object;)Lio/getstream/android/video/generated/models/CoordinatesResponse; public fun equals (Ljava/lang/Object;)Z public final fun getLatitude ()F public final fun getLongitude ()F @@ -2759,17 +2856,6 @@ public final class io/getstream/android/video/generated/models/DeleteTranscripti public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/DeliveryReceipts { - public fun (Z)V - public final fun component1 ()Z - public final fun copy (Z)Lio/getstream/android/video/generated/models/DeliveryReceipts; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/DeliveryReceipts;ZILjava/lang/Object;)Lio/getstream/android/video/generated/models/DeliveryReceipts; - public fun equals (Ljava/lang/Object;)Z - public final fun getEnabled ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class io/getstream/android/video/generated/models/DeviceResponse { public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2893,19 +2979,23 @@ public final class io/getstream/android/video/generated/models/EndCallResponse { public final class io/getstream/android/video/generated/models/FeedsPreferencesResponse { public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/getstream/android/video/generated/models/FeedsPreferencesResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/FeedsPreferencesResponse; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/getstream/android/video/generated/models/FeedsPreferencesResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/FeedsPreferencesResponse; public fun equals (Ljava/lang/Object;)Z public final fun getComment ()Ljava/lang/String; + public final fun getCommentMention ()Ljava/lang/String; public final fun getCommentReaction ()Ljava/lang/String; + public final fun getCommentReply ()Ljava/lang/String; public final fun getCustomActivityTypes ()Ljava/util/Map; public final fun getFollow ()Ljava/lang/String; public final fun getMention ()Ljava/lang/String; @@ -3433,12 +3523,15 @@ public final class io/getstream/android/video/generated/models/IndividualRecordi } public final class io/getstream/android/video/generated/models/IndividualRecordingSettingsRequest { - public fun (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode;)V + public fun (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode;Ljava/util/List;)V + public synthetic fun (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode; - public final fun copy (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode;)Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest;Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode;Ljava/util/List;)Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest;Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest; public fun equals (Ljava/lang/Object;)Z public final fun getMode ()Lio/getstream/android/video/generated/models/IndividualRecordingSettingsRequest$Mode; + public final fun getOutputTypes ()Ljava/util/List; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3486,12 +3579,15 @@ public final class io/getstream/android/video/generated/models/IndividualRecordi } public final class io/getstream/android/video/generated/models/IndividualRecordingSettingsResponse { - public fun (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode;)V + public fun (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode;Ljava/util/List;)V + public synthetic fun (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode; - public final fun copy (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode;)Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse;Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode;Ljava/util/List;)Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse;Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse; public fun equals (Ljava/lang/Object;)Z public final fun getMode ()Lio/getstream/android/video/generated/models/IndividualRecordingSettingsResponse$Mode; + public final fun getOutputTypes ()Ljava/util/List; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3890,20 +3986,20 @@ public final class io/getstream/android/video/generated/models/IngressVideoLayer } public final class io/getstream/android/video/generated/models/JoinCallRequest { - public fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lio/getstream/android/video/generated/models/CallRequest;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lio/getstream/android/video/generated/models/CallRequest;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Lio/getstream/android/video/generated/models/CallRequest;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Lio/getstream/android/video/generated/models/CallRequest;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Ljava/lang/Boolean; + public final fun component10 ()Lio/getstream/android/video/generated/models/CallRequest; public final fun component2 ()Ljava/lang/Boolean; - public final fun component3 ()Ljava/lang/Integer; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/util/List; + public final fun component3 ()Ljava/lang/Boolean; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/Boolean; public final fun component7 ()Ljava/lang/Boolean; public final fun component8 ()Ljava/lang/Boolean; - public final fun component9 ()Lio/getstream/android/video/generated/models/CallRequest; - public final fun copy (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lio/getstream/android/video/generated/models/CallRequest;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/JoinCallRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/JoinCallRequest;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lio/getstream/android/video/generated/models/CallRequest;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/JoinCallRequest; + public final fun component9 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Lio/getstream/android/video/generated/models/CallRequest;)Lio/getstream/android/video/generated/models/JoinCallRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/JoinCallRequest;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Lio/getstream/android/video/generated/models/CallRequest;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/JoinCallRequest; public fun equals (Ljava/lang/Object;)Z public final fun getCreate ()Ljava/lang/Boolean; public final fun getData ()Lio/getstream/android/video/generated/models/CallRequest; @@ -4162,13 +4258,13 @@ public abstract class io/getstream/android/video/generated/models/LocalEvent : i public fun ()V } -public final class io/getstream/android/video/generated/models/Location { +public final class io/getstream/android/video/generated/models/LocationResponse { public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/Location; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/Location;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/Location; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/LocationResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/LocationResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/LocationResponse; public fun equals (Ljava/lang/Object;)Z public final fun getContinentCode ()Ljava/lang/String; public final fun getCountryIsoCode ()Ljava/lang/String; @@ -4875,23 +4971,6 @@ public final class io/getstream/android/video/generated/models/PinResponse { public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/PrivacySettings { - public fun ()V - public fun (Lio/getstream/android/video/generated/models/DeliveryReceipts;Lio/getstream/android/video/generated/models/ReadReceipts;Lio/getstream/android/video/generated/models/TypingIndicators;)V - public synthetic fun (Lio/getstream/android/video/generated/models/DeliveryReceipts;Lio/getstream/android/video/generated/models/ReadReceipts;Lio/getstream/android/video/generated/models/TypingIndicators;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/getstream/android/video/generated/models/DeliveryReceipts; - public final fun component2 ()Lio/getstream/android/video/generated/models/ReadReceipts; - public final fun component3 ()Lio/getstream/android/video/generated/models/TypingIndicators; - public final fun copy (Lio/getstream/android/video/generated/models/DeliveryReceipts;Lio/getstream/android/video/generated/models/ReadReceipts;Lio/getstream/android/video/generated/models/TypingIndicators;)Lio/getstream/android/video/generated/models/PrivacySettings; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/PrivacySettings;Lio/getstream/android/video/generated/models/DeliveryReceipts;Lio/getstream/android/video/generated/models/ReadReceipts;Lio/getstream/android/video/generated/models/TypingIndicators;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/PrivacySettings; - public fun equals (Ljava/lang/Object;)Z - public final fun getDeliveryReceipts ()Lio/getstream/android/video/generated/models/DeliveryReceipts; - public final fun getReadReceipts ()Lio/getstream/android/video/generated/models/ReadReceipts; - public final fun getTypingIndicators ()Lio/getstream/android/video/generated/models/TypingIndicators; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class io/getstream/android/video/generated/models/PublishedTrackFlags { public fun (ZZZZ)V public final fun component1 ()Z @@ -4952,18 +5031,20 @@ public final class io/getstream/android/video/generated/models/PublisherStatsRes public final class io/getstream/android/video/generated/models/PushPreferencesResponse { public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/ChatPreferencesResponse;Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/ChatPreferencesResponse;Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lorg/threeten/bp/OffsetDateTime; public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lio/getstream/android/video/generated/models/FeedsPreferencesResponse; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;)Lio/getstream/android/video/generated/models/PushPreferencesResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/PushPreferencesResponse;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/PushPreferencesResponse; + public final fun component5 ()Lio/getstream/android/video/generated/models/ChatPreferencesResponse; + public final fun component6 ()Lio/getstream/android/video/generated/models/FeedsPreferencesResponse; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/ChatPreferencesResponse;Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;)Lio/getstream/android/video/generated/models/PushPreferencesResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/PushPreferencesResponse;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/ChatPreferencesResponse;Lio/getstream/android/video/generated/models/FeedsPreferencesResponse;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/PushPreferencesResponse; public fun equals (Ljava/lang/Object;)Z public final fun getCallLevel ()Ljava/lang/String; public final fun getChatLevel ()Ljava/lang/String; + public final fun getChatPreferences ()Lio/getstream/android/video/generated/models/ChatPreferencesResponse; public final fun getDisabledUntil ()Lorg/threeten/bp/OffsetDateTime; public final fun getFeedsLevel ()Ljava/lang/String; public final fun getFeedsPreferences ()Lio/getstream/android/video/generated/models/FeedsPreferencesResponse; @@ -5150,11 +5231,12 @@ public final class io/getstream/android/video/generated/models/QueryCallParticip } public final class io/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component10 ()Ljava/lang/String; public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Ljava/util/List; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; @@ -5163,10 +5245,11 @@ public final class io/getstream/android/video/generated/models/QueryCallSessionP public final fun component7 ()Lorg/threeten/bp/OffsetDateTime; public final fun component8 ()Lorg/threeten/bp/OffsetDateTime; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lio/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/QueryCallSessionParticipantStatsResponse; public fun equals (Ljava/lang/Object;)Z public final fun getCallEndedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getCallEvents ()Ljava/util/List; public final fun getCallId ()Ljava/lang/String; public final fun getCallSessionId ()Ljava/lang/String; public final fun getCallStartedAt ()Lorg/threeten/bp/OffsetDateTime; @@ -5205,6 +5288,45 @@ public final class io/getstream/android/video/generated/models/QueryCallSessionP public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/QueryCallSessionStatsRequest { + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/util/Map; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)Lio/getstream/android/video/generated/models/QueryCallSessionStatsRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/QueryCallSessionStatsRequest;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/QueryCallSessionStatsRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getFilterConditions ()Ljava/util/Map; + public final fun getLimit ()Ljava/lang/Integer; + public final fun getNext ()Ljava/lang/String; + public final fun getPrev ()Ljava/lang/String; + public final fun getSort ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/QueryCallSessionStatsResponse { + public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/QueryCallSessionStatsResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/QueryCallSessionStatsResponse;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/QueryCallSessionStatsResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getCallStats ()Ljava/util/List; + public final fun getDuration ()Ljava/lang/String; + public final fun getNext ()Ljava/lang/String; + public final fun getPrev ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/QueryCallStatsMapResponse { public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallStatsMapPublishers;Lio/getstream/android/video/generated/models/CallStatsMapSFUs;Lio/getstream/android/video/generated/models/CallStatsMapSubscribers;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CallStatsParticipantCounts;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallStatsMapPublishers;Lio/getstream/android/video/generated/models/CallStatsMapSFUs;Lio/getstream/android/video/generated/models/CallStatsMapSubscribers;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5534,11 +5656,14 @@ public final class io/getstream/android/video/generated/models/RawRecordingRespo } public final class io/getstream/android/video/generated/models/RawRecordingSettingsRequest { - public fun (Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode;)V + public fun (Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode;Ljava/lang/Boolean;)V + public synthetic fun (Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode; - public final fun copy (Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode;)Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest;Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest; + public final fun component2 ()Ljava/lang/Boolean; + public final fun copy (Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest;Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getAudioOnly ()Ljava/lang/Boolean; public final fun getMode ()Lio/getstream/android/video/generated/models/RawRecordingSettingsRequest$Mode; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -5587,11 +5712,14 @@ public final class io/getstream/android/video/generated/models/RawRecordingSetti } public final class io/getstream/android/video/generated/models/RawRecordingSettingsResponse { - public fun (Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode;)V + public fun (Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode;Ljava/lang/Boolean;)V + public synthetic fun (Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode; - public final fun copy (Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode;)Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse;Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse; + public final fun component2 ()Ljava/lang/Boolean; + public final fun copy (Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse;Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse; public fun equals (Ljava/lang/Object;)Z + public final fun getAudioOnly ()Ljava/lang/Boolean; public final fun getMode ()Lio/getstream/android/video/generated/models/RawRecordingSettingsResponse$Mode; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -5657,17 +5785,6 @@ public final class io/getstream/android/video/generated/models/ReactionResponse public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/ReadReceipts { - public fun (Z)V - public final fun component1 ()Z - public final fun copy (Z)Lio/getstream/android/video/generated/models/ReadReceipts; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReadReceipts;ZILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReadReceipts; - public fun equals (Ljava/lang/Object;)Z - public final fun getEnabled ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class io/getstream/android/video/generated/models/RecordSettingsRequest { public fun (Lio/getstream/android/video/generated/models/RecordSettingsRequest$Mode;Ljava/lang/Boolean;Lio/getstream/android/video/generated/models/RecordSettingsRequest$Quality;)V public synthetic fun (Lio/getstream/android/video/generated/models/RecordSettingsRequest$Mode;Ljava/lang/Boolean;Lio/getstream/android/video/generated/models/RecordSettingsRequest$Quality;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -5855,112 +5972,411 @@ public final class io/getstream/android/video/generated/models/ReportByHistogram public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/ReportResponse { - public fun (Lio/getstream/android/video/generated/models/CallReportResponse;Lio/getstream/android/video/generated/models/ParticipantReportResponse;Lio/getstream/android/video/generated/models/UserRatingReportResponse;)V - public final fun component1 ()Lio/getstream/android/video/generated/models/CallReportResponse; - public final fun component2 ()Lio/getstream/android/video/generated/models/ParticipantReportResponse; - public final fun component3 ()Lio/getstream/android/video/generated/models/UserRatingReportResponse; - public final fun copy (Lio/getstream/android/video/generated/models/CallReportResponse;Lio/getstream/android/video/generated/models/ParticipantReportResponse;Lio/getstream/android/video/generated/models/UserRatingReportResponse;)Lio/getstream/android/video/generated/models/ReportResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportResponse;Lio/getstream/android/video/generated/models/CallReportResponse;Lio/getstream/android/video/generated/models/ParticipantReportResponse;Lio/getstream/android/video/generated/models/UserRatingReportResponse;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportResponse; +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest { + public fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; + public final fun component12 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; + public final fun component13 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; + public final fun component14 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component15 ()Ljava/lang/Integer; + public final fun component16 ()Ljava/lang/String; + public final fun component17 ()Ljava/lang/String; + public final fun component18 ()Ljava/lang/String; + public final fun component19 ()Ljava/lang/String; + public final fun component2 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; + public final fun component20 ()Ljava/lang/Boolean; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; + public final fun component6 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest; public fun equals (Ljava/lang/Object;)Z - public final fun getCall ()Lio/getstream/android/video/generated/models/CallReportResponse; - public final fun getParticipants ()Lio/getstream/android/video/generated/models/ParticipantReportResponse; - public final fun getUserRatings ()Lio/getstream/android/video/generated/models/UserRatingReportResponse; + public final fun getCallSessionId ()Ljava/lang/String; + public final fun getEventSessionId ()Ljava/lang/String; + public final fun getEventType ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; + public final fun getIceState ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; + public final fun getId ()Ljava/lang/String; + public final fun getOutcome ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; + public final fun getPeerConnection ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; + public final fun getPreviouslyConnectedTimestamp ()Lorg/threeten/bp/OffsetDateTime; + public final fun getRetryCountAttempt ()Ljava/lang/Integer; + public final fun getRetryFailureCode ()Ljava/lang/String; + public final fun getRetryFailureReason ()Ljava/lang/String; + public final fun getSdkVersion ()Ljava/lang/String; + public final fun getSfuId ()Ljava/lang/String; + public final fun getStage ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; + public final fun getTimestamp ()Lorg/threeten/bp/OffsetDateTime; + public final fun getType ()Ljava/lang/String; + public final fun getUserAgent ()Ljava/lang/String; + public final fun getUserId ()Ljava/lang/String; + public final fun getUserSessionId ()Ljava/lang/String; + public final fun getWasPreviouslyConnected ()Ljava/lang/Boolean; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/RequestPermissionRequest { - public fun ()V - public fun (Ljava/util/List;)V - public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lio/getstream/android/video/generated/models/RequestPermissionRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/RequestPermissionRequest;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/RequestPermissionRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getPermissions ()Ljava/util/List; - public fun hashCode ()I +public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType { + public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getValue ()Ljava/lang/String; public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/RequestPermissionResponse { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/RequestPermissionResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/RequestPermissionResponse;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/RequestPermissionResponse; - public fun equals (Ljava/lang/Object;)Z - public final fun getDuration ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Companion { + public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; } -public final class io/getstream/android/video/generated/models/ResolutionMetricsTimeSeries { +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Completed : io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Completed; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$EventTypeAdapter : com/squareup/moshi/JsonAdapter { public fun ()V - public fun (Lio/getstream/android/video/generated/models/MetricTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;)V - public synthetic fun (Lio/getstream/android/video/generated/models/MetricTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/getstream/android/video/generated/models/MetricTimeSeries; - public final fun component2 ()Lio/getstream/android/video/generated/models/MetricTimeSeries; - public final fun copy (Lio/getstream/android/video/generated/models/MetricTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;)Lio/getstream/android/video/generated/models/ResolutionMetricsTimeSeries; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ResolutionMetricsTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ResolutionMetricsTimeSeries; - public fun equals (Ljava/lang/Object;)Z - public final fun getHeight ()Lio/getstream/android/video/generated/models/MetricTimeSeries; - public final fun getWidth ()Lio/getstream/android/video/generated/models/MetricTimeSeries; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; + public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; + public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; + public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;)V + public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V } -public final class io/getstream/android/video/generated/models/ResolveSipInboundRequest { - public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/SIPChallenge;Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/SIPChallenge;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Initiated : io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Initiated; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType { + public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/getstream/android/video/generated/models/SIPChallenge; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/SIPChallenge;Ljava/lang/String;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ResolveSipInboundRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ResolveSipInboundRequest;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/SIPChallenge;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ResolveSipInboundRequest; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Unknown; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Unknown; public fun equals (Ljava/lang/Object;)Z - public final fun getChallenge ()Lio/getstream/android/video/generated/models/SIPChallenge; - public final fun getRoutingNumber ()Ljava/lang/String; - public final fun getSipCallerNumber ()Ljava/lang/String; - public final fun getSipHeaders ()Ljava/util/Map; - public final fun getSipTrunkNumber ()Ljava/lang/String; + public final fun getUnknownValue ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/ResolveSipInboundResponse { - public fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/SipInboundCredentials;Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse;Lio/getstream/android/video/generated/models/SIPTrunkResponse;)V - public synthetic fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/SipInboundCredentials;Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse;Lio/getstream/android/video/generated/models/SIPTrunkResponse;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/getstream/android/video/generated/models/SipInboundCredentials; - public final fun component3 ()Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse; - public final fun component4 ()Lio/getstream/android/video/generated/models/SIPTrunkResponse; - public final fun copy (Ljava/lang/String;Lio/getstream/android/video/generated/models/SipInboundCredentials;Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse;Lio/getstream/android/video/generated/models/SIPTrunkResponse;)Lio/getstream/android/video/generated/models/ResolveSipInboundResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ResolveSipInboundResponse;Ljava/lang/String;Lio/getstream/android/video/generated/models/SipInboundCredentials;Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse;Lio/getstream/android/video/generated/models/SIPTrunkResponse;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ResolveSipInboundResponse; - public fun equals (Ljava/lang/Object;)Z - public final fun getCredentials ()Lio/getstream/android/video/generated/models/SipInboundCredentials; - public final fun getDuration ()Ljava/lang/String; - public final fun getSipRoutingRule ()Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse; - public final fun getSipTrunk ()Lio/getstream/android/video/generated/models/SIPTrunkResponse; - public fun hashCode ()I +public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { + public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getValue ()Ljava/lang/String; public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/Response { +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$CONNECTED : io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$CONNECTED; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Companion { + public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$FAILED : io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$FAILED; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$IceStateAdapter : com/squareup/moshi/JsonAdapter { + public fun ()V + public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; + public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; + public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;)V + public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$NOTCONNECTED : io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$NOTCONNECTED; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/Response; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/Response;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/Response; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Unknown; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Unknown; public fun equals (Ljava/lang/Object;)Z - public final fun getDuration ()Ljava/lang/String; + public final fun getUnknownValue ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/RingCallRequest { - public fun ()V - public fun (Ljava/lang/Boolean;Ljava/util/List;)V +public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome { + public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getValue ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Companion { + public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Failure : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Failure; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$OutcomeAdapter : com/squareup/moshi/JsonAdapter { + public fun ()V + public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; + public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; + public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;)V + public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Success : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Success; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Unknown; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Unknown; + public fun equals (Ljava/lang/Object;)Z + public final fun getUnknownValue ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection { + public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getValue ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Companion { + public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$PeerConnectionAdapter : com/squareup/moshi/JsonAdapter { + public fun ()V + public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; + public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; + public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;)V + public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Publish : io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Publish; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Subscribe : io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Subscribe; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Unknown; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Unknown; + public fun equals (Ljava/lang/Object;)Z + public final fun getUnknownValue ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { + public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getValue ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Companion { + public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$CoordinatorJoin : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$CoordinatorJoin; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$PeerConnectionConnect : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$PeerConnectionConnect; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$StageAdapter : com/squareup/moshi/JsonAdapter { + public fun ()V + public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; + public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; + public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;)V + public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Unknown; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Unknown; + public fun equals (Ljava/lang/Object;)Z + public final fun getUnknownValue ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$WSJoin : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { + public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$WSJoin; +} + +public final class io/getstream/android/video/generated/models/ReportClientCallEventResponse { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventResponse;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getDuration ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ReportResponse { + public fun (Lio/getstream/android/video/generated/models/CallReportResponse;Lio/getstream/android/video/generated/models/ParticipantReportResponse;Lio/getstream/android/video/generated/models/UserRatingReportResponse;)V + public final fun component1 ()Lio/getstream/android/video/generated/models/CallReportResponse; + public final fun component2 ()Lio/getstream/android/video/generated/models/ParticipantReportResponse; + public final fun component3 ()Lio/getstream/android/video/generated/models/UserRatingReportResponse; + public final fun copy (Lio/getstream/android/video/generated/models/CallReportResponse;Lio/getstream/android/video/generated/models/ParticipantReportResponse;Lio/getstream/android/video/generated/models/UserRatingReportResponse;)Lio/getstream/android/video/generated/models/ReportResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportResponse;Lio/getstream/android/video/generated/models/CallReportResponse;Lio/getstream/android/video/generated/models/ParticipantReportResponse;Lio/getstream/android/video/generated/models/UserRatingReportResponse;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getCall ()Lio/getstream/android/video/generated/models/CallReportResponse; + public final fun getParticipants ()Lio/getstream/android/video/generated/models/ParticipantReportResponse; + public final fun getUserRatings ()Lio/getstream/android/video/generated/models/UserRatingReportResponse; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/RequestPermissionRequest { + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lio/getstream/android/video/generated/models/RequestPermissionRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/RequestPermissionRequest;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/RequestPermissionRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getPermissions ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/RequestPermissionResponse { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/RequestPermissionResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/RequestPermissionResponse;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/RequestPermissionResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getDuration ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ResolutionMetricsTimeSeries { + public fun ()V + public fun (Lio/getstream/android/video/generated/models/MetricTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;)V + public synthetic fun (Lio/getstream/android/video/generated/models/MetricTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/android/video/generated/models/MetricTimeSeries; + public final fun component2 ()Lio/getstream/android/video/generated/models/MetricTimeSeries; + public final fun copy (Lio/getstream/android/video/generated/models/MetricTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;)Lio/getstream/android/video/generated/models/ResolutionMetricsTimeSeries; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ResolutionMetricsTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;Lio/getstream/android/video/generated/models/MetricTimeSeries;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ResolutionMetricsTimeSeries; + public fun equals (Ljava/lang/Object;)Z + public final fun getHeight ()Lio/getstream/android/video/generated/models/MetricTimeSeries; + public final fun getWidth ()Lio/getstream/android/video/generated/models/MetricTimeSeries; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ResolveSipAuthRequest { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/ResolveSipAuthRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ResolveSipAuthRequest;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ResolveSipAuthRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getFromHost ()Ljava/lang/String; + public final fun getSipCallerNumber ()Ljava/lang/String; + public final fun getSipTrunkNumber ()Ljava/lang/String; + public final fun getSourceIp ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ResolveSipAuthResponse { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/ResolveSipAuthResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ResolveSipAuthResponse;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ResolveSipAuthResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getAuthResult ()Ljava/lang/String; + public final fun getDuration ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; + public final fun getTrunkId ()Ljava/lang/String; + public final fun getUsername ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ResolveSipInboundRequest { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/SIPChallengeRequest;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/SIPChallengeRequest;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lio/getstream/android/video/generated/models/SIPChallengeRequest; + public final fun component6 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/SIPChallengeRequest;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ResolveSipInboundRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ResolveSipInboundRequest;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/SIPChallengeRequest;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ResolveSipInboundRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getChallenge ()Lio/getstream/android/video/generated/models/SIPChallengeRequest; + public final fun getRoutingNumber ()Ljava/lang/String; + public final fun getSipCallerNumber ()Ljava/lang/String; + public final fun getSipHeaders ()Ljava/util/Map; + public final fun getSipTrunkNumber ()Ljava/lang/String; + public final fun getTrunkId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/ResolveSipInboundResponse { + public fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/SipInboundCredentials;Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse;Lio/getstream/android/video/generated/models/SIPTrunkResponse;)V + public synthetic fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/SipInboundCredentials;Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse;Lio/getstream/android/video/generated/models/SIPTrunkResponse;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/getstream/android/video/generated/models/SipInboundCredentials; + public final fun component3 ()Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse; + public final fun component4 ()Lio/getstream/android/video/generated/models/SIPTrunkResponse; + public final fun copy (Ljava/lang/String;Lio/getstream/android/video/generated/models/SipInboundCredentials;Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse;Lio/getstream/android/video/generated/models/SIPTrunkResponse;)Lio/getstream/android/video/generated/models/ResolveSipInboundResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ResolveSipInboundResponse;Ljava/lang/String;Lio/getstream/android/video/generated/models/SipInboundCredentials;Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse;Lio/getstream/android/video/generated/models/SIPTrunkResponse;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ResolveSipInboundResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getCredentials ()Lio/getstream/android/video/generated/models/SipInboundCredentials; + public final fun getDuration ()Ljava/lang/String; + public final fun getSipRoutingRule ()Lio/getstream/android/video/generated/models/SIPInboundRoutingRuleResponse; + public final fun getSipTrunk ()Lio/getstream/android/video/generated/models/SIPTrunkResponse; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/Response { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/Response; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/Response;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/Response; + public fun equals (Ljava/lang/Object;)Z + public final fun getDuration ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/RingCallRequest { + public fun ()V + public fun (Ljava/lang/Boolean;Ljava/util/List;)V public synthetic fun (Ljava/lang/Boolean;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Boolean; public final fun component2 ()Ljava/util/List; @@ -6045,21 +6461,21 @@ public final class io/getstream/android/video/generated/models/SDKUsageReportRes } public final class io/getstream/android/video/generated/models/SFULocationResponse { - public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/Coordinates;Lio/getstream/android/video/generated/models/Location;Ljava/lang/Integer;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/Coordinates;Lio/getstream/android/video/generated/models/Location;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CoordinatesResponse;Lio/getstream/android/video/generated/models/LocationResponse;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CoordinatesResponse;Lio/getstream/android/video/generated/models/LocationResponse;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/getstream/android/video/generated/models/Coordinates; - public final fun component4 ()Lio/getstream/android/video/generated/models/Location; + public final fun component3 ()Lio/getstream/android/video/generated/models/CoordinatesResponse; + public final fun component4 ()Lio/getstream/android/video/generated/models/LocationResponse; public final fun component5 ()Ljava/lang/Integer; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/Coordinates;Lio/getstream/android/video/generated/models/Location;Ljava/lang/Integer;)Lio/getstream/android/video/generated/models/SFULocationResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SFULocationResponse;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/Coordinates;Lio/getstream/android/video/generated/models/Location;Ljava/lang/Integer;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SFULocationResponse; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CoordinatesResponse;Lio/getstream/android/video/generated/models/LocationResponse;Ljava/lang/Integer;)Lio/getstream/android/video/generated/models/SFULocationResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SFULocationResponse;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/CoordinatesResponse;Lio/getstream/android/video/generated/models/LocationResponse;Ljava/lang/Integer;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SFULocationResponse; public fun equals (Ljava/lang/Object;)Z - public final fun getCoordinates ()Lio/getstream/android/video/generated/models/Coordinates; + public final fun getCoordinates ()Lio/getstream/android/video/generated/models/CoordinatesResponse; public final fun getCount ()Ljava/lang/Integer; public final fun getDatacenter ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; - public final fun getLocation ()Lio/getstream/android/video/generated/models/Location; + public final fun getLocation ()Lio/getstream/android/video/generated/models/LocationResponse; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -6106,7 +6522,7 @@ public final class io/getstream/android/video/generated/models/SIPCallerConfigsR public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/SIPChallenge { +public final class io/getstream/android/video/generated/models/SIPChallengeRequest { public fun ()V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -6126,8 +6542,8 @@ public final class io/getstream/android/video/generated/models/SIPChallenge { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Lio/getstream/android/video/generated/models/SIPChallenge; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SIPChallenge;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SIPChallenge; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Lio/getstream/android/video/generated/models/SIPChallengeRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SIPChallengeRequest;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SIPChallengeRequest; public fun equals (Ljava/lang/Object;)Z public final fun getA1 ()Ljava/lang/String; public final fun getAlgorithm ()Ljava/lang/String; @@ -6238,8 +6654,8 @@ public final class io/getstream/android/video/generated/models/SIPPinProtectionC } public final class io/getstream/android/video/generated/models/SIPTrunkResponse { - public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V - public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; @@ -6248,9 +6664,11 @@ public final class io/getstream/android/video/generated/models/SIPTrunkResponse public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/util/List; - public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lio/getstream/android/video/generated/models/SIPTrunkResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SIPTrunkResponse;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SIPTrunkResponse; + public final fun component9 ()Ljava/util/List; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Lio/getstream/android/video/generated/models/SIPTrunkResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SIPTrunkResponse;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SIPTrunkResponse; public fun equals (Ljava/lang/Object;)Z + public final fun getAllowedIps ()Ljava/util/List; public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; public final fun getId ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; @@ -6420,17 +6838,19 @@ public final class io/getstream/android/video/generated/models/SessionWarningRes } public final class io/getstream/android/video/generated/models/SipInboundCredentials { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/util/Map; + public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)Lio/getstream/android/video/generated/models/SipInboundCredentials; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SipInboundCredentials;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SipInboundCredentials; + public final fun component7 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)Lio/getstream/android/video/generated/models/SipInboundCredentials; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SipInboundCredentials;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SipInboundCredentials; public fun equals (Ljava/lang/Object;)Z + public final fun getApiKey ()Ljava/lang/String; public final fun getCallCustomData ()Ljava/util/Map; public final fun getCallId ()Ljava/lang/String; public final fun getCallType ()Ljava/lang/String; @@ -6443,59 +6863,17 @@ public final class io/getstream/android/video/generated/models/SipInboundCredent public final class io/getstream/android/video/generated/models/SortParamRequest { public fun ()V - public fun (Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/android/video/generated/models/SortParamRequest$Type;)V - public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/android/video/generated/models/SortParamRequest$Type;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Integer; public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/getstream/android/video/generated/models/SortParamRequest$Type; - public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/android/video/generated/models/SortParamRequest$Type;)Lio/getstream/android/video/generated/models/SortParamRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SortParamRequest;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/android/video/generated/models/SortParamRequest$Type;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SortParamRequest; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/SortParamRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SortParamRequest;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SortParamRequest; public fun equals (Ljava/lang/Object;)Z public final fun getDirection ()Ljava/lang/Integer; public final fun getField ()Ljava/lang/String; - public final fun getType ()Lio/getstream/android/video/generated/models/SortParamRequest$Type; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class io/getstream/android/video/generated/models/SortParamRequest$Type { - public static final field Companion Lio/getstream/android/video/generated/models/SortParamRequest$Type$Companion; - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getValue ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/android/video/generated/models/SortParamRequest$Type$Boolean : io/getstream/android/video/generated/models/SortParamRequest$Type { - public static final field INSTANCE Lio/getstream/android/video/generated/models/SortParamRequest$Type$Boolean; -} - -public final class io/getstream/android/video/generated/models/SortParamRequest$Type$Companion { - public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/SortParamRequest$Type; -} - -public final class io/getstream/android/video/generated/models/SortParamRequest$Type$Empty : io/getstream/android/video/generated/models/SortParamRequest$Type { - public static final field INSTANCE Lio/getstream/android/video/generated/models/SortParamRequest$Type$Empty; -} - -public final class io/getstream/android/video/generated/models/SortParamRequest$Type$Number : io/getstream/android/video/generated/models/SortParamRequest$Type { - public static final field INSTANCE Lio/getstream/android/video/generated/models/SortParamRequest$Type$Number; -} - -public final class io/getstream/android/video/generated/models/SortParamRequest$Type$TypeAdapter : com/squareup/moshi/JsonAdapter { - public fun ()V - public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/SortParamRequest$Type; - public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; - public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/SortParamRequest$Type;)V - public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V -} - -public final class io/getstream/android/video/generated/models/SortParamRequest$Type$Unknown : io/getstream/android/video/generated/models/SortParamRequest$Type { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/SortParamRequest$Type$Unknown; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/SortParamRequest$Type$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/SortParamRequest$Type$Unknown; - public fun equals (Ljava/lang/Object;)Z - public final fun getUnknownValue ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -7829,17 +8207,6 @@ public final class io/getstream/android/video/generated/models/TranslationSettin public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/TypingIndicators { - public fun (Z)V - public final fun component1 ()Z - public final fun copy (Z)Lio/getstream/android/video/generated/models/TypingIndicators; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/TypingIndicators;ZILjava/lang/Object;)Lio/getstream/android/video/generated/models/TypingIndicators; - public fun equals (Ljava/lang/Object;)Z - public final fun getEnabled ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class io/getstream/android/video/generated/models/UnblockUserRequest { public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; @@ -8031,100 +8398,103 @@ public final class io/getstream/android/video/generated/models/UpdatedCallPermis public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/User { - public fun (ZLjava/lang/String;ZLjava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;)V - public synthetic fun (ZLjava/lang/String;ZLjava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z +public final class io/getstream/android/video/generated/models/UserBannedEvent : io/getstream/android/video/generated/models/VideoEvent { + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; public final fun component10 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component11 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component12 ()Ljava/lang/Boolean; - public final fun component13 ()Ljava/lang/String; - public final fun component14 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component15 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component16 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component17 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component18 ()Ljava/util/List; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Z + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component13 ()Ljava/lang/Boolean; + public final fun component14 ()Ljava/lang/String; + public final fun component15 ()Ljava/lang/Integer; + public final fun component16 ()Ljava/util/Map; + public final fun component17 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/util/Map; - public final fun component6 ()Ljava/util/Map; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; public final fun component7 ()Ljava/lang/Integer; - public final fun component8 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component9 ()Lorg/threeten/bp/OffsetDateTime; - public final fun copy (ZLjava/lang/String;ZLjava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;)Lio/getstream/android/video/generated/models/User; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/User;ZLjava/lang/String;ZLjava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/User; - public fun equals (Ljava/lang/Object;)Z - public final fun getAvgResponseTime ()Ljava/lang/Integer; - public final fun getBanExpires ()Lorg/threeten/bp/OffsetDateTime; - public final fun getBanned ()Z - public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; - public final fun getCustom ()Ljava/util/Map; - public final fun getDeactivatedAt ()Lorg/threeten/bp/OffsetDateTime; - public final fun getDeletedAt ()Lorg/threeten/bp/OffsetDateTime; - public final fun getId ()Ljava/lang/String; - public final fun getInvisible ()Ljava/lang/Boolean; - public final fun getLanguage ()Ljava/lang/String; - public final fun getLastActive ()Lorg/threeten/bp/OffsetDateTime; - public final fun getLastEngagedAt ()Lorg/threeten/bp/OffsetDateTime; - public final fun getOnline ()Z - public final fun getRevokeTokensIssuedBefore ()Lorg/threeten/bp/OffsetDateTime; - public final fun getRole ()Ljava/lang/String; - public final fun getTeams ()Ljava/util/List; - public final fun getTeamsRole ()Ljava/util/Map; - public final fun getUpdatedAt ()Lorg/threeten/bp/OffsetDateTime; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/android/video/generated/models/UserBannedEvent : io/getstream/android/video/generated/models/VideoEvent { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ZLio/getstream/android/video/generated/models/User;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ZLio/getstream/android/video/generated/models/User;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Ljava/lang/String; - public final fun component11 ()Lio/getstream/android/video/generated/models/User; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component5 ()Z - public final fun component6 ()Lio/getstream/android/video/generated/models/User; - public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ZLio/getstream/android/video/generated/models/User;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;)Lio/getstream/android/video/generated/models/UserBannedEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserBannedEvent;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ZLio/getstream/android/video/generated/models/User;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserBannedEvent; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;)Lio/getstream/android/video/generated/models/UserBannedEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserBannedEvent;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserBannedEvent; public fun equals (Ljava/lang/Object;)Z + public final fun getChannelCustom ()Ljava/util/Map; public final fun getChannelId ()Ljava/lang/String; + public final fun getChannelMemberCount ()Ljava/lang/Integer; + public final fun getChannelMessageCount ()Ljava/lang/Integer; public final fun getChannelType ()Ljava/lang/String; public final fun getCid ()Ljava/lang/String; public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; - public final fun getCreatedBy ()Lio/getstream/android/video/generated/models/User; + public final fun getCreatedBy ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun getCustom ()Ljava/util/Map; public fun getEventType ()Ljava/lang/String; public final fun getExpiration ()Lorg/threeten/bp/OffsetDateTime; public final fun getReason ()Ljava/lang/String; - public final fun getShadow ()Z + public final fun getReceivedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getShadow ()Ljava/lang/Boolean; public final fun getTeam ()Ljava/lang/String; + public final fun getTotalBans ()Ljava/lang/Integer; public final fun getType ()Ljava/lang/String; - public final fun getUser ()Lio/getstream/android/video/generated/models/User; + public final fun getUser ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class io/getstream/android/video/generated/models/UserDeactivatedEvent : io/getstream/android/video/generated/models/VideoEvent { - public fun (Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/User;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;)V - public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/User;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/UserResponseCommonFields;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/UserResponseCommonFields;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component2 ()Lio/getstream/android/video/generated/models/User; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lio/getstream/android/video/generated/models/User; - public final fun copy (Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/User;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;)Lio/getstream/android/video/generated/models/UserDeactivatedEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserDeactivatedEvent;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/User;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserDeactivatedEvent; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component6 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/UserResponseCommonFields;)Lio/getstream/android/video/generated/models/UserDeactivatedEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserDeactivatedEvent;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/UserResponseCommonFields;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserDeactivatedEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getCreatedBy ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun getCustom ()Ljava/util/Map; + public fun getEventType ()Ljava/lang/String; + public final fun getReceivedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getType ()Ljava/lang/String; + public final fun getUser ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/UserDeletedEvent : io/getstream/android/video/generated/models/VideoEvent { + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;ZZLjava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;ZZLjava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Z + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Z + public final fun component7 ()Z + public final fun component8 ()Ljava/util/Map; + public final fun component9 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;ZZLjava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;)Lio/getstream/android/video/generated/models/UserDeletedEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserDeletedEvent;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;ZZLjava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserDeletedEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; - public final fun getCreatedBy ()Lio/getstream/android/video/generated/models/User; + public final fun getCustom ()Ljava/util/Map; + public final fun getDeleteConversation ()Ljava/lang/String; + public final fun getDeleteConversationChannels ()Z + public final fun getDeleteMessages ()Ljava/lang/String; + public final fun getDeleteUser ()Ljava/lang/String; public fun getEventType ()Ljava/lang/String; + public final fun getHardDelete ()Z + public final fun getMarkMessagesDeleted ()Z + public final fun getReceivedAt ()Lorg/threeten/bp/OffsetDateTime; public final fun getType ()Ljava/lang/String; - public final fun getUser ()Lio/getstream/android/video/generated/models/User; + public final fun getUser ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -8156,23 +8526,23 @@ public final class io/getstream/android/video/generated/models/UserFeedbackRepor public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/UserMutedEvent : io/getstream/android/video/generated/models/VideoEvent { - public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/User;)V - public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/User;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class io/getstream/android/video/generated/models/UserPresenceChangedEvent : io/getstream/android/video/generated/models/VideoEvent { + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/util/List; - public final fun component5 ()Lio/getstream/android/video/generated/models/User; - public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/User;)Lio/getstream/android/video/generated/models/UserMutedEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserMutedEvent;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/android/video/generated/models/User;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserMutedEvent; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lorg/threeten/bp/OffsetDateTime; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;)Lio/getstream/android/video/generated/models/UserPresenceChangedEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserPresenceChangedEvent;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserPresenceChangedEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getCustom ()Ljava/util/Map; public fun getEventType ()Ljava/lang/String; - public final fun getTargetUser ()Ljava/lang/String; - public final fun getTargetUsers ()Ljava/util/List; + public final fun getReceivedAt ()Lorg/threeten/bp/OffsetDateTime; public final fun getType ()Ljava/lang/String; - public final fun getUser ()Lio/getstream/android/video/generated/models/User; + public final fun getUser ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -8191,18 +8561,24 @@ public final class io/getstream/android/video/generated/models/UserRatingReportR } public final class io/getstream/android/video/generated/models/UserReactivatedEvent : io/getstream/android/video/generated/models/VideoEvent { - public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;)V - public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/UserResponseCommonFields;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/UserResponseCommonFields;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/getstream/android/video/generated/models/User; - public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;)Lio/getstream/android/video/generated/models/UserReactivatedEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserReactivatedEvent;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lio/getstream/android/video/generated/models/User;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserReactivatedEvent; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component6 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/UserResponseCommonFields;)Lio/getstream/android/video/generated/models/UserReactivatedEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserReactivatedEvent;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/UserResponseCommonFields;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserReactivatedEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getCreatedBy ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun getCustom ()Ljava/util/Map; public fun getEventType ()Ljava/lang/String; + public final fun getReceivedAt ()Lorg/threeten/bp/OffsetDateTime; public final fun getType ()Ljava/lang/String; - public final fun getUser ()Lio/getstream/android/video/generated/models/User; + public final fun getUser ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -8271,6 +8647,48 @@ public final class io/getstream/android/video/generated/models/UserResponse { public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/UserResponseCommonFields { + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component10 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component11 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component12 ()Ljava/lang/String; + public final fun component13 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component14 ()Ljava/lang/String; + public final fun component15 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component16 ()Ljava/util/Map; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component6 ()Ljava/util/List; + public final fun component7 ()Ljava/util/List; + public final fun component8 ()Ljava/util/Map; + public final fun component9 ()Ljava/lang/Integer; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;)Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserResponseCommonFields;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public fun equals (Ljava/lang/Object;)Z + public final fun getAvgResponseTime ()Ljava/lang/Integer; + public final fun getBlockedUserIds ()Ljava/util/List; + public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getCustom ()Ljava/util/Map; + public final fun getDeactivatedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getDeletedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getId ()Ljava/lang/String; + public final fun getImage ()Ljava/lang/String; + public final fun getLanguage ()Ljava/lang/String; + public final fun getLastActive ()Lorg/threeten/bp/OffsetDateTime; + public final fun getName ()Ljava/lang/String; + public final fun getRevokeTokensIssuedBefore ()Lorg/threeten/bp/OffsetDateTime; + public final fun getRole ()Ljava/lang/String; + public final fun getTeams ()Ljava/util/List; + public final fun getTeamsRole ()Ljava/util/Map; + public final fun getUpdatedAt ()Lorg/threeten/bp/OffsetDateTime; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/UserResponsePrivacyFields { public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/Boolean;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;)V public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/Boolean;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -8315,6 +8733,45 @@ public final class io/getstream/android/video/generated/models/UserResponsePriva public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/UserUnbannedEvent : io/getstream/android/video/generated/models/VideoEvent { + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component10 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component11 ()Ljava/lang/Boolean; + public final fun component12 ()Ljava/lang/String; + public final fun component13 ()Ljava/util/Map; + public final fun component14 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Ljava/lang/Integer; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;)Lio/getstream/android/video/generated/models/UserUnbannedEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/UserUnbannedEvent;Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponseCommonFields;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/UserUnbannedEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getChannelCustom ()Ljava/util/Map; + public final fun getChannelId ()Ljava/lang/String; + public final fun getChannelMemberCount ()Ljava/lang/Integer; + public final fun getChannelMessageCount ()Ljava/lang/Integer; + public final fun getChannelType ()Ljava/lang/String; + public final fun getCid ()Ljava/lang/String; + public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getCreatedBy ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public final fun getCustom ()Ljava/util/Map; + public fun getEventType ()Ljava/lang/String; + public final fun getReceivedAt ()Lorg/threeten/bp/OffsetDateTime; + public final fun getShadow ()Ljava/lang/Boolean; + public final fun getTeam ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public final fun getUser ()Lio/getstream/android/video/generated/models/UserResponseCommonFields; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/UserUpdatedEvent : io/getstream/android/video/generated/models/VideoEvent { public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponsePrivacyFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;)V public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/util/Map;Lio/getstream/android/video/generated/models/UserResponsePrivacyFields;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;ILkotlin/jvm/internal/DefaultConstructorMarker;)V From 47c2b79dd218efd8018629e732fbf2a694baf9ed Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 18 May 2026 13:58:17 +0530 Subject: [PATCH 04/63] internal: create call event reporter --- .../io/getstream/video/android/core/Call.kt | 79 +++- .../video/android/core/StreamVideoClient.kt | 12 + .../video/android/core/call/RtcSession.kt | 30 ++ .../events/reporting/CallEventReporter.kt | 359 ++++++++++++++++++ .../events/reporting/ClientCallEventData.kt | 94 +++++ 5 files changed, 563 insertions(+), 11 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 1891bdfb2b2..bb959b67943 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -76,6 +76,8 @@ import io.getstream.video.android.core.closedcaptions.ClosedCaptionsSettings import io.getstream.video.android.core.events.GoAwayEvent import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener +import io.getstream.video.android.core.events.reporting.CallEventReporter +import io.getstream.video.android.core.events.reporting.PeerConnectionRole import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.model.AudioTrack @@ -445,6 +447,9 @@ public class Call( private var sfuListener: Job? = null private var sfuEvents: Job? = null + /** Reports join lifecycle events to the backend for success-rate analytics. Set by StreamVideoClient. */ + internal var eventReporter: CallEventReporter? = null + init { scope.launch { soundInputProcessor.currentAudioLevel.collect { @@ -760,15 +765,22 @@ public class Call( .flatMapLatest { publisher -> publisher.iceState.map { publisher to it } } - .collect { (publisher, state) -> - when (state) { + .collect { (publisher, iceState) -> + if (iceState != null) { + eventReporter?.onPeerConnectionIceStateChanged( + role = PeerConnectionRole.PUBLISH, + iceState = iceState, + dtlsState = publisher.state.value, + ) + } + when (iceState) { PeerConnection.IceConnectionState.FAILED, PeerConnection.IceConnectionState.DISCONNECTED, -> { publisher.connection.restartIce() } else -> { - logger.d { "[monitorPubConnectionState] Ice connection state is $state" } + logger.d { "[monitorPubConnectionState] Ice connection state is $iceState" } } } } @@ -776,17 +788,31 @@ public class Call( monitorSubscriberPCStateJob?.cancel() monitorSubscriberPCStateJob = scope.launch { - session.value?.subscriber?.value?.iceState?.collect { - when (it) { - PeerConnection.IceConnectionState.FAILED, PeerConnection.IceConnectionState.DISCONNECTED -> { - session.value?.requestSubscriberIceRestart() + session + .filterNotNull() + .flatMapLatest { it.subscriber.filterNotNull() } + .flatMapLatest { subscriber -> + subscriber.iceState.map { subscriber to it } + } + .collect { (subscriber, iceState) -> + if (iceState != null) { + eventReporter?.onPeerConnectionIceStateChanged( + role = PeerConnectionRole.SUBSCRIBE, + iceState = iceState, + dtlsState = subscriber.state.value, + ) } - - else -> { - logger.d { "[monitorSubConnectionState] Ice connection state is $it" } + when (iceState) { + PeerConnection.IceConnectionState.FAILED, + PeerConnection.IceConnectionState.DISCONNECTED, + -> { + session.value?.requestSubscriberIceRestart() + } + else -> { + logger.d { "[monitorSubConnectionState] Ice connection state is $iceState" } + } } } - } } network.subscribe(listener) } @@ -1264,6 +1290,14 @@ public class Call( clientImpl.scope.launch { val leaveReason = "[reason=$reason, error=${disconnectionReason?.message}]" + eventReporter?.let { reporter -> + val abortReason = if (disconnectionReason != null) { + CallEventReporter.AbortReason.BACKEND_LEAVE + } else { + CallEventReporter.AbortReason.CLIENT_ABORTED + } + reporter.abortAllInFlight(abortReason) + } safeCall { session.value?.sfuTracer?.trace("leave-call", leaveReason) val stats = collectStats() @@ -1801,6 +1835,10 @@ public class Call( notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, ): Result { + val reporter = eventReporter + reporter?.resetJoinSuccessId() + val coordEventId = reporter?.reportCoordinatorJoinInitiated() + val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( type, id, @@ -1819,6 +1857,25 @@ public class Call( ) result.onSuccess { state.updateFromResponse(it) + coordEventId?.let { id -> + reporter?.reportCoordinatorJoinCompleted( + eventSessionId = id, + success = true, + retryCount = 0, + callSessionId = it.call.currentSessionId, + ) + } + } + if (result is Failure) { + coordEventId?.let { id -> + reporter?.reportCoordinatorJoinCompleted( + eventSessionId = id, + success = false, + retryCount = 0, + failureReason = (result.value as? Error)?.message, + failureCode = null, + ) + } } return result } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index b2dad697f82..f0b504a7059 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -87,8 +87,10 @@ import io.getstream.video.android.core.audio.AudioExecutionContext import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.errors.VideoErrorCode import io.getstream.video.android.core.events.VideoEventListener +import io.getstream.video.android.core.events.reporting.CallEventReporter import io.getstream.video.android.core.filter.Filters import io.getstream.video.android.core.filter.toMap +import io.getstream.video.android.core.header.HeadersUtil import io.getstream.video.android.core.internal.module.CoordinatorConnectionModule import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.model.EdgeData @@ -1179,6 +1181,16 @@ internal class StreamVideoClient internal constructor( calls[cid]!! } else { val call = Call(this, type, idOrRandom, user) + call.eventReporter = CallEventReporter( + api = coordinatorConnectionModule.api, + callType = type, + callId = idOrRandom, + callCid = cid, + userId = user.id, + userAgent = { HeadersUtil().buildSdkTrackingHeaders() }, + sdkVersion = BuildConfig.STREAM_VIDEO_VERSION, + scope = call.scope, + ) calls[cid] = call call } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index f7cb3b2968d..4d19cb2b167 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -880,6 +880,11 @@ public class RtcSession internal constructor( options: List? = null, ): SfuConnectionResult { logger.i { "[connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } + val wsReporter = call.eventReporter + val wsEventId = wsReporter?.reportWsJoinInitiated( + sfuId = sfuName, + wasPreviouslyConnected = reconnectDetails != null, + ) val request = buildJoinRequest(reconnectDetails, options) sfuTracer.trace( PeerConnectionTraceKey.JOIN_REQUEST.value, @@ -896,6 +901,13 @@ public class RtcSession internal constructor( } return when (terminalState) { is SfuSocketState.Connected -> { + wsEventId?.let { + wsReporter?.reportWsJoinCompleted( + it, + success = true, + retryCount = 0, + ) + } sendConnectionTimeStats(reconnectDetails?.strategy) SfuConnectionResult.Connected } @@ -909,11 +921,29 @@ public class RtcSession internal constructor( } logger.w { "[connectInternal] $msg" } sfuTracer.trace("connect-failed", msg) + wsEventId?.let { + wsReporter?.reportWsJoinCompleted( + it, + success = false, + retryCount = 0, + failureReason = msg, + failureCode = "WS_DISCONNECTED", + ) + } sendCallStats() SfuConnectionResult.Failed(Exception(msg)) } else -> { sfuTracer.trace("connect-failed", "Connection timed out") + wsEventId?.let { + wsReporter?.reportWsJoinCompleted( + it, + success = false, + retryCount = 0, + failureReason = "SFU connection timed out", + failureCode = "REQUEST_TIMEOUT", + ) + } sendCallStats() SfuConnectionResult.Failed(Exception("SFU connection timed out")) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt new file mode 100644 index 00000000000..b9f8436be07 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.events.reporting + +import io.getstream.android.video.generated.apis.ProductvideoApi +import io.getstream.android.video.generated.models.SendCallEventRequest +import io.getstream.log.taggedLogger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.webrtc.PeerConnection +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +internal class CallEventReporter( + private val api: ProductvideoApi, + private val callType: String, + private val callId: String, + private val callCid: String, + private val userId: String, + private val userAgent: () -> String, + private val sdkVersion: String, + private val scope: CoroutineScope, +) { + private val logger by taggedLogger("CallEventReporter:$callCid") + + @Volatile private var joinSuccessId: String = UUID.randomUUID().toString() + + private val inFlightSessions = ConcurrentHashMap() + + // Active event_session_id per PC role — drives the ICE state machine + private val activePcSessionIds = ConcurrentHashMap() + + // Whether each PC role has ever reached CONNECTED (for was_previously_connected) + private val pcEverConnected = ConcurrentHashMap() + + private data class InFlightSession( + val eventSessionId: String, + val stage: CallEventStage, + val startedAtMs: Long, + val joinSuccessIdSnapshot: String, + val sfuId: String? = null, + val callSessionId: String? = null, + val userSessionId: String? = null, + val peerConnectionRole: PeerConnectionRole? = null, + val wasPreviouslyConnected: Boolean = false, + ) + + enum class AbortReason(val code: String, val message: String) { + CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), + BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), + } + + // --- join_success_id --- + + internal fun resetJoinSuccessId() { + joinSuccessId = UUID.randomUUID().toString() + } + + // --- CoordinatorJoin --- + + internal fun reportCoordinatorJoinInitiated(): String { + val eventSessionId = UUID.randomUUID().toString() + val now = System.currentTimeMillis() + inFlightSessions[eventSessionId] = InFlightSession( + eventSessionId = eventSessionId, + stage = CallEventStage.COORDINATOR_JOIN, + startedAtMs = now, + joinSuccessIdSnapshot = joinSuccessId, + ) + sendEvent( + buildEventMap( + userId = userId, + callType = callType, + callId = callId, + callCid = callCid, + stage = CallEventStage.COORDINATOR_JOIN, + eventType = CallEventType.INITIATED, + eventSessionId = eventSessionId, + joinSuccessId = joinSuccessId, + userAgent = userAgent.invoke(), + sdkVersion = sdkVersion, + ), + ) + return eventSessionId + } + + internal fun reportCoordinatorJoinCompleted( + eventSessionId: String, + success: Boolean, + retryCount: Int, + failureReason: String? = null, + failureCode: String? = null, + callSessionId: String? = null, + ) { + val session = inFlightSessions.remove(eventSessionId) ?: return + val elapsedTime = System.currentTimeMillis() - session.startedAtMs + sendEvent( + buildEventMap( + userId = userId, + callType = callType, + callId = callId, + callCid = callCid, + stage = CallEventStage.COORDINATOR_JOIN, + eventType = CallEventType.COMPLETED, + eventSessionId = eventSessionId, + joinSuccessId = session.joinSuccessIdSnapshot, + userAgent = userAgent.invoke(), + sdkVersion = sdkVersion, + elapsedTime = elapsedTime, + outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, + retryCountAttempt = retryCount, + retryFailureReason = if (!success) failureReason else null, + retryFailureCode = if (!success) failureCode else null, + callSessionId = callSessionId, + ), + ) + } + + // --- WSJoin --- + + internal fun reportWsJoinInitiated( + sfuId: String, + wasPreviouslyConnected: Boolean, + ): String { + val eventSessionId = UUID.randomUUID().toString() + val now = System.currentTimeMillis() + inFlightSessions[eventSessionId] = InFlightSession( + eventSessionId = eventSessionId, + stage = CallEventStage.WS_JOIN, + startedAtMs = now, + joinSuccessIdSnapshot = joinSuccessId, + sfuId = sfuId, + wasPreviouslyConnected = wasPreviouslyConnected, + ) + sendEvent( + buildEventMap( + userId = userId, + callType = callType, + callId = callId, + callCid = callCid, + stage = CallEventStage.WS_JOIN, + eventType = CallEventType.INITIATED, + eventSessionId = eventSessionId, + joinSuccessId = joinSuccessId, + userAgent = userAgent.invoke(), + sdkVersion = sdkVersion, + sfuId = sfuId, + wasPreviouslyConnected = wasPreviouslyConnected, + ), + ) + return eventSessionId + } + + internal fun reportWsJoinCompleted( + eventSessionId: String, + success: Boolean, + retryCount: Int, + failureReason: String? = null, + failureCode: String? = null, + callSessionId: String? = null, + ) { + val session = inFlightSessions.remove(eventSessionId) ?: return + val elapsedTime = System.currentTimeMillis() - session.startedAtMs + sendEvent( + buildEventMap( + userId = userId, + callType = callType, + callId = callId, + callCid = callCid, + stage = CallEventStage.WS_JOIN, + eventType = CallEventType.COMPLETED, + eventSessionId = eventSessionId, + joinSuccessId = session.joinSuccessIdSnapshot, + userAgent = userAgent.invoke(), + sdkVersion = sdkVersion, + elapsedTime = elapsedTime, + outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, + retryCountAttempt = retryCount, + retryFailureReason = if (!success) failureReason else null, + retryFailureCode = if (!success) failureCode else null, + sfuId = session.sfuId, + callSessionId = callSessionId, + ), + ) + } + + // --- PeerConnectionConnect (ICE state machine) --- + + internal fun onPeerConnectionIceStateChanged( + role: PeerConnectionRole, + iceState: PeerConnection.IceConnectionState, + dtlsState: PeerConnection.PeerConnectionState?, + ) { + when (iceState) { + PeerConnection.IceConnectionState.CHECKING -> { + val wasPrev = pcEverConnected[role] == true + // If an existing session is still in-flight, close it as failed first + activePcSessionIds.remove(role)?.let { oldId -> + completePeerConnectionSession( + eventSessionId = oldId, + success = false, + iceStateName = iceState.name, + dtlsStateName = dtlsState?.name, + failureReason = "ICE restart superseded previous attempt", + failureCode = "ICE_CONNECTIVITY_FAILED", + ) + } + val eventSessionId = UUID.randomUUID().toString() + val now = System.currentTimeMillis() + inFlightSessions[eventSessionId] = InFlightSession( + eventSessionId = eventSessionId, + stage = CallEventStage.PEER_CONNECTION_CONNECT, + startedAtMs = now, + joinSuccessIdSnapshot = joinSuccessId, + peerConnectionRole = role, + wasPreviouslyConnected = wasPrev, + ) + activePcSessionIds[role] = eventSessionId + sendEvent( + buildEventMap( + userId = userId, + callType = callType, + callId = callId, + callCid = callCid, + stage = CallEventStage.PEER_CONNECTION_CONNECT, + eventType = CallEventType.INITIATED, + eventSessionId = eventSessionId, + joinSuccessId = joinSuccessId, + userAgent = userAgent.invoke(), + sdkVersion = sdkVersion, + peerConnection = role, + wasPreviouslyConnected = wasPrev, + ), + ) + } + + PeerConnection.IceConnectionState.CONNECTED -> { + val eventSessionId = activePcSessionIds.remove(role) ?: return + pcEverConnected[role] = true + completePeerConnectionSession( + eventSessionId = eventSessionId, + success = true, + iceStateName = iceState.name, + dtlsStateName = dtlsState?.name, + ) + } + + PeerConnection.IceConnectionState.FAILED -> { + val eventSessionId = activePcSessionIds.remove(role) ?: return + completePeerConnectionSession( + eventSessionId = eventSessionId, + success = false, + iceStateName = iceState.name, + dtlsStateName = dtlsState?.name, + failureReason = "ICE connectivity checks failed", + failureCode = "ICE_CONNECTIVITY_FAILED", + ) + } + + else -> { /* DISCONNECTED handled by ICE restart → CHECKING */ } + } + } + + private fun completePeerConnectionSession( + eventSessionId: String, + success: Boolean, + iceStateName: String?, + dtlsStateName: String?, + failureReason: String? = null, + failureCode: String? = null, + ) { + val session = inFlightSessions.remove(eventSessionId) ?: return + val elapsedTime = System.currentTimeMillis() - session.startedAtMs + sendEvent( + buildEventMap( + userId = userId, + callType = callType, + callId = callId, + callCid = callCid, + stage = CallEventStage.PEER_CONNECTION_CONNECT, + eventType = CallEventType.COMPLETED, + eventSessionId = eventSessionId, + joinSuccessId = session.joinSuccessIdSnapshot, + userAgent = userAgent.invoke(), + sdkVersion = sdkVersion, + elapsedTime = elapsedTime, + outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, + retryCountAttempt = 0, + retryFailureReason = if (!success) failureReason else null, + retryFailureCode = if (!success) failureCode else null, + peerConnection = session.peerConnectionRole, + wasPreviouslyConnected = session.wasPreviouslyConnected, + iceState = iceStateName, + dtlsState = dtlsStateName, + ), + ) + } + + // --- Abort all in-flight sessions (user left or backend ended call) --- + + internal fun abortAllInFlight(reason: AbortReason) { + val snapshot = inFlightSessions.values.toList() + inFlightSessions.clear() + activePcSessionIds.clear() + val now = System.currentTimeMillis() + for (session in snapshot) { + sendEvent( + buildEventMap( + userId = userId, + callType = callType, + callId = callId, + callCid = callCid, + stage = session.stage, + eventType = CallEventType.COMPLETED, + eventSessionId = session.eventSessionId, + joinSuccessId = session.joinSuccessIdSnapshot, + userAgent = userAgent.invoke(), + sdkVersion = sdkVersion, + elapsedTime = now - session.startedAtMs, + outcome = CallEventOutcome.FAILURE, + retryCountAttempt = 0, + retryFailureReason = reason.message, + retryFailureCode = reason.code, + sfuId = session.sfuId, + callSessionId = session.callSessionId, + peerConnection = session.peerConnectionRole, + wasPreviouslyConnected = session.wasPreviouslyConnected, + ), + ) + } + } + + // --- Delivery --- + + private fun sendEvent(customMap: Map) { + scope.launch { + // TODO: wrap with StreamRetryPolicy when retries are added + runCatching { + api.sendCallEvent(callType, callId, SendCallEventRequest(custom = customMap)) + }.onFailure { e -> + logger.w { "[sendEvent] Failed to send client event: ${e.message}" } + } + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt new file mode 100644 index 00000000000..5f13fe2edc0 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.events.reporting + +import org.threeten.bp.OffsetDateTime +import org.threeten.bp.ZoneOffset +import org.threeten.bp.format.DateTimeFormatter + +internal enum class CallEventStage(val value: String) { + COORDINATOR_JOIN("coordinator_join"), + WS_JOIN("ws_join"), + PEER_CONNECTION_CONNECT("peer_connection_connect"), +} + +internal enum class CallEventType(val value: String) { + INITIATED("initiated"), + COMPLETED("completed"), +} + +internal enum class CallEventOutcome(val value: String) { + SUCCESS("success"), + FAILURE("failure"), +} + +internal enum class PeerConnectionRole(val value: String) { + PUBLISH("publish"), + SUBSCRIBE("subscribe"), +} + +internal fun buildEventMap( + userId: String, + callType: String, + callId: String, + callCid: String, + stage: CallEventStage, + eventType: CallEventType, + eventSessionId: String, + joinSuccessId: String, + userAgent: String, + sdkVersion: String, + elapsedTime: Long? = null, + outcome: CallEventOutcome? = null, + retryCountAttempt: Int? = null, + retryFailureReason: String? = null, + retryFailureCode: String? = null, + callSessionId: String? = null, + sfuId: String? = null, + peerConnection: PeerConnectionRole? = null, + wasPreviouslyConnected: Boolean? = null, + iceState: String? = null, + dtlsState: String? = null, + userSessionId: String? = null, +): Map = buildMap { + put("user_id", userId) + put("type", callType) + put("id", callId) // review with Gulzar: already in URL + put("call_cid", callCid) // review: already in URL + put("stage", stage.value) + put("event_type", eventType.value) + put("event_session_id", eventSessionId) + put("join_success_id", joinSuccessId) + put("timestamp", currentRfc3339Timestamp()) + put("user_agent", userAgent.take(512)) + put("sdk_version", sdkVersion) // review: already comes in header + elapsedTime?.let { put("elapsed_time", it) } + outcome?.let { put("outcome", it.value) } + retryCountAttempt?.let { put("retry_count_attempt", it) } + retryFailureReason?.let { put("retry_failure_reason", it) } + retryFailureCode?.let { put("retry_failure_code", it) } + callSessionId?.let { put("call_session_id", it) } + sfuId?.let { put("sfu_id", it) } + peerConnection?.let { put("peer_connection", it.value) } + wasPreviouslyConnected?.let { put("was_previously_connected", it) } + iceState?.let { put("ice_state", it) } + dtlsState?.let { put("dtls_state", it) } + userSessionId?.let { put("user_session_id", it) } +} + +internal fun currentRfc3339Timestamp(): String = + OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) From 30d130decb9c1a73bfaaa6327a9cb742ded92ea8 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 18 May 2026 17:33:59 +0530 Subject: [PATCH 05/63] internal: create call leave reason --- .../video/android/core/ActiveStateGate.kt | 7 +- .../io/getstream/video/android/core/Call.kt | 34 ++++--- .../video/android/core/CallLeaveReason.kt | 95 +++++++++++++++++++ .../getstream/video/android/core/CallState.kt | 48 ++++++++-- .../core/ExternalCallRejectionHandler.kt | 7 +- .../video/android/core/StreamVideoClient.kt | 4 +- .../receivers/LeaveCallBroadcastReceiver.kt | 9 +- .../managers/CallServiceLifecycleManager.kt | 16 +++- 8 files changed, 195 insertions(+), 25 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ActiveStateGate.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ActiveStateGate.kt index 0be74b77679..403ad040e27 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ActiveStateGate.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ActiveStateGate.kt @@ -149,7 +149,12 @@ internal class ActiveStateGate( } catch (e: CallJoinInterceptionException) { val message = "[CallJoinInterceptor] aborted with reason: ${e.reason}" logger.e(e) { message } - call.leave(reason = message) + call.leave( + CallLeaveReason.UserAction( + cause = UserActionCause.CALL_JOIN_ABORT, + message = e.reason, + ), + ) clearAllJobs() false } catch (e: Exception) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index bb959b67943..a851abb3dfd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -428,10 +428,11 @@ public class Call( } return@launch } + val message = "Leaving after being disconnected for ${clientImpl.leaveAfterDisconnectSeconds}" logger.d { - "[NetworkStateListener#onDisconnected] #network; Leaving after being disconnected for ${clientImpl.leaveAfterDisconnectSeconds} (connection=$conn)" + "[NetworkStateListener#onDisconnected] #network; $message (connection=$conn)" } - leave() + leave(CallLeaveReason.Backend(cause = BackendCause.LEAVE_TIMEOUT_AFTER_DISCONNECT, message = message)) // TODO Rahul before merge } logger.d { "[NetworkStateListener#onDisconnected] #network; at $lastDisconnect" } } @@ -1244,12 +1245,18 @@ public class Call( // endregion /** Leave the call, but don't end it for other users */ + internal fun leave(reason: CallLeaveReason) { + logger.d { "[leave] #ringing; call_cid:$cid" } + internalLeave(reason) + } + fun leave(reason: String = "user") { logger.d { "[leave] #ringing; no args, call_cid:$cid" } - internalLeave(null, reason) + internalLeave(CallLeaveReason.Custom(reason)) } - private fun internalLeave(disconnectionReason: Throwable?, reason: String) = atomicLeave { +// private fun internalLeave(reason: CallLeaveReason) = atomicLeave { + private fun internalLeave(reason: CallLeaveReason) = atomicLeave { monitorSubscriberPCStateJob?.cancel() monitorPublisherPCStateJob?.cancel() monitorPublisherPCStateJob = null @@ -1259,7 +1266,7 @@ public class Call( sfuListener?.cancel() sfuEvents?.cancel() state._connection.value = RealtimeConnection.Disconnected - logger.v { "[leave] #ringing; disconnectionReason: $disconnectionReason, call_id = $id" } + logger.v { "[leave] #ringing; call_id = $id" } if (isDestroyed) { logger.w { "[leave] #ringing; Call already destroyed, ignoring" } return@atomicLeave @@ -1289,12 +1296,12 @@ public class Call( (client as StreamVideoClient).onCallCleanUp(this) clientImpl.scope.launch { - val leaveReason = "[reason=$reason, error=${disconnectionReason?.message}]" + val leaveReason = "[reason=${reason::class.simpleName}, message=${reason.message}]" +// val leaveReason = "[reason=$reason]" eventReporter?.let { reporter -> - val abortReason = if (disconnectionReason != null) { - CallEventReporter.AbortReason.BACKEND_LEAVE - } else { - CallEventReporter.AbortReason.CLIENT_ABORTED + val abortReason = when (reason) { + is CallLeaveReason.Backend -> CallEventReporter.AbortReason.BACKEND_LEAVE + else -> CallEventReporter.AbortReason.CLIENT_ABORTED } reporter.abortAllInFlight(abortReason) } @@ -1314,7 +1321,12 @@ public class Call( // end the call for everyone val result = clientImpl.endCall(type, id) // cleanup - leave("call-ended") + leave( + CallLeaveReason.SdkDriven( + cause = SdkCause.END_CALL, + message = "CALL_ENDED", // Call ended by local user + ), + ) return result } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt new file mode 100644 index 00000000000..0eafdf7a3c8 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core + +internal enum class SdkCause { + /** App was swiped from the recents screen. */ + TASK_REMOVED, + + /** Telecom system put the call on hold for another call. */ + CALL_ON_HOLD, + + /** SDK-level cleanup (e.g. logout, StreamVideo instance teardown). */ + CLIENT_CLEANUP, + + /** Outgoing call auto-cancel timeout elapsed with no answer. */ + RING_TIMEOUT, + + ACCEPTED_ON_OTHER_DEVICE, + LOCAL_CALL_MISSED_EVENT, + REJECTED_BY_ALL, + END_CALL, +} + +internal enum class UserActionCause { + /** User rejected the call from a paired wearable device. */ + WEARABLE_REJECTED, + + /** A [io.getstream.video.android.core.CallJoinInterceptor] aborted the join sequence. */ + CALL_JOIN_ABORT, + REJECTED_BY_SELF, + LEAVE_FROM_NOTIFICATION, +} + +internal enum class BackendCause { + LEAVE_TIMEOUT_AFTER_DISCONNECT, + CALL_ENDED_EVENT, + CALL_ENDED_SFU_EVENT, +} + +internal sealed interface CallLeaveReason { + + val message: String? + + val metadata: Map + + /** The local user explicitly chose to leave. */ + data class UserAction( + val cause: UserActionCause, + override val message: String? = null, + override val metadata: Map = emptyMap(), + ) : CallLeaveReason + + /** The backend ended or rejected the call (CallEndedEvent, SFU termination, etc.). */ + data class Backend( + val cause: BackendCause, + val backendCode: String? = null, + override val message: String?, + override val metadata: Map = emptyMap(), + ) : CallLeaveReason + + /** All reconnect attempts were exhausted after a network or SFU failure. */ + data class RetryExhausted( + val retryCount: Int, + val failureCode: String?, + override val message: String?, + override val metadata: Map = emptyMap(), + ) : CallLeaveReason + + /** A platform/system event (task removal, hold, wearable, interceptor, etc.) caused the leave. */ + data class SdkDriven( + val cause: SdkCause, + override val message: String? = null, + override val metadata: Map = emptyMap(), + ) : CallLeaveReason + + /** SDK consumer supplied an arbitrary reason. */ + data class Custom( + override val message: String? = null, + override val metadata: Map = emptyMap(), + ) : CallLeaveReason +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index 3045747b1f2..a849e066788 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -795,7 +795,14 @@ public class CallState( } else if (callRingState is RingingState.Incoming && event.user.id == client.userId) { // Call accepted by me + this device is Incoming => I accepted on another device // Then leave the call on this device - if (!acceptedOnThisDevice) call.leave("accepted-on-another-device") + if (!acceptedOnThisDevice) { + call.leave( + CallLeaveReason.SdkDriven( + SdkCause.ACCEPTED_ON_OTHER_DEVICE, + message = "accepted-on-another-device", + ), + ) + } } call.fireEvent( LocalCallAcceptedPostEvent( @@ -847,7 +854,12 @@ public class CallState( } _rejectedBy.value = newRejectedBySet.toSet() _ringingState.value = RingingState.RejectedByAll - call.leave("LocalCallMissedEvent") + call.leave( + CallLeaveReason.SdkDriven( + SdkCause.LOCAL_CALL_MISSED_EVENT, + message = "LocalCallMissedEvent", + ), + ) val activeCallExists = client.state.activeCall.value != null if (activeCallExists) { @@ -868,12 +880,22 @@ public class CallState( _endedAt.value = OffsetDateTime.now(Clock.systemUTC()) _endedByUser.value = event.user?.toUser() updateRingingState() - call.leave("CallEndedEvent") + call.leave( + CallLeaveReason.Backend( + cause = BackendCause.CALL_ENDED_EVENT, + message = "CallEndedEvent", + ), + ) // Call ended by backend } is CallEndedSfuEvent -> { _endedAt.value = OffsetDateTime.now(Clock.systemUTC()) - call.leave("CallEndedSfuEvent") + call.leave( + CallLeaveReason.Backend( + cause = BackendCause.CALL_ENDED_SFU_EVENT, + message = "CallEndedSfuEvent", + ), + ) // Call ended by SFU } is CallMemberUpdatedEvent -> { @@ -1318,13 +1340,23 @@ public class CallState( cancelTimeout() RingingState.RejectedByAll } else if (isRejectedByMe) { - call.leave("updateRingingState-rejected-self") + call.leave( + CallLeaveReason.UserAction( + UserActionCause.REJECTED_BY_SELF, + message = "updateRingingState-rejected-self", + ), + ) // User rejected the call cancelTimeout() RingingState.RejectedByAll } else if ((rejectedBy.isNotEmpty() && rejectedBy.size >= outgoingMembersCount) || (rejectedBy.contains(createdBy?.id) && hasRingingCall) ) { - call.leave("updateRingingState-rejected") + call.leave( + CallLeaveReason.SdkDriven( + cause = SdkCause.REJECTED_BY_ALL, + message = "All participants rejected the call", + ), + ) cancelTimeout() if (rejectReason?.alias == REJECT_REASON_TIMEOUT) { @@ -1453,7 +1485,7 @@ public class CallState( if (_ringingState.value is RingingState.Outgoing || _ringingState.value is RingingState.Incoming && client.state.activeCall.value == null) { isJoinAndRingInProgress.set(false) call.reject(reason = RejectReason.Custom(alias = REJECT_REASON_TIMEOUT)) - call.leave("start-ringing-timeout") + call.leave(CallLeaveReason.SdkDriven(cause = SdkCause.RING_TIMEOUT, message = "Outgoing call timed out with no answer")) } } else { logger.w { "[startRingingTimer] No autoCancelTimeoutMs set - call ring with no timeout" } @@ -1829,7 +1861,7 @@ public class CallState( .collect { isOnHold -> when (ringingState.value) { is RingingState.Active -> { - call.leave("call-on-hold") + call.leave(CallLeaveReason.SdkDriven(cause = SdkCause.CALL_ON_HOLD, message = "Call put on hold by the system")) } else -> {} } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt index 36ba54d23fa..0ab8e84a7ab 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt @@ -68,7 +68,12 @@ internal class ExternalCallRejectionHandler() { * onSuccess: (suspend (Call) -> Unit)?, * onError: (suspend (Exception) -> Unit)?,) */ - call.leave("rejected-on-wearable") + call.leave( + CallLeaveReason.UserAction( + cause = UserActionCause.WEARABLE_REJECTED, + message = "Call rejected on wearable", + ), + ) } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index f0b504a7059..91708c5fead 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -253,7 +253,9 @@ internal class StreamVideoClient internal constructor( } } } - activeCall?.leave("client-cleanup") + activeCall?.leave( + CallLeaveReason.SdkDriven(cause = SdkCause.CLIENT_CLEANUP, message = "client-cleanup"), + ) // SDK client cleanup audioExecutionContext.release() } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/LeaveCallBroadcastReceiver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/LeaveCallBroadcastReceiver.kt index a1ba9da074f..0135570cd2e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/LeaveCallBroadcastReceiver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/LeaveCallBroadcastReceiver.kt @@ -21,6 +21,8 @@ import android.content.Intent import androidx.core.app.NotificationManagerCompat import io.getstream.log.taggedLogger import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason +import io.getstream.video.android.core.UserActionCause import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_LEAVE_CALL import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_NOTIFICATION_ID @@ -37,7 +39,12 @@ internal class LeaveCallBroadcastReceiver : GenericCallActionBroadcastReceiver() override suspend fun onReceive(call: Call, context: Context, intent: Intent) { logger.d { "[onReceive] #ringing; callId: ${call.id}, action: ${intent.action}" } - call.leave("LeaveCallBroadcastReceiver") + call.leave( + CallLeaveReason.UserAction( + UserActionCause.LEAVE_FROM_NOTIFICATION, + message = "User left via notification action", + ), + ) val notificationId = intent.getIntExtra(INTENT_EXTRA_NOTIFICATION_ID, 0) logger.d { "[onReceive], notificationId: $notificationId" } NotificationManagerCompat.from(context).cancel(notificationId) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/managers/CallServiceLifecycleManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/managers/CallServiceLifecycleManager.kt index c2c6b8f56ec..40c4645680b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/managers/CallServiceLifecycleManager.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/managers/CallServiceLifecycleManager.kt @@ -19,7 +19,9 @@ package io.getstream.video.android.core.notifications.internal.service.managers import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.RingingState +import io.getstream.video.android.core.SdkCause import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.model.StreamCallId @@ -96,7 +98,12 @@ internal class CallServiceLifecycleManager { } else -> { - call.leave("call-service-end-call-unknown") + call.leave( + CallLeaveReason.SdkDriven( + cause = SdkCause.TASK_REMOVED, + message = "App removed from recents (unknown call state)", + ), + ) logger.i { "[onTaskRemoved] Ended ongoing call for me" } } } @@ -114,7 +121,12 @@ internal class CallServiceLifecycleManager { logger.i { "[handleIncomingCallTaskRemoved] Ended incoming call for both users" } } } else { - call.leave("call-service-end-call-incoming") + call.leave( + CallLeaveReason.SdkDriven( + cause = SdkCause.TASK_REMOVED, + message = "App removed from recents (incoming call)", + ), + ) logger.i { "[handleIncomingCallTaskRemoved] Ended incoming call for me" } } } From 5de5a92a92d3d9cbbc809f5b5003c6730903d7cf Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 19 May 2026 16:18:19 +0530 Subject: [PATCH 06/63] chore: create typedalias EventSessionId --- .../android/core/events/reporting/CallEventReporter.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt index b9f8436be07..5dd278882e6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt @@ -25,6 +25,8 @@ import org.webrtc.PeerConnection import java.util.UUID import java.util.concurrent.ConcurrentHashMap +private typealias EventSessionId = String + internal class CallEventReporter( private val api: ProductvideoApi, private val callType: String, @@ -39,7 +41,7 @@ internal class CallEventReporter( @Volatile private var joinSuccessId: String = UUID.randomUUID().toString() - private val inFlightSessions = ConcurrentHashMap() + private val inFlightSessions = ConcurrentHashMap() // Active event_session_id per PC role — drives the ICE state machine private val activePcSessionIds = ConcurrentHashMap() @@ -48,7 +50,7 @@ internal class CallEventReporter( private val pcEverConnected = ConcurrentHashMap() private data class InFlightSession( - val eventSessionId: String, + val eventSessionId: EventSessionId, val stage: CallEventStage, val startedAtMs: Long, val joinSuccessIdSnapshot: String, From 713fc0a3cfcfa96713038565f6a8332a5ff20b21 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 19 May 2026 18:39:30 +0530 Subject: [PATCH 07/63] chore: update openapi models --- .../api/stream-video-android-core.api | 27 +++++----- .../video/generated/apis/ProductvideoApi.kt | 49 ++++++++++++++----- .../generated/models/CallReactionEvent.kt | 9 ++++ .../models/ReportClientCallEventRequest.kt | 3 ++ 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index b67d132be92..59883670784 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -48,6 +48,8 @@ public abstract interface class io/getstream/android/video/generated/apis/Produc public static synthetic fun queryCallParticipants$default (Lio/getstream/android/video/generated/apis/ProductvideoApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public abstract fun queryCallSessionParticipantStats (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun queryCallSessionParticipantStats$default (Lio/getstream/android/video/generated/apis/ProductvideoApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public abstract fun queryCallSessionStats (Lio/getstream/android/video/generated/models/QueryCallSessionStatsRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun queryCallSessionStats (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun queryCallStats (Lio/getstream/android/video/generated/models/QueryCallStatsRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun queryCallStats (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun queryCalls (Ljava/lang/String;Lio/getstream/android/video/generated/models/QueryCallsRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -58,6 +60,7 @@ public abstract interface class io/getstream/android/video/generated/apis/Produc public abstract fun rejectCall (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun reportClientCallEvent (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun requestPermission (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/RequestPermissionRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun resolveSipAuth (Lio/getstream/android/video/generated/models/ResolveSipAuthRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun resolveSipInbound (Lio/getstream/android/video/generated/models/ResolveSipInboundRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun ringCall (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/RingCallRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun ringCall (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -5973,21 +5976,22 @@ public final class io/getstream/android/video/generated/models/ReportByHistogram } public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest { - public fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component10 ()Ljava/lang/String; - public final fun component11 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; - public final fun component12 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; - public final fun component13 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; - public final fun component14 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component15 ()Ljava/lang/Integer; - public final fun component16 ()Ljava/lang/String; + public final fun component11 ()Ljava/lang/Integer; + public final fun component12 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; + public final fun component13 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; + public final fun component14 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; + public final fun component15 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component16 ()Ljava/lang/Integer; public final fun component17 ()Ljava/lang/String; public final fun component18 ()Ljava/lang/String; public final fun component19 ()Ljava/lang/String; public final fun component2 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; - public final fun component20 ()Ljava/lang/Boolean; + public final fun component20 ()Ljava/lang/String; + public final fun component21 ()Ljava/lang/Boolean; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; @@ -5995,10 +5999,11 @@ public final class io/getstream/android/video/generated/models/ReportClientCallE public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest; + public final fun copy (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest; public fun equals (Ljava/lang/Object;)Z public final fun getCallSessionId ()Ljava/lang/String; + public final fun getElapsedTime ()Ljava/lang/Integer; public final fun getEventSessionId ()Ljava/lang/String; public final fun getEventType ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; public final fun getIceState ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt index d72ca11abc3..1f908ab7fa0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt @@ -31,18 +31,10 @@ import retrofit2.http.PATCH import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query +import retrofit2.http.PUT interface ProductvideoApi { - /** - * Report client-side call event - * Reports a single client-side join-lifecycle event (initiated or completed) for one of CoordinatorJoin, WSJoin, or PeerConnectionConnect. Pairs are correlated by event_session_id. - */ - @POST("/video/call/client_event") - suspend fun reportClientCallEvent( - @Body reportClientCallEventRequest: io.getstream.android.video.generated.models.ReportClientCallEventRequest - ): io.getstream.android.video.generated.models.ReportClientCallEventResponse - /** * Query call members * Query call members with filter query @@ -724,7 +716,33 @@ interface ProductvideoApi { @Path("session") session: kotlin.String, @Path("filename") filename: kotlin.String ): io.getstream.android.video.generated.models.DeleteTranscriptionResponse - + + /** + * Report client-side call event + * Reports a single client-side join-lifecycle event (initiated or completed) for one of CoordinatorJoin, WSJoin, or PeerConnectionConnect. Pairs are correlated by event_session_id. + */ + @POST("/video/call_client_event") + suspend fun reportClientCallEvent( + @Body reportClientCallEventRequest: io.getstream.android.video.generated.models.ReportClientCallEventRequest + ): io.getstream.android.video.generated.models.ReportClientCallEventResponse + + /** + * Query call session stats + * + */ + @POST("/video/call_stats") + suspend fun queryCallSessionStats( + @Body queryCallSessionStatsRequest: io.getstream.android.video.generated.models.QueryCallSessionStatsRequest + ): io.getstream.android.video.generated.models.QueryCallSessionStatsResponse + + /** + * Query call session stats + * + */ + @POST("/video/call_stats") + suspend fun queryCallSessionStats( + ): io.getstream.android.video.generated.models.QueryCallSessionStatsResponse + /** * Map call participants by location * @@ -858,7 +876,16 @@ interface ProductvideoApi { @GET("/video/longpoll") suspend fun videoConnect( ) - + + /** + * Resolve SIP Auth + * Determine authentication requirements for an inbound SIP call before sending a digest challenge + */ + @POST("/video/sip/auth") + suspend fun resolveSipAuth( + @Body resolveSipAuthRequest: io.getstream.android.video.generated.models.ResolveSipAuthRequest + ): io.getstream.android.video.generated.models.ResolveSipAuthResponse + /** * Resolve SIP Inbound Routing * Resolve SIP inbound routing based on trunk number, caller number, and challenge authentication diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt index 9993f14c172..e78e8db37df 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallReactionEvent.kt @@ -23,7 +23,16 @@ package io.getstream.android.video.generated.models +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson /** * This event is sent when a reaction is sent in a call, clients should use this to show the reaction in the call screen diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt index cf2591c4920..794438417c0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt @@ -69,6 +69,9 @@ data class ReportClientCallEventRequest ( @Json(name = "call_session_id") val callSessionId: kotlin.String? = null, + @Json(name = "elapsed_time") + val elapsedTime: kotlin.Int? = null, + @Json(name = "ice_state") val iceState: IceState? = null, From b56d3e0756d54ed1332bd5c950795dd96e4c2abb Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 19 May 2026 19:15:40 +0530 Subject: [PATCH 08/63] chore: Update CallEventReporter --- .../events/reporting/CallEventReporter.kt | 157 ++++++++++-------- .../events/reporting/ClientCallEventData.kt | 56 ------- 2 files changed, 88 insertions(+), 125 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt index 5dd278882e6..e171141e748 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt @@ -17,10 +17,12 @@ package io.getstream.video.android.core.events.reporting import io.getstream.android.video.generated.apis.ProductvideoApi -import io.getstream.android.video.generated.models.SendCallEventRequest +import io.getstream.android.video.generated.models.ReportClientCallEventRequest import io.getstream.log.taggedLogger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.threeten.bp.OffsetDateTime +import org.threeten.bp.ZoneOffset import org.webrtc.PeerConnection import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -84,17 +86,10 @@ internal class CallEventReporter( joinSuccessIdSnapshot = joinSuccessId, ) sendEvent( - buildEventMap( - userId = userId, - callType = callType, - callId = callId, - callCid = callCid, + buildRequest( stage = CallEventStage.COORDINATOR_JOIN, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, - joinSuccessId = joinSuccessId, - userAgent = userAgent.invoke(), - sdkVersion = sdkVersion, ), ) return eventSessionId @@ -111,17 +106,10 @@ internal class CallEventReporter( val session = inFlightSessions.remove(eventSessionId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( - buildEventMap( - userId = userId, - callType = callType, - callId = callId, - callCid = callCid, + buildRequest( stage = CallEventStage.COORDINATOR_JOIN, eventType = CallEventType.COMPLETED, eventSessionId = eventSessionId, - joinSuccessId = session.joinSuccessIdSnapshot, - userAgent = userAgent.invoke(), - sdkVersion = sdkVersion, elapsedTime = elapsedTime, outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, retryCountAttempt = retryCount, @@ -149,17 +137,10 @@ internal class CallEventReporter( wasPreviouslyConnected = wasPreviouslyConnected, ) sendEvent( - buildEventMap( - userId = userId, - callType = callType, - callId = callId, - callCid = callCid, + buildRequest( stage = CallEventStage.WS_JOIN, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, - joinSuccessId = joinSuccessId, - userAgent = userAgent.invoke(), - sdkVersion = sdkVersion, sfuId = sfuId, wasPreviouslyConnected = wasPreviouslyConnected, ), @@ -178,17 +159,10 @@ internal class CallEventReporter( val session = inFlightSessions.remove(eventSessionId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( - buildEventMap( - userId = userId, - callType = callType, - callId = callId, - callCid = callCid, + buildRequest( stage = CallEventStage.WS_JOIN, eventType = CallEventType.COMPLETED, eventSessionId = eventSessionId, - joinSuccessId = session.joinSuccessIdSnapshot, - userAgent = userAgent.invoke(), - sdkVersion = sdkVersion, elapsedTime = elapsedTime, outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, retryCountAttempt = retryCount, @@ -215,8 +189,7 @@ internal class CallEventReporter( completePeerConnectionSession( eventSessionId = oldId, success = false, - iceStateName = iceState.name, - dtlsStateName = dtlsState?.name, + iceState = iceState, failureReason = "ICE restart superseded previous attempt", failureCode = "ICE_CONNECTIVITY_FAILED", ) @@ -233,17 +206,10 @@ internal class CallEventReporter( ) activePcSessionIds[role] = eventSessionId sendEvent( - buildEventMap( - userId = userId, - callType = callType, - callId = callId, - callCid = callCid, + buildRequest( stage = CallEventStage.PEER_CONNECTION_CONNECT, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, - joinSuccessId = joinSuccessId, - userAgent = userAgent.invoke(), - sdkVersion = sdkVersion, peerConnection = role, wasPreviouslyConnected = wasPrev, ), @@ -256,8 +222,7 @@ internal class CallEventReporter( completePeerConnectionSession( eventSessionId = eventSessionId, success = true, - iceStateName = iceState.name, - dtlsStateName = dtlsState?.name, + iceState = iceState, ) } @@ -266,8 +231,7 @@ internal class CallEventReporter( completePeerConnectionSession( eventSessionId = eventSessionId, success = false, - iceStateName = iceState.name, - dtlsStateName = dtlsState?.name, + iceState = iceState, failureReason = "ICE connectivity checks failed", failureCode = "ICE_CONNECTIVITY_FAILED", ) @@ -280,25 +244,17 @@ internal class CallEventReporter( private fun completePeerConnectionSession( eventSessionId: String, success: Boolean, - iceStateName: String?, - dtlsStateName: String?, + iceState: PeerConnection.IceConnectionState, failureReason: String? = null, failureCode: String? = null, ) { val session = inFlightSessions.remove(eventSessionId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( - buildEventMap( - userId = userId, - callType = callType, - callId = callId, - callCid = callCid, + buildRequest( stage = CallEventStage.PEER_CONNECTION_CONNECT, eventType = CallEventType.COMPLETED, eventSessionId = eventSessionId, - joinSuccessId = session.joinSuccessIdSnapshot, - userAgent = userAgent.invoke(), - sdkVersion = sdkVersion, elapsedTime = elapsedTime, outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, retryCountAttempt = 0, @@ -306,8 +262,7 @@ internal class CallEventReporter( retryFailureCode = if (!success) failureCode else null, peerConnection = session.peerConnectionRole, wasPreviouslyConnected = session.wasPreviouslyConnected, - iceState = iceStateName, - dtlsState = dtlsStateName, + iceState = iceState.toRequestIceState(), ), ) } @@ -321,17 +276,10 @@ internal class CallEventReporter( val now = System.currentTimeMillis() for (session in snapshot) { sendEvent( - buildEventMap( - userId = userId, - callType = callType, - callId = callId, - callCid = callCid, + buildRequest( stage = session.stage, eventType = CallEventType.COMPLETED, eventSessionId = session.eventSessionId, - joinSuccessId = session.joinSuccessIdSnapshot, - userAgent = userAgent.invoke(), - sdkVersion = sdkVersion, elapsedTime = now - session.startedAtMs, outcome = CallEventOutcome.FAILURE, retryCountAttempt = 0, @@ -341,21 +289,92 @@ internal class CallEventReporter( callSessionId = session.callSessionId, peerConnection = session.peerConnectionRole, wasPreviouslyConnected = session.wasPreviouslyConnected, + userSessionId = session.userSessionId, ), ) } } + // --- Request builder --- + + private fun buildRequest( + stage: CallEventStage, + eventType: CallEventType, + eventSessionId: String, + elapsedTime: Long? = null, + outcome: CallEventOutcome? = null, + retryCountAttempt: Int? = null, + retryFailureReason: String? = null, + retryFailureCode: String? = null, + callSessionId: String? = null, + sfuId: String? = null, + peerConnection: PeerConnectionRole? = null, + wasPreviouslyConnected: Boolean? = null, + iceState: ReportClientCallEventRequest.IceState? = null, + userSessionId: String? = null, + ): ReportClientCallEventRequest = ReportClientCallEventRequest( + eventSessionId = eventSessionId, + eventType = eventType.toRequestEventType(), + id = callId, + sdkVersion = sdkVersion, + stage = stage.toRequestStage(), + timestamp = OffsetDateTime.now(ZoneOffset.UTC), + type = callType, + userAgent = userAgent.invoke().take(512), + userId = userId, + callSessionId = callSessionId, + elapsedTime = elapsedTime?.toInt(), + iceState = iceState, + outcome = outcome?.toRequestOutcome(), + peerConnection = peerConnection?.toRequestPeerConnection(), + previouslyConnectedTimestamp = null, + retryCountAttempt = retryCountAttempt, + retryFailureCode = retryFailureCode, + retryFailureReason = retryFailureReason, + sfuId = sfuId, + userSessionId = userSessionId, + wasPreviouslyConnected = wasPreviouslyConnected, + ) + // --- Delivery --- - private fun sendEvent(customMap: Map) { + private fun sendEvent(request: ReportClientCallEventRequest) { scope.launch { // TODO: wrap with StreamRetryPolicy when retries are added runCatching { - api.sendCallEvent(callType, callId, SendCallEventRequest(custom = customMap)) + api.reportClientCallEvent(request) }.onFailure { e -> logger.w { "[sendEvent] Failed to send client event: ${e.message}" } } } } } + +// --- Enum mappers --- + +private fun CallEventStage.toRequestStage(): ReportClientCallEventRequest.Stage = when (this) { + CallEventStage.COORDINATOR_JOIN -> ReportClientCallEventRequest.Stage.CoordinatorJoin + CallEventStage.WS_JOIN -> ReportClientCallEventRequest.Stage.WSJoin + CallEventStage.PEER_CONNECTION_CONNECT -> ReportClientCallEventRequest.Stage.PeerConnectionConnect +} + +private fun CallEventType.toRequestEventType(): ReportClientCallEventRequest.EventType = when (this) { + CallEventType.INITIATED -> ReportClientCallEventRequest.EventType.Initiated + CallEventType.COMPLETED -> ReportClientCallEventRequest.EventType.Completed +} + +private fun CallEventOutcome.toRequestOutcome(): ReportClientCallEventRequest.Outcome = when (this) { + CallEventOutcome.SUCCESS -> ReportClientCallEventRequest.Outcome.Success + CallEventOutcome.FAILURE -> ReportClientCallEventRequest.Outcome.Failure +} + +private fun PeerConnectionRole.toRequestPeerConnection(): ReportClientCallEventRequest.PeerConnection = when (this) { + PeerConnectionRole.PUBLISH -> ReportClientCallEventRequest.PeerConnection.Publish + PeerConnectionRole.SUBSCRIBE -> ReportClientCallEventRequest.PeerConnection.Subscribe +} + +private fun PeerConnection.IceConnectionState.toRequestIceState(): ReportClientCallEventRequest.IceState? = when (this) { + PeerConnection.IceConnectionState.CONNECTED -> ReportClientCallEventRequest.IceState.CONNECTED + PeerConnection.IceConnectionState.FAILED -> ReportClientCallEventRequest.IceState.FAILED + else -> null +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt index 5f13fe2edc0..56d675e6da5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt @@ -16,10 +16,6 @@ package io.getstream.video.android.core.events.reporting -import org.threeten.bp.OffsetDateTime -import org.threeten.bp.ZoneOffset -import org.threeten.bp.format.DateTimeFormatter - internal enum class CallEventStage(val value: String) { COORDINATOR_JOIN("coordinator_join"), WS_JOIN("ws_join"), @@ -40,55 +36,3 @@ internal enum class PeerConnectionRole(val value: String) { PUBLISH("publish"), SUBSCRIBE("subscribe"), } - -internal fun buildEventMap( - userId: String, - callType: String, - callId: String, - callCid: String, - stage: CallEventStage, - eventType: CallEventType, - eventSessionId: String, - joinSuccessId: String, - userAgent: String, - sdkVersion: String, - elapsedTime: Long? = null, - outcome: CallEventOutcome? = null, - retryCountAttempt: Int? = null, - retryFailureReason: String? = null, - retryFailureCode: String? = null, - callSessionId: String? = null, - sfuId: String? = null, - peerConnection: PeerConnectionRole? = null, - wasPreviouslyConnected: Boolean? = null, - iceState: String? = null, - dtlsState: String? = null, - userSessionId: String? = null, -): Map = buildMap { - put("user_id", userId) - put("type", callType) - put("id", callId) // review with Gulzar: already in URL - put("call_cid", callCid) // review: already in URL - put("stage", stage.value) - put("event_type", eventType.value) - put("event_session_id", eventSessionId) - put("join_success_id", joinSuccessId) - put("timestamp", currentRfc3339Timestamp()) - put("user_agent", userAgent.take(512)) - put("sdk_version", sdkVersion) // review: already comes in header - elapsedTime?.let { put("elapsed_time", it) } - outcome?.let { put("outcome", it.value) } - retryCountAttempt?.let { put("retry_count_attempt", it) } - retryFailureReason?.let { put("retry_failure_reason", it) } - retryFailureCode?.let { put("retry_failure_code", it) } - callSessionId?.let { put("call_session_id", it) } - sfuId?.let { put("sfu_id", it) } - peerConnection?.let { put("peer_connection", it.value) } - wasPreviouslyConnected?.let { put("was_previously_connected", it) } - iceState?.let { put("ice_state", it) } - dtlsState?.let { put("dtls_state", it) } - userSessionId?.let { put("user_session_id", it) } -} - -internal fun currentRfc3339Timestamp(): String = - OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) From 086d285a793a6f860285616272c684961ffa0a2d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 20 May 2026 16:21:51 +0530 Subject: [PATCH 09/63] chore: Update ClientEventReporter --- .../video/generated/apis/ProductvideoApi.kt | 8 +- .../generated/infrastructure/Serializer.kt | 5 - .../video/generated/models/ClientEvent.kt | 104 +++++++ .../models/ReportClientCallEventRequest.kt | 266 ------------------ .../models/ReportClientEventRequest.kt | 44 +++ ...sponse.kt => ReportClientEventResponse.kt} | 4 +- .../io/getstream/video/android/core/Call.kt | 8 +- .../video/android/core/StreamVideoClient.kt | 5 +- ...ventReporter.kt => ClientEventReporter.kt} | 71 ++--- 9 files changed, 188 insertions(+), 327 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientEventRequest.kt rename stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/{ReportClientCallEventResponse.kt => ReportClientEventResponse.kt} (92%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/{CallEventReporter.kt => ClientEventReporter.kt} (85%) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt index 1f908ab7fa0..de3b2e7aabf 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/apis/ProductvideoApi.kt @@ -718,13 +718,13 @@ interface ProductvideoApi { ): io.getstream.android.video.generated.models.DeleteTranscriptionResponse /** - * Report client-side call event - * Reports a single client-side join-lifecycle event (initiated or completed) for one of CoordinatorJoin, WSJoin, or PeerConnectionConnect. Pairs are correlated by event_session_id. + * Report client-side events + * Reports a batch of client-side telemetry events. Events are processed independently; one invalid event does not block the rest of the batch. */ @POST("/video/call_client_event") suspend fun reportClientCallEvent( - @Body reportClientCallEventRequest: io.getstream.android.video.generated.models.ReportClientCallEventRequest - ): io.getstream.android.video.generated.models.ReportClientCallEventResponse + @Body reportClientEventRequest: io.getstream.android.video.generated.models.ReportClientEventRequest + ): io.getstream.android.video.generated.models.ReportClientEventResponse /** * Query call session stats diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/infrastructure/Serializer.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/infrastructure/Serializer.kt index 1e3c2c9a0cf..e38a6358875 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/infrastructure/Serializer.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/infrastructure/Serializer.kt @@ -52,11 +52,6 @@ object Serializer { .add(io.getstream.android.video.generated.models.RawRecordingSettingsResponse.Mode.ModeAdapter()) .add(io.getstream.android.video.generated.models.RecordSettingsRequest.Mode.ModeAdapter()) .add(io.getstream.android.video.generated.models.RecordSettingsRequest.Quality.QualityAdapter()) - .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.EventType.EventTypeAdapter()) - .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.Stage.StageAdapter()) - .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.IceState.IceStateAdapter()) - .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.Outcome.OutcomeAdapter()) - .add(io.getstream.android.video.generated.models.ReportClientCallEventRequest.PeerConnection.PeerConnectionAdapter()) .add(io.getstream.android.video.generated.models.StartClosedCaptionsRequest.Language.LanguageAdapter()) .add(io.getstream.android.video.generated.models.StartTranscriptionRequest.Language.LanguageAdapter()) .add(io.getstream.android.video.generated.models.TranscriptionSettingsRequest.ClosedCaptionMode.ClosedCaptionModeAdapter()) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt new file mode 100644 index 00000000000..5511001f8a1 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * A single client-side telemetry event. When stage is CoordinatorJoin, WSJoin, or PeerConnectionConnect the event reports a join-lifecycle attempt; initiation and completion of a stage attempt share the same event_session_id. Other stage values denote generic client events. + */ + +data class ClientEvent ( + @Json(name = "call_session_id") + val callSessionId: kotlin.String? = null, + + @Json(name = "elapsed_time") + val elapsedTime: kotlin.Int? = null, + + @Json(name = "event_session_id") + val eventSessionId: kotlin.String? = null, + + @Json(name = "event_type") + val eventType: kotlin.String? = null, + + @Json(name = "ice_state") + val iceState: kotlin.String? = null, + + @Json(name = "id") + val id: kotlin.String? = null, + + @Json(name = "outcome") + val outcome: kotlin.String? = null, + + @Json(name = "peer_connection") + val peerConnection: kotlin.String? = null, + + @Json(name = "previously_connected_timestamp") + val previouslyConnectedTimestamp: org.threeten.bp.OffsetDateTime? = null, + + @Json(name = "retry_count_attempt") + val retryCountAttempt: kotlin.Int? = null, + + @Json(name = "retry_failure_code") + val retryFailureCode: kotlin.String? = null, + + @Json(name = "retry_failure_reason") + val retryFailureReason: kotlin.String? = null, + + @Json(name = "sdk_version") + val sdkVersion: kotlin.String? = null, + + @Json(name = "sfu_id") + val sfuId: kotlin.String? = null, + + @Json(name = "stage") + val stage: kotlin.String? = null, + + @Json(name = "timestamp") + val timestamp: org.threeten.bp.OffsetDateTime? = null, + + @Json(name = "type") + val type: kotlin.String? = null, + + @Json(name = "user_agent") + val userAgent: kotlin.String? = null, + + @Json(name = "user_id") + val userId: kotlin.String? = null, + + @Json(name = "user_session_id") + val userSessionId: kotlin.String? = null, + + @Json(name = "was_previously_connected") + val wasPreviouslyConnected: kotlin.Boolean? = null +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt deleted file mode 100644 index 794438417c0..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventRequest.kt +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress( - "ArrayInDataClass", - "EnumEntryName", - "RemoveRedundantQualifierName", - "UnusedImport" -) - -package io.getstream.android.video.generated.models - -import kotlin.collections.List -import kotlin.collections.Map -import kotlin.collections.* -import kotlin.io.* -import com.squareup.moshi.FromJson -import com.squareup.moshi.Json -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson - -/** - * Reports a single client-side join-lifecycle event (initiated or completed) for one of CoordinatorJoin, WSJoin, or PeerConnectionConnect. Initiation and completion of a stage attempt share the same event_session_id. - */ - -data class ReportClientCallEventRequest ( - @Json(name = "event_session_id") - val eventSessionId: kotlin.String, - - @Json(name = "event_type") - val eventType: EventType, - - @Json(name = "id") - val id: kotlin.String, - - @Json(name = "sdk_version") - val sdkVersion: kotlin.String, - - @Json(name = "stage") - val stage: Stage, - - @Json(name = "timestamp") - val timestamp: org.threeten.bp.OffsetDateTime, - - @Json(name = "type") - val type: kotlin.String, - - @Json(name = "user_agent") - val userAgent: kotlin.String, - - @Json(name = "user_id") - val userId: kotlin.String, - - @Json(name = "call_session_id") - val callSessionId: kotlin.String? = null, - - @Json(name = "elapsed_time") - val elapsedTime: kotlin.Int? = null, - - @Json(name = "ice_state") - val iceState: IceState? = null, - - @Json(name = "outcome") - val outcome: Outcome? = null, - - @Json(name = "peer_connection") - val peerConnection: PeerConnection? = null, - - @Json(name = "previously_connected_timestamp") - val previouslyConnectedTimestamp: org.threeten.bp.OffsetDateTime? = null, - - @Json(name = "retry_count_attempt") - val retryCountAttempt: kotlin.Int? = null, - - @Json(name = "retry_failure_code") - val retryFailureCode: kotlin.String? = null, - - @Json(name = "retry_failure_reason") - val retryFailureReason: kotlin.String? = null, - - @Json(name = "sfu_id") - val sfuId: kotlin.String? = null, - - @Json(name = "user_session_id") - val userSessionId: kotlin.String? = null, - - @Json(name = "was_previously_connected") - val wasPreviouslyConnected: kotlin.Boolean? = null -) -{ - - /** - * EventType Enum - */ - sealed class EventType(val value: kotlin.String) { - override fun toString(): String = value - - companion object { - fun fromString(s: kotlin.String): EventType = when (s) { - "completed" -> Completed - "initiated" -> Initiated - else -> Unknown(s) - } - } - object Completed : EventType("completed") - object Initiated : EventType("initiated") - data class Unknown(val unknownValue: kotlin.String) : EventType(unknownValue) - - - class EventTypeAdapter : JsonAdapter() { - @FromJson - override fun fromJson(reader: JsonReader): EventType? { - val s = reader.nextString() ?: return null - return EventType.fromString(s) - } - - @ToJson - override fun toJson(writer: JsonWriter, value: EventType?) { - writer.value(value?.value) - } - } - } - /** - * Stage Enum - */ - sealed class Stage(val value: kotlin.String) { - override fun toString(): String = value - - companion object { - fun fromString(s: kotlin.String): Stage = when (s) { - "CoordinatorJoin" -> CoordinatorJoin - "PeerConnectionConnect" -> PeerConnectionConnect - "WSJoin" -> WSJoin - else -> Unknown(s) - } - } - object CoordinatorJoin : Stage("CoordinatorJoin") - object PeerConnectionConnect : Stage("PeerConnectionConnect") - object WSJoin : Stage("WSJoin") - data class Unknown(val unknownValue: kotlin.String) : Stage(unknownValue) - - - class StageAdapter : JsonAdapter() { - @FromJson - override fun fromJson(reader: JsonReader): Stage? { - val s = reader.nextString() ?: return null - return Stage.fromString(s) - } - - @ToJson - override fun toJson(writer: JsonWriter, value: Stage?) { - writer.value(value?.value) - } - } - } - /** - * IceState Enum - */ - sealed class IceState(val value: kotlin.String) { - override fun toString(): String = value - - companion object { - fun fromString(s: kotlin.String): IceState = when (s) { - "CONNECTED" -> CONNECTED - "FAILED" -> FAILED - "NOT_CONNECTED" -> NOTCONNECTED - else -> Unknown(s) - } - } - object CONNECTED : IceState("CONNECTED") - object FAILED : IceState("FAILED") - object NOTCONNECTED : IceState("NOT_CONNECTED") - data class Unknown(val unknownValue: kotlin.String) : IceState(unknownValue) - - - class IceStateAdapter : JsonAdapter() { - @FromJson - override fun fromJson(reader: JsonReader): IceState? { - val s = reader.nextString() ?: return null - return IceState.fromString(s) - } - - @ToJson - override fun toJson(writer: JsonWriter, value: IceState?) { - writer.value(value?.value) - } - } - } - /** - * Outcome Enum - */ - sealed class Outcome(val value: kotlin.String) { - override fun toString(): String = value - - companion object { - fun fromString(s: kotlin.String): Outcome = when (s) { - "failure" -> Failure - "success" -> Success - else -> Unknown(s) - } - } - object Failure : Outcome("failure") - object Success : Outcome("success") - data class Unknown(val unknownValue: kotlin.String) : Outcome(unknownValue) - - - class OutcomeAdapter : JsonAdapter() { - @FromJson - override fun fromJson(reader: JsonReader): Outcome? { - val s = reader.nextString() ?: return null - return Outcome.fromString(s) - } - - @ToJson - override fun toJson(writer: JsonWriter, value: Outcome?) { - writer.value(value?.value) - } - } - } - /** - * PeerConnection Enum - */ - sealed class PeerConnection(val value: kotlin.String) { - override fun toString(): String = value - - companion object { - fun fromString(s: kotlin.String): PeerConnection = when (s) { - "publish" -> Publish - "subscribe" -> Subscribe - else -> Unknown(s) - } - } - object Publish : PeerConnection("publish") - object Subscribe : PeerConnection("subscribe") - data class Unknown(val unknownValue: kotlin.String) : PeerConnection(unknownValue) - - - class PeerConnectionAdapter : JsonAdapter() { - @FromJson - override fun fromJson(reader: JsonReader): PeerConnection? { - val s = reader.nextString() ?: return null - return PeerConnection.fromString(s) - } - - @ToJson - override fun toJson(writer: JsonWriter, value: PeerConnection?) { - writer.value(value?.value) - } - } - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientEventRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientEventRequest.kt new file mode 100644 index 00000000000..7e3d7ba91c1 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientEventRequest.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.getstream.android.video.generated.models + +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +/** + * Reports a batch of client-side telemetry events. Each event is validated and processed independently; one invalid event does not block the rest of the batch. + */ + +data class ReportClientEventRequest ( + @Json(name = "events") + val events: kotlin.collections.List = emptyList() +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientEventResponse.kt similarity index 92% rename from stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventResponse.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientEventResponse.kt index d148e380e6e..3406dafe068 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientCallEventResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ReportClientEventResponse.kt @@ -35,10 +35,10 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * Response for reporting a client-side call event + * Response for reporting client-side telemetry events */ -data class ReportClientCallEventResponse ( +data class ReportClientEventResponse ( @Json(name = "duration") val duration: kotlin.String ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index a851abb3dfd..96d9f642be2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -76,7 +76,7 @@ import io.getstream.video.android.core.closedcaptions.ClosedCaptionsSettings import io.getstream.video.android.core.events.GoAwayEvent import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener -import io.getstream.video.android.core.events.reporting.CallEventReporter +import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.events.reporting.PeerConnectionRole import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.internal.network.NetworkStateProvider @@ -449,7 +449,7 @@ public class Call( private var sfuEvents: Job? = null /** Reports join lifecycle events to the backend for success-rate analytics. Set by StreamVideoClient. */ - internal var eventReporter: CallEventReporter? = null + internal var eventReporter: ClientEventReporter? = null init { scope.launch { @@ -1300,8 +1300,8 @@ public class Call( // val leaveReason = "[reason=$reason]" eventReporter?.let { reporter -> val abortReason = when (reason) { - is CallLeaveReason.Backend -> CallEventReporter.AbortReason.BACKEND_LEAVE - else -> CallEventReporter.AbortReason.CLIENT_ABORTED + is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE + else -> ClientEventReporter.AbortReason.CLIENT_ABORTED } reporter.abortAllInFlight(abortReason) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index 91708c5fead..3eb1292f407 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -87,7 +87,7 @@ import io.getstream.video.android.core.audio.AudioExecutionContext import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.errors.VideoErrorCode import io.getstream.video.android.core.events.VideoEventListener -import io.getstream.video.android.core.events.reporting.CallEventReporter +import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.filter.Filters import io.getstream.video.android.core.filter.toMap import io.getstream.video.android.core.header.HeadersUtil @@ -1183,7 +1183,7 @@ internal class StreamVideoClient internal constructor( calls[cid]!! } else { val call = Call(this, type, idOrRandom, user) - call.eventReporter = CallEventReporter( + call.eventReporter = ClientEventReporter( api = coordinatorConnectionModule.api, callType = type, callId = idOrRandom, @@ -1191,7 +1191,6 @@ internal class StreamVideoClient internal constructor( userId = user.id, userAgent = { HeadersUtil().buildSdkTrackingHeaders() }, sdkVersion = BuildConfig.STREAM_VIDEO_VERSION, - scope = call.scope, ) calls[cid] = call call diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt similarity index 85% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index e171141e748..cc8f3384141 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/CallEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -17,8 +17,11 @@ package io.getstream.video.android.core.events.reporting import io.getstream.android.video.generated.apis.ProductvideoApi -import io.getstream.android.video.generated.models.ReportClientCallEventRequest +import io.getstream.android.video.generated.models.ClientEvent +import io.getstream.android.video.generated.models.ReportClientEventRequest import io.getstream.log.taggedLogger +import io.getstream.video.android.core.socket.common.scope.ClientScope +import io.getstream.video.android.core.socket.common.scope.UserScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.threeten.bp.OffsetDateTime @@ -29,7 +32,7 @@ import java.util.concurrent.ConcurrentHashMap private typealias EventSessionId = String -internal class CallEventReporter( +internal class ClientEventReporter( private val api: ProductvideoApi, private val callType: String, private val callId: String, @@ -37,7 +40,7 @@ internal class CallEventReporter( private val userId: String, private val userAgent: () -> String, private val sdkVersion: String, - private val scope: CoroutineScope, + private val scope: CoroutineScope = UserScope(ClientScope()), ) { private val logger by taggedLogger("CallEventReporter:$callCid") @@ -262,7 +265,7 @@ internal class CallEventReporter( retryFailureCode = if (!success) failureCode else null, peerConnection = session.peerConnectionRole, wasPreviouslyConnected = session.wasPreviouslyConnected, - iceState = iceState.toRequestIceState(), + iceState = iceState, ), ) } @@ -310,23 +313,23 @@ internal class CallEventReporter( sfuId: String? = null, peerConnection: PeerConnectionRole? = null, wasPreviouslyConnected: Boolean? = null, - iceState: ReportClientCallEventRequest.IceState? = null, + iceState: PeerConnection.IceConnectionState? = null, userSessionId: String? = null, - ): ReportClientCallEventRequest = ReportClientCallEventRequest( + ): ClientEvent = ClientEvent( eventSessionId = eventSessionId, - eventType = eventType.toRequestEventType(), + eventType = eventType.value, id = callId, sdkVersion = sdkVersion, - stage = stage.toRequestStage(), + stage = stage.value, timestamp = OffsetDateTime.now(ZoneOffset.UTC), type = callType, userAgent = userAgent.invoke().take(512), userId = userId, callSessionId = callSessionId, elapsedTime = elapsedTime?.toInt(), - iceState = iceState, - outcome = outcome?.toRequestOutcome(), - peerConnection = peerConnection?.toRequestPeerConnection(), + iceState = iceState?.name, + outcome = outcome?.value, + peerConnection = peerConnection?.value, previouslyConnectedTimestamp = null, retryCountAttempt = retryCountAttempt, retryFailureCode = retryFailureCode, @@ -338,43 +341,25 @@ internal class CallEventReporter( // --- Delivery --- - private fun sendEvent(request: ReportClientCallEventRequest) { +// private fun sendEvent(request: ReportClientEventRequest) { +// scope.launch { +// // TODO: wrap with StreamRetryPolicy when retries are added +// runCatching { +// api.reportClientCallEvent(request) +// }.onFailure { e -> +// logger.w { "[sendEvent] Failed to send client event: ${e.message}" } +// } +// } +// } + + private fun sendEvent(request: ClientEvent) { scope.launch { // TODO: wrap with StreamRetryPolicy when retries are added runCatching { - api.reportClientCallEvent(request) + api.reportClientCallEvent(ReportClientEventRequest(arrayListOf(request))) }.onFailure { e -> logger.w { "[sendEvent] Failed to send client event: ${e.message}" } } } } -} - -// --- Enum mappers --- - -private fun CallEventStage.toRequestStage(): ReportClientCallEventRequest.Stage = when (this) { - CallEventStage.COORDINATOR_JOIN -> ReportClientCallEventRequest.Stage.CoordinatorJoin - CallEventStage.WS_JOIN -> ReportClientCallEventRequest.Stage.WSJoin - CallEventStage.PEER_CONNECTION_CONNECT -> ReportClientCallEventRequest.Stage.PeerConnectionConnect -} - -private fun CallEventType.toRequestEventType(): ReportClientCallEventRequest.EventType = when (this) { - CallEventType.INITIATED -> ReportClientCallEventRequest.EventType.Initiated - CallEventType.COMPLETED -> ReportClientCallEventRequest.EventType.Completed -} - -private fun CallEventOutcome.toRequestOutcome(): ReportClientCallEventRequest.Outcome = when (this) { - CallEventOutcome.SUCCESS -> ReportClientCallEventRequest.Outcome.Success - CallEventOutcome.FAILURE -> ReportClientCallEventRequest.Outcome.Failure -} - -private fun PeerConnectionRole.toRequestPeerConnection(): ReportClientCallEventRequest.PeerConnection = when (this) { - PeerConnectionRole.PUBLISH -> ReportClientCallEventRequest.PeerConnection.Publish - PeerConnectionRole.SUBSCRIBE -> ReportClientCallEventRequest.PeerConnection.Subscribe -} - -private fun PeerConnection.IceConnectionState.toRequestIceState(): ReportClientCallEventRequest.IceState? = when (this) { - PeerConnection.IceConnectionState.CONNECTED -> ReportClientCallEventRequest.IceState.CONNECTED - PeerConnection.IceConnectionState.FAILED -> ReportClientCallEventRequest.IceState.FAILED - else -> null -} +} \ No newline at end of file From 7f5fe0352abafbf18efea6a3f58ca19a90cc6c0d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 21 May 2026 00:51:05 +0530 Subject: [PATCH 10/63] chore: update with new client error reporting code --- .../api/stream-video-android-core.api | 312 ++++-------------- .../io/getstream/video/android/core/Call.kt | 50 +-- .../video/android/core/ClientState.kt | 8 + .../video/android/core/StreamVideoClient.kt | 11 - .../video/android/core/call/RtcSession.kt | 10 +- .../events/reporting/ClientEventReporter.kt | 48 ++- 6 files changed, 144 insertions(+), 295 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 59883670784..49c641a644b 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -58,7 +58,7 @@ public abstract interface class io/getstream/android/video/generated/apis/Produc public static synthetic fun queryCalls$default (Lio/getstream/android/video/generated/apis/ProductvideoApi;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public abstract fun rejectCall (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/RejectCallRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun rejectCall (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun reportClientCallEvent (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun reportClientCallEvent (Lio/getstream/android/video/generated/models/ReportClientEventRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun requestPermission (Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/RequestPermissionRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun resolveSipAuth (Lio/getstream/android/video/generated/models/ResolveSipAuthRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun resolveSipInbound (Lio/getstream/android/video/generated/models/ResolveSipInboundRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -2455,6 +2455,59 @@ public final class io/getstream/android/video/generated/models/ChatPreferencesRe public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/ClientEvent { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Ljava/lang/Integer; + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Ljava/lang/String; + public final fun component13 ()Ljava/lang/String; + public final fun component14 ()Ljava/lang/String; + public final fun component15 ()Ljava/lang/String; + public final fun component16 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component17 ()Ljava/lang/String; + public final fun component18 ()Ljava/lang/String; + public final fun component19 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun component20 ()Ljava/lang/String; + public final fun component21 ()Ljava/lang/Boolean; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Lorg/threeten/bp/OffsetDateTime; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getCallSessionId ()Ljava/lang/String; + public final fun getElapsedTime ()Ljava/lang/Integer; + public final fun getEventSessionId ()Ljava/lang/String; + public final fun getEventType ()Ljava/lang/String; + public final fun getIceState ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public final fun getOutcome ()Ljava/lang/String; + public final fun getPeerConnection ()Ljava/lang/String; + public final fun getPreviouslyConnectedTimestamp ()Lorg/threeten/bp/OffsetDateTime; + public final fun getRetryCountAttempt ()Ljava/lang/Integer; + public final fun getRetryFailureCode ()Ljava/lang/String; + public final fun getRetryFailureReason ()Ljava/lang/String; + public final fun getSdkVersion ()Ljava/lang/String; + public final fun getSfuId ()Ljava/lang/String; + public final fun getStage ()Ljava/lang/String; + public final fun getTimestamp ()Lorg/threeten/bp/OffsetDateTime; + public final fun getType ()Ljava/lang/String; + public final fun getUserAgent ()Ljava/lang/String; + public final fun getUserId ()Ljava/lang/String; + public final fun getUserSessionId ()Ljava/lang/String; + public final fun getWasPreviouslyConnected ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/ClosedCaptionEvent : io/getstream/android/video/generated/models/VideoEvent, io/getstream/android/video/generated/models/WSCallEvent { public fun (Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Lio/getstream/android/video/generated/models/CallClosedCaption;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; @@ -5975,261 +6028,24 @@ public final class io/getstream/android/video/generated/models/ReportByHistogram public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest { - public fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Ljava/lang/String; - public final fun component11 ()Ljava/lang/Integer; - public final fun component12 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; - public final fun component13 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; - public final fun component14 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; - public final fun component15 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component16 ()Ljava/lang/Integer; - public final fun component17 ()Ljava/lang/String; - public final fun component18 ()Ljava/lang/String; - public final fun component19 ()Ljava/lang/String; - public final fun component2 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; - public final fun component20 ()Ljava/lang/String; - public final fun component21 ()Ljava/lang/Boolean; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; - public final fun component6 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Ljava/lang/String; - public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;Ljava/lang/String;Ljava/lang/String;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getCallSessionId ()Ljava/lang/String; - public final fun getElapsedTime ()Ljava/lang/Integer; - public final fun getEventSessionId ()Ljava/lang/String; - public final fun getEventType ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; - public final fun getIceState ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; - public final fun getId ()Ljava/lang/String; - public final fun getOutcome ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; - public final fun getPeerConnection ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; - public final fun getPreviouslyConnectedTimestamp ()Lorg/threeten/bp/OffsetDateTime; - public final fun getRetryCountAttempt ()Ljava/lang/Integer; - public final fun getRetryFailureCode ()Ljava/lang/String; - public final fun getRetryFailureReason ()Ljava/lang/String; - public final fun getSdkVersion ()Ljava/lang/String; - public final fun getSfuId ()Ljava/lang/String; - public final fun getStage ()Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; - public final fun getTimestamp ()Lorg/threeten/bp/OffsetDateTime; - public final fun getType ()Ljava/lang/String; - public final fun getUserAgent ()Ljava/lang/String; - public final fun getUserId ()Ljava/lang/String; - public final fun getUserSessionId ()Ljava/lang/String; - public final fun getWasPreviouslyConnected ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType { - public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Companion; - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getValue ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Companion { - public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Completed : io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Completed; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$EventTypeAdapter : com/squareup/moshi/JsonAdapter { - public fun ()V - public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType; - public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; - public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType;)V - public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Initiated : io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Initiated; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Unknown; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$EventType$Unknown; - public fun equals (Ljava/lang/Object;)Z - public final fun getUnknownValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { - public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Companion; - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getValue ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$CONNECTED : io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$CONNECTED; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Companion { - public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$FAILED : io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$FAILED; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$IceStateAdapter : com/squareup/moshi/JsonAdapter { +public final class io/getstream/android/video/generated/models/ReportClientEventRequest { public fun ()V - public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState; - public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; - public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState;)V - public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$NOTCONNECTED : io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$NOTCONNECTED; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Unknown; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$IceState$Unknown; - public fun equals (Ljava/lang/Object;)Z - public final fun getUnknownValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome { - public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Companion; - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getValue ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Companion { - public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Failure : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Failure; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$OutcomeAdapter : com/squareup/moshi/JsonAdapter { - public fun ()V - public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome; - public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; - public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome;)V - public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Success : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Success; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Unknown; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Outcome$Unknown; - public fun equals (Ljava/lang/Object;)Z - public final fun getUnknownValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection { - public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Companion; - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getValue ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Companion { - public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$PeerConnectionAdapter : com/squareup/moshi/JsonAdapter { - public fun ()V - public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection; - public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; - public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection;)V - public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Publish : io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Publish; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Subscribe : io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Subscribe; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Unknown; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$PeerConnection$Unknown; - public fun equals (Ljava/lang/Object;)Z - public final fun getUnknownValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { - public static final field Companion Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Companion; - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getValue ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Companion { - public final fun fromString (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$CoordinatorJoin : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$CoordinatorJoin; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$PeerConnectionConnect : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$PeerConnectionConnect; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$StageAdapter : com/squareup/moshi/JsonAdapter { - public fun ()V - public fun fromJson (Lcom/squareup/moshi/JsonReader;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage; - public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; - public fun toJson (Lcom/squareup/moshi/JsonWriter;Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage;)V - public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Unknown : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Unknown; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$Unknown; + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lio/getstream/android/video/generated/models/ReportClientEventRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientEventRequest;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientEventRequest; public fun equals (Ljava/lang/Object;)Z - public final fun getUnknownValue ()Ljava/lang/String; + public final fun getEvents ()Ljava/util/List; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$WSJoin : io/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage { - public static final field INSTANCE Lio/getstream/android/video/generated/models/ReportClientCallEventRequest$Stage$WSJoin; -} - -public final class io/getstream/android/video/generated/models/ReportClientCallEventResponse { +public final class io/getstream/android/video/generated/models/ReportClientEventResponse { public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientCallEventResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientCallEventResponse;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientCallEventResponse; + public final fun copy (Ljava/lang/String;)Lio/getstream/android/video/generated/models/ReportClientEventResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ReportClientEventResponse;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ReportClientEventResponse; public fun equals (Ljava/lang/Object;)Z public final fun getDuration ()Ljava/lang/String; public fun hashCode ()I diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 96d9f642be2..17e0e4e4375 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -449,7 +449,6 @@ public class Call( private var sfuEvents: Job? = null /** Reports join lifecycle events to the backend for success-rate analytics. Set by StreamVideoClient. */ - internal var eventReporter: ClientEventReporter? = null init { scope.launch { @@ -768,7 +767,9 @@ public class Call( } .collect { (publisher, iceState) -> if (iceState != null) { - eventReporter?.onPeerConnectionIceStateChanged( + client.state.clientEventReporter.onPeerConnectionIceStateChanged( + callId = this@Call.id, + callType = this@Call.type, role = PeerConnectionRole.PUBLISH, iceState = iceState, dtlsState = publisher.state.value, @@ -797,7 +798,9 @@ public class Call( } .collect { (subscriber, iceState) -> if (iceState != null) { - eventReporter?.onPeerConnectionIceStateChanged( + client.state.clientEventReporter.onPeerConnectionIceStateChanged( + callId = this@Call.id, + callType = this@Call.type, role = PeerConnectionRole.SUBSCRIBE, iceState = iceState, dtlsState = subscriber.state.value, @@ -1298,7 +1301,7 @@ public class Call( clientImpl.scope.launch { val leaveReason = "[reason=${reason::class.simpleName}, message=${reason.message}]" // val leaveReason = "[reason=$reason]" - eventReporter?.let { reporter -> + client.state.clientEventReporter.let { reporter -> val abortReason = when (reason) { is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE else -> ClientEventReporter.AbortReason.CLIENT_ABORTED @@ -1847,9 +1850,12 @@ public class Call( notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, ): Result { - val reporter = eventReporter - reporter?.resetJoinSuccessId() - val coordEventId = reporter?.reportCoordinatorJoinInitiated() + val reporter = client.state.clientEventReporter + reporter.resetJoinSuccessId() + val coordEventId = reporter.reportCoordinatorJoinInitiated( + callType = this.type, + callId = this.id, + ) val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( @@ -1869,25 +1875,21 @@ public class Call( ) result.onSuccess { state.updateFromResponse(it) - coordEventId?.let { id -> - reporter?.reportCoordinatorJoinCompleted( - eventSessionId = id, - success = true, - retryCount = 0, - callSessionId = it.call.currentSessionId, - ) - } + reporter.reportCoordinatorJoinCompleted( + eventSessionId = id, + success = true, + retryCount = 0, + callSessionId = it.call.currentSessionId, + ) } if (result is Failure) { - coordEventId?.let { id -> - reporter?.reportCoordinatorJoinCompleted( - eventSessionId = id, - success = false, - retryCount = 0, - failureReason = (result.value as? Error)?.message, - failureCode = null, - ) - } + reporter.reportCoordinatorJoinCompleted( + eventSessionId = id, + success = false, + retryCount = 0, + failureReason = result.value.message, + failureCode = null, + ) } return result } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index 3f56f463da7..c340e351de4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -23,6 +23,8 @@ import io.getstream.android.video.generated.models.ConnectedEvent import io.getstream.android.video.generated.models.VideoEvent import io.getstream.log.taggedLogger import io.getstream.result.Error +import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.header.HeadersUtil import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.notifications.internal.service.CallService import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder @@ -87,6 +89,12 @@ class ClientState(private val client: StreamVideo) { public val callConfigRegistry = (client as StreamVideoClient).callServiceConfigRegistry private val serviceLauncher = ServiceLauncher(client.context) + internal val clientEventReporter = + ClientEventReporter( + streamVideoClient.coordinatorConnectionModule.api, + userAgent = { HeadersUtil().buildSdkTrackingHeaders() }, + sdkVersion = BuildConfig.STREAM_VIDEO_VERSION, + ) @InternalStreamVideoApi public val rejectCallWhenBusy: Boolean = (client as StreamVideoClient).rejectCallWhenBusy diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index 3eb1292f407..7fa1692cd54 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -87,10 +87,8 @@ import io.getstream.video.android.core.audio.AudioExecutionContext import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.errors.VideoErrorCode import io.getstream.video.android.core.events.VideoEventListener -import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.filter.Filters import io.getstream.video.android.core.filter.toMap -import io.getstream.video.android.core.header.HeadersUtil import io.getstream.video.android.core.internal.module.CoordinatorConnectionModule import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.model.EdgeData @@ -1183,15 +1181,6 @@ internal class StreamVideoClient internal constructor( calls[cid]!! } else { val call = Call(this, type, idOrRandom, user) - call.eventReporter = ClientEventReporter( - api = coordinatorConnectionModule.api, - callType = type, - callId = idOrRandom, - callCid = cid, - userId = user.id, - userAgent = { HeadersUtil().buildSdkTrackingHeaders() }, - sdkVersion = BuildConfig.STREAM_VIDEO_VERSION, - ) calls[cid] = call call } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index 4d19cb2b167..d5fb1138606 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -880,8 +880,10 @@ public class RtcSession internal constructor( options: List? = null, ): SfuConnectionResult { logger.i { "[connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } - val wsReporter = call.eventReporter - val wsEventId = wsReporter?.reportWsJoinInitiated( + val wsReporter = call.client.state.clientEventReporter + val wsEventId = wsReporter.reportWsJoinInitiated( + callId = call.id, + callType = call.type, sfuId = sfuName, wasPreviouslyConnected = reconnectDetails != null, ) @@ -902,7 +904,7 @@ public class RtcSession internal constructor( return when (terminalState) { is SfuSocketState.Connected -> { wsEventId?.let { - wsReporter?.reportWsJoinCompleted( + wsReporter.reportWsJoinCompleted( it, success = true, retryCount = 0, @@ -922,7 +924,7 @@ public class RtcSession internal constructor( logger.w { "[connectInternal] $msg" } sfuTracer.trace("connect-failed", msg) wsEventId?.let { - wsReporter?.reportWsJoinCompleted( + wsReporter.reportWsJoinCompleted( it, success = false, retryCount = 0, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index cc8f3384141..9d1061f43c7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -20,6 +20,7 @@ import io.getstream.android.video.generated.apis.ProductvideoApi import io.getstream.android.video.generated.models.ClientEvent import io.getstream.android.video.generated.models.ReportClientEventRequest import io.getstream.log.taggedLogger +import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope import kotlinx.coroutines.CoroutineScope @@ -34,15 +35,11 @@ private typealias EventSessionId = String internal class ClientEventReporter( private val api: ProductvideoApi, - private val callType: String, - private val callId: String, - private val callCid: String, - private val userId: String, private val userAgent: () -> String, private val sdkVersion: String, private val scope: CoroutineScope = UserScope(ClientScope()), ) { - private val logger by taggedLogger("CallEventReporter:$callCid") + private val logger by taggedLogger("CallEventReporter") @Volatile private var joinSuccessId: String = UUID.randomUUID().toString() @@ -56,6 +53,8 @@ internal class ClientEventReporter( private data class InFlightSession( val eventSessionId: EventSessionId, + val callId: String, + val callType: String, val stage: CallEventStage, val startedAtMs: Long, val joinSuccessIdSnapshot: String, @@ -79,17 +78,20 @@ internal class ClientEventReporter( // --- CoordinatorJoin --- - internal fun reportCoordinatorJoinInitiated(): String { + internal fun reportCoordinatorJoinInitiated(callId: String, callType: String): String { val eventSessionId = UUID.randomUUID().toString() val now = System.currentTimeMillis() inFlightSessions[eventSessionId] = InFlightSession( eventSessionId = eventSessionId, + callId = callId, callType = callType, stage = CallEventStage.COORDINATOR_JOIN, startedAtMs = now, joinSuccessIdSnapshot = joinSuccessId, ) sendEvent( buildRequest( + callId, + callType, stage = CallEventStage.COORDINATOR_JOIN, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, @@ -110,6 +112,8 @@ internal class ClientEventReporter( val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( buildRequest( + callId = session.callId, + callType = session.callType, stage = CallEventStage.COORDINATOR_JOIN, eventType = CallEventType.COMPLETED, eventSessionId = eventSessionId, @@ -127,11 +131,15 @@ internal class ClientEventReporter( internal fun reportWsJoinInitiated( sfuId: String, + callId: String, + callType: String, wasPreviouslyConnected: Boolean, ): String { val eventSessionId = UUID.randomUUID().toString() val now = System.currentTimeMillis() inFlightSessions[eventSessionId] = InFlightSession( + callId = callId, + callType = callType, eventSessionId = eventSessionId, stage = CallEventStage.WS_JOIN, startedAtMs = now, @@ -141,6 +149,8 @@ internal class ClientEventReporter( ) sendEvent( buildRequest( + callId = callId, + callType = callType, stage = CallEventStage.WS_JOIN, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, @@ -163,6 +173,8 @@ internal class ClientEventReporter( val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( buildRequest( + callId = session.callId, + callType = session.callType, stage = CallEventStage.WS_JOIN, eventType = CallEventType.COMPLETED, eventSessionId = eventSessionId, @@ -180,6 +192,8 @@ internal class ClientEventReporter( // --- PeerConnectionConnect (ICE state machine) --- internal fun onPeerConnectionIceStateChanged( + callId: String, + callType: String, role: PeerConnectionRole, iceState: PeerConnection.IceConnectionState, dtlsState: PeerConnection.PeerConnectionState?, @@ -190,6 +204,8 @@ internal class ClientEventReporter( // If an existing session is still in-flight, close it as failed first activePcSessionIds.remove(role)?.let { oldId -> completePeerConnectionSession( + callId = callId, + callType = callType, eventSessionId = oldId, success = false, iceState = iceState, @@ -200,6 +216,8 @@ internal class ClientEventReporter( val eventSessionId = UUID.randomUUID().toString() val now = System.currentTimeMillis() inFlightSessions[eventSessionId] = InFlightSession( + callId = callId, + callType = callType, eventSessionId = eventSessionId, stage = CallEventStage.PEER_CONNECTION_CONNECT, startedAtMs = now, @@ -210,6 +228,8 @@ internal class ClientEventReporter( activePcSessionIds[role] = eventSessionId sendEvent( buildRequest( + callId = callId, + callType = callType, stage = CallEventStage.PEER_CONNECTION_CONNECT, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, @@ -223,6 +243,8 @@ internal class ClientEventReporter( val eventSessionId = activePcSessionIds.remove(role) ?: return pcEverConnected[role] = true completePeerConnectionSession( + callId = callId, + callType = callType, eventSessionId = eventSessionId, success = true, iceState = iceState, @@ -232,6 +254,8 @@ internal class ClientEventReporter( PeerConnection.IceConnectionState.FAILED -> { val eventSessionId = activePcSessionIds.remove(role) ?: return completePeerConnectionSession( + callId = callId, + callType = callType, eventSessionId = eventSessionId, success = false, iceState = iceState, @@ -245,6 +269,8 @@ internal class ClientEventReporter( } private fun completePeerConnectionSession( + callId: String, + callType: String, eventSessionId: String, success: Boolean, iceState: PeerConnection.IceConnectionState, @@ -255,6 +281,8 @@ internal class ClientEventReporter( val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( buildRequest( + callId = callId, + callType = callType, stage = CallEventStage.PEER_CONNECTION_CONNECT, eventType = CallEventType.COMPLETED, eventSessionId = eventSessionId, @@ -280,6 +308,8 @@ internal class ClientEventReporter( for (session in snapshot) { sendEvent( buildRequest( + callId = session.callId, + callType = session.callType, stage = session.stage, eventType = CallEventType.COMPLETED, eventSessionId = session.eventSessionId, @@ -301,6 +331,8 @@ internal class ClientEventReporter( // --- Request builder --- private fun buildRequest( + callId: String, + callType: String, stage: CallEventStage, eventType: CallEventType, eventSessionId: String, @@ -324,7 +356,7 @@ internal class ClientEventReporter( timestamp = OffsetDateTime.now(ZoneOffset.UTC), type = callType, userAgent = userAgent.invoke().take(512), - userId = userId, + userId = StreamVideo.instanceOrNull()?.userId, callSessionId = callSessionId, elapsedTime = elapsedTime?.toInt(), iceState = iceState?.name, @@ -362,4 +394,4 @@ internal class ClientEventReporter( } } } -} \ No newline at end of file +} From c90823b94f8e24b6679ed745e18c16cae7dfb8e7 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 21 May 2026 12:28:52 +0530 Subject: [PATCH 11/63] chore: add missing values in client event reporting --- .../io/getstream/video/android/core/Call.kt | 64 ++++---- .../video/android/core/call/RtcSession.kt | 51 +++--- .../events/reporting/ClientCallEventData.kt | 6 +- .../events/reporting/ClientEventReporter.kt | 149 ++++++++++++------ 4 files changed, 161 insertions(+), 109 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 17e0e4e4375..976875732d9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -140,6 +140,7 @@ import kotlin.coroutines.resume level = DeprecationLevel.WARNING, ) const val sfuReconnectTimeoutMillis = 30_000 +internal typealias CallSessionId = String /** * Outcome of a single reconnect attempt. Each reconnect method returns one of @@ -554,6 +555,12 @@ public class Call( logger.d { "[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions" } + val eventSessionId = UUID.randomUUID().toString() + client.state.clientEventReporter.reportCoordinatorJoinInitiated( + eventSessionId, + callType = this.type, + callId = this.id, + ) val permissionPass = clientImpl.permissionCheck.checkAndroidPermissionsGroup(clientImpl.context, this) // Check android permissions and log a warning to make sure developers requested adequate permissions prior to using the call. @@ -585,8 +592,9 @@ public class Call( atomicLeave = AtomicUnitCall() while (retryCount < 3) { - result = _join(create, createOptions, ring, notify, hintHighScaleLivestreamPublisher) - if (result is Success) { + val tempResult = + _join(create, createOptions, ring, notify, hintHighScaleLivestreamPublisher) + if (tempResult is Success) { // we initialise the camera, mic and other according to local + backend settings // only when the call is joined to make sure we don't switch and override // the settings during a call. @@ -599,13 +607,27 @@ public class Call( "is joined. MediaManager will not be initialised with server settings." } } + client.state.clientEventReporter.reportCoordinatorJoinCompleted( + eventSessionId = eventSessionId, + success = true, + retryCount = retryCount, + callSessionId = tempResult.value.second, + ) + result = Success(tempResult.value.first) return result } - if (result is Failure) { + if (tempResult is Failure) { + result = tempResult session.value = null logger.e { "Join failed with error $result" } if (isPermanentError(result.value)) { state._connection.value = RealtimeConnection.Failed(result.value) + client.state.clientEventReporter.reportCoordinatorJoinCompleted( + eventSessionId = eventSessionId, + success = false, + retryCount = retryCount, + failureReason = result.value.message, + ) return result } else { retryCount += 1 @@ -616,6 +638,12 @@ public class Call( session.value = null val errorMessage = "Join failed after 3 retries" state._connection.value = RealtimeConnection.Failed(errorMessage) + client.state.clientEventReporter.reportCoordinatorJoinCompleted( + eventSessionId = id, + success = false, + retryCount = retryCount, + failureReason = errorMessage, + ) return Failure(value = Error.GenericError(errorMessage)) } @@ -660,7 +688,7 @@ public class Call( ring: Boolean = false, notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, - ): Result { + ): Result> { nonFastReconnectAttempts = 0 sfuEvents?.cancel() sfuListener?.cancel() @@ -738,7 +766,7 @@ public class Call( } client.state.setActiveCall(this) monitorSession(result.value) - return Success(value = session.value!!) + return Success(value = Pair(session.value!!, result.value.call.currentSessionId)) } private fun Call.monitorSession(result: JoinCallResponse) { @@ -772,7 +800,7 @@ public class Call( callType = this@Call.type, role = PeerConnectionRole.PUBLISH, iceState = iceState, - dtlsState = publisher.state.value, + peerConnectionState = publisher.state.value, ) } when (iceState) { @@ -803,7 +831,7 @@ public class Call( callType = this@Call.type, role = PeerConnectionRole.SUBSCRIBE, iceState = iceState, - dtlsState = subscriber.state.value, + peerConnectionState = subscriber.state.value, ) } when (iceState) { @@ -1258,7 +1286,6 @@ public class Call( internalLeave(CallLeaveReason.Custom(reason)) } -// private fun internalLeave(reason: CallLeaveReason) = atomicLeave { private fun internalLeave(reason: CallLeaveReason) = atomicLeave { monitorSubscriberPCStateJob?.cancel() monitorPublisherPCStateJob?.cancel() @@ -1306,6 +1333,7 @@ public class Call( is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE else -> ClientEventReporter.AbortReason.CLIENT_ABORTED } + reporter.sendAllPendingEvents() reporter.abortAllInFlight(abortReason) } safeCall { @@ -1851,11 +1879,6 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, ): Result { val reporter = client.state.clientEventReporter - reporter.resetJoinSuccessId() - val coordEventId = reporter.reportCoordinatorJoinInitiated( - callType = this.type, - callId = this.id, - ) val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( @@ -1875,21 +1898,6 @@ public class Call( ) result.onSuccess { state.updateFromResponse(it) - reporter.reportCoordinatorJoinCompleted( - eventSessionId = id, - success = true, - retryCount = 0, - callSessionId = it.call.currentSessionId, - ) - } - if (result is Failure) { - reporter.reportCoordinatorJoinCompleted( - eventSessionId = id, - success = false, - retryCount = 0, - failureReason = result.value.message, - failureCode = null, - ) } return result } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index d5fb1138606..2cd942091e5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -162,6 +162,7 @@ import stream.video.sfu.signal.UpdateMuteStatesResponse import stream.video.sfu.signal.UpdateSubscriptionsRequest import stream.video.sfu.signal.UpdateSubscriptionsResponse import java.util.Collections + /** * Keeps track of which track is being rendered at what resolution. * Also stores if the track is visible or not @@ -880,8 +881,8 @@ public class RtcSession internal constructor( options: List? = null, ): SfuConnectionResult { logger.i { "[connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } - val wsReporter = call.client.state.clientEventReporter - val wsEventId = wsReporter.reportWsJoinInitiated( + val reporter = call.client.state.clientEventReporter + val telemetryWsEventSessionId = reporter.reportWsJoinInitiated( callId = call.id, callType = call.type, sfuId = sfuName, @@ -903,13 +904,12 @@ public class RtcSession internal constructor( } return when (terminalState) { is SfuSocketState.Connected -> { - wsEventId?.let { - wsReporter.reportWsJoinCompleted( - it, - success = true, - retryCount = 0, - ) - } + reporter.reportWsJoinCompleted( + telemetryWsEventSessionId, + success = true, + retryCount = 0, + ) + sendConnectionTimeStats(reconnectDetails?.strategy) SfuConnectionResult.Connected } @@ -923,29 +923,26 @@ public class RtcSession internal constructor( } logger.w { "[connectInternal] $msg" } sfuTracer.trace("connect-failed", msg) - wsEventId?.let { - wsReporter.reportWsJoinCompleted( - it, - success = false, - retryCount = 0, - failureReason = msg, - failureCode = "WS_DISCONNECTED", - ) - } + reporter.reportWsJoinCompleted( + telemetryWsEventSessionId, + success = false, + retryCount = 0, + failureReason = msg, + failureCode = "WS_DISCONNECTED", + ) sendCallStats() SfuConnectionResult.Failed(Exception(msg)) } else -> { sfuTracer.trace("connect-failed", "Connection timed out") - wsEventId?.let { - wsReporter?.reportWsJoinCompleted( - it, - success = false, - retryCount = 0, - failureReason = "SFU connection timed out", - failureCode = "REQUEST_TIMEOUT", - ) - } + reporter.reportWsJoinCompleted( + telemetryWsEventSessionId, + success = false, + retryCount = 0, + failureReason = "SFU connection timed out", + failureCode = "REQUEST_TIMEOUT", + ) + sendCallStats() SfuConnectionResult.Failed(Exception("SFU connection timed out")) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt index 56d675e6da5..1453ef9509a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt @@ -17,9 +17,9 @@ package io.getstream.video.android.core.events.reporting internal enum class CallEventStage(val value: String) { - COORDINATOR_JOIN("coordinator_join"), - WS_JOIN("ws_join"), - PEER_CONNECTION_CONNECT("peer_connection_connect"), + COORDINATOR_JOIN("CoordinatorJoin"), + WS_JOIN("WSJoin"), + PEER_CONNECTION_CONNECT("PeerConnectionConnect"), } internal enum class CallEventType(val value: String) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index 9d1061f43c7..85a02ae7336 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -28,22 +28,37 @@ import kotlinx.coroutines.launch import org.threeten.bp.OffsetDateTime import org.threeten.bp.ZoneOffset import org.webrtc.PeerConnection +import java.util.Collections import java.util.UUID import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.set -private typealias EventSessionId = String +internal typealias EventSessionId = String +internal typealias CallId = String + +/** + * TODO + * [ClientEvent.previouslyConnectedTimestamp] : Ask clarification + * [ClientEvent.retryFailureCode] : Ask clarification + */ + +internal enum class TelemetrySendingStrategy { IN_PLACE, BATCH } internal class ClientEventReporter( private val api: ProductvideoApi, private val userAgent: () -> String, private val sdkVersion: String, + private val sendingStrategy: TelemetrySendingStrategy = TelemetrySendingStrategy.IN_PLACE, private val scope: CoroutineScope = UserScope(ClientScope()), ) { private val logger by taggedLogger("CallEventReporter") - @Volatile private var joinSuccessId: String = UUID.randomUUID().toString() - private val inFlightSessions = ConcurrentHashMap() + private val joinStageAttemptIdMap = ConcurrentHashMap() + private val callSessionIdMap = ConcurrentHashMap() + private val batchEvents: MutableList = Collections.synchronizedList( + mutableListOf(), + ) // TODO Rahul, should be thread safe // Active event_session_id per PC role — drives the ICE state machine private val activePcSessionIds = ConcurrentHashMap() @@ -51,13 +66,13 @@ internal class ClientEventReporter( // Whether each PC role has ever reached CONNECTED (for was_previously_connected) private val pcEverConnected = ConcurrentHashMap() - private data class InFlightSession( + internal data class InFlightSession( val eventSessionId: EventSessionId, val callId: String, val callType: String, val stage: CallEventStage, val startedAtMs: Long, - val joinSuccessIdSnapshot: String, + val joinStageAttemptIdSnapshot: String, val sfuId: String? = null, val callSessionId: String? = null, val userSessionId: String? = null, @@ -70,23 +85,32 @@ internal class ClientEventReporter( BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), } - // --- join_success_id --- - - internal fun resetJoinSuccessId() { - joinSuccessId = UUID.randomUUID().toString() + enum class FailureCodes(val code: String, val message: String) { + CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), + BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), + NETWORK_OFFLINE("NETWORK_OFFLINE", "Device offline"), + ICE_GATHERING_FAILED("ICE_GATHERING_FAILED", "ICE gathering failed"), + ICE_CONNECTIVITY_FAILED("ICE_CONNECTIVITY_FAILED", "ICE connectivity failed"), +// REQUEST_TIMEOUT("REQUEST_TIMEOUT", "Device offline"), } // --- CoordinatorJoin --- - internal fun reportCoordinatorJoinInitiated(callId: String, callType: String): String { - val eventSessionId = UUID.randomUUID().toString() + internal fun reportCoordinatorJoinInitiated( + eventSessionId: String, + callId: String, + callType: String, + ): String { + val joinStageAttemptId = UUID.randomUUID().toString() + joinStageAttemptIdMap[callId] = joinStageAttemptId val now = System.currentTimeMillis() inFlightSessions[eventSessionId] = InFlightSession( eventSessionId = eventSessionId, - callId = callId, callType = callType, + callId = callId, + callType = callType, stage = CallEventStage.COORDINATOR_JOIN, startedAtMs = now, - joinSuccessIdSnapshot = joinSuccessId, + joinStageAttemptIdSnapshot = joinStageAttemptId, ) sendEvent( buildRequest( @@ -105,11 +129,12 @@ internal class ClientEventReporter( success: Boolean, retryCount: Int, failureReason: String? = null, - failureCode: String? = null, + failureCode: String? = null, // TODO Rahul, ask tomorrow callSessionId: String? = null, ) { val session = inFlightSessions.remove(eventSessionId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs + callSessionIdMap[session.callId] = callSessionId ?: "" sendEvent( buildRequest( callId = session.callId, @@ -137,15 +162,17 @@ internal class ClientEventReporter( ): String { val eventSessionId = UUID.randomUUID().toString() val now = System.currentTimeMillis() + val callSessionId = callSessionIdMap[callId] inFlightSessions[eventSessionId] = InFlightSession( callId = callId, callType = callType, eventSessionId = eventSessionId, stage = CallEventStage.WS_JOIN, startedAtMs = now, - joinSuccessIdSnapshot = joinSuccessId, + joinStageAttemptIdSnapshot = joinStageAttemptIdMap[callId] ?: "", sfuId = sfuId, wasPreviouslyConnected = wasPreviouslyConnected, + callSessionId = callSessionId, ) sendEvent( buildRequest( @@ -167,7 +194,6 @@ internal class ClientEventReporter( retryCount: Int, failureReason: String? = null, failureCode: String? = null, - callSessionId: String? = null, ) { val session = inFlightSessions.remove(eventSessionId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs @@ -184,7 +210,7 @@ internal class ClientEventReporter( retryFailureReason = if (!success) failureReason else null, retryFailureCode = if (!success) failureCode else null, sfuId = session.sfuId, - callSessionId = callSessionId, + callSessionId = session.callSessionId, ), ) } @@ -196,11 +222,12 @@ internal class ClientEventReporter( callType: String, role: PeerConnectionRole, iceState: PeerConnection.IceConnectionState, - dtlsState: PeerConnection.PeerConnectionState?, + peerConnectionState: PeerConnection.PeerConnectionState?, ) { when (iceState) { PeerConnection.IceConnectionState.CHECKING -> { val wasPrev = pcEverConnected[role] == true + // TODO Rahul, maybe this `completePeerConnectionSession` is not needed // If an existing session is still in-flight, close it as failed first activePcSessionIds.remove(role)?.let { oldId -> completePeerConnectionSession( @@ -209,6 +236,7 @@ internal class ClientEventReporter( eventSessionId = oldId, success = false, iceState = iceState, + peerConnectionState = peerConnectionState, failureReason = "ICE restart superseded previous attempt", failureCode = "ICE_CONNECTIVITY_FAILED", ) @@ -221,9 +249,10 @@ internal class ClientEventReporter( eventSessionId = eventSessionId, stage = CallEventStage.PEER_CONNECTION_CONNECT, startedAtMs = now, - joinSuccessIdSnapshot = joinSuccessId, + joinStageAttemptIdSnapshot = joinStageAttemptIdMap[callId] ?: "", peerConnectionRole = role, wasPreviouslyConnected = wasPrev, + callSessionId = callSessionIdMap[callId], ) activePcSessionIds[role] = eventSessionId sendEvent( @@ -235,6 +264,9 @@ internal class ClientEventReporter( eventSessionId = eventSessionId, peerConnection = role, wasPreviouslyConnected = wasPrev, + callSessionId = callSessionIdMap[callId], + iceState = iceState, + peerConnectionState = peerConnectionState, ), ) } @@ -248,6 +280,7 @@ internal class ClientEventReporter( eventSessionId = eventSessionId, success = true, iceState = iceState, + peerConnectionState = peerConnectionState, ) } @@ -259,6 +292,7 @@ internal class ClientEventReporter( eventSessionId = eventSessionId, success = false, iceState = iceState, + peerConnectionState = peerConnectionState, failureReason = "ICE connectivity checks failed", failureCode = "ICE_CONNECTIVITY_FAILED", ) @@ -274,6 +308,7 @@ internal class ClientEventReporter( eventSessionId: String, success: Boolean, iceState: PeerConnection.IceConnectionState, + peerConnectionState: PeerConnection.PeerConnectionState?, failureReason: String? = null, failureCode: String? = null, ) { @@ -294,38 +329,42 @@ internal class ClientEventReporter( peerConnection = session.peerConnectionRole, wasPreviouslyConnected = session.wasPreviouslyConnected, iceState = iceState, + peerConnectionState = peerConnectionState, ), ) } - // --- Abort all in-flight sessions (user left or backend ended call) --- + internal fun sendAllPendingEvents() { + val batchEventsClone = batchEvents.toList() + batchEvents.clear() + sendEvents(batchEventsClone) + } internal fun abortAllInFlight(reason: AbortReason) { val snapshot = inFlightSessions.values.toList() inFlightSessions.clear() activePcSessionIds.clear() val now = System.currentTimeMillis() - for (session in snapshot) { - sendEvent( - buildRequest( - callId = session.callId, - callType = session.callType, - stage = session.stage, - eventType = CallEventType.COMPLETED, - eventSessionId = session.eventSessionId, - elapsedTime = now - session.startedAtMs, - outcome = CallEventOutcome.FAILURE, - retryCountAttempt = 0, - retryFailureReason = reason.message, - retryFailureCode = reason.code, - sfuId = session.sfuId, - callSessionId = session.callSessionId, - peerConnection = session.peerConnectionRole, - wasPreviouslyConnected = session.wasPreviouslyConnected, - userSessionId = session.userSessionId, - ), + val events = snapshot.map { session -> + buildRequest( + callId = session.callId, + callType = session.callType, + stage = session.stage, + eventType = CallEventType.COMPLETED, + eventSessionId = session.eventSessionId, + elapsedTime = now - session.startedAtMs, + outcome = CallEventOutcome.FAILURE, + retryCountAttempt = 0, + retryFailureReason = reason.message, + retryFailureCode = reason.code, + sfuId = session.sfuId, + callSessionId = session.callSessionId, + peerConnection = session.peerConnectionRole, + wasPreviouslyConnected = session.wasPreviouslyConnected, + userSessionId = session.userSessionId, ) } + sendEvents(events) } // --- Request builder --- @@ -346,6 +385,7 @@ internal class ClientEventReporter( peerConnection: PeerConnectionRole? = null, wasPreviouslyConnected: Boolean? = null, iceState: PeerConnection.IceConnectionState? = null, + peerConnectionState: PeerConnection.PeerConnectionState? = null, userSessionId: String? = null, ): ClientEvent = ClientEvent( eventSessionId = eventSessionId, @@ -373,22 +413,29 @@ internal class ClientEventReporter( // --- Delivery --- -// private fun sendEvent(request: ReportClientEventRequest) { -// scope.launch { -// // TODO: wrap with StreamRetryPolicy when retries are added -// runCatching { -// api.reportClientCallEvent(request) -// }.onFailure { e -> -// logger.w { "[sendEvent] Failed to send client event: ${e.message}" } -// } -// } -// } + private fun sendEvent(event: ClientEvent) { + when (sendingStrategy) { + TelemetrySendingStrategy.BATCH -> { + batchEvents.add(event) + } + TelemetrySendingStrategy.IN_PLACE -> { + scope.launch { + // TODO: wrap with StreamRetryPolicy when retries are added + runCatching { + api.reportClientCallEvent(ReportClientEventRequest(arrayListOf(event))) + }.onFailure { e -> + logger.w { "[sendEvent] Failed to send client event: ${e.message}" } + } + } + } + } + } - private fun sendEvent(request: ClientEvent) { + private fun sendEvents(events: List) { scope.launch { // TODO: wrap with StreamRetryPolicy when retries are added runCatching { - api.reportClientCallEvent(ReportClientEventRequest(arrayListOf(request))) + api.reportClientCallEvent(ReportClientEventRequest(events)) }.onFailure { e -> logger.w { "[sendEvent] Failed to send client event: ${e.message}" } } From ec6887081b3a3c812e9358dea0d687e1b895ef1f Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 21 May 2026 12:52:26 +0530 Subject: [PATCH 12/63] chore: add todo --- .../io/getstream/video/android/core/StreamVideoClient.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index 7fa1692cd54..e8c11bd8b35 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -1170,7 +1170,10 @@ internal class StreamVideoClient internal constructor( override fun logOut() { scope.launch( CoroutineName("logOut"), - ) { streamNotificationManager.deviceTokenStorage.clear() } + ) { + streamNotificationManager.deviceTokenStorage.clear() + // TODO Rahul should we flush our pending analytics events + } } override fun call(type: String, id: String): Call { From 801da737056525a1d6d5055e17f0ef3b37381b60 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 21 May 2026 13:03:51 +0530 Subject: [PATCH 13/63] chore: refactor code --- .../video/android/core/CallLeaveReason.kt | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt index 0eafdf7a3c8..7b1d2ce8c7a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt @@ -16,41 +16,6 @@ package io.getstream.video.android.core -internal enum class SdkCause { - /** App was swiped from the recents screen. */ - TASK_REMOVED, - - /** Telecom system put the call on hold for another call. */ - CALL_ON_HOLD, - - /** SDK-level cleanup (e.g. logout, StreamVideo instance teardown). */ - CLIENT_CLEANUP, - - /** Outgoing call auto-cancel timeout elapsed with no answer. */ - RING_TIMEOUT, - - ACCEPTED_ON_OTHER_DEVICE, - LOCAL_CALL_MISSED_EVENT, - REJECTED_BY_ALL, - END_CALL, -} - -internal enum class UserActionCause { - /** User rejected the call from a paired wearable device. */ - WEARABLE_REJECTED, - - /** A [io.getstream.video.android.core.CallJoinInterceptor] aborted the join sequence. */ - CALL_JOIN_ABORT, - REJECTED_BY_SELF, - LEAVE_FROM_NOTIFICATION, -} - -internal enum class BackendCause { - LEAVE_TIMEOUT_AFTER_DISCONNECT, - CALL_ENDED_EVENT, - CALL_ENDED_SFU_EVENT, -} - internal sealed interface CallLeaveReason { val message: String? @@ -93,3 +58,38 @@ internal sealed interface CallLeaveReason { override val metadata: Map = emptyMap(), ) : CallLeaveReason } + +internal enum class SdkCause { + /** App was swiped from the recents screen. */ + TASK_REMOVED, + + /** Telecom system put the call on hold for another call. */ + CALL_ON_HOLD, + + /** SDK-level cleanup (e.g. logout, StreamVideo instance teardown). */ + CLIENT_CLEANUP, + + /** Outgoing call auto-cancel timeout elapsed with no answer. */ + RING_TIMEOUT, + + ACCEPTED_ON_OTHER_DEVICE, + LOCAL_CALL_MISSED_EVENT, + REJECTED_BY_ALL, + END_CALL, +} + +internal enum class UserActionCause { + /** User rejected the call from a paired wearable device. */ + WEARABLE_REJECTED, + + /** A [io.getstream.video.android.core.CallJoinInterceptor] aborted the join sequence. */ + CALL_JOIN_ABORT, + REJECTED_BY_SELF, + LEAVE_FROM_NOTIFICATION, +} + +internal enum class BackendCause { + LEAVE_TIMEOUT_AFTER_DISCONNECT, + CALL_ENDED_EVENT, + CALL_ENDED_SFU_EVENT, +} From 5cd656d31b9dbb303dccce4409780d259d80fab0 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 25 May 2026 17:22:42 +0530 Subject: [PATCH 14/63] chore: add telemetry model --- .../io/getstream/video/android/core/Call.kt | 59 +++++++++++++++---- .../video/android/core/call/RtcSession.kt | 6 +- .../events/reporting/ClientEventReporter.kt | 2 +- .../ClientEventReporterErrorMappers.kt | 46 +++++++++++++++ .../core/events/reporting/TelemetryModel.kt | 19 ++++++ 5 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporterErrorMappers.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index cd3588a9e51..2323792f82d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -77,7 +77,9 @@ import io.getstream.video.android.core.events.GoAwayEvent import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.events.reporting.ClientEventReporterErrorMappers import io.getstream.video.android.core.events.reporting.PeerConnectionRole +import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.model.AudioTrack @@ -147,7 +149,7 @@ internal typealias CallSessionId = String * these instead of throwing, making the control flow in the reconnect loop * explicit and exhaustively checked by the compiler. */ -private sealed class ReconnectOutcome { +internal sealed class ReconnectOutcome { /** Reconnect succeeded — exit the loop. */ object Success : ReconnectOutcome() @@ -555,9 +557,7 @@ public class Call( logger.d { "[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions" } - val eventSessionId = UUID.randomUUID().toString() - client.state.clientEventReporter.reportCoordinatorJoinInitiated( - eventSessionId, + val eventSessionId = client.state.clientEventReporter.reportCoordinatorJoinInitiated( callType = this.type, callId = this.id, ) @@ -1021,12 +1021,38 @@ public class Call( WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_REJOIN -> { nonFastReconnectAttempts++ - reconnectRejoin(reason) + val telemetrySessionId = + client.state.clientEventReporter.reportCoordinatorJoinInitiated( + this.id, + this.type, + ) + + val result = + reconnectRejoin(reason, TelemetryModel(nonFastReconnectAttempts)) + client.state.clientEventReporter.reportCoordinatorJoinCompleted( + telemetrySessionId, + result is ReconnectOutcome.Success, + nonFastReconnectAttempts, + ClientEventReporterErrorMappers().getFailureReason(result), + ) + result } WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_MIGRATE -> { nonFastReconnectAttempts++ - reconnectMigrate() + val telemetrySessionId = + client.state.clientEventReporter.reportCoordinatorJoinInitiated( + this.id, + this.type, + ) + val result = reconnectMigrate(TelemetryModel(nonFastReconnectAttempts)) + client.state.clientEventReporter.reportCoordinatorJoinCompleted( + telemetrySessionId, + result is ReconnectOutcome.Success, + nonFastReconnectAttempts, + ClientEventReporterErrorMappers().getFailureReason(result), + ) + result } WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT -> @@ -1130,7 +1156,10 @@ public class Call( * previous_session_id is set so the SFU can transfer state (tracks, * subscriptions) from the old session to the new one. */ - private suspend fun reconnectRejoin(reason: String): ReconnectOutcome { + private suspend fun reconnectRejoin( + reason: String, + telemetryModel: TelemetryModel? = null, + ): ReconnectOutcome { logger.d { "[reconnectRejoin] reconnectAttempts=$nonFastReconnectAttempts" } state._connection.value = RealtimeConnection.Reconnecting val loc = location @@ -1178,7 +1207,13 @@ public class Call( ) this.session.value = newSession - return when (val result = newSession.connectInternal(reconnectDetails, currentOptions)) { + return when ( + val result = newSession.connectInternal( + reconnectDetails, + currentOptions, + telemetryModel, + ) + ) { is SfuConnectionResult.Connected -> { newSession.sfuTracer.trace("rejoin", reason) monitorSession(joinResponse.value) @@ -1192,7 +1227,7 @@ public class Call( * Migrate to another SFU. Reuses the same session ID — the SFU * identifies the participant via from_sfu_id, not previous_session_id. */ - private suspend fun reconnectMigrate(): ReconnectOutcome { + private suspend fun reconnectMigrate(telemetryModel: TelemetryModel? = null): ReconnectOutcome { logger.d { "[reconnectMigrate] Migrating" } state._connection.value = RealtimeConnection.Migrating val loc = location @@ -1247,7 +1282,11 @@ public class Call( this.session.value = newSession return try { - val result = newSession.connectInternal(reconnectDetails, currentOptions) + val result = newSession.connectInternal( + reconnectDetails, + currentOptions, + telemetryModel, + ) when (result) { is SfuConnectionResult.Connected -> { monitorSession(joinResponse.value) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index 2cd942091e5..7101cdb79b6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -71,6 +71,7 @@ import io.getstream.video.android.core.events.SfuDataRequest import io.getstream.video.android.core.events.SubscriberOfferEvent import io.getstream.video.android.core.events.TrackPublishedEvent import io.getstream.video.android.core.events.TrackUnpublishedEvent +import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.internal.module.SfuConnectionModule import io.getstream.video.android.core.model.AudioTrack import io.getstream.video.android.core.model.IceCandidate @@ -879,6 +880,7 @@ public class RtcSession internal constructor( internal suspend fun connectInternal( reconnectDetails: ReconnectDetails? = null, options: List? = null, + telemetryModel: TelemetryModel? = null, ): SfuConnectionResult { logger.i { "[connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } val reporter = call.client.state.clientEventReporter @@ -926,7 +928,7 @@ public class RtcSession internal constructor( reporter.reportWsJoinCompleted( telemetryWsEventSessionId, success = false, - retryCount = 0, + retryCount = telemetryModel?.retryAttempt ?: 0, failureReason = msg, failureCode = "WS_DISCONNECTED", ) @@ -938,7 +940,7 @@ public class RtcSession internal constructor( reporter.reportWsJoinCompleted( telemetryWsEventSessionId, success = false, - retryCount = 0, + retryCount = telemetryModel?.retryAttempt ?: 0, failureReason = "SFU connection timed out", failureCode = "REQUEST_TIMEOUT", ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index 85a02ae7336..b93ac7996ae 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -97,11 +97,11 @@ internal class ClientEventReporter( // --- CoordinatorJoin --- internal fun reportCoordinatorJoinInitiated( - eventSessionId: String, callId: String, callType: String, ): String { val joinStageAttemptId = UUID.randomUUID().toString() + val eventSessionId = UUID.randomUUID().toString() joinStageAttemptIdMap[callId] = joinStageAttemptId val now = System.currentTimeMillis() inFlightSessions[eventSessionId] = InFlightSession( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporterErrorMappers.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporterErrorMappers.kt new file mode 100644 index 00000000000..c1ff623f9c7 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporterErrorMappers.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.events.reporting + +import io.getstream.video.android.core.ReconnectOutcome + +internal class ClientEventReporterErrorMappers { + + fun getFailureReason(result: ReconnectOutcome): String { + return when (result) { + is ReconnectOutcome.Success -> { + "" + } + + is ReconnectOutcome.PreconditionNotMet -> { + result.reason + } + + is ReconnectOutcome.PeerConnectionStale -> { + "Peer connections are stale and can't be reused. Should escalate to REJOIN" + } + + is ReconnectOutcome.Disconnect -> { + "Server-initiated disconnect" + } + + is ReconnectOutcome.Failed -> { + result.error.message ?: result.error.toString() + } + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt new file mode 100644 index 00000000000..392d594e82f --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.events.reporting + +internal data class TelemetryModel(val retryAttempt: Int) From 9125e8ec5ea2dc5bc82bb5200b2d3e8463ef2dd6 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 26 May 2026 02:01:38 +0530 Subject: [PATCH 15/63] chore: add fault injector --- .../getstream/video/android/MainActivity.kt | 5 + .../video/android/ui/DogfoodingNavHost.kt | 3 + .../video/android/ui/FaultInjectorImpl.kt | 50 +++++ .../video/android/ui/FaultInjectorUi.kt | 208 ++++++++++++++++++ .../video/android/ui/join/CallJoinScreen.kt | 30 ++- demo-app/src/main/res/values/strings.xml | 1 + .../video/android/core/ClientState.kt | 5 + .../events/reporting/BatchNetworkRequest.kt | 77 +++++++ .../events/reporting/ClientEventReporter.kt | 19 +- .../core/faultinjector/FaultInjector.kt | 35 +++ .../android/core/faultinjector/FaultKey.kt | 38 ++++ .../core/faultinjector/NoOpFaultInjector.kt | 32 +++ 12 files changed, 487 insertions(+), 16 deletions(-) create mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt create mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/BatchNetworkRequest.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt b/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt index 857b54e4123..f42260672ae 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt @@ -40,6 +40,7 @@ import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.ui.AppNavHost import io.getstream.video.android.ui.AppScreens +import io.getstream.video.android.ui.FaultInjectorImpl import io.getstream.video.android.ui.common.StreamCallActivity import io.getstream.video.android.ui.common.StreamCallActivityConfiguration import io.getstream.video.android.util.InAppUpdateHelper @@ -85,6 +86,9 @@ class MainActivity : ComponentActivity() { lifecycleScope.launch { val isLoggedIn = dataStore.user.firstOrNull() != null + val faultInjector = FaultInjectorImpl() + //TODO Rahul, remove before merge + StreamVideo.instance().state.faultInjector = faultInjector setContent { VideoTheme { @@ -98,6 +102,7 @@ class MainActivity : ComponentActivity() { AppScreens.CallJoin.route }, prefilledCallId = launchIntentCallId, + faultInjector = faultInjector, ) } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/DogfoodingNavHost.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/DogfoodingNavHost.kt index 49f92f0816f..fc086d8ea39 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/DogfoodingNavHost.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/DogfoodingNavHost.kt @@ -27,6 +27,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import io.getstream.video.android.CallActivity +import io.getstream.video.android.core.faultinjector.FaultInjector import io.getstream.video.android.core.notifications.NotificationHandler import io.getstream.video.android.ui.common.StreamCallActivity import io.getstream.video.android.ui.common.StreamCallActivityConfiguration @@ -40,6 +41,7 @@ import io.getstream.video.android.ui.outgoing.DirectCallJoinScreen fun AppNavHost( modifier: Modifier = Modifier, navController: NavHostController = rememberNavController(), + faultInjector: FaultInjector, startDestination: String = AppScreens.Login.route, prefilledCallId: String? = null, ) { @@ -62,6 +64,7 @@ fun AppNavHost( composable(AppScreens.CallJoin.route) { CallJoinScreen( prefilledCallId = prefilledCallId, + faultInjector = faultInjector, navigateToCallLobby = { cid -> navController.navigate(AppScreens.CallLobby.routeWithArg(cid)) }, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt new file mode 100644 index 00000000000..c344cbcdaaf --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.ui + +import io.getstream.video.android.core.faultinjector.FaultInjector +import io.getstream.video.android.core.faultinjector.FaultKey + +internal class FaultInjectorImpl : FaultInjector { + private val enabledFaults = mutableMapOf() + + override fun enable(key: FaultKey) { + enabledFaults[key] = true + } + + override fun disable(key: FaultKey) { + enabledFaults[key] = false + } + + override fun setEnabled(key: FaultKey, enabled: Boolean) { + enabledFaults[key] = enabled + } + + override fun isEnabled(key: FaultKey): Boolean { + return enabledFaults[key] == true + } + + override fun clear() { + enabledFaults.clear() + } + + override fun throwDebugFault(key: FaultKey) { + if (enabledFaults[key] == true) { + throw RuntimeException("Fault injected: $key") + } + } +} diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt new file mode 100644 index 00000000000..3909f0da0c7 --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Checkbox +import androidx.compose.material.CheckboxDefaults +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.getstream.video.android.compose.theme.VideoTheme +import io.getstream.video.android.core.faultinjector.FaultInjector +import io.getstream.video.android.core.faultinjector.FaultKey +import io.getstream.video.android.core.internal.InternalStreamVideoApi + +@OptIn(InternalStreamVideoApi::class) +@Composable +fun FaultInjectorUi( + modifier: Modifier = Modifier, + faultInjector: FaultInjector, + onClose: () -> Unit, +) { + val checkedState = remember { + mutableStateMapOf().apply { + FaultKey.entries.forEach { key -> put(key, faultInjector.isEnabled(key)) } + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .background(VideoTheme.colors.baseSheetPrimary), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(VideoTheme.colors.baseSheetSecondary) + .padding(horizontal = 16.dp, vertical = 14.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "Fault Injection", + style = VideoTheme.typography.subtitleM, + color = VideoTheme.colors.basePrimary, + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "Clear all", + modifier = Modifier.clickable { + faultInjector.clear() + FaultKey.entries.forEach { key -> checkedState[key] = false } + }, + style = VideoTheme.typography.bodyS, + color = VideoTheme.colors.brandPrimary, + ) + + Box( + modifier = Modifier + .background( + color = VideoTheme.colors.baseSheetTertiary, + shape = RoundedCornerShape(999.dp), + ) + .clickable(onClick = onClose) + .padding(8.dp), + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Close", + tint = VideoTheme.colors.basePrimary, + ) + } + } + } + + LazyColumn( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + items(FaultKey.entries) { key -> + val checked = checkedState[key] ?: false + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + val next = !checked + checkedState[key] = next + faultInjector.setEnabled(key, next) + } + .padding(horizontal = 8.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Checkbox( + checked = checked, + onCheckedChange = { next -> + checkedState[key] = next + faultInjector.setEnabled(key, next) + }, + colors = CheckboxDefaults.colors( + checkedColor = VideoTheme.colors.brandPrimary, + uncheckedColor = VideoTheme.colors.baseSecondary, + checkmarkColor = VideoTheme.colors.baseSheetPrimary, + ), + ) + Text( + text = key.name, + style = VideoTheme.typography.bodyM, + color = VideoTheme.colors.basePrimary, + ) + } + } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .background(VideoTheme.colors.baseSheetSecondary) + .padding(horizontal = 16.dp, vertical = 12.dp), + ) { + Button( + onClick = onClose, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = VideoTheme.colors.brandPrimary, + contentColor = VideoTheme.colors.baseSheetPrimary, + ), + ) { + Text( + text = "OK", + style = VideoTheme.typography.labelL, + ) + } + } + } +} + +@Preview +@Composable +fun FaultInjectorUiDemo() { + VideoTheme { + FaultInjectorUi( + Modifier, + object : FaultInjector { + override fun enable(key: FaultKey) {} + + override fun disable(key: FaultKey) {} + + override fun setEnabled( + key: FaultKey, + enabled: Boolean, + ) {} + + override fun isEnabled(key: FaultKey): Boolean = false + + override fun clear() {} + + override fun throwDebugFault(key: FaultKey) {} + }, + {}, + ) + } +} diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt index 7c31cf4367e..6cc757ef8c4 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt @@ -50,6 +50,7 @@ import androidx.compose.material.icons.filled.Call import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.VideoCall +import androidx.compose.material.icons.filled.Warning import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -98,11 +99,13 @@ import io.getstream.video.android.compose.ui.components.base.StreamButton import io.getstream.video.android.compose.ui.components.base.StreamDialogPositiveNegative import io.getstream.video.android.compose.ui.components.base.StreamIconToggleButton import io.getstream.video.android.compose.ui.components.base.StreamTextField +import io.getstream.video.android.core.faultinjector.FaultInjector import io.getstream.video.android.defaultCallId import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewUsers import io.getstream.video.android.model.User import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil +import io.getstream.video.android.ui.FaultInjectorUi import io.getstream.video.android.ui.LogFilesScreen import io.getstream.video.android.ui.SingleButtonDialog import io.getstream.video.android.util.config.AppConfig @@ -112,6 +115,7 @@ import io.getstream.video.android.util.config.types.StreamEnvironment fun CallJoinScreen( prefilledCallId: String? = null, callJoinViewModel: CallJoinViewModel = hiltViewModel(), + faultInjector: FaultInjector, navigateToCallLobby: (callId: String) -> Unit, navigateUpToLogin: (autoLogIn: Boolean) -> Unit, navigateToDirectCallJoin: () -> Unit, @@ -125,6 +129,7 @@ fun CallJoinScreen( val isNetworkAvailable by callJoinViewModel.isNetworkAvailable.collectAsStateWithLifecycle() var renderLogsFileUi by remember { mutableStateOf(false) } + var renderFaultInjectorUi by remember { mutableStateOf(false) } HandleCallJoinUiState( callJoinUiState = uiState, @@ -151,6 +156,9 @@ fun CallJoinScreen( onLogsClick = { renderLogsFileUi = true }, + onFaultInjectionClick = { + renderFaultInjectorUi = true + }, ) CallJoinBody( @@ -195,6 +203,12 @@ fun CallJoinScreen( renderLogsFileUi = false }) } + + if (renderFaultInjectorUi) { + FaultInjectorUi(faultInjector = faultInjector, onClose = { + renderFaultInjectorUi = false + }) + } } @Composable @@ -224,6 +238,7 @@ private fun CallJoinHeader( onDirectCallClick: () -> Unit, onSignOutClick: () -> Unit, onLogsClick: () -> Unit, + onFaultInjectionClick: () -> Unit, ) { Row( modifier = Modifier @@ -340,6 +355,19 @@ private fun CallJoinHeader( }, ) Spacer(modifier = Modifier.width(5.dp)) + StreamButton( + modifier = Modifier + .fillMaxWidth() + .testTag("Stream_FaultInjectorButton"), + icon = Icons.Filled.Warning, + style = VideoTheme.styles.buttonStyles.tertiaryButtonStyle(), + text = stringResource(id = R.string.fault_injector), + onClick = { + showMenu = false + onFaultInjectionClick() + }, + ) + Spacer(modifier = Modifier.width(5.dp)) StreamButton( modifier = Modifier .fillMaxWidth() @@ -743,6 +771,6 @@ private fun CallJoinScreenLandscapePreview() { private fun CallJoinScreenHeader() { StreamPreviewDataUtils.initializeStreamVideo(LocalContext.current) VideoTheme { - CallJoinHeader(previewUsers[0], false, true, {}, {}, {}, {}) + CallJoinHeader(previewUsers[0], false, true, {}, {}, {}, {}, {}) } } diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index 91c04e705a5..acbd397eaf2 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -30,6 +30,7 @@ Contact us Sign out Share Logs + Fault Injector Start a new call, join a meeting by \nentering the call ID or by scanning \na QR code. Join Call Scan QR meeting code diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index c340e351de4..48dbf0d7fdd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -24,6 +24,8 @@ import io.getstream.android.video.generated.models.VideoEvent import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.faultinjector.FaultInjector +import io.getstream.video.android.core.faultinjector.NoOpFaultInjector import io.getstream.video.android.core.header.HeadersUtil import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.notifications.internal.service.CallService @@ -96,6 +98,9 @@ class ClientState(private val client: StreamVideo) { sdkVersion = BuildConfig.STREAM_VIDEO_VERSION, ) + @InternalStreamVideoApi + public var faultInjector: FaultInjector = NoOpFaultInjector() + @InternalStreamVideoApi public val rejectCallWhenBusy: Boolean = (client as StreamVideoClient).rejectCallWhenBusy diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/BatchNetworkRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/BatchNetworkRequest.kt new file mode 100644 index 00000000000..62031dd8226 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/BatchNetworkRequest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.events.reporting + +import android.content.Context +import io.getstream.android.video.generated.apis.ProductvideoApi +import io.getstream.android.video.generated.infrastructure.Serializer +import io.getstream.android.video.generated.models.ClientEvent +import io.getstream.android.video.generated.models.ReportClientEventRequest +import io.getstream.log.taggedLogger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.io.File + +internal class BatchNetworkRequest( + private val context: Context, + private val file: File, + private val api: ProductvideoApi, + private val scope: CoroutineScope, +) { + + private val logger by taggedLogger("BatchNetworkRequest") + private val adapter = Serializer.moshi.adapter(ClientEvent::class.java) + + fun write(clientEvent: ClientEvent) { + file.appendText(adapter.toJson(clientEvent) + "\n") + } + + fun readAll(size: Int): List { + if (!file.exists()) return emptyList() + return file.readLines() + .take(size) + .mapNotNull { runCatching { adapter.fromJson(it) }.getOrNull() } + } + + fun sendBatchNetworkRequest() { + scope.launch { + // TODO: wrap with StreamRetryPolicy when retries are added + var clientEventsSize = 0 + runCatching { + val clientEvents = readAll(Integer.MAX_VALUE) + clientEventsSize = clientEvents.size + api.reportClientCallEvent(ReportClientEventRequest(clientEvents)) + }.onFailure { e -> + logger.w { "[sendEvent] Failed to send client event: ${e.message}" } + }.onSuccess { + removeLinesFromFile(clientEventsSize) + } + } + } + + fun removeLinesFromFile(linesToRemove: Int) { + if (!file.exists() || linesToRemove <= 0) return + val remaining = file.readLines().drop(linesToRemove) + file.writeText( + if (remaining.isEmpty()) { + "" + } else { + remaining.joinToString("\n", postfix = "\n") + }, + ) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index b93ac7996ae..aa30a7a8a20 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -28,7 +28,6 @@ import kotlinx.coroutines.launch import org.threeten.bp.OffsetDateTime import org.threeten.bp.ZoneOffset import org.webrtc.PeerConnection -import java.util.Collections import java.util.UUID import java.util.concurrent.ConcurrentHashMap import kotlin.collections.set @@ -56,9 +55,6 @@ internal class ClientEventReporter( private val inFlightSessions = ConcurrentHashMap() private val joinStageAttemptIdMap = ConcurrentHashMap() private val callSessionIdMap = ConcurrentHashMap() - private val batchEvents: MutableList = Collections.synchronizedList( - mutableListOf(), - ) // TODO Rahul, should be thread safe // Active event_session_id per PC role — drives the ICE state machine private val activePcSessionIds = ConcurrentHashMap() @@ -335,9 +331,7 @@ internal class ClientEventReporter( } internal fun sendAllPendingEvents() { - val batchEventsClone = batchEvents.toList() - batchEvents.clear() - sendEvents(batchEventsClone) +// batchNetworkRequest.sendBatchNetworkRequest() } internal fun abortAllInFlight(reason: AbortReason) { @@ -416,7 +410,7 @@ internal class ClientEventReporter( private fun sendEvent(event: ClientEvent) { when (sendingStrategy) { TelemetrySendingStrategy.BATCH -> { - batchEvents.add(event) +// batchNetworkRequest.write(event) } TelemetrySendingStrategy.IN_PLACE -> { scope.launch { @@ -432,13 +426,8 @@ internal class ClientEventReporter( } private fun sendEvents(events: List) { - scope.launch { - // TODO: wrap with StreamRetryPolicy when retries are added - runCatching { - api.reportClientCallEvent(ReportClientEventRequest(events)) - }.onFailure { e -> - logger.w { "[sendEvent] Failed to send client event: ${e.message}" } - } + events.map { +// batchNetworkRequest.write(it) } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt new file mode 100644 index 00000000000..6c4d86f0502 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.faultinjector + +import io.getstream.video.android.core.internal.InternalStreamVideoApi + +@InternalStreamVideoApi +public interface FaultInjector { + + fun enable(key: FaultKey) + + fun disable(key: FaultKey) + + fun setEnabled(key: FaultKey, enabled: Boolean) + + fun isEnabled(key: FaultKey): Boolean + + fun clear() + + fun throwDebugFault(key: FaultKey) +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt new file mode 100644 index 00000000000..d9f3f5016cf --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.faultinjector + +import io.getstream.video.android.core.internal.InternalStreamVideoApi + +@InternalStreamVideoApi +public enum class FaultKey { + + // REST + FAIL_JOIN_CALL, + FAIL_CREATE_CALL, + + // WebSocket + FAIL_WS_CONNECT, + FAIL_WS_SEND, + + // RTC + FAIL_PEER_CONNECTION_CREATE, + FAIL_SET_REMOTE_DESCRIPTION, + + // MediaCodec + FAIL_VIDEO_ENCODER_INIT, +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt new file mode 100644 index 00000000000..0d5aac8e1d0 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.faultinjector + +internal class NoOpFaultInjector : FaultInjector { + + override fun enable(key: FaultKey) {} + + override fun disable(key: FaultKey) {} + + override fun setEnabled(key: FaultKey, enabled: Boolean) {} + + override fun isEnabled(key: FaultKey): Boolean = false + + override fun clear() {} + + override fun throwDebugFault(key: FaultKey) {} +} From 43c875efb8d871ac2b451dd7fb3110bfa15be210 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 26 May 2026 12:39:06 +0530 Subject: [PATCH 16/63] chore: refactor code with fault injectors --- .../kotlin/io/getstream/video/android/App.kt | 7 +++ .../getstream/video/android/CallActivity.kt | 10 ++++ .../getstream/video/android/MainActivity.kt | 5 -- .../video/android/ui/DogfoodingNavHost.kt | 3 - .../video/android/ui/FaultInjectorImpl.kt | 19 +++++- .../video/android/ui/FaultInjectorUi.kt | 11 +++- .../video/android/ui/join/CallJoinScreen.kt | 12 ++-- .../video/generated/models/ClientEvent.kt | 60 ++++++++++++++++++- .../io/getstream/video/android/core/Call.kt | 17 ++++-- .../video/android/core/StreamVideoClient.kt | 4 ++ .../video/android/core/call/RtcSession.kt | 8 ++- .../events/reporting/ClientEventReporter.kt | 24 ++++---- .../core/faultinjector/FaultInjector.kt | 2 + .../android/core/faultinjector/FaultKey.kt | 13 ++-- .../core/faultinjector/NoOpFaultInjector.kt | 12 ++-- .../android/core/socket/sfu/SfuSocket.kt | 28 +++++++++ 16 files changed, 186 insertions(+), 49 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/App.kt b/demo-app/src/main/kotlin/io/getstream/video/android/App.kt index 58f5a473f57..747c3bc9d4b 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/App.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/App.kt @@ -28,6 +28,7 @@ import io.getstream.video.android.core.moderations.CallModerationConstants import io.getstream.video.android.data.model.PolicyViolationUiData import io.getstream.video.android.datastore.delegate.StreamUserDataStore import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil +import io.getstream.video.android.ui.FaultInjectorImpl import io.getstream.video.android.util.StreamVideoInitHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -80,6 +81,12 @@ class App : Application() { observePolicyViolation() observeCallReadyToJoin() + injectFault() + } + + private fun injectFault() { + val faultInjector = FaultInjectorImpl() + StreamVideo.instanceOrNull()?.state?.faultInjector = faultInjector } private fun observeCallReadyToJoin() { diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/CallActivity.kt b/demo-app/src/main/kotlin/io/getstream/video/android/CallActivity.kt index bf4df4beff9..31358b8d887 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/CallActivity.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/CallActivity.kt @@ -67,6 +67,7 @@ class CallActivity : ComposeStreamCallActivity() { override val uiDelegate: StreamActivityUiDelegate = StreamDemoUiDelegate() var observeCallReadyToJoinJob: Job? = null var observeRingingJob: Job? = null + var isNavigatingBackToMainScreen = false private val previousRingingStates = ConcurrentHashMap.newKeySet() override val callJoinInterceptor = DemoCallJoinInterceptor(previousRingingStates) @@ -194,6 +195,7 @@ class CallActivity : ComposeStreamCallActivity() { private fun StreamCallActivity.goBackToMainScreen() { if (!isFinishing) { + (this as CallActivity).isNavigatingBackToMainScreen = true val intent = Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } @@ -208,5 +210,13 @@ class CallActivity : ComposeStreamCallActivity() { observeCallReadyToJoinJob?.cancel() observeRingingJob?.cancel() previousRingingStates.clear() + + if (!isNavigatingBackToMainScreen) { + isNavigatingBackToMainScreen = true + val intent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + startActivity(intent) + } } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt b/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt index f42260672ae..857b54e4123 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt @@ -40,7 +40,6 @@ import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.ui.AppNavHost import io.getstream.video.android.ui.AppScreens -import io.getstream.video.android.ui.FaultInjectorImpl import io.getstream.video.android.ui.common.StreamCallActivity import io.getstream.video.android.ui.common.StreamCallActivityConfiguration import io.getstream.video.android.util.InAppUpdateHelper @@ -86,9 +85,6 @@ class MainActivity : ComponentActivity() { lifecycleScope.launch { val isLoggedIn = dataStore.user.firstOrNull() != null - val faultInjector = FaultInjectorImpl() - //TODO Rahul, remove before merge - StreamVideo.instance().state.faultInjector = faultInjector setContent { VideoTheme { @@ -102,7 +98,6 @@ class MainActivity : ComponentActivity() { AppScreens.CallJoin.route }, prefilledCallId = launchIntentCallId, - faultInjector = faultInjector, ) } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/DogfoodingNavHost.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/DogfoodingNavHost.kt index fc086d8ea39..49f92f0816f 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/DogfoodingNavHost.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/DogfoodingNavHost.kt @@ -27,7 +27,6 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import io.getstream.video.android.CallActivity -import io.getstream.video.android.core.faultinjector.FaultInjector import io.getstream.video.android.core.notifications.NotificationHandler import io.getstream.video.android.ui.common.StreamCallActivity import io.getstream.video.android.ui.common.StreamCallActivityConfiguration @@ -41,7 +40,6 @@ import io.getstream.video.android.ui.outgoing.DirectCallJoinScreen fun AppNavHost( modifier: Modifier = Modifier, navController: NavHostController = rememberNavController(), - faultInjector: FaultInjector, startDestination: String = AppScreens.Login.route, prefilledCallId: String? = null, ) { @@ -64,7 +62,6 @@ fun AppNavHost( composable(AppScreens.CallJoin.route) { CallJoinScreen( prefilledCallId = prefilledCallId, - faultInjector = faultInjector, navigateToCallLobby = { cid -> navController.navigate(AppScreens.CallLobby.routeWithArg(cid)) }, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt index c344cbcdaaf..1ca50990c78 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt @@ -16,8 +16,11 @@ package io.getstream.video.android.ui +import io.getstream.result.Error import io.getstream.video.android.core.faultinjector.FaultInjector import io.getstream.video.android.core.faultinjector.FaultKey +import retrofit2.HttpException +import retrofit2.Response internal class FaultInjectorImpl : FaultInjector { private val enabledFaults = mutableMapOf() @@ -44,7 +47,21 @@ internal class FaultInjectorImpl : FaultInjector { override fun throwDebugFault(key: FaultKey) { if (enabledFaults[key] == true) { - throw RuntimeException("Fault injected: $key") + throw when (key) { + FaultKey.FAIL_LOCATION -> HttpException( + Response.error( + 100, + okhttp3.ResponseBody.create(null, ""), + ), + ) + else -> RuntimeException("Fault injected: $key") + } } } + + override fun sendFailResult(key: FaultKey): io.getstream.result.Result.Failure { + return io.getstream.result.Result.Failure( + Error.GenericError("Fault injected: $key"), + ) + } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt index 3909f0da0c7..9ab17beccdd 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.getstream.result.Error import io.getstream.video.android.compose.theme.VideoTheme import io.getstream.video.android.core.faultinjector.FaultInjector import io.getstream.video.android.core.faultinjector.FaultKey @@ -201,8 +202,14 @@ fun FaultInjectorUiDemo() { override fun clear() {} override fun throwDebugFault(key: FaultKey) {} + override fun sendFailResult( + key: FaultKey, + ): io.getstream.result.Result.Failure { + return io.getstream.result.Result.Failure( + Error.GenericError("Fault injected: $key"), + ) + } }, - {}, - ) + ) {} } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt index 6cc757ef8c4..4b49e33efc5 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt @@ -99,7 +99,7 @@ import io.getstream.video.android.compose.ui.components.base.StreamButton import io.getstream.video.android.compose.ui.components.base.StreamDialogPositiveNegative import io.getstream.video.android.compose.ui.components.base.StreamIconToggleButton import io.getstream.video.android.compose.ui.components.base.StreamTextField -import io.getstream.video.android.core.faultinjector.FaultInjector +import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.defaultCallId import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewUsers @@ -115,7 +115,6 @@ import io.getstream.video.android.util.config.types.StreamEnvironment fun CallJoinScreen( prefilledCallId: String? = null, callJoinViewModel: CallJoinViewModel = hiltViewModel(), - faultInjector: FaultInjector, navigateToCallLobby: (callId: String) -> Unit, navigateUpToLogin: (autoLogIn: Boolean) -> Unit, navigateToDirectCallJoin: () -> Unit, @@ -205,9 +204,12 @@ fun CallJoinScreen( } if (renderFaultInjectorUi) { - FaultInjectorUi(faultInjector = faultInjector, onClose = { - renderFaultInjectorUi = false - }) + val faultInjector = StreamVideo.instanceOrNull()?.state?.faultInjector + faultInjector?.let { + FaultInjectorUi(faultInjector = it, onClose = { + renderFaultInjectorUi = false + }) + } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt index 5511001f8a1..b21c54599b8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt @@ -101,4 +101,62 @@ data class ClientEvent ( @Json(name = "was_previously_connected") val wasPreviouslyConnected: kotlin.Boolean? = null -) +) { + + fun toLog(): String { + return buildString { + append("ClientEvent(") + + appendIfNotNull("type", type) + appendIfNotNull("eventType", eventType) + appendIfNotNull("stage", stage) + appendIfNotNull("outcome", outcome) + + appendIfNotNull("callSessionId", callSessionId) + appendIfNotNull("eventSessionId", eventSessionId) + appendIfNotNull("userSessionId", userSessionId) + + appendIfNotNull("userId", userId) + appendIfNotNull("sfuId", sfuId) + + appendIfNotNull("peerConnection", peerConnection) + appendIfNotNull("iceState", iceState) + + appendIfNotNull("retryCountAttempt", retryCountAttempt) + appendIfNotNull("retryFailureCode", retryFailureCode) + appendIfNotNull("retryFailureReason", retryFailureReason) + + appendIfNotNull("elapsedTime", elapsedTime) + + appendIfNotNull( + "wasPreviouslyConnected", + wasPreviouslyConnected + ) + + appendIfNotNull( + "previouslyConnectedTimestamp", + previouslyConnectedTimestamp + ) + + appendIfNotNull("sdkVersion", sdkVersion) + appendIfNotNull("timestamp", timestamp) + + append(")") + } + } + + private fun StringBuilder.appendIfNotNull( + key: String, + value: Any?, + ) { + if (value == null) return + + if (!endsWith("(")) { + append(",\n") + } + + append(key) + append("=") + append(value) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 2323792f82d..e285e298c0c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -80,6 +80,7 @@ import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.events.reporting.ClientEventReporterErrorMappers import io.getstream.video.android.core.events.reporting.PeerConnectionRole import io.getstream.video.android.core.events.reporting.TelemetryModel +import io.getstream.video.android.core.faultinjector.FaultKey import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.model.AudioTrack @@ -1017,7 +1018,7 @@ public class Call( val outcome = when (currentStrategy) { WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_FAST, WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED, - -> reconnectFast(reason) + -> { reconnectFast(reason, TelemetryModel(loopIteration)) } WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_REJOIN -> { nonFastReconnectAttempts++ @@ -1123,7 +1124,10 @@ public class Call( * Reuses the existing session ID — no previous_session_id needed since the * SFU already knows this participant. */ - private suspend fun reconnectFast(reason: String): ReconnectOutcome { + private suspend fun reconnectFast( + reason: String, + telemetryModel: TelemetryModel? = null, + ): ReconnectOutcome { logger.d { "[reconnectFast] reconnectAttempts=$nonFastReconnectAttempts" } val currentSession = session.value ?: return ReconnectOutcome.PreconditionNotMet("No active session for fast reconnect") @@ -1144,7 +1148,7 @@ public class Call( reconnect_attempt = nonFastReconnectAttempts, reason = reason, ) - return when (val result = currentSession.fastReconnect(reconnectDetails)) { + return when (val result = currentSession.fastReconnect(reconnectDetails, telemetryModel)) { is FastReconnectResult.Connected -> ReconnectOutcome.Success is FastReconnectResult.PeerConnectionStale -> ReconnectOutcome.PeerConnectionStale is FastReconnectResult.Failed -> ReconnectOutcome.Failed(result.error) @@ -1372,7 +1376,6 @@ public class Call( is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE else -> ClientEventReporter.AbortReason.CLIENT_ABORTED } - reporter.sendAllPendingEvents() reporter.abortAllInFlight(abortReason) } safeCall { @@ -1917,7 +1920,11 @@ public class Call( notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, ): Result { - val reporter = client.state.clientEventReporter + with(client.state.faultInjector) { + if (isEnabled(FaultKey.FAIL_JOIN_CALL)) { + return sendFailResult(FaultKey.FAIL_JOIN_CALL) + } + } val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index e8c11bd8b35..348799b35f1 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -87,6 +87,7 @@ import io.getstream.video.android.core.audio.AudioExecutionContext import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.errors.VideoErrorCode import io.getstream.video.android.core.events.VideoEventListener +import io.getstream.video.android.core.faultinjector.FaultKey import io.getstream.video.android.core.filter.Filters import io.getstream.video.android.core.filter.toMap import io.getstream.video.android.core.internal.module.CoordinatorConnectionModule @@ -442,6 +443,9 @@ internal class StreamVideoClient internal constructor( var location: String? = null internal suspend fun getCachedLocation(): Result { + if (state.faultInjector.isEnabled(FaultKey.FAIL_LOCATION)) { + return state.faultInjector.sendFailResult(FaultKey.FAIL_LOCATION) + } val job = loadLocationAsync() job.join() location?.let { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index 7101cdb79b6..df7effa1e6b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -71,6 +71,7 @@ import io.getstream.video.android.core.events.SfuDataRequest import io.getstream.video.android.core.events.SubscriberOfferEvent import io.getstream.video.android.core.events.TrackPublishedEvent import io.getstream.video.android.core.events.TrackUnpublishedEvent +import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.internal.module.SfuConnectionModule import io.getstream.video.android.core.model.AudioTrack @@ -941,8 +942,8 @@ public class RtcSession internal constructor( telemetryWsEventSessionId, success = false, retryCount = telemetryModel?.retryAttempt ?: 0, - failureReason = "SFU connection timed out", - failureCode = "REQUEST_TIMEOUT", + failureReason = ClientEventReporter.FailureCodes.SFU_REQUEST_TIMEOUT.message, + failureCode = ClientEventReporter.FailureCodes.SFU_REQUEST_TIMEOUT.code, ) sendCallStats() @@ -1912,7 +1913,7 @@ public class RtcSession internal constructor( return Triple(previousSessionId, currentSubscriptions, publisherTracks) } - internal suspend fun fastReconnect(reconnectDetails: ReconnectDetails?): FastReconnectResult { + internal suspend fun fastReconnect(reconnectDetails: ReconnectDetails?, telemetryModel: TelemetryModel? = null): FastReconnectResult { logger.d { "[fastReconnect] Starting fast reconnect." } sfuTracer.trace("fastReconnect", reconnectDetails.toString()) val (_, _, publisherTracks) = currentSfuInfo() @@ -1921,6 +1922,7 @@ public class RtcSession internal constructor( val connectResult = connectInternal( reconnectDetails, publisher.value?.currentOptions(), + telemetryModel, ) if (connectResult is SfuConnectionResult.Failed) { return FastReconnectResult.Failed(connectResult.error) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index aa30a7a8a20..78b333e3d3f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -50,7 +50,7 @@ internal class ClientEventReporter( private val sendingStrategy: TelemetrySendingStrategy = TelemetrySendingStrategy.IN_PLACE, private val scope: CoroutineScope = UserScope(ClientScope()), ) { - private val logger by taggedLogger("CallEventReporter") + private val logger by taggedLogger("ClientEventReporter") private val inFlightSessions = ConcurrentHashMap() private val joinStageAttemptIdMap = ConcurrentHashMap() @@ -87,7 +87,8 @@ internal class ClientEventReporter( NETWORK_OFFLINE("NETWORK_OFFLINE", "Device offline"), ICE_GATHERING_FAILED("ICE_GATHERING_FAILED", "ICE gathering failed"), ICE_CONNECTIVITY_FAILED("ICE_CONNECTIVITY_FAILED", "ICE connectivity failed"), -// REQUEST_TIMEOUT("REQUEST_TIMEOUT", "Device offline"), + REQUEST_TIMEOUT("REQUEST_TIMEOUT", "Device offline"), + SFU_REQUEST_TIMEOUT("REQUEST_TIMEOUT", "SFU connection timed out"), } // --- CoordinatorJoin --- @@ -330,10 +331,6 @@ internal class ClientEventReporter( ) } - internal fun sendAllPendingEvents() { -// batchNetworkRequest.sendBatchNetworkRequest() - } - internal fun abortAllInFlight(reason: AbortReason) { val snapshot = inFlightSessions.values.toList() inFlightSessions.clear() @@ -409,13 +406,12 @@ internal class ClientEventReporter( private fun sendEvent(event: ClientEvent) { when (sendingStrategy) { - TelemetrySendingStrategy.BATCH -> { -// batchNetworkRequest.write(event) - } + TelemetrySendingStrategy.BATCH -> { } TelemetrySendingStrategy.IN_PLACE -> { scope.launch { // TODO: wrap with StreamRetryPolicy when retries are added runCatching { + logger.d { event.toLog() } api.reportClientCallEvent(ReportClientEventRequest(arrayListOf(event))) }.onFailure { e -> logger.w { "[sendEvent] Failed to send client event: ${e.message}" } @@ -426,8 +422,14 @@ internal class ClientEventReporter( } private fun sendEvents(events: List) { - events.map { -// batchNetworkRequest.write(it) + scope.launch { + // TODO: wrap with StreamRetryPolicy when retries are added + runCatching { + logger.d { events.map { it.toLog() }.joinToString { "," } } + api.reportClientCallEvent(ReportClientEventRequest(events)) + }.onFailure { e -> + logger.w { "[sendEvent] Failed to send client event: ${e.message}" } + } } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt index 6c4d86f0502..1feec512a62 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt @@ -32,4 +32,6 @@ public interface FaultInjector { fun clear() fun throwDebugFault(key: FaultKey) + + fun sendFailResult(key: FaultKey): io.getstream.result.Result.Failure } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt index d9f3f5016cf..a363c7e2b35 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt @@ -23,16 +23,13 @@ public enum class FaultKey { // REST FAIL_JOIN_CALL, - FAIL_CREATE_CALL, + FAIL_LOCATION, // WebSocket FAIL_WS_CONNECT, - FAIL_WS_SEND, - // RTC - FAIL_PEER_CONNECTION_CREATE, - FAIL_SET_REMOTE_DESCRIPTION, - - // MediaCodec - FAIL_VIDEO_ENCODER_INIT, + // Reconnect Strategy + FAIL_FAST_RECONNECT, + FAIL_FULL_REJOIN, + FAIL_MIGRATE, } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt index 0d5aac8e1d0..5b30920dbdd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt @@ -16,17 +16,19 @@ package io.getstream.video.android.core.faultinjector +import io.getstream.result.Error + internal class NoOpFaultInjector : FaultInjector { override fun enable(key: FaultKey) {} - override fun disable(key: FaultKey) {} - override fun setEnabled(key: FaultKey, enabled: Boolean) {} - override fun isEnabled(key: FaultKey): Boolean = false - override fun clear() {} - override fun throwDebugFault(key: FaultKey) {} + override fun sendFailResult(key: FaultKey): io.getstream.result.Result.Failure { + return io.getstream.result.Result.Failure( + Error.GenericError("Fault injected: $key"), + ) + } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt index afeb22012b7..7528e651a8c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt @@ -20,6 +20,7 @@ package io.getstream.video.android.core.socket.sfu import io.getstream.log.taggedLogger import io.getstream.result.Error +import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.dispatchers.DispatcherProvider import io.getstream.video.android.core.errors.DisconnectCause import io.getstream.video.android.core.errors.VideoErrorCode @@ -29,6 +30,7 @@ import io.getstream.video.android.core.events.SFUHealthCheckEvent import io.getstream.video.android.core.events.SfuDataEvent import io.getstream.video.android.core.events.SfuDataRequest import io.getstream.video.android.core.events.UnknownEvent +import io.getstream.video.android.core.faultinjector.FaultKey import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.lifecycle.NoOpLifecycleHandler import io.getstream.video.android.core.lifecycle.StreamLifecycleObserver @@ -107,6 +109,7 @@ internal open class SfuSocket( streamWebSocket?.close("connectUser:cleanup") when (networkStateProvider.isConnected()) { true -> { + debugFaultInjectors(connectionConf) streamWebSocket = socketFactory.createSocket(connectionConf, "#sfu").apply { listeners.forEach { it.onCreated() } @@ -219,6 +222,31 @@ internal open class SfuSocket( } } + private fun debugFaultInjectors(connectionConf: ConnectionConf.SfuConnectionConf) { + StreamVideo.instanceOrNull()?.state?.faultInjector?.let { faultInjector -> + if (faultInjector.isEnabled(FaultKey.FAIL_WS_CONNECT)) { + faultInjector.throwDebugFault(FaultKey.FAIL_WS_CONNECT) + } + val isFastReconnect = + connectionConf.joinRequest.reconnect_details?.strategy == WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_FAST + if (isFastReconnect && faultInjector.isEnabled(FaultKey.FAIL_FAST_RECONNECT)) { + faultInjector.throwDebugFault(FaultKey.FAIL_FAST_RECONNECT) + } + + val isFullRejoin = + connectionConf.joinRequest.reconnect_details?.strategy == WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_REJOIN + if (isFullRejoin && faultInjector.isEnabled(FaultKey.FAIL_FULL_REJOIN)) { + faultInjector.throwDebugFault(FaultKey.FAIL_FULL_REJOIN) + } + + val isMigrate = + connectionConf.joinRequest.reconnect_details?.strategy == WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_MIGRATE + if (isMigrate && faultInjector.isEnabled(FaultKey.FAIL_MIGRATE)) { + faultInjector.throwDebugFault(FaultKey.FAIL_MIGRATE) + } + } + } + suspend fun connect(joinRequest: JoinRequest) { logger.d { "[connect] request: ${joinRequest.client_details}" } socketListenerJob?.cancel() From 7b3db272be4b3b24c69446471110fac7a32a7da3 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 26 May 2026 12:39:53 +0530 Subject: [PATCH 17/63] chore: refactor code with fault injectors --- .../video/generated/models/ClientEvent.kt | 2 +- .../events/reporting/BatchNetworkRequest.kt | 77 ------------------- 2 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/BatchNetworkRequest.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt index b21c54599b8..8433789af30 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt @@ -103,7 +103,7 @@ data class ClientEvent ( val wasPreviouslyConnected: kotlin.Boolean? = null ) { - fun toLog(): String { + internal fun toLog(): String { return buildString { append("ClientEvent(") diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/BatchNetworkRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/BatchNetworkRequest.kt deleted file mode 100644 index 62031dd8226..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/BatchNetworkRequest.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * 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. - */ - -package io.getstream.video.android.core.events.reporting - -import android.content.Context -import io.getstream.android.video.generated.apis.ProductvideoApi -import io.getstream.android.video.generated.infrastructure.Serializer -import io.getstream.android.video.generated.models.ClientEvent -import io.getstream.android.video.generated.models.ReportClientEventRequest -import io.getstream.log.taggedLogger -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.io.File - -internal class BatchNetworkRequest( - private val context: Context, - private val file: File, - private val api: ProductvideoApi, - private val scope: CoroutineScope, -) { - - private val logger by taggedLogger("BatchNetworkRequest") - private val adapter = Serializer.moshi.adapter(ClientEvent::class.java) - - fun write(clientEvent: ClientEvent) { - file.appendText(adapter.toJson(clientEvent) + "\n") - } - - fun readAll(size: Int): List { - if (!file.exists()) return emptyList() - return file.readLines() - .take(size) - .mapNotNull { runCatching { adapter.fromJson(it) }.getOrNull() } - } - - fun sendBatchNetworkRequest() { - scope.launch { - // TODO: wrap with StreamRetryPolicy when retries are added - var clientEventsSize = 0 - runCatching { - val clientEvents = readAll(Integer.MAX_VALUE) - clientEventsSize = clientEvents.size - api.reportClientCallEvent(ReportClientEventRequest(clientEvents)) - }.onFailure { e -> - logger.w { "[sendEvent] Failed to send client event: ${e.message}" } - }.onSuccess { - removeLinesFromFile(clientEventsSize) - } - } - } - - fun removeLinesFromFile(linesToRemove: Int) { - if (!file.exists() || linesToRemove <= 0) return - val remaining = file.readLines().drop(linesToRemove) - file.writeText( - if (remaining.isEmpty()) { - "" - } else { - remaining.joinToString("\n", postfix = "\n") - }, - ) - } -} From d4dbc923e29e3be0ffaffc1437aa1f86b08be3c2 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 26 May 2026 15:16:26 +0530 Subject: [PATCH 18/63] chore: rename from fault injector to failure injector --- .../kotlin/io/getstream/video/android/App.kt | 6 +-- ...InjectorImpl.kt => FailureInjectorImpl.kt} | 26 +++++------ ...aultInjectorUi.kt => FailureInjectorUi.kt} | 44 +++++++++---------- .../video/android/ui/join/CallJoinScreen.kt | 12 +++-- demo-app/src/main/res/values/strings.xml | 2 +- .../io/getstream/video/android/core/Call.kt | 8 ++-- .../video/android/core/ClientState.kt | 6 +-- .../video/android/core/StreamVideoClient.kt | 6 +-- .../{FaultInjector.kt => FailureInjector.kt} | 14 +++--- .../{FaultKey.kt => FailureKey.kt} | 2 +- ...aultInjector.kt => NoOpFailureInjector.kt} | 16 +++---- .../android/core/socket/sfu/SfuSocket.kt | 20 ++++----- 12 files changed, 82 insertions(+), 80 deletions(-) rename demo-app/src/main/kotlin/io/getstream/video/android/ui/{FaultInjectorImpl.kt => FailureInjectorImpl.kt} (63%) rename demo-app/src/main/kotlin/io/getstream/video/android/ui/{FaultInjectorUi.kt => FailureInjectorUi.kt} (85%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/{FaultInjector.kt => FailureInjector.kt} (71%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/{FaultKey.kt => FailureKey.kt} (96%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/{NoOpFaultInjector.kt => NoOpFailureInjector.kt} (63%) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/App.kt b/demo-app/src/main/kotlin/io/getstream/video/android/App.kt index 747c3bc9d4b..2c2659077cc 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/App.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/App.kt @@ -28,7 +28,7 @@ import io.getstream.video.android.core.moderations.CallModerationConstants import io.getstream.video.android.data.model.PolicyViolationUiData import io.getstream.video.android.datastore.delegate.StreamUserDataStore import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil -import io.getstream.video.android.ui.FaultInjectorImpl +import io.getstream.video.android.ui.FailureInjectorImpl import io.getstream.video.android.util.StreamVideoInitHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -85,8 +85,8 @@ class App : Application() { } private fun injectFault() { - val faultInjector = FaultInjectorImpl() - StreamVideo.instanceOrNull()?.state?.faultInjector = faultInjector + val faultInjector = FailureInjectorImpl() + StreamVideo.instanceOrNull()?.state?.failureInjector = faultInjector } private fun observeCallReadyToJoin() { diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorImpl.kt similarity index 63% rename from demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt rename to demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorImpl.kt index 1ca50990c78..511541f75fc 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorImpl.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorImpl.kt @@ -17,27 +17,27 @@ package io.getstream.video.android.ui import io.getstream.result.Error -import io.getstream.video.android.core.faultinjector.FaultInjector -import io.getstream.video.android.core.faultinjector.FaultKey +import io.getstream.video.android.core.faultinjector.FailureInjector +import io.getstream.video.android.core.faultinjector.FailureKey import retrofit2.HttpException import retrofit2.Response -internal class FaultInjectorImpl : FaultInjector { - private val enabledFaults = mutableMapOf() +internal class FailureInjectorImpl : FailureInjector { + private val enabledFaults = mutableMapOf() - override fun enable(key: FaultKey) { + override fun enable(key: FailureKey) { enabledFaults[key] = true } - override fun disable(key: FaultKey) { + override fun disable(key: FailureKey) { enabledFaults[key] = false } - override fun setEnabled(key: FaultKey, enabled: Boolean) { + override fun setEnabled(key: FailureKey, enabled: Boolean) { enabledFaults[key] = enabled } - override fun isEnabled(key: FaultKey): Boolean { + override fun isEnabled(key: FailureKey): Boolean { return enabledFaults[key] == true } @@ -45,23 +45,23 @@ internal class FaultInjectorImpl : FaultInjector { enabledFaults.clear() } - override fun throwDebugFault(key: FaultKey) { + override fun throwDebugFault(key: FailureKey) { if (enabledFaults[key] == true) { throw when (key) { - FaultKey.FAIL_LOCATION -> HttpException( + FailureKey.FAIL_LOCATION -> HttpException( Response.error( 100, okhttp3.ResponseBody.create(null, ""), ), ) - else -> RuntimeException("Fault injected: $key") + else -> RuntimeException("Failure injected: $key") } } } - override fun sendFailResult(key: FaultKey): io.getstream.result.Result.Failure { + override fun sendFailResult(key: FailureKey): io.getstream.result.Result.Failure { return io.getstream.result.Result.Failure( - Error.GenericError("Fault injected: $key"), + Error.GenericError("Failure injected: $key"), ) } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorUi.kt similarity index 85% rename from demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt rename to demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorUi.kt index 9ab17beccdd..59445ae38b1 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FaultInjectorUi.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorUi.kt @@ -37,30 +37,28 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.getstream.result.Error import io.getstream.video.android.compose.theme.VideoTheme -import io.getstream.video.android.core.faultinjector.FaultInjector -import io.getstream.video.android.core.faultinjector.FaultKey +import io.getstream.video.android.core.faultinjector.FailureInjector +import io.getstream.video.android.core.faultinjector.FailureKey import io.getstream.video.android.core.internal.InternalStreamVideoApi @OptIn(InternalStreamVideoApi::class) @Composable -fun FaultInjectorUi( +fun FailureInjectorUi( modifier: Modifier = Modifier, - faultInjector: FaultInjector, + failureInjector: FailureInjector, onClose: () -> Unit, ) { val checkedState = remember { - mutableStateMapOf().apply { - FaultKey.entries.forEach { key -> put(key, faultInjector.isEnabled(key)) } + mutableStateMapOf().apply { + FailureKey.entries.forEach { key -> put(key, failureInjector.isEnabled(key)) } } } @@ -78,7 +76,7 @@ fun FaultInjectorUi( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = "Fault Injection", + text = "Failure Injection", style = VideoTheme.typography.subtitleM, color = VideoTheme.colors.basePrimary, ) @@ -90,8 +88,8 @@ fun FaultInjectorUi( Text( text = "Clear all", modifier = Modifier.clickable { - faultInjector.clear() - FaultKey.entries.forEach { key -> checkedState[key] = false } + failureInjector.clear() + FailureKey.entries.forEach { key -> checkedState[key] = false } }, style = VideoTheme.typography.bodyS, color = VideoTheme.colors.brandPrimary, @@ -122,7 +120,7 @@ fun FaultInjectorUi( .padding(horizontal = 16.dp, vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { - items(FaultKey.entries) { key -> + items(FailureKey.entries) { key -> val checked = checkedState[key] ?: false Row( modifier = Modifier @@ -130,7 +128,7 @@ fun FaultInjectorUi( .clickable { val next = !checked checkedState[key] = next - faultInjector.setEnabled(key, next) + failureInjector.setEnabled(key, next) } .padding(horizontal = 8.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, @@ -140,7 +138,7 @@ fun FaultInjectorUi( checked = checked, onCheckedChange = { next -> checkedState[key] = next - faultInjector.setEnabled(key, next) + failureInjector.setEnabled(key, next) }, colors = CheckboxDefaults.colors( checkedColor = VideoTheme.colors.brandPrimary, @@ -185,28 +183,28 @@ fun FaultInjectorUi( @Composable fun FaultInjectorUiDemo() { VideoTheme { - FaultInjectorUi( + FailureInjectorUi( Modifier, - object : FaultInjector { - override fun enable(key: FaultKey) {} + object : FailureInjector { + override fun enable(key: FailureKey) {} - override fun disable(key: FaultKey) {} + override fun disable(key: FailureKey) {} override fun setEnabled( - key: FaultKey, + key: FailureKey, enabled: Boolean, ) {} - override fun isEnabled(key: FaultKey): Boolean = false + override fun isEnabled(key: FailureKey): Boolean = false override fun clear() {} - override fun throwDebugFault(key: FaultKey) {} + override fun throwDebugFault(key: FailureKey) {} override fun sendFailResult( - key: FaultKey, + key: FailureKey, ): io.getstream.result.Result.Failure { return io.getstream.result.Result.Failure( - Error.GenericError("Fault injected: $key"), + Error.GenericError("Failure injected: $key"), ) } }, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt index 4b49e33efc5..767b22fcce8 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt @@ -105,7 +105,7 @@ import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewUsers import io.getstream.video.android.model.User import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil -import io.getstream.video.android.ui.FaultInjectorUi +import io.getstream.video.android.ui.FailureInjectorUi import io.getstream.video.android.ui.LogFilesScreen import io.getstream.video.android.ui.SingleButtonDialog import io.getstream.video.android.util.config.AppConfig @@ -129,6 +129,7 @@ fun CallJoinScreen( var renderLogsFileUi by remember { mutableStateOf(false) } var renderFaultInjectorUi by remember { mutableStateOf(false) } + var renderNetworkSettingsUi by remember { mutableStateOf(false) } HandleCallJoinUiState( callJoinUiState = uiState, @@ -204,13 +205,16 @@ fun CallJoinScreen( } if (renderFaultInjectorUi) { - val faultInjector = StreamVideo.instanceOrNull()?.state?.faultInjector + val faultInjector = StreamVideo.instanceOrNull()?.state?.failureInjector faultInjector?.let { - FaultInjectorUi(faultInjector = it, onClose = { + FailureInjectorUi(failureInjector = it, onClose = { renderFaultInjectorUi = false }) } } + + if (renderNetworkSettingsUi) { + } } @Composable @@ -363,7 +367,7 @@ private fun CallJoinHeader( .testTag("Stream_FaultInjectorButton"), icon = Icons.Filled.Warning, style = VideoTheme.styles.buttonStyles.tertiaryButtonStyle(), - text = stringResource(id = R.string.fault_injector), + text = stringResource(id = R.string.failure_injector), onClick = { showMenu = false onFaultInjectionClick() diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index acbd397eaf2..ad051aaa8c7 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -30,7 +30,7 @@ Contact us Sign out Share Logs - Fault Injector + Fault Injector Start a new call, join a meeting by \nentering the call ID or by scanning \na QR code. Join Call Scan QR meeting code diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index e285e298c0c..c08d4000081 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -80,7 +80,7 @@ import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.events.reporting.ClientEventReporterErrorMappers import io.getstream.video.android.core.events.reporting.PeerConnectionRole import io.getstream.video.android.core.events.reporting.TelemetryModel -import io.getstream.video.android.core.faultinjector.FaultKey +import io.getstream.video.android.core.faultinjector.FailureKey import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.model.AudioTrack @@ -1920,9 +1920,9 @@ public class Call( notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, ): Result { - with(client.state.faultInjector) { - if (isEnabled(FaultKey.FAIL_JOIN_CALL)) { - return sendFailResult(FaultKey.FAIL_JOIN_CALL) + with(client.state.failureInjector) { + if (isEnabled(FailureKey.FAIL_JOIN_CALL)) { + return sendFailResult(FailureKey.FAIL_JOIN_CALL) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index 48dbf0d7fdd..bec2443ed74 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -24,8 +24,8 @@ import io.getstream.android.video.generated.models.VideoEvent import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.events.reporting.ClientEventReporter -import io.getstream.video.android.core.faultinjector.FaultInjector -import io.getstream.video.android.core.faultinjector.NoOpFaultInjector +import io.getstream.video.android.core.faultinjector.FailureInjector +import io.getstream.video.android.core.faultinjector.NoOpFailureInjector import io.getstream.video.android.core.header.HeadersUtil import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.notifications.internal.service.CallService @@ -99,7 +99,7 @@ class ClientState(private val client: StreamVideo) { ) @InternalStreamVideoApi - public var faultInjector: FaultInjector = NoOpFaultInjector() + public var failureInjector: FailureInjector = NoOpFailureInjector() @InternalStreamVideoApi public val rejectCallWhenBusy: Boolean = (client as StreamVideoClient).rejectCallWhenBusy diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index 348799b35f1..fc67f0da5d7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -87,7 +87,7 @@ import io.getstream.video.android.core.audio.AudioExecutionContext import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.errors.VideoErrorCode import io.getstream.video.android.core.events.VideoEventListener -import io.getstream.video.android.core.faultinjector.FaultKey +import io.getstream.video.android.core.faultinjector.FailureKey import io.getstream.video.android.core.filter.Filters import io.getstream.video.android.core.filter.toMap import io.getstream.video.android.core.internal.module.CoordinatorConnectionModule @@ -443,8 +443,8 @@ internal class StreamVideoClient internal constructor( var location: String? = null internal suspend fun getCachedLocation(): Result { - if (state.faultInjector.isEnabled(FaultKey.FAIL_LOCATION)) { - return state.faultInjector.sendFailResult(FaultKey.FAIL_LOCATION) + if (state.failureInjector.isEnabled(FailureKey.FAIL_LOCATION)) { + return state.failureInjector.sendFailResult(FailureKey.FAIL_LOCATION) } val job = loadLocationAsync() job.join() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureInjector.kt similarity index 71% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureInjector.kt index 1feec512a62..dd807b77e1d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultInjector.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureInjector.kt @@ -19,19 +19,19 @@ package io.getstream.video.android.core.faultinjector import io.getstream.video.android.core.internal.InternalStreamVideoApi @InternalStreamVideoApi -public interface FaultInjector { +public interface FailureInjector { - fun enable(key: FaultKey) + fun enable(key: FailureKey) - fun disable(key: FaultKey) + fun disable(key: FailureKey) - fun setEnabled(key: FaultKey, enabled: Boolean) + fun setEnabled(key: FailureKey, enabled: Boolean) - fun isEnabled(key: FaultKey): Boolean + fun isEnabled(key: FailureKey): Boolean fun clear() - fun throwDebugFault(key: FaultKey) + fun throwDebugFault(key: FailureKey) - fun sendFailResult(key: FaultKey): io.getstream.result.Result.Failure + fun sendFailResult(key: FailureKey): io.getstream.result.Result.Failure } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureKey.kt similarity index 96% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureKey.kt index a363c7e2b35..1411d43400f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FaultKey.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureKey.kt @@ -19,7 +19,7 @@ package io.getstream.video.android.core.faultinjector import io.getstream.video.android.core.internal.InternalStreamVideoApi @InternalStreamVideoApi -public enum class FaultKey { +public enum class FailureKey { // REST FAIL_JOIN_CALL, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFailureInjector.kt similarity index 63% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFailureInjector.kt index 5b30920dbdd..d38e4b77e92 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFaultInjector.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFailureInjector.kt @@ -18,17 +18,17 @@ package io.getstream.video.android.core.faultinjector import io.getstream.result.Error -internal class NoOpFaultInjector : FaultInjector { +internal class NoOpFailureInjector : FailureInjector { - override fun enable(key: FaultKey) {} - override fun disable(key: FaultKey) {} - override fun setEnabled(key: FaultKey, enabled: Boolean) {} - override fun isEnabled(key: FaultKey): Boolean = false + override fun enable(key: FailureKey) {} + override fun disable(key: FailureKey) {} + override fun setEnabled(key: FailureKey, enabled: Boolean) {} + override fun isEnabled(key: FailureKey): Boolean = false override fun clear() {} - override fun throwDebugFault(key: FaultKey) {} - override fun sendFailResult(key: FaultKey): io.getstream.result.Result.Failure { + override fun throwDebugFault(key: FailureKey) {} + override fun sendFailResult(key: FailureKey): io.getstream.result.Result.Failure { return io.getstream.result.Result.Failure( - Error.GenericError("Fault injected: $key"), + Error.GenericError("Failure injected: $key"), ) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt index 7528e651a8c..38095832221 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt @@ -30,7 +30,7 @@ import io.getstream.video.android.core.events.SFUHealthCheckEvent import io.getstream.video.android.core.events.SfuDataEvent import io.getstream.video.android.core.events.SfuDataRequest import io.getstream.video.android.core.events.UnknownEvent -import io.getstream.video.android.core.faultinjector.FaultKey +import io.getstream.video.android.core.faultinjector.FailureKey import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.lifecycle.NoOpLifecycleHandler import io.getstream.video.android.core.lifecycle.StreamLifecycleObserver @@ -223,26 +223,26 @@ internal open class SfuSocket( } private fun debugFaultInjectors(connectionConf: ConnectionConf.SfuConnectionConf) { - StreamVideo.instanceOrNull()?.state?.faultInjector?.let { faultInjector -> - if (faultInjector.isEnabled(FaultKey.FAIL_WS_CONNECT)) { - faultInjector.throwDebugFault(FaultKey.FAIL_WS_CONNECT) + StreamVideo.instanceOrNull()?.state?.failureInjector?.let { faultInjector -> + if (faultInjector.isEnabled(FailureKey.FAIL_WS_CONNECT)) { + faultInjector.throwDebugFault(FailureKey.FAIL_WS_CONNECT) } val isFastReconnect = connectionConf.joinRequest.reconnect_details?.strategy == WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_FAST - if (isFastReconnect && faultInjector.isEnabled(FaultKey.FAIL_FAST_RECONNECT)) { - faultInjector.throwDebugFault(FaultKey.FAIL_FAST_RECONNECT) + if (isFastReconnect && faultInjector.isEnabled(FailureKey.FAIL_FAST_RECONNECT)) { + faultInjector.throwDebugFault(FailureKey.FAIL_FAST_RECONNECT) } val isFullRejoin = connectionConf.joinRequest.reconnect_details?.strategy == WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_REJOIN - if (isFullRejoin && faultInjector.isEnabled(FaultKey.FAIL_FULL_REJOIN)) { - faultInjector.throwDebugFault(FaultKey.FAIL_FULL_REJOIN) + if (isFullRejoin && faultInjector.isEnabled(FailureKey.FAIL_FULL_REJOIN)) { + faultInjector.throwDebugFault(FailureKey.FAIL_FULL_REJOIN) } val isMigrate = connectionConf.joinRequest.reconnect_details?.strategy == WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_MIGRATE - if (isMigrate && faultInjector.isEnabled(FaultKey.FAIL_MIGRATE)) { - faultInjector.throwDebugFault(FaultKey.FAIL_MIGRATE) + if (isMigrate && faultInjector.isEnabled(FailureKey.FAIL_MIGRATE)) { + faultInjector.throwDebugFault(FailureKey.FAIL_MIGRATE) } } } From 36af925cedbd5fbec34bddce2fd147b715b20f2a Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 26 May 2026 18:35:54 +0530 Subject: [PATCH 19/63] chore: update the hook for join-completed --- .../io/getstream/video/android/core/Call.kt | 77 ++++++++++++++----- .../core/events/reporting/TelemetryModel.kt | 4 + 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index c08d4000081..7d0b57ec0c2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -78,6 +78,7 @@ import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.events.reporting.ClientEventReporterErrorMappers +import io.getstream.video.android.core.events.reporting.JoinRequestTelemetryModel import io.getstream.video.android.core.events.reporting.PeerConnectionRole import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.faultinjector.FailureKey @@ -594,7 +595,14 @@ public class Call( atomicLeave = AtomicUnitCall() while (retryCount < 3) { val tempResult = - _join(create, createOptions, ring, notify, hintHighScaleLivestreamPublisher) + _join( + create, + createOptions, + ring, + notify, + hintHighScaleLivestreamPublisher, + JoinRequestTelemetryModel(eventSessionId, retryCount), + ) if (tempResult is Success) { // we initialise the camera, mic and other according to local + backend settings // only when the call is joined to make sure we don't switch and override @@ -608,12 +616,7 @@ public class Call( "is joined. MediaManager will not be initialised with server settings." } } - client.state.clientEventReporter.reportCoordinatorJoinCompleted( - eventSessionId = eventSessionId, - success = true, - retryCount = retryCount, - callSessionId = tempResult.value.second, - ) + result = Success(tempResult.value.first) return result } @@ -689,6 +692,7 @@ public class Call( ring: Boolean = false, notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, + joinRequestTelemetryModel: JoinRequestTelemetryModel, ): Result> { nonFastReconnectAttempts = 0 sfuEvents?.cancel() @@ -723,6 +727,7 @@ public class Call( ring = ring, notify = notify, hintHighScaleLivestreamPublisher = hintHighScaleLivestreamPublisher, + joinRequestTelemetryModel = joinRequestTelemetryModel, ) if (result !is Success) { @@ -1029,13 +1034,22 @@ public class Call( ) val result = - reconnectRejoin(reason, TelemetryModel(nonFastReconnectAttempts)) - client.state.clientEventReporter.reportCoordinatorJoinCompleted( - telemetrySessionId, - result is ReconnectOutcome.Success, - nonFastReconnectAttempts, - ClientEventReporterErrorMappers().getFailureReason(result), - ) + reconnectRejoin( + reason, + JoinRequestTelemetryModel( + telemetrySessionId, + nonFastReconnectAttempts, + ), + ) + if (result !is ReconnectOutcome.Success) { + client.state.clientEventReporter.reportCoordinatorJoinCompleted( + telemetrySessionId, + false, + nonFastReconnectAttempts, + ClientEventReporterErrorMappers().getFailureReason(result), + ) + } + result } @@ -1046,7 +1060,13 @@ public class Call( this.id, this.type, ) - val result = reconnectMigrate(TelemetryModel(nonFastReconnectAttempts)) + val result = + reconnectMigrate( + JoinRequestTelemetryModel( + telemetrySessionId, + nonFastReconnectAttempts, + ), + ) client.state.clientEventReporter.reportCoordinatorJoinCompleted( telemetrySessionId, result is ReconnectOutcome.Success, @@ -1162,7 +1182,7 @@ public class Call( */ private suspend fun reconnectRejoin( reason: String, - telemetryModel: TelemetryModel? = null, + joinRequestTelemetryModel: JoinRequestTelemetryModel, ): ReconnectOutcome { logger.d { "[reconnectRejoin] reconnectAttempts=$nonFastReconnectAttempts" } state._connection.value = RealtimeConnection.Reconnecting @@ -1172,7 +1192,8 @@ public class Call( ?: return ReconnectOutcome.PreconditionNotMet("No active session for rejoin") reconnectStartTime = System.currentTimeMillis() - val joinResponse = joinRequest(location = loc) + val joinResponse = + joinRequest(location = loc, joinRequestTelemetryModel = joinRequestTelemetryModel) if (joinResponse !is Success) { return ReconnectOutcome.Failed( Exception("Failed to get join response: ${joinResponse.errorOrNull()}"), @@ -1215,7 +1236,7 @@ public class Call( val result = newSession.connectInternal( reconnectDetails, currentOptions, - telemetryModel, + TelemetryModel(joinRequestTelemetryModel.retryAttempt), ) ) { is SfuConnectionResult.Connected -> { @@ -1231,7 +1252,9 @@ public class Call( * Migrate to another SFU. Reuses the same session ID — the SFU * identifies the participant via from_sfu_id, not previous_session_id. */ - private suspend fun reconnectMigrate(telemetryModel: TelemetryModel? = null): ReconnectOutcome { + private suspend fun reconnectMigrate( + joinRequestTelemetryModel: JoinRequestTelemetryModel, + ): ReconnectOutcome { logger.d { "[reconnectMigrate] Migrating" } state._connection.value = RealtimeConnection.Migrating val loc = location @@ -1241,7 +1264,12 @@ public class Call( reconnectStartTime = System.currentTimeMillis() addFailedSfuId(oldSession.sfuName) - val joinResponse = joinRequest(location = loc, migratingFrom = oldSession.sfuName) + val joinResponse = + joinRequest( + location = loc, + migratingFrom = oldSession.sfuName, + joinRequestTelemetryModel = joinRequestTelemetryModel, + ) if (joinResponse !is Success) { return ReconnectOutcome.Failed( Exception( @@ -1289,7 +1317,7 @@ public class Call( val result = newSession.connectInternal( reconnectDetails, currentOptions, - telemetryModel, + TelemetryModel(joinRequestTelemetryModel.retryAttempt), ) when (result) { is SfuConnectionResult.Connected -> { @@ -1919,6 +1947,7 @@ public class Call( ring: Boolean = false, notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, + joinRequestTelemetryModel: JoinRequestTelemetryModel, ): Result { with(client.state.failureInjector) { if (isEnabled(FailureKey.FAIL_JOIN_CALL)) { @@ -1943,6 +1972,12 @@ public class Call( hintHighScaleLivestreamPublisher = hintHighScaleLivestreamPublisher, ) result.onSuccess { + client.state.clientEventReporter.reportCoordinatorJoinCompleted( + eventSessionId = joinRequestTelemetryModel.eventSessionId, + success = true, + retryCount = joinRequestTelemetryModel.retryAttempt, + callSessionId = it.call.currentSessionId, + ) state.updateFromResponse(it) } return result diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt index 392d594e82f..dd8a5a611d7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt @@ -17,3 +17,7 @@ package io.getstream.video.android.core.events.reporting internal data class TelemetryModel(val retryAttempt: Int) +internal data class JoinRequestTelemetryModel( + val eventSessionId: EventSessionId, + val retryAttempt: Int, +) From ef46a3334ecef3fb0a49ff057563f6d3277ff46b Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 27 May 2026 02:28:00 +0530 Subject: [PATCH 20/63] temp: send `auto` in joinRequest if we fail to fetch location --- .../io/getstream/video/android/core/Call.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 7d0b57ec0c2..7efc2b2ee6f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -709,10 +709,9 @@ public class Call( // step 1. call the join endpoint to get a list of SFUs val locationResult = clientImpl.getCachedLocation() - if (locationResult !is Success) { - return locationResult as Failure + if (locationResult is Success) { + location = locationResult.value } - location = locationResult.value val options = createOptions ?: if (create) { @@ -723,7 +722,7 @@ public class Call( val result = joinRequest( options, - locationResult.value, + location ?: "auto", ring = ring, notify = notify, hintHighScaleLivestreamPublisher = hintHighScaleLivestreamPublisher, @@ -1186,8 +1185,8 @@ public class Call( ): ReconnectOutcome { logger.d { "[reconnectRejoin] reconnectAttempts=$nonFastReconnectAttempts" } state._connection.value = RealtimeConnection.Reconnecting - val loc = location - ?: return ReconnectOutcome.PreconditionNotMet("No location available for rejoin") + val loc = location ?: "auto" +// ?: return ReconnectOutcome.PreconditionNotMet("No location available for rejoin") val oldSession = session.value ?: return ReconnectOutcome.PreconditionNotMet("No active session for rejoin") reconnectStartTime = System.currentTimeMillis() @@ -1257,8 +1256,8 @@ public class Call( ): ReconnectOutcome { logger.d { "[reconnectMigrate] Migrating" } state._connection.value = RealtimeConnection.Migrating - val loc = location - ?: return ReconnectOutcome.PreconditionNotMet("No location available for migrate") + val loc = location ?: "auto" +// ?: return ReconnectOutcome.PreconditionNotMet("No location available for migrate") val oldSession = session.value ?: return ReconnectOutcome.PreconditionNotMet("No active session for migrate") reconnectStartTime = System.currentTimeMillis() From ae2e77ea53922973509273574041d86d2afa52c0 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 27 May 2026 03:16:13 +0530 Subject: [PATCH 21/63] use analytics hook class to write implementation of sending events --- .../io/getstream/video/android/core/Call.kt | 117 +++++------------- .../core/analytics/CallAnalyticsHooks.kt | 32 +++++ .../core/analytics/JoinRequestHooks.kt | 56 +++++++++ .../video/android/core/analytics/WsHook.kt | 48 +++++++ .../video/android/core/call/RtcSession.kt | 18 +-- .../core/events/reporting/TelemetryModel.kt | 6 +- 6 files changed, 172 insertions(+), 105 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 7efc2b2ee6f..459ac870186 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -60,6 +60,9 @@ import io.getstream.result.Result import io.getstream.result.Result.Failure import io.getstream.result.Result.Success import io.getstream.result.flatMap +import io.getstream.video.android.core.analytics.CallAnalyticsHooks +import io.getstream.video.android.core.analytics.JoinRequestHooks +import io.getstream.video.android.core.analytics.WsHook import io.getstream.video.android.core.audio.StreamAudioDevice import io.getstream.video.android.core.call.FastReconnectResult import io.getstream.video.android.core.call.RtcSession @@ -76,9 +79,6 @@ import io.getstream.video.android.core.closedcaptions.ClosedCaptionsSettings import io.getstream.video.android.core.events.GoAwayEvent import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener -import io.getstream.video.android.core.events.reporting.ClientEventReporter -import io.getstream.video.android.core.events.reporting.ClientEventReporterErrorMappers -import io.getstream.video.android.core.events.reporting.JoinRequestTelemetryModel import io.getstream.video.android.core.events.reporting.PeerConnectionRole import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.faultinjector.FailureKey @@ -316,6 +316,11 @@ public class Call( _peerConnectionFactory = value } + internal val callAnalyticsHooks = CallAnalyticsHooks( + JoinRequestHooks(this.id, this.type, client.state.clientEventReporter), + WsHook(this.id, this.type, client.state.clientEventReporter), + ) + /** * Checks if the audioBitrateProfile has changed since the factory was created, * and recreates the factory if needed. This should only be called before joining. @@ -559,10 +564,6 @@ public class Call( logger.d { "[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions" } - val eventSessionId = client.state.clientEventReporter.reportCoordinatorJoinInitiated( - callType = this.type, - callId = this.id, - ) val permissionPass = clientImpl.permissionCheck.checkAndroidPermissionsGroup(clientImpl.context, this) // Check android permissions and log a warning to make sure developers requested adequate permissions prior to using the call. @@ -601,7 +602,7 @@ public class Call( ring, notify, hintHighScaleLivestreamPublisher, - JoinRequestTelemetryModel(eventSessionId, retryCount), + TelemetryModel(retryCount), ) if (tempResult is Success) { // we initialise the camera, mic and other according to local + backend settings @@ -626,11 +627,9 @@ public class Call( logger.e { "Join failed with error $result" } if (isPermanentError(result.value)) { state._connection.value = RealtimeConnection.Failed(result.value) - client.state.clientEventReporter.reportCoordinatorJoinCompleted( - eventSessionId = eventSessionId, - success = false, - retryCount = retryCount, - failureReason = result.value.message, + callAnalyticsHooks.joinRequestHooks.onJoinRequestPermanentError( + retryCount, + result.value.message, ) return result } else { @@ -642,12 +641,7 @@ public class Call( session.value = null val errorMessage = "Join failed after 3 retries" state._connection.value = RealtimeConnection.Failed(errorMessage) - client.state.clientEventReporter.reportCoordinatorJoinCompleted( - eventSessionId = id, - success = false, - retryCount = retryCount, - failureReason = errorMessage, - ) + callAnalyticsHooks.joinRequestHooks.onJoinRequestRetryExhausted(retryCount, errorMessage) return Failure(value = Error.GenericError(errorMessage)) } @@ -692,7 +686,7 @@ public class Call( ring: Boolean = false, notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, - joinRequestTelemetryModel: JoinRequestTelemetryModel, + telemetryModel: TelemetryModel, ): Result> { nonFastReconnectAttempts = 0 sfuEvents?.cancel() @@ -726,7 +720,7 @@ public class Call( ring = ring, notify = notify, hintHighScaleLivestreamPublisher = hintHighScaleLivestreamPublisher, - joinRequestTelemetryModel = joinRequestTelemetryModel, + telemetryModel = telemetryModel, ) if (result !is Success) { @@ -1026,53 +1020,12 @@ public class Call( WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_REJOIN -> { nonFastReconnectAttempts++ - val telemetrySessionId = - client.state.clientEventReporter.reportCoordinatorJoinInitiated( - this.id, - this.type, - ) - - val result = - reconnectRejoin( - reason, - JoinRequestTelemetryModel( - telemetrySessionId, - nonFastReconnectAttempts, - ), - ) - if (result !is ReconnectOutcome.Success) { - client.state.clientEventReporter.reportCoordinatorJoinCompleted( - telemetrySessionId, - false, - nonFastReconnectAttempts, - ClientEventReporterErrorMappers().getFailureReason(result), - ) - } - - result + reconnectRejoin(reason, TelemetryModel(nonFastReconnectAttempts)) } WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_MIGRATE -> { nonFastReconnectAttempts++ - val telemetrySessionId = - client.state.clientEventReporter.reportCoordinatorJoinInitiated( - this.id, - this.type, - ) - val result = - reconnectMigrate( - JoinRequestTelemetryModel( - telemetrySessionId, - nonFastReconnectAttempts, - ), - ) - client.state.clientEventReporter.reportCoordinatorJoinCompleted( - telemetrySessionId, - result is ReconnectOutcome.Success, - nonFastReconnectAttempts, - ClientEventReporterErrorMappers().getFailureReason(result), - ) - result + reconnectMigrate(TelemetryModel(nonFastReconnectAttempts)) } WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT -> @@ -1181,7 +1134,7 @@ public class Call( */ private suspend fun reconnectRejoin( reason: String, - joinRequestTelemetryModel: JoinRequestTelemetryModel, + telemetryModel: TelemetryModel, ): ReconnectOutcome { logger.d { "[reconnectRejoin] reconnectAttempts=$nonFastReconnectAttempts" } state._connection.value = RealtimeConnection.Reconnecting @@ -1192,7 +1145,7 @@ public class Call( reconnectStartTime = System.currentTimeMillis() val joinResponse = - joinRequest(location = loc, joinRequestTelemetryModel = joinRequestTelemetryModel) + joinRequest(location = loc, telemetryModel = telemetryModel) if (joinResponse !is Success) { return ReconnectOutcome.Failed( Exception("Failed to get join response: ${joinResponse.errorOrNull()}"), @@ -1235,7 +1188,7 @@ public class Call( val result = newSession.connectInternal( reconnectDetails, currentOptions, - TelemetryModel(joinRequestTelemetryModel.retryAttempt), + TelemetryModel(telemetryModel.retryAttempt), ) ) { is SfuConnectionResult.Connected -> { @@ -1251,9 +1204,7 @@ public class Call( * Migrate to another SFU. Reuses the same session ID — the SFU * identifies the participant via from_sfu_id, not previous_session_id. */ - private suspend fun reconnectMigrate( - joinRequestTelemetryModel: JoinRequestTelemetryModel, - ): ReconnectOutcome { + private suspend fun reconnectMigrate(telemetryModel: TelemetryModel): ReconnectOutcome { logger.d { "[reconnectMigrate] Migrating" } state._connection.value = RealtimeConnection.Migrating val loc = location ?: "auto" @@ -1267,7 +1218,7 @@ public class Call( joinRequest( location = loc, migratingFrom = oldSession.sfuName, - joinRequestTelemetryModel = joinRequestTelemetryModel, + telemetryModel = telemetryModel, ) if (joinResponse !is Success) { return ReconnectOutcome.Failed( @@ -1316,7 +1267,7 @@ public class Call( val result = newSession.connectInternal( reconnectDetails, currentOptions, - TelemetryModel(joinRequestTelemetryModel.retryAttempt), + TelemetryModel(telemetryModel.retryAttempt), ) when (result) { is SfuConnectionResult.Connected -> { @@ -1397,14 +1348,8 @@ public class Call( clientImpl.scope.launch { val leaveReason = "[reason=${reason::class.simpleName}, message=${reason.message}]" -// val leaveReason = "[reason=$reason]" - client.state.clientEventReporter.let { reporter -> - val abortReason = when (reason) { - is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE - else -> ClientEventReporter.AbortReason.CLIENT_ABORTED - } - reporter.abortAllInFlight(abortReason) - } + callAnalyticsHooks.onCallLeave(reason) + safeCall { session.value?.sfuTracer?.trace("leave-call", leaveReason) val stats = collectStats() @@ -1946,14 +1891,14 @@ public class Call( ring: Boolean = false, notify: Boolean = false, hintHighScaleLivestreamPublisher: Boolean? = null, - joinRequestTelemetryModel: JoinRequestTelemetryModel, + telemetryModel: TelemetryModel, ): Result { with(client.state.failureInjector) { if (isEnabled(FailureKey.FAIL_JOIN_CALL)) { return sendFailResult(FailureKey.FAIL_JOIN_CALL) } } - + callAnalyticsHooks.joinRequestHooks.onJoinRequestStart() val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( type, id, @@ -1971,11 +1916,9 @@ public class Call( hintHighScaleLivestreamPublisher = hintHighScaleLivestreamPublisher, ) result.onSuccess { - client.state.clientEventReporter.reportCoordinatorJoinCompleted( - eventSessionId = joinRequestTelemetryModel.eventSessionId, - success = true, - retryCount = joinRequestTelemetryModel.retryAttempt, - callSessionId = it.call.currentSessionId, + callAnalyticsHooks.joinRequestHooks.onJoinRequestSuccess( + telemetryModel, + it.call.currentSessionId, ) state.updateFromResponse(it) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt new file mode 100644 index 00000000000..0976337d1fe --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.CallLeaveReason +import io.getstream.video.android.core.events.reporting.ClientEventReporter + +internal class CallAnalyticsHooks(val joinRequestHooks: JoinRequestHooks, val wsHook: WsHook) { + + val eventReporter = joinRequestHooks.eventReporter + fun onCallLeave(callLeaveReason: CallLeaveReason) { + val abortReason = when (callLeaveReason) { + is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE + else -> ClientEventReporter.AbortReason.CLIENT_ABORTED + } + eventReporter.abortAllInFlight(abortReason) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt new file mode 100644 index 00000000000..c470c36a444 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.events.reporting.TelemetryModel + +internal class JoinRequestHooks(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { + + var eventSessionId = "" + fun onJoinRequestStart() { + eventSessionId = eventReporter.reportCoordinatorJoinInitiated( + callType = callType, + callId = callId, + ) + } + fun onJoinRequestSuccess(telemetryModel: TelemetryModel, currentSessionId: String) { + if (eventSessionId.isNotEmpty()) { + eventReporter.reportCoordinatorJoinCompleted( + eventSessionId = eventSessionId, + success = true, + retryCount = telemetryModel.retryAttempt, + callSessionId = currentSessionId, + ) + } + } + + fun onJoinRequestPermanentError(retryCount: Int, message: String) { + if (eventSessionId.isNotEmpty()) { + eventReporter.reportCoordinatorJoinCompleted( + eventSessionId = eventSessionId, + success = false, + retryCount = retryCount, + failureReason = message, + ) + } + } + + fun onJoinRequestRetryExhausted(retryCount: Int, message: String) { + onJoinRequestPermanentError(retryCount, message) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt new file mode 100644 index 00000000000..a549e721054 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.events.reporting.ClientEventReporter + +internal class WsHook(val callId: String, val callType: String, val reporter: ClientEventReporter) { + var telemetryWsEventSessionId = "" + fun onWsInitiated(sfuName: String, wasPreviouslyConnected: Boolean) { + telemetryWsEventSessionId = reporter.reportWsJoinInitiated( + callId = callId, + callType = callType, + sfuId = sfuName, + wasPreviouslyConnected = wasPreviouslyConnected, + ) + } + + fun onWsCompleted( + success: Boolean, + retryCount: Int, + failureReason: String? = null, + failureCode: String? = null, + ) { + if (telemetryWsEventSessionId.isNotEmpty()) { + reporter.reportWsJoinCompleted( + eventSessionId = telemetryWsEventSessionId, + success = success, + retryCount = retryCount, + failureReason = failureReason, + failureCode = failureCode, + ) + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index df7effa1e6b..59ef0ec1826 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -884,13 +884,8 @@ public class RtcSession internal constructor( telemetryModel: TelemetryModel? = null, ): SfuConnectionResult { logger.i { "[connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } - val reporter = call.client.state.clientEventReporter - val telemetryWsEventSessionId = reporter.reportWsJoinInitiated( - callId = call.id, - callType = call.type, - sfuId = sfuName, - wasPreviouslyConnected = reconnectDetails != null, - ) + call.callAnalyticsHooks.wsHook.onWsInitiated(sfuName, reconnectDetails != null) + val request = buildJoinRequest(reconnectDetails, options) sfuTracer.trace( PeerConnectionTraceKey.JOIN_REQUEST.value, @@ -907,8 +902,7 @@ public class RtcSession internal constructor( } return when (terminalState) { is SfuSocketState.Connected -> { - reporter.reportWsJoinCompleted( - telemetryWsEventSessionId, + call.callAnalyticsHooks.wsHook.onWsCompleted( success = true, retryCount = 0, ) @@ -926,8 +920,7 @@ public class RtcSession internal constructor( } logger.w { "[connectInternal] $msg" } sfuTracer.trace("connect-failed", msg) - reporter.reportWsJoinCompleted( - telemetryWsEventSessionId, + call.callAnalyticsHooks.wsHook.onWsCompleted( success = false, retryCount = telemetryModel?.retryAttempt ?: 0, failureReason = msg, @@ -938,8 +931,7 @@ public class RtcSession internal constructor( } else -> { sfuTracer.trace("connect-failed", "Connection timed out") - reporter.reportWsJoinCompleted( - telemetryWsEventSessionId, + call.callAnalyticsHooks.wsHook.onWsCompleted( success = false, retryCount = telemetryModel?.retryAttempt ?: 0, failureReason = ClientEventReporter.FailureCodes.SFU_REQUEST_TIMEOUT.message, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt index dd8a5a611d7..b868a3ea2f8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt @@ -16,8 +16,4 @@ package io.getstream.video.android.core.events.reporting -internal data class TelemetryModel(val retryAttempt: Int) -internal data class JoinRequestTelemetryModel( - val eventSessionId: EventSessionId, - val retryAttempt: Int, -) +internal data class TelemetryModel(val retryAttempt: Int) \ No newline at end of file From 144ff57ff5418bd4c65db7bc661ad0656cc8ba3d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 27 May 2026 16:42:09 +0530 Subject: [PATCH 22/63] send call leave reason from ui layer --- .../api/stream-video-android-core.api | 81 +++++++++++++++++++ .../io/getstream/video/android/core/Call.kt | 22 ++++- .../video/android/core/CallLeaveReason.kt | 22 +++-- .../telecom/IncomingCallTelecomAction.kt | 9 ++- .../ui/StreamCallActivityComposeDelegate.kt | 9 ++- .../components/audio/AudioControlActions.kt | 9 ++- .../ui/components/audio/AudioRoomContent.kt | 9 ++- .../components/call/activecall/CallContent.kt | 9 ++- .../api/stream-video-android-ui-core.api | 8 ++ .../android/ui/common/AbstractCallActivity.kt | 19 ++++- .../ui/common/ActivityCallOperations.kt | 11 +++ ...tivityCallOperationsWithCallLeaveReason.kt | 34 ++++++++ .../android/ui/common/StreamCallActivity.kt | 60 ++++++++++++-- 13 files changed, 277 insertions(+), 25 deletions(-) create mode 100644 stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason.kt diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 99e4db0c7cf..c3618d5093a 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -8978,6 +8978,87 @@ public final class io/getstream/video/android/core/CallKt { public static final field sfuReconnectTimeoutMillis I } +public final class io/getstream/video/android/core/CallLeaveReason$Backend : io/getstream/video/android/core/CallLeaveReason { + public fun (Lio/getstream/video/android/core/BackendCause;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Lio/getstream/video/android/core/BackendCause;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/core/BackendCause; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Lio/getstream/video/android/core/BackendCause;Ljava/lang/String;Ljava/util/Map;)Lio/getstream/video/android/core/CallLeaveReason$Backend; + public static synthetic fun copy$default (Lio/getstream/video/android/core/CallLeaveReason$Backend;Lio/getstream/video/android/core/BackendCause;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/video/android/core/CallLeaveReason$Backend; + public fun equals (Ljava/lang/Object;)Z + public final fun getCause ()Lio/getstream/video/android/core/BackendCause; + public fun getMessage ()Ljava/lang/String; + public fun getMetadata ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/CallLeaveReason$Custom : io/getstream/video/android/core/CallLeaveReason { + public fun ()V + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/getstream/video/android/core/CallLeaveReason$Custom; + public static synthetic fun copy$default (Lio/getstream/video/android/core/CallLeaveReason$Custom;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/video/android/core/CallLeaveReason$Custom; + public fun equals (Ljava/lang/Object;)Z + public fun getMessage ()Ljava/lang/String; + public fun getMetadata ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/CallLeaveReason$RetryExhausted : io/getstream/video/android/core/CallLeaveReason { + public fun (ILjava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (ILjava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/util/Map; + public final fun copy (ILjava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/getstream/video/android/core/CallLeaveReason$RetryExhausted; + public static synthetic fun copy$default (Lio/getstream/video/android/core/CallLeaveReason$RetryExhausted;ILjava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/video/android/core/CallLeaveReason$RetryExhausted; + public fun equals (Ljava/lang/Object;)Z + public final fun getFailureCode ()Ljava/lang/String; + public fun getMessage ()Ljava/lang/String; + public fun getMetadata ()Ljava/util/Map; + public final fun getRetryCount ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/CallLeaveReason$SdkDriven : io/getstream/video/android/core/CallLeaveReason { + public fun (Lio/getstream/video/android/core/SdkCause;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Lio/getstream/video/android/core/SdkCause;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/core/SdkCause; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Lio/getstream/video/android/core/SdkCause;Ljava/lang/String;Ljava/util/Map;)Lio/getstream/video/android/core/CallLeaveReason$SdkDriven; + public static synthetic fun copy$default (Lio/getstream/video/android/core/CallLeaveReason$SdkDriven;Lio/getstream/video/android/core/SdkCause;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/video/android/core/CallLeaveReason$SdkDriven; + public fun equals (Ljava/lang/Object;)Z + public final fun getCause ()Lio/getstream/video/android/core/SdkCause; + public fun getMessage ()Ljava/lang/String; + public fun getMetadata ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/CallLeaveReason$UserAction : io/getstream/video/android/core/CallLeaveReason { + public fun (Lio/getstream/video/android/core/UserActionCause;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Lio/getstream/video/android/core/UserActionCause;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/core/UserActionCause; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Lio/getstream/video/android/core/UserActionCause;Ljava/lang/String;Ljava/util/Map;)Lio/getstream/video/android/core/CallLeaveReason$UserAction; + public static synthetic fun copy$default (Lio/getstream/video/android/core/CallLeaveReason$UserAction;Lio/getstream/video/android/core/UserActionCause;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/video/android/core/CallLeaveReason$UserAction; + public fun equals (Ljava/lang/Object;)Z + public final fun getCause ()Lio/getstream/video/android/core/UserActionCause; + public fun getMessage ()Ljava/lang/String; + public fun getMetadata ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/video/android/core/CallState { public fun (Lio/getstream/video/android/core/StreamVideo;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/model/User;Lkotlinx/coroutines/CoroutineScope;)V public final fun clearParticipants ()V diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 459ac870186..fd69b286ac4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -666,7 +666,12 @@ public class Call( }.onError { logger.e { "[joinAndRing] Ring failed #ringing; #track; error: $it" } state.toggleJoinAndRingProgress(false) - leave("ring-failed (${it.message})") + leave( + CallLeaveReason.Backend( + BackendCause.RING_FAILED, + message = "ring-failed (${it.message})", + ), + ) } } } @@ -1037,7 +1042,9 @@ public class Call( is ReconnectOutcome.Disconnect -> { logger.w { "[reconnect] DISCONNECT requested — leaving call" } - leave("SFU:DISCONNECT") + leave( + CallLeaveReason.Backend(BackendCause.SFU_DISCONNECT, "SFU:DISCONNECT"), + ) break } @@ -1079,7 +1086,13 @@ public class Call( if (state.connection.value is RealtimeConnection.ReconnectingFailed) { logger.w { "[reconnect] All recovery attempts exhausted — leaving call ($reason)" } - leave("reconnect-failed:$reason") + leave( + CallLeaveReason.RetryExhausted( + loopIteration, + "reconnect-failed", + "All recovery attempts exhausted — leaving call ($reason)", + ), + ) } } finally { // Always release the mutex — even on exceptions or coroutine @@ -1297,7 +1310,8 @@ public class Call( // endregion /** Leave the call, but don't end it for other users */ - internal fun leave(reason: CallLeaveReason) { + @InternalStreamVideoApi + fun leave(reason: CallLeaveReason) { logger.d { "[leave] #ringing; call_cid:$cid" } internalLeave(reason) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt index 7b1d2ce8c7a..0aac13ab2b5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt @@ -16,7 +16,10 @@ package io.getstream.video.android.core -internal sealed interface CallLeaveReason { +import io.getstream.video.android.core.internal.InternalStreamVideoApi + +@InternalStreamVideoApi +sealed interface CallLeaveReason { val message: String? @@ -32,7 +35,6 @@ internal sealed interface CallLeaveReason { /** The backend ended or rejected the call (CallEndedEvent, SFU termination, etc.). */ data class Backend( val cause: BackendCause, - val backendCode: String? = null, override val message: String?, override val metadata: Map = emptyMap(), ) : CallLeaveReason @@ -59,7 +61,8 @@ internal sealed interface CallLeaveReason { ) : CallLeaveReason } -internal enum class SdkCause { +@InternalStreamVideoApi +enum class SdkCause { /** App was swiped from the recents screen. */ TASK_REMOVED, @@ -76,20 +79,29 @@ internal enum class SdkCause { LOCAL_CALL_MISSED_EVENT, REJECTED_BY_ALL, END_CALL, + PIP_ERROR, + PIP_STOPPED, + ACTIVITY_DESTROYED, } -internal enum class UserActionCause { +@InternalStreamVideoApi +enum class UserActionCause { /** User rejected the call from a paired wearable device. */ WEARABLE_REJECTED, + WEARABLE_CANCEL, /** A [io.getstream.video.android.core.CallJoinInterceptor] aborted the join sequence. */ CALL_JOIN_ABORT, REJECTED_BY_SELF, + CANCELLED_BY_SELF, LEAVE_FROM_NOTIFICATION, } -internal enum class BackendCause { +@InternalStreamVideoApi +enum class BackendCause { LEAVE_TIMEOUT_AFTER_DISCONNECT, + SFU_DISCONNECT, CALL_ENDED_EVENT, CALL_ENDED_SFU_EVENT, + RING_FAILED, } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt index c30d84a49b1..7e711e7022c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt @@ -16,10 +16,12 @@ package io.getstream.video.android.core.notifications.internal.telecom +import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.ExternalCallRejectionHandler import io.getstream.video.android.core.ExternalCallRejectionSource import io.getstream.video.android.core.RingingState import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.UserActionCause import io.getstream.video.android.core.notifications.IncomingNotificationAction import io.getstream.video.android.model.StreamCallId import kotlinx.coroutines.launch @@ -48,7 +50,12 @@ internal class IncomingCallTelecomAction(private val streamVideo: StreamVideo) { } is RingingState.Active -> { - streamVideo.call(callId.type, callId.id).leave() + streamVideo.call(callId.type, callId.id).leave( + CallLeaveReason.UserAction( + UserActionCause.WEARABLE_CANCEL, + "cancel from wearable", + ), + ) } is RingingState.Incoming -> { val pendingIntentMap = streamVideo.call(callId.type, callId.id) diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt index b03211e7723..b741845ec7d 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt @@ -70,9 +70,11 @@ import io.getstream.video.android.compose.ui.components.call.ringing.RingingCall import io.getstream.video.android.compose.ui.components.livestream.LivestreamPlayer import io.getstream.video.android.compose.ui.components.video.config.videoRenderConfig import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.MemberState import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.UserActionCause import io.getstream.video.android.core.call.CallType import io.getstream.video.android.core.call.state.CallAction import io.getstream.video.android.core.call.state.CancelCall @@ -224,7 +226,12 @@ public open class StreamCallActivityComposeDelegate : StreamCallActivityComposeU call = call, centerContent = { }, onCallAction = { - call.leave() + call.leave( + CallLeaveReason.UserAction( + UserActionCause.CANCELLED_BY_SELF, + "Cancelled the call", + ), + ) safeFinish() }, ) diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/audio/AudioControlActions.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/audio/AudioControlActions.kt index c144b3f0da1..6f5879c8017 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/audio/AudioControlActions.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/audio/AudioControlActions.kt @@ -38,6 +38,8 @@ import io.getstream.video.android.compose.theme.VideoTheme import io.getstream.video.android.compose.ui.components.base.StreamButton import io.getstream.video.android.compose.ui.components.call.controls.actions.ToggleMicrophoneAction import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason +import io.getstream.video.android.core.UserActionCause import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall @@ -69,7 +71,12 @@ public fun AudioControlActions( style = VideoTheme.styles.buttonStyles.secondaryButtonStyle(), onClick = { onLeaveRoom?.invoke() ?: let { - call.leave() + call.leave( + CallLeaveReason.UserAction( + UserActionCause.CANCELLED_BY_SELF, + "Cancelled the call", + ), + ) activity?.onBackPressedDispatcher?.onBackPressed() } }, diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/audio/AudioRoomContent.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/audio/AudioRoomContent.kt index a1778716882..279ce5a940c 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/audio/AudioRoomContent.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/audio/AudioRoomContent.kt @@ -47,7 +47,9 @@ import io.getstream.video.android.compose.pip.enterPictureInPicture import io.getstream.video.android.compose.pip.rememberIsInPipMode import io.getstream.video.android.compose.theme.VideoTheme import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.ParticipantState +import io.getstream.video.android.core.SdkCause import io.getstream.video.android.core.pip.PictureInPictureConfiguration import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall @@ -139,7 +141,12 @@ public fun AudioRoomContent( enterPictureInPicture(context = context, call = call, pictureInPictureConfiguration) } catch (e: Exception) { StreamLog.e(tag = "AudioRoomContent") { e.stackTraceToString() } - call.leave() + call.leave( + CallLeaveReason.SdkDriven( + SdkCause.PIP_ERROR, + "Error in Pip: ${e.message}", + ), + ) } } else { onBackPressed.invoke() diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/activecall/CallContent.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/activecall/CallContent.kt index 505337986fa..d4718acdd78 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/activecall/CallContent.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/activecall/CallContent.kt @@ -76,7 +76,9 @@ import io.getstream.video.android.compose.ui.components.call.renderer.VideoRende import io.getstream.video.android.compose.ui.components.call.renderer.internal.LocalVideoContentSize import io.getstream.video.android.compose.ui.components.video.VideoRenderer import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.ParticipantState +import io.getstream.video.android.core.SdkCause import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.call.state.CallAction import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig @@ -189,7 +191,12 @@ public fun CallContent( enterPictureInPicture(context = context, call = call, pictureInPictureConfiguration) } catch (e: Exception) { StreamLog.e(tag = "CallContent") { e.stackTraceToString() } - call.leave() + call.leave( + CallLeaveReason.SdkDriven( + SdkCause.PIP_ERROR, + "Error in Pip: ${e.message}", + ), + ) } } else { onBackPressed.invoke() diff --git a/stream-video-android-ui-core/api/stream-video-android-ui-core.api b/stream-video-android-ui-core/api/stream-video-android-ui-core.api index 35f2bde4592..975f096bb87 100644 --- a/stream-video-android-ui-core/api/stream-video-android-ui-core.api +++ b/stream-video-android-ui-core/api/stream-video-android-ui-core.api @@ -35,12 +35,19 @@ public abstract interface class io/getstream/video/android/ui/common/ActivityCal public static synthetic fun get$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public abstract fun join (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V public static synthetic fun join$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public fun leave (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V public abstract fun leave (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun leave$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static synthetic fun leave$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public abstract fun reject (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V public static synthetic fun reject$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V } +public abstract interface class io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason { + public fun leave (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun leave$default (Lio/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V +} + public abstract class io/getstream/video/android/ui/common/IgnoreReason { } @@ -107,6 +114,7 @@ public abstract class io/getstream/video/android/ui/common/StreamCallActivity : public fun isCurrentAcceptedCall (Lio/getstream/video/android/core/Call;)Z public fun isVideoCall (Lio/getstream/video/android/core/Call;)Z public fun join (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V + public fun leave (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V public fun leave (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V protected fun loadConfigFromIntent (Landroid/content/Intent;)Lio/getstream/video/android/ui/common/StreamCallActivityConfiguration; public fun onBackPressed (Lio/getstream/video/android/core/Call;)V diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/AbstractCallActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/AbstractCallActivity.kt index 845f2b20905..85e392759ae 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/AbstractCallActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/AbstractCallActivity.kt @@ -30,6 +30,8 @@ import android.view.WindowInsetsController import android.view.WindowManager import androidx.activity.ComponentActivity import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason +import io.getstream.video.android.core.SdkCause import io.getstream.video.android.core.call.state.ToggleScreenConfiguration import io.getstream.video.android.core.pip.PictureInPictureConfiguration import io.getstream.video.android.model.StreamCallId @@ -125,7 +127,12 @@ public abstract class AbstractCallActivity : ComponentActivity() { try { enterPictureInPicture() } catch (error: Throwable) { - call.leave() + call.leave( + CallLeaveReason.SdkDriven( + SdkCause.PIP_ERROR, + "Error in Pip: ${error.message}", + ), + ) } } @@ -171,14 +178,18 @@ public abstract class AbstractCallActivity : ComponentActivity() { val isInPiP = isInPictureInPictureMode if (isInPiP) { - call.leave() + call.leave(CallLeaveReason.SdkDriven(SdkCause.PIP_STOPPED, "PIP stopped")) } } override fun onDestroy() { super.onDestroy() - - call.leave() + call.leave( + CallLeaveReason.SdkDriven( + SdkCause.ACTIVITY_DESTROYED, + "${this.localClassName} destroyed", + ), + ) } override fun onConfigurationChanged(newConfig: Configuration) { diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperations.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperations.kt index 1eb8c2db341..49bade2dea3 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperations.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperations.kt @@ -17,6 +17,7 @@ package io.getstream.video.android.ui.common import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.ui.common.util.StreamCallActivityDelicateApi @@ -46,6 +47,16 @@ public interface ActivityCallOperations { onError: (suspend (Exception) -> Unit)? = null, ) +// @StreamCallActivityDelicateApi +// public fun leave( +// call: Call, +// callLeaveReason: CallLeaveReason, +// onSuccess: (suspend (Call) -> Unit)? = null, +// onError: (suspend (Exception) -> Unit)? = null, +// ) { +// // Do nothing +// } + @StreamCallActivityDelicateApi public fun end( call: Call, diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason.kt new file mode 100644 index 00000000000..e8646c664c4 --- /dev/null +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.ui.common + +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallLeaveReason +import io.getstream.video.android.ui.common.util.StreamCallActivityDelicateApi + +public interface ActivityCallOperationsWithCallLeaveReason: ActivityCallOperations { + + @StreamCallActivityDelicateApi + public fun leave( + call: Call, + callLeaveReason: CallLeaveReason, + onSuccess: (suspend (Call) -> Unit)? = null, + onError: (suspend (Exception) -> Unit)? = null, + ) { + // Do nothing + } +} diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt index 9d7c3f15e4b..12217844289 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt @@ -47,9 +47,12 @@ import io.getstream.result.onErrorSuspend import io.getstream.result.onSuccessSuspend import io.getstream.video.android.core.Call import io.getstream.video.android.core.CallJoinInterceptor +import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.DeviceStatus import io.getstream.video.android.core.RealtimeConnection +import io.getstream.video.android.core.SdkCause import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.UserActionCause import io.getstream.video.android.core.call.RtcSession import io.getstream.video.android.core.call.state.AcceptCall import io.getstream.video.android.core.call.state.CallAction @@ -92,7 +95,7 @@ import kotlinx.coroutines.withContext import java.util.Locale @OptIn(StreamCallActivityDelicateApi::class) -public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOperations { +public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOperationsWithCallLeaveReason { // Factory and creation public companion object { // Extra keys @@ -473,7 +476,15 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper participantCountJob = null // We want to leave the ongoing active call - leave(activeCall, onSuccessFinish, onErrorFinish) + leave( + activeCall, + CallLeaveReason.UserAction( + UserActionCause.CANCELLED_BY_SELF, + "Leaving current ongoing call to pick incoming-call with call_cid:$newCallCid", + ), + onSuccessFinish, + onErrorFinish, + ) lifecycleScope.launch(Dispatchers.Default) { delay( getCallTransitionTime(), @@ -764,7 +775,12 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper * @param call the call. */ public open fun onBackPressed(call: Call) { - leave(call, onSuccessFinish, onErrorFinish) + leave( + call, + CallLeaveReason.UserAction(UserActionCause.CANCELLED_BY_SELF, "on back press"), + onSuccessFinish, + onErrorFinish, + ) } // Decision making @@ -1037,7 +1053,12 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper } // Leave regardless of outcome - call.leave() + call.leave( + CallLeaveReason.UserAction( + UserActionCause.REJECTED_BY_SELF, + "Rejected the call", + ), + ) } } @@ -1071,12 +1092,21 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper call: Call, onSuccess: (suspend (Call) -> Unit)?, onError: (suspend (Exception) -> Unit)?, + ) { + leave(call, CallLeaveReason.Custom(""), onSuccess, onError) + } + + override fun leave( + call: Call, + callLeaveReason: CallLeaveReason, + onSuccess: (suspend (Call) -> Unit)?, + onError: (suspend (Exception) -> Unit)?, ) { logger.d { "Leave call, ${call.cid}" } lifecycleScope.launch(Dispatchers.IO) { // Will quietly leave the call, leaving it intact for the other participants. try { - call.leave() + call.leave(callLeaveReason) onSuccess?.invoke(call) } catch (e: Exception) { onError?.invoke(e) @@ -1116,7 +1146,15 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper logger.i { "[onCallAction] #ringing; action: $action, call.cid: ${call.cid}" } when (action) { is LeaveCall -> { - leave(call, onSuccessFinish, onErrorFinish) + leave( + call, + CallLeaveReason.UserAction( + UserActionCause.CANCELLED_BY_SELF, + "cancelling the call", + ), + onSuccessFinish, + onErrorFinish, + ) } is DeclineCall -> { @@ -1162,7 +1200,15 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper when (event) { is CallEndedEvent, is CallEndedSfuEvent, is CallSessionEndedEvent, is LocalCallMissedEvent -> { // In any case finish the activity, the call is done for - leave(call, onSuccess = onSuccessFinish, onError = onErrorFinish) + leave( + call, + CallLeaveReason.SdkDriven( + SdkCause.END_CALL, + "received event: ${event.javaClass.name}", + ), + onSuccess = onSuccessFinish, + onError = onErrorFinish, + ) } is ParticipantLeftEvent, is CallSessionParticipantLeftEvent -> { From b00efd049ecbd38d1f68de5d69410b5a28066134 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 28 May 2026 14:45:34 +0530 Subject: [PATCH 23/63] spotless --- .../video/android/core/events/reporting/TelemetryModel.kt | 2 +- .../getstream/video/android/ui/common/ActivityCallOperations.kt | 1 - .../ui/common/ActivityCallOperationsWithCallLeaveReason.kt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt index b868a3ea2f8..392d594e82f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt @@ -16,4 +16,4 @@ package io.getstream.video.android.core.events.reporting -internal data class TelemetryModel(val retryAttempt: Int) \ No newline at end of file +internal data class TelemetryModel(val retryAttempt: Int) diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperations.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperations.kt index 49bade2dea3..57a810c8c37 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperations.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperations.kt @@ -17,7 +17,6 @@ package io.getstream.video.android.ui.common import io.getstream.video.android.core.Call -import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.ui.common.util.StreamCallActivityDelicateApi diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason.kt index e8646c664c4..7a6c0ad5b25 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason.kt @@ -20,7 +20,7 @@ import io.getstream.video.android.core.Call import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.ui.common.util.StreamCallActivityDelicateApi -public interface ActivityCallOperationsWithCallLeaveReason: ActivityCallOperations { +public interface ActivityCallOperationsWithCallLeaveReason : ActivityCallOperations { @StreamCallActivityDelicateApi public fun leave( From 11983561078e7a6fdb5ee17c60a401e59a670d5f Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 28 May 2026 14:46:38 +0530 Subject: [PATCH 24/63] api dump --- .../api/stream-video-android-ui-core.api | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stream-video-android-ui-core/api/stream-video-android-ui-core.api b/stream-video-android-ui-core/api/stream-video-android-ui-core.api index 975f096bb87..100fe7788fa 100644 --- a/stream-video-android-ui-core/api/stream-video-android-ui-core.api +++ b/stream-video-android-ui-core/api/stream-video-android-ui-core.api @@ -35,15 +35,13 @@ public abstract interface class io/getstream/video/android/ui/common/ActivityCal public static synthetic fun get$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public abstract fun join (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V public static synthetic fun join$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V - public fun leave (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V public abstract fun leave (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V - public static synthetic fun leave$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static synthetic fun leave$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public abstract fun reject (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V public static synthetic fun reject$default (Lio/getstream/video/android/ui/common/ActivityCallOperations;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V } -public abstract interface class io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason { +public abstract interface class io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason : io/getstream/video/android/ui/common/ActivityCallOperations { public fun leave (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V public static synthetic fun leave$default (Lio/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/CallLeaveReason;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V } @@ -87,7 +85,7 @@ public abstract interface class io/getstream/video/android/ui/common/StreamActiv public abstract fun setContent (Lio/getstream/video/android/ui/common/StreamCallActivity;Lio/getstream/video/android/core/Call;)V } -public abstract class io/getstream/video/android/ui/common/StreamCallActivity : androidx/activity/ComponentActivity, io/getstream/video/android/ui/common/ActivityCallOperations { +public abstract class io/getstream/video/android/ui/common/StreamCallActivity : androidx/activity/ComponentActivity, io/getstream/video/android/ui/common/ActivityCallOperationsWithCallLeaveReason { public static final field Companion Lio/getstream/video/android/ui/common/StreamCallActivity$Companion; public fun ()V public fun accept (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V From 3254302a69f8cc877278b33a9266e290026f2ab1 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 28 May 2026 16:59:14 +0530 Subject: [PATCH 25/63] fix: fix stage attempts --- .../io/getstream/video/android/core/Call.kt | 4 ++ .../core/analytics/JoinRequestHooks.kt | 53 ++++++++++++------- .../video/android/core/analytics/Stage.kt | 3 ++ .../video/android/core/analytics/WsHook.kt | 39 +++++++++----- 4 files changed, 67 insertions(+), 32 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index fd69b286ac4..25f71ff9a07 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -1086,6 +1086,10 @@ public class Call( if (state.connection.value is RealtimeConnection.ReconnectingFailed) { logger.w { "[reconnect] All recovery attempts exhausted — leaving call ($reason)" } + callAnalyticsHooks.joinRequestHooks.onJoinRequestRetryExhausted( + loopIteration, + "All recovery attempts exhausted — leaving call ($reason)", + ) leave( CallLeaveReason.RetryExhausted( loopIteration, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index c470c36a444..51b1c8e79ff 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -22,35 +22,52 @@ import io.getstream.video.android.core.events.reporting.TelemetryModel internal class JoinRequestHooks(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { var eventSessionId = "" + var joinStage = Stage.NOT_STARTED fun onJoinRequestStart() { - eventSessionId = eventReporter.reportCoordinatorJoinInitiated( - callType = callType, - callId = callId, - ) + if (joinStage == Stage.NOT_STARTED) { + eventSessionId = eventReporter.reportCoordinatorJoinInitiated( + callType = callType, + callId = callId, + ) + joinStage = Stage.IN_PROGRESS + } + } fun onJoinRequestSuccess(telemetryModel: TelemetryModel, currentSessionId: String) { - if (eventSessionId.isNotEmpty()) { - eventReporter.reportCoordinatorJoinCompleted( - eventSessionId = eventSessionId, - success = true, - retryCount = telemetryModel.retryAttempt, - callSessionId = currentSessionId, - ) + if (joinStage == Stage.IN_PROGRESS) { + if (eventSessionId.isNotEmpty()) { + eventReporter.reportCoordinatorJoinCompleted( + eventSessionId = eventSessionId, + success = true, + retryCount = telemetryModel.retryAttempt, + callSessionId = currentSessionId, + ) + } + resetStage() } + } fun onJoinRequestPermanentError(retryCount: Int, message: String) { - if (eventSessionId.isNotEmpty()) { - eventReporter.reportCoordinatorJoinCompleted( - eventSessionId = eventSessionId, - success = false, - retryCount = retryCount, - failureReason = message, - ) + if (joinStage == Stage.IN_PROGRESS) { + if (eventSessionId.isNotEmpty()) { + eventReporter.reportCoordinatorJoinCompleted( + eventSessionId = eventSessionId, + success = false, + retryCount = retryCount, + failureReason = message, + ) + } + resetStage() } + } fun onJoinRequestRetryExhausted(retryCount: Int, message: String) { onJoinRequestPermanentError(retryCount, message) } + + fun resetStage() { + joinStage = Stage.NOT_STARTED + } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt new file mode 100644 index 00000000000..11f88b8958b --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt @@ -0,0 +1,3 @@ +package io.getstream.video.android.core.analytics + +enum class Stage { NOT_STARTED, IN_PROGRESS } \ No newline at end of file diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt index a549e721054..7796520a5fd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt @@ -20,13 +20,17 @@ import io.getstream.video.android.core.events.reporting.ClientEventReporter internal class WsHook(val callId: String, val callType: String, val reporter: ClientEventReporter) { var telemetryWsEventSessionId = "" + var wsStage = Stage.NOT_STARTED fun onWsInitiated(sfuName: String, wasPreviouslyConnected: Boolean) { - telemetryWsEventSessionId = reporter.reportWsJoinInitiated( - callId = callId, - callType = callType, - sfuId = sfuName, - wasPreviouslyConnected = wasPreviouslyConnected, - ) + if (wsStage == Stage.NOT_STARTED) { + telemetryWsEventSessionId = reporter.reportWsJoinInitiated( + callId = callId, + callType = callType, + sfuId = sfuName, + wasPreviouslyConnected = wasPreviouslyConnected, + ) + wsStage = Stage.IN_PROGRESS + } } fun onWsCompleted( @@ -35,14 +39,21 @@ internal class WsHook(val callId: String, val callType: String, val reporter: Cl failureReason: String? = null, failureCode: String? = null, ) { - if (telemetryWsEventSessionId.isNotEmpty()) { - reporter.reportWsJoinCompleted( - eventSessionId = telemetryWsEventSessionId, - success = success, - retryCount = retryCount, - failureReason = failureReason, - failureCode = failureCode, - ) + if (wsStage == Stage.IN_PROGRESS) { + if (telemetryWsEventSessionId.isNotEmpty()) { + reporter.reportWsJoinCompleted( + eventSessionId = telemetryWsEventSessionId, + success = success, + retryCount = retryCount, + failureReason = failureReason, + failureCode = failureCode, + ) + } + resetStage() } } + + fun resetStage() { + wsStage = Stage.NOT_STARTED + } } From 22f85c893038aed4d82f1debe382636511548d72 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 28 May 2026 17:04:13 +0530 Subject: [PATCH 26/63] chore: update open api models --- .../getstream/android/video/generated/models/ClientEvent.kt | 4 ++++ .../android/video/generated/models/CreateDeviceRequest.kt | 3 +++ .../android/video/generated/models/DeviceResponse.kt | 3 +++ 3 files changed, 10 insertions(+) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt index 8433789af30..b77450c3ba1 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt @@ -57,6 +57,9 @@ data class ClientEvent ( @Json(name = "id") val id: kotlin.String? = null, + @Json(name = "join_success_id") + val joinSuccessId: kotlin.String? = null, + @Json(name = "outcome") val outcome: kotlin.String? = null, @@ -115,6 +118,7 @@ data class ClientEvent ( appendIfNotNull("callSessionId", callSessionId) appendIfNotNull("eventSessionId", eventSessionId) appendIfNotNull("userSessionId", userSessionId) + appendIfNotNull("joinSuccessId", joinSuccessId) appendIfNotNull("userId", userId) appendIfNotNull("sfuId", sfuId) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CreateDeviceRequest.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CreateDeviceRequest.kt index f61b31333fc..864fbf13507 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CreateDeviceRequest.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CreateDeviceRequest.kt @@ -45,6 +45,9 @@ data class CreateDeviceRequest ( @Json(name = "push_provider") val pushProvider: PushProvider, + @Json(name = "hardware_id") + val hardwareId: kotlin.String? = null, + @Json(name = "push_provider_name") val pushProviderName: kotlin.String? = null, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/DeviceResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/DeviceResponse.kt index 8ba795cd7b3..3437894fcac 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/DeviceResponse.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/DeviceResponse.kt @@ -57,6 +57,9 @@ data class DeviceResponse ( @Json(name = "disabled_reason") val disabledReason: kotlin.String? = null, + @Json(name = "hardware_id") + val hardwareId: kotlin.String? = null, + @Json(name = "push_provider_name") val pushProviderName: kotlin.String? = null, From 5dfce233604b1c9f17be07fa9d4d99ea1c12a575 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 28 May 2026 17:06:11 +0530 Subject: [PATCH 27/63] chore: spotless --- .../api/stream-video-android-core.api | 54 ++++++++++++------- .../core/analytics/JoinRequestHooks.kt | 3 -- .../video/android/core/analytics/Stage.kt | 18 ++++++- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index c3618d5093a..8be8f5527da 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -2457,31 +2457,32 @@ public final class io/getstream/android/video/generated/models/ChatPreferencesRe public final class io/getstream/android/video/generated/models/ClientEvent { public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Ljava/lang/Integer; - public final fun component11 ()Ljava/lang/String; + public final fun component10 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component11 ()Ljava/lang/Integer; public final fun component12 ()Ljava/lang/String; public final fun component13 ()Ljava/lang/String; public final fun component14 ()Ljava/lang/String; public final fun component15 ()Ljava/lang/String; - public final fun component16 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component17 ()Ljava/lang/String; + public final fun component16 ()Ljava/lang/String; + public final fun component17 ()Lorg/threeten/bp/OffsetDateTime; public final fun component18 ()Ljava/lang/String; public final fun component19 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/Integer; public final fun component20 ()Ljava/lang/String; - public final fun component21 ()Ljava/lang/Boolean; + public final fun component21 ()Ljava/lang/String; + public final fun component22 ()Ljava/lang/Boolean; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String; - public final fun component9 ()Lorg/threeten/bp/OffsetDateTime; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCallSessionId ()Ljava/lang/String; public final fun getElapsedTime ()Ljava/lang/Integer; @@ -2489,6 +2490,7 @@ public final class io/getstream/android/video/generated/models/ClientEvent { public final fun getEventType ()Ljava/lang/String; public final fun getIceState ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; + public final fun getJoinSuccessId ()Ljava/lang/String; public final fun getOutcome ()Ljava/lang/String; public final fun getPeerConnection ()Ljava/lang/String; public final fun getPreviouslyConnectedTimestamp ()Lorg/threeten/bp/OffsetDateTime; @@ -2656,15 +2658,17 @@ public final class io/getstream/android/video/generated/models/CountByMinuteResp } public final class io/getstream/android/video/generated/models/CreateDeviceRequest { - public fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider;Ljava/lang/String;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider; public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/String;Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/CreateDeviceRequest; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CreateDeviceRequest;Ljava/lang/String;Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CreateDeviceRequest; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/String;Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/CreateDeviceRequest; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CreateDeviceRequest;Ljava/lang/String;Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CreateDeviceRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getHardwareId ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; public final fun getPushProvider ()Lio/getstream/android/video/generated/models/CreateDeviceRequest$PushProvider; public final fun getPushProviderName ()Ljava/lang/String; @@ -2913,8 +2917,8 @@ public final class io/getstream/android/video/generated/models/DeleteTranscripti } public final class io/getstream/android/video/generated/models/DeviceResponse { - public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V - public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lorg/threeten/bp/OffsetDateTime; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; @@ -2922,13 +2926,15 @@ public final class io/getstream/android/video/generated/models/DeviceResponse { public final fun component5 ()Ljava/lang/Boolean; public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Ljava/lang/Boolean; - public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/DeviceResponse; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/DeviceResponse;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/DeviceResponse; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/Boolean; + public final fun copy (Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/DeviceResponse; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/DeviceResponse;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/DeviceResponse; public fun equals (Ljava/lang/Object;)Z public final fun getCreatedAt ()Lorg/threeten/bp/OffsetDateTime; public final fun getDisabled ()Ljava/lang/Boolean; public final fun getDisabledReason ()Ljava/lang/String; + public final fun getHardwareId ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; public final fun getPushProvider ()Ljava/lang/String; public final fun getPushProviderName ()Ljava/lang/String; @@ -9873,6 +9879,14 @@ public final class io/getstream/video/android/core/StreamVideoConfigDefault : io public fun getJoinOnAcceptedByCallee ()Z } +public final class io/getstream/video/android/core/analytics/Stage : java/lang/Enum { + public static final field IN_PROGRESS Lio/getstream/video/android/core/analytics/Stage; + public static final field NOT_STARTED Lio/getstream/video/android/core/analytics/Stage; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/analytics/Stage; + public static fun values ()[Lio/getstream/video/android/core/analytics/Stage; +} + public abstract interface class io/getstream/video/android/core/api/SignalServerService { public abstract fun iceRestart (Lstream/video/sfu/signal/ICERestartRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun iceTrickle (Lstream/video/sfu/models/ICETrickle;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index 51b1c8e79ff..2d39fc78fec 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -31,7 +31,6 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev ) joinStage = Stage.IN_PROGRESS } - } fun onJoinRequestSuccess(telemetryModel: TelemetryModel, currentSessionId: String) { if (joinStage == Stage.IN_PROGRESS) { @@ -45,7 +44,6 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev } resetStage() } - } fun onJoinRequestPermanentError(retryCount: Int, message: String) { @@ -60,7 +58,6 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev } resetStage() } - } fun onJoinRequestRetryExhausted(retryCount: Int, message: String) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt index 11f88b8958b..9eba7a58c08 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + package io.getstream.video.android.core.analytics -enum class Stage { NOT_STARTED, IN_PROGRESS } \ No newline at end of file +enum class Stage { NOT_STARTED, IN_PROGRESS } From 3a5a07e1127bc2f0d7e495d83cb6c2464b3fa83a Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 28 May 2026 17:55:50 +0530 Subject: [PATCH 28/63] chore: update failure injector logic --- .../video/android/ui/FailureInjectorImpl.kt | 37 +++++-- .../video/android/ui/FailureInjectorUi.kt | 98 +++++++++++++------ .../io/getstream/video/android/core/Call.kt | 6 -- .../video/android/core/StreamVideoClient.kt | 5 + .../core/faultinjector/FailureInjector.kt | 4 + .../core/faultinjector/NoOpFailureInjector.kt | 7 ++ 6 files changed, 114 insertions(+), 43 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorImpl.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorImpl.kt index 511541f75fc..be360ebd310 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorImpl.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorImpl.kt @@ -23,30 +23,40 @@ import retrofit2.HttpException import retrofit2.Response internal class FailureInjectorImpl : FailureInjector { - private val enabledFaults = mutableMapOf() + private val faultCounts = mutableMapOf() override fun enable(key: FailureKey) { - enabledFaults[key] = true + if ((faultCounts[key] ?: 0) == 0) faultCounts[key] = 1 } override fun disable(key: FailureKey) { - enabledFaults[key] = false + faultCounts[key] = 0 } override fun setEnabled(key: FailureKey, enabled: Boolean) { - enabledFaults[key] = enabled + if (enabled) enable(key) else disable(key) } override fun isEnabled(key: FailureKey): Boolean { - return enabledFaults[key] == true + return (faultCounts[key] ?: 0) > 0 + } + + override fun setCount(key: FailureKey, count: Int) { + faultCounts[key] = count + } + + override fun getCount(key: FailureKey): Int { + return faultCounts[key] ?: 0 } override fun clear() { - enabledFaults.clear() + faultCounts.clear() } override fun throwDebugFault(key: FailureKey) { - if (enabledFaults[key] == true) { + val count = faultCounts[key] ?: 0 + if (count > 0) { + faultCounts[key] = count - 1 throw when (key) { FailureKey.FAIL_LOCATION -> HttpException( Response.error( @@ -60,8 +70,19 @@ internal class FailureInjectorImpl : FailureInjector { } override fun sendFailResult(key: FailureKey): io.getstream.result.Result.Failure { + val count = faultCounts[key] ?: 0 + if (count > 0) { + faultCounts[key] = count - 1 + } + val message = when (key) { + FailureKey.FAIL_JOIN_CALL -> "Unable to resolve host" + else -> "Failure injected: $key" + } return io.getstream.result.Result.Failure( - Error.GenericError("Failure injected: $key"), + Error.ThrowableError( + message, + RuntimeException(message), + ), ) } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorUi.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorUi.kt index 59445ae38b1..3d539da7bb1 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorUi.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/FailureInjectorUi.kt @@ -30,15 +30,19 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults -import androidx.compose.material.Checkbox -import androidx.compose.material.CheckboxDefaults +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview @@ -49,6 +53,8 @@ import io.getstream.video.android.core.faultinjector.FailureInjector import io.getstream.video.android.core.faultinjector.FailureKey import io.getstream.video.android.core.internal.InternalStreamVideoApi +private val countOptions = listOf(0, 1, 2, 3, 5, 10) + @OptIn(InternalStreamVideoApi::class) @Composable fun FailureInjectorUi( @@ -56,9 +62,9 @@ fun FailureInjectorUi( failureInjector: FailureInjector, onClose: () -> Unit, ) { - val checkedState = remember { - mutableStateMapOf().apply { - FailureKey.entries.forEach { key -> put(key, failureInjector.isEnabled(key)) } + val countState = remember { + mutableStateMapOf().apply { + FailureKey.entries.forEach { key -> put(key, failureInjector.getCount(key)) } } } @@ -89,7 +95,7 @@ fun FailureInjectorUi( text = "Clear all", modifier = Modifier.clickable { failureInjector.clear() - FailureKey.entries.forEach { key -> checkedState[key] = false } + FailureKey.entries.forEach { key -> countState[key] = 0 } }, style = VideoTheme.typography.bodyS, color = VideoTheme.colors.brandPrimary, @@ -121,36 +127,68 @@ fun FailureInjectorUi( verticalArrangement = Arrangement.spacedBy(4.dp), ) { items(FailureKey.entries) { key -> - val checked = checkedState[key] ?: false + val count = countState[key] ?: 0 + var expanded by remember { mutableStateOf(false) } + Row( modifier = Modifier .fillMaxWidth() - .clickable { - val next = !checked - checkedState[key] = next - failureInjector.setEnabled(key, next) - } .padding(horizontal = 8.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), + horizontalArrangement = Arrangement.SpaceBetween, ) { - Checkbox( - checked = checked, - onCheckedChange = { next -> - checkedState[key] = next - failureInjector.setEnabled(key, next) - }, - colors = CheckboxDefaults.colors( - checkedColor = VideoTheme.colors.brandPrimary, - uncheckedColor = VideoTheme.colors.baseSecondary, - checkmarkColor = VideoTheme.colors.baseSheetPrimary, - ), - ) Text( text = key.name, style = VideoTheme.typography.bodyM, color = VideoTheme.colors.basePrimary, + modifier = Modifier.weight(1f), ) + + Box { + Row( + modifier = Modifier + .background( + color = VideoTheme.colors.baseSheetTertiary, + shape = RoundedCornerShape(6.dp), + ) + .clickable { expanded = true } + .padding(horizontal = 12.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = "$count", + style = VideoTheme.typography.bodyM, + color = if (count > 0) VideoTheme.colors.brandPrimary else VideoTheme.colors.baseSecondary, + ) + Icon( + imageVector = Icons.Default.ArrowDropDown, + contentDescription = null, + tint = VideoTheme.colors.baseSecondary, + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + countOptions.forEach { option -> + DropdownMenuItem( + onClick = { + countState[key] = option + failureInjector.setCount(key, option) + expanded = false + }, + ) { + Text( + text = "$option", + style = VideoTheme.typography.bodyM, + color = if (option > 0) VideoTheme.colors.brandPrimary else VideoTheme.colors.basePrimary, + ) + } + } + } + } } } } @@ -190,16 +228,18 @@ fun FaultInjectorUiDemo() { override fun disable(key: FailureKey) {} - override fun setEnabled( - key: FailureKey, - enabled: Boolean, - ) {} + override fun setEnabled(key: FailureKey, enabled: Boolean) {} override fun isEnabled(key: FailureKey): Boolean = false + override fun setCount(key: FailureKey, count: Int) {} + + override fun getCount(key: FailureKey): Int = 0 + override fun clear() {} override fun throwDebugFault(key: FailureKey) {} + override fun sendFailResult( key: FailureKey, ): io.getstream.result.Result.Failure { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 25f71ff9a07..56b150025dc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -81,7 +81,6 @@ import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener import io.getstream.video.android.core.events.reporting.PeerConnectionRole import io.getstream.video.android.core.events.reporting.TelemetryModel -import io.getstream.video.android.core.faultinjector.FailureKey import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.model.AudioTrack @@ -1911,11 +1910,6 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, telemetryModel: TelemetryModel, ): Result { - with(client.state.failureInjector) { - if (isEnabled(FailureKey.FAIL_JOIN_CALL)) { - return sendFailResult(FailureKey.FAIL_JOIN_CALL) - } - } callAnalyticsHooks.joinRequestHooks.onJoinRequestStart() val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index fc67f0da5d7..0bbb56b86ad 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -815,6 +815,11 @@ internal class StreamVideoClient internal constructor( migratingFromList: List? = null, hintHighScaleLivestreamPublisher: Boolean? = null, ): Result { + with(state.failureInjector) { + if (isEnabled(FailureKey.FAIL_JOIN_CALL)) { + return sendFailResult(FailureKey.FAIL_JOIN_CALL) + } + } val joinCallRequest = JoinCallRequest( create = create, data = CallRequest( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureInjector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureInjector.kt index dd807b77e1d..e524a828539 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureInjector.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/FailureInjector.kt @@ -34,4 +34,8 @@ public interface FailureInjector { fun throwDebugFault(key: FailureKey) fun sendFailResult(key: FailureKey): io.getstream.result.Result.Failure + + fun setCount(key: FailureKey, count: Int) + + fun getCount(key: FailureKey): Int } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFailureInjector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFailureInjector.kt index d38e4b77e92..f2114c11c47 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFailureInjector.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/faultinjector/NoOpFailureInjector.kt @@ -31,4 +31,11 @@ internal class NoOpFailureInjector : FailureInjector { Error.GenericError("Failure injected: $key"), ) } + + override fun setCount( + key: FailureKey, + count: Int, + ) {} + + override fun getCount(key: FailureKey): Int = 0 } From 01776cf5736cbe825fa10c1dcf7b435082278594 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 29 May 2026 13:21:16 +0530 Subject: [PATCH 29/63] chore: add new call leave reason --- .../io/getstream/video/android/core/CallLeaveReason.kt | 1 + .../getstream/video/android/ui/common/StreamCallActivity.kt | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt index 0aac13ab2b5..e29c2f43a9e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt @@ -82,6 +82,7 @@ enum class SdkCause { PIP_ERROR, PIP_STOPPED, ACTIVITY_DESTROYED, + STREAM_CALL_ACTIVITY_EXCEPTION, } @InternalStreamVideoApi diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt index 12217844289..34960f08906 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt @@ -245,6 +245,12 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper val configuration = configurationMap[error.call.id] if (configuration?.closeScreenOnError == true) { logger.e(error) { "Finishing the activity" } + error.call.leave( + CallLeaveReason.SdkDriven( + SdkCause.STREAM_CALL_ACTIVITY_EXCEPTION, + "${error.message}", + ), + ) safeFinish() } } else { From f08af11f0b421a555eb13f2792dc16c0e8ef7555 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 29 May 2026 15:57:07 +0530 Subject: [PATCH 30/63] chore: add peer connection hook --- .../io/getstream/video/android/core/Call.kt | 12 ++--- .../core/analytics/CallAnalyticsHooks.kt | 11 ++++- .../core/analytics/JoinRequestHooks.kt | 4 ++ .../core/analytics/PeerConnectionHook.kt | 46 +++++++++++++++++++ .../video/android/core/analytics/WsHook.kt | 9 +++- .../events/reporting/ClientEventReporter.kt | 19 +++++++- 6 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 56b150025dc..56b395606cd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -61,8 +61,6 @@ import io.getstream.result.Result.Failure import io.getstream.result.Result.Success import io.getstream.result.flatMap import io.getstream.video.android.core.analytics.CallAnalyticsHooks -import io.getstream.video.android.core.analytics.JoinRequestHooks -import io.getstream.video.android.core.analytics.WsHook import io.getstream.video.android.core.audio.StreamAudioDevice import io.getstream.video.android.core.call.FastReconnectResult import io.getstream.video.android.core.call.RtcSession @@ -315,10 +313,8 @@ public class Call( _peerConnectionFactory = value } - internal val callAnalyticsHooks = CallAnalyticsHooks( - JoinRequestHooks(this.id, this.type, client.state.clientEventReporter), - WsHook(this.id, this.type, client.state.clientEventReporter), - ) + internal val callAnalyticsHooks = + CallAnalyticsHooks(this.id, this.type, client.state.clientEventReporter) /** * Checks if the audioBitrateProfile has changed since the factory was created, @@ -798,7 +794,7 @@ public class Call( } .collect { (publisher, iceState) -> if (iceState != null) { - client.state.clientEventReporter.onPeerConnectionIceStateChanged( + callAnalyticsHooks.peerConnectionHook.onPeerConnectionIceStateChanged( callId = this@Call.id, callType = this@Call.type, role = PeerConnectionRole.PUBLISH, @@ -829,7 +825,7 @@ public class Call( } .collect { (subscriber, iceState) -> if (iceState != null) { - client.state.clientEventReporter.onPeerConnectionIceStateChanged( + callAnalyticsHooks.peerConnectionHook.onPeerConnectionIceStateChanged( callId = this@Call.id, callType = this@Call.type, role = PeerConnectionRole.SUBSCRIBE, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index 0976337d1fe..a61ca24107e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -19,9 +19,16 @@ package io.getstream.video.android.core.analytics import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.events.reporting.ClientEventReporter -internal class CallAnalyticsHooks(val joinRequestHooks: JoinRequestHooks, val wsHook: WsHook) { +internal class CallAnalyticsHooks(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { + val joinRequestHooks = JoinRequestHooks(callId, callType, eventReporter) + val wsHook = WsHook(callId, callType, eventReporter) { + joinRequestHooks.joinStageAttemptId + } + + val peerConnectionHook = PeerConnectionHook(callId, callType, eventReporter) { + joinRequestHooks.joinStageAttemptId + } - val eventReporter = joinRequestHooks.eventReporter fun onCallLeave(callLeaveReason: CallLeaveReason) { val abortReason = when (callLeaveReason) { is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index 2d39fc78fec..c51414bca71 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -18,16 +18,20 @@ package io.getstream.video.android.core.analytics import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.events.reporting.TelemetryModel +import java.util.UUID internal class JoinRequestHooks(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { var eventSessionId = "" var joinStage = Stage.NOT_STARTED + var joinStageAttemptId = "" fun onJoinRequestStart() { if (joinStage == Stage.NOT_STARTED) { + joinStageAttemptId = UUID.randomUUID().toString() eventSessionId = eventReporter.reportCoordinatorJoinInitiated( callType = callType, callId = callId, + joinStageAttemptId = joinStageAttemptId ) joinStage = Stage.IN_PROGRESS } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt new file mode 100644 index 00000000000..f4ae007c2ad --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.events.reporting.PeerConnectionRole +import org.webrtc.PeerConnection + +internal class PeerConnectionHook( + val callId: String, + val callType: String, + val reporter: ClientEventReporter, + val getJoinStageAttemptId: () -> String, +) { + + internal fun onPeerConnectionIceStateChanged( + callId: String, + callType: String, + role: PeerConnectionRole, + iceState: PeerConnection.IceConnectionState, + peerConnectionState: PeerConnection.PeerConnectionState?, + ) { + reporter.onPeerConnectionIceStateChanged( + callId = callId, + callType = callType, + role = role, + iceState = iceState, + peerConnectionState = peerConnectionState, + joinStageAttemptId = getJoinStageAttemptId.invoke(), + ) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt index 7796520a5fd..3fed642df44 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt @@ -18,7 +18,12 @@ package io.getstream.video.android.core.analytics import io.getstream.video.android.core.events.reporting.ClientEventReporter -internal class WsHook(val callId: String, val callType: String, val reporter: ClientEventReporter) { +internal class WsHook( + val callId: String, + val callType: String, + val reporter: ClientEventReporter, + val getJoinStageAttemptId: () -> String, +) { var telemetryWsEventSessionId = "" var wsStage = Stage.NOT_STARTED fun onWsInitiated(sfuName: String, wasPreviouslyConnected: Boolean) { @@ -28,6 +33,7 @@ internal class WsHook(val callId: String, val callType: String, val reporter: Cl callType = callType, sfuId = sfuName, wasPreviouslyConnected = wasPreviouslyConnected, + joinStageAttemptId = getJoinStageAttemptId.invoke(), ) wsStage = Stage.IN_PROGRESS } @@ -47,6 +53,7 @@ internal class WsHook(val callId: String, val callType: String, val reporter: Cl retryCount = retryCount, failureReason = failureReason, failureCode = failureCode, + joinStageAttemptId = getJoinStageAttemptId.invoke(), ) } resetStage() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index 78b333e3d3f..6bb69de448d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -96,8 +96,9 @@ internal class ClientEventReporter( internal fun reportCoordinatorJoinInitiated( callId: String, callType: String, + joinStageAttemptId:String ): String { - val joinStageAttemptId = UUID.randomUUID().toString() + val eventSessionId = UUID.randomUUID().toString() joinStageAttemptIdMap[callId] = joinStageAttemptId val now = System.currentTimeMillis() @@ -116,6 +117,7 @@ internal class ClientEventReporter( stage = CallEventStage.COORDINATOR_JOIN, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, ), ) return eventSessionId @@ -145,6 +147,7 @@ internal class ClientEventReporter( retryFailureReason = if (!success) failureReason else null, retryFailureCode = if (!success) failureCode else null, callSessionId = callSessionId, + joinStageAttemptId = session.joinStageAttemptIdSnapshot, ), ) } @@ -155,6 +158,7 @@ internal class ClientEventReporter( sfuId: String, callId: String, callType: String, + joinStageAttemptId: String, wasPreviouslyConnected: Boolean, ): String { val eventSessionId = UUID.randomUUID().toString() @@ -178,6 +182,7 @@ internal class ClientEventReporter( stage = CallEventStage.WS_JOIN, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, sfuId = sfuId, wasPreviouslyConnected = wasPreviouslyConnected, ), @@ -187,6 +192,7 @@ internal class ClientEventReporter( internal fun reportWsJoinCompleted( eventSessionId: String, + joinStageAttemptId: String, success: Boolean, retryCount: Int, failureReason: String? = null, @@ -208,6 +214,7 @@ internal class ClientEventReporter( retryFailureCode = if (!success) failureCode else null, sfuId = session.sfuId, callSessionId = session.callSessionId, + joinStageAttemptId = joinStageAttemptId, ), ) } @@ -217,6 +224,7 @@ internal class ClientEventReporter( internal fun onPeerConnectionIceStateChanged( callId: String, callType: String, + joinStageAttemptId: String, role: PeerConnectionRole, iceState: PeerConnection.IceConnectionState, peerConnectionState: PeerConnection.PeerConnectionState?, @@ -231,6 +239,7 @@ internal class ClientEventReporter( callId = callId, callType = callType, eventSessionId = oldId, + joinStageAttemptId = joinStageAttemptId, success = false, iceState = iceState, peerConnectionState = peerConnectionState, @@ -259,6 +268,7 @@ internal class ClientEventReporter( stage = CallEventStage.PEER_CONNECTION_CONNECT, eventType = CallEventType.INITIATED, eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, peerConnection = role, wasPreviouslyConnected = wasPrev, callSessionId = callSessionIdMap[callId], @@ -275,6 +285,7 @@ internal class ClientEventReporter( callId = callId, callType = callType, eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, success = true, iceState = iceState, peerConnectionState = peerConnectionState, @@ -287,6 +298,7 @@ internal class ClientEventReporter( callId = callId, callType = callType, eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, success = false, iceState = iceState, peerConnectionState = peerConnectionState, @@ -303,6 +315,7 @@ internal class ClientEventReporter( callId: String, callType: String, eventSessionId: String, + joinStageAttemptId: String, success: Boolean, iceState: PeerConnection.IceConnectionState, peerConnectionState: PeerConnection.PeerConnectionState?, @@ -318,6 +331,7 @@ internal class ClientEventReporter( stage = CallEventStage.PEER_CONNECTION_CONNECT, eventType = CallEventType.COMPLETED, eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, elapsedTime = elapsedTime, outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, retryCountAttempt = 0, @@ -353,6 +367,7 @@ internal class ClientEventReporter( peerConnection = session.peerConnectionRole, wasPreviouslyConnected = session.wasPreviouslyConnected, userSessionId = session.userSessionId, + joinStageAttemptId = session.joinStageAttemptIdSnapshot, ) } sendEvents(events) @@ -366,6 +381,7 @@ internal class ClientEventReporter( stage: CallEventStage, eventType: CallEventType, eventSessionId: String, + joinStageAttemptId: String, elapsedTime: Long? = null, outcome: CallEventOutcome? = null, retryCountAttempt: Int? = null, @@ -380,6 +396,7 @@ internal class ClientEventReporter( userSessionId: String? = null, ): ClientEvent = ClientEvent( eventSessionId = eventSessionId, + joinSuccessId = joinStageAttemptId, eventType = eventType.value, id = callId, sdkVersion = sdkVersion, From 690b9ab8e076558768d750414525cde528345a8c Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 29 May 2026 15:58:28 +0530 Subject: [PATCH 31/63] chore: spotless --- .../getstream/video/android/core/analytics/JoinRequestHooks.kt | 2 +- .../video/android/core/events/reporting/ClientEventReporter.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index c51414bca71..f52ca3b896c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -31,7 +31,7 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev eventSessionId = eventReporter.reportCoordinatorJoinInitiated( callType = callType, callId = callId, - joinStageAttemptId = joinStageAttemptId + joinStageAttemptId = joinStageAttemptId, ) joinStage = Stage.IN_PROGRESS } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index 6bb69de448d..0e5d2b2df42 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -96,9 +96,8 @@ internal class ClientEventReporter( internal fun reportCoordinatorJoinInitiated( callId: String, callType: String, - joinStageAttemptId:String + joinStageAttemptId: String, ): String { - val eventSessionId = UUID.randomUUID().toString() joinStageAttemptIdMap[callId] = joinStageAttemptId val now = System.currentTimeMillis() From e787c693ed78b1db184354111fd6a36bf471929b Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 29 May 2026 17:51:35 +0530 Subject: [PATCH 32/63] chore: add pc analytics observer --- .../io/getstream/video/android/core/Call.kt | 56 +++++---------- .../core/analytics/CallAnalyticsHooks.kt | 14 +++- .../PeerConnectionAnalyticsObserver.kt | 68 +++++++++++++++++++ .../core/analytics/PeerConnectionHook.kt | 8 +-- .../events/reporting/ClientEventReporter.kt | 4 +- 5 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 56b395606cd..a3f006d4da8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -77,7 +77,6 @@ import io.getstream.video.android.core.closedcaptions.ClosedCaptionsSettings import io.getstream.video.android.core.events.GoAwayEvent import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener -import io.getstream.video.android.core.events.reporting.PeerConnectionRole import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.internal.network.NetworkStateProvider @@ -314,7 +313,7 @@ public class Call( } internal val callAnalyticsHooks = - CallAnalyticsHooks(this.id, this.type, client.state.clientEventReporter) + CallAnalyticsHooks(this.id, this.type, client.state.clientEventReporter, scope) /** * Checks if the audioBitrateProfile has changed since the factory was created, @@ -784,7 +783,8 @@ public class Call( } } monitorPublisherPCStateJob?.cancel() - + callAnalyticsHooks.peerConnectionAnalyticsObserver.stop() + callAnalyticsHooks.peerConnectionAnalyticsObserver.observePeerConnections(session) monitorPublisherPCStateJob = scope.launch { session .filterNotNull() @@ -792,24 +792,15 @@ public class Call( .flatMapLatest { publisher -> publisher.iceState.map { publisher to it } } - .collect { (publisher, iceState) -> - if (iceState != null) { - callAnalyticsHooks.peerConnectionHook.onPeerConnectionIceStateChanged( - callId = this@Call.id, - callType = this@Call.type, - role = PeerConnectionRole.PUBLISH, - iceState = iceState, - peerConnectionState = publisher.state.value, - ) - } - when (iceState) { + .collect { (publisher, state) -> + when (state) { PeerConnection.IceConnectionState.FAILED, PeerConnection.IceConnectionState.DISCONNECTED, -> { - publisher.connection.restartIce() + publisher.connection.restartIce() // TODO Rahul might send a request to the server } else -> { - logger.d { "[monitorPubConnectionState] Ice connection state is $iceState" } + logger.d { "[monitorPubConnectionState] Ice connection state is $state" } } } } @@ -817,33 +808,17 @@ public class Call( monitorSubscriberPCStateJob?.cancel() monitorSubscriberPCStateJob = scope.launch { - session - .filterNotNull() - .flatMapLatest { it.subscriber.filterNotNull() } - .flatMapLatest { subscriber -> - subscriber.iceState.map { subscriber to it } - } - .collect { (subscriber, iceState) -> - if (iceState != null) { - callAnalyticsHooks.peerConnectionHook.onPeerConnectionIceStateChanged( - callId = this@Call.id, - callType = this@Call.type, - role = PeerConnectionRole.SUBSCRIBE, - iceState = iceState, - peerConnectionState = subscriber.state.value, - ) + session.value?.subscriber?.value?.iceState?.collect { + when (it) { + PeerConnection.IceConnectionState.FAILED, PeerConnection.IceConnectionState.DISCONNECTED -> { + session.value?.requestSubscriberIceRestart() // TODO Rahul might send a request to the server } - when (iceState) { - PeerConnection.IceConnectionState.FAILED, - PeerConnection.IceConnectionState.DISCONNECTED, - -> { - session.value?.requestSubscriberIceRestart() - } - else -> { - logger.d { "[monitorSubConnectionState] Ice connection state is $iceState" } - } + + else -> { + logger.d { "[monitorSubConnectionState] Ice connection state is $it" } } } + } } network.subscribe(listener) } @@ -1323,6 +1298,7 @@ public class Call( private fun internalLeave(reason: CallLeaveReason) = atomicLeave { monitorSubscriberPCStateJob?.cancel() monitorPublisherPCStateJob?.cancel() + callAnalyticsHooks.stopObservers() monitorPublisherPCStateJob = null monitorSubscriberPCStateJob = null leaveTimeoutAfterDisconnect?.cancel() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index a61ca24107e..64b1c0007f5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -18,8 +18,14 @@ package io.getstream.video.android.core.analytics import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.events.reporting.ClientEventReporter +import kotlinx.coroutines.CoroutineScope -internal class CallAnalyticsHooks(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { +internal class CallAnalyticsHooks( + val callId: String, + val callType: String, + val eventReporter: ClientEventReporter, + val scope: CoroutineScope, +) { val joinRequestHooks = JoinRequestHooks(callId, callType, eventReporter) val wsHook = WsHook(callId, callType, eventReporter) { joinRequestHooks.joinStageAttemptId @@ -29,6 +35,8 @@ internal class CallAnalyticsHooks(val callId: String, val callType: String, val joinRequestHooks.joinStageAttemptId } + val peerConnectionAnalyticsObserver = PeerConnectionAnalyticsObserver(scope, peerConnectionHook) + fun onCallLeave(callLeaveReason: CallLeaveReason) { val abortReason = when (callLeaveReason) { is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE @@ -36,4 +44,8 @@ internal class CallAnalyticsHooks(val callId: String, val callType: String, val } eventReporter.abortAllInFlight(abortReason) } + + fun stopObservers() { + peerConnectionAnalyticsObserver.stop() + } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt new file mode 100644 index 00000000000..7e267055025 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.call.RtcSession +import io.getstream.video.android.core.events.reporting.PeerConnectionRole +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.launch + +internal class PeerConnectionAnalyticsObserver( + private val scope: CoroutineScope, + private val hook: PeerConnectionHook, +) { + + private var peerConnectionObserverJob: Job? = null + + fun observePeerConnections(session: StateFlow) { + peerConnectionObserverJob?.cancel() + peerConnectionObserverJob = scope.launch { + launch { + session.filterNotNull() + .flatMapLatest { it.publisher.filterNotNull() } + .flatMapLatest { it.state.filterNotNull() } + .collect { state -> + hook.onPeerConnectionStateChanged( + role = PeerConnectionRole.PUBLISH, + iceState = session.value?.publisher?.value?.iceState?.value, + peerConnectionState = state, + ) + } + + session.filterNotNull() + .flatMapLatest { it.subscriber.filterNotNull() } + .flatMapLatest { it.state.filterNotNull() } + .collect { state -> + hook.onPeerConnectionStateChanged( + role = PeerConnectionRole.SUBSCRIBE, + iceState = session.value?.subscriber?.value?.iceState?.value, + peerConnectionState = state, + ) + } + } + } + } + + fun stop() { + peerConnectionObserverJob?.cancel() + peerConnectionObserverJob = null + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt index f4ae007c2ad..e5a5248f8fc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt @@ -27,14 +27,12 @@ internal class PeerConnectionHook( val getJoinStageAttemptId: () -> String, ) { - internal fun onPeerConnectionIceStateChanged( - callId: String, - callType: String, + internal fun onPeerConnectionStateChanged( role: PeerConnectionRole, - iceState: PeerConnection.IceConnectionState, + iceState: PeerConnection.IceConnectionState?, peerConnectionState: PeerConnection.PeerConnectionState?, ) { - reporter.onPeerConnectionIceStateChanged( + reporter.onPeerConnectionStateChanged( callId = callId, callType = callType, role = role, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index 0e5d2b2df42..2080d989c1c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -220,12 +220,12 @@ internal class ClientEventReporter( // --- PeerConnectionConnect (ICE state machine) --- - internal fun onPeerConnectionIceStateChanged( + internal fun onPeerConnectionStateChanged( callId: String, callType: String, joinStageAttemptId: String, role: PeerConnectionRole, - iceState: PeerConnection.IceConnectionState, + iceState: PeerConnection.IceConnectionState?, peerConnectionState: PeerConnection.PeerConnectionState?, ) { when (iceState) { From a5dd29a5236e4fe5f7684f4c0d76bc7aebd48eb1 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Jun 2026 13:28:59 +0530 Subject: [PATCH 33/63] fix: fix 1 self cancelling coroutine code --- .../kotlin/io/getstream/video/android/core/Call.kt | 8 +++++++- .../android/core/analytics/CallAnalyticsHooks.kt | 5 ++++- .../video/android/core/analytics/WsHook.kt | 5 +++++ .../video/android/core/call/RtcSession.kt | 14 ++++++++------ .../android/core/socket/sfu/SfuSocketConnection.kt | 2 +- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index a3f006d4da8..5857c166596 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -313,7 +313,13 @@ public class Call( } internal val callAnalyticsHooks = - CallAnalyticsHooks(this.id, this.type, client.state.clientEventReporter, scope) + CallAnalyticsHooks( + this.id, + this.type, + state.connection, + client.state.clientEventReporter, + scope, + ) /** * Checks if the audioBitrateProfile has changed since the factory was created, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index 64b1c0007f5..7a43943fb77 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -17,17 +17,20 @@ package io.getstream.video.android.core.analytics import io.getstream.video.android.core.CallLeaveReason +import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.events.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow internal class CallAnalyticsHooks( val callId: String, val callType: String, + val connectionFlow: StateFlow, val eventReporter: ClientEventReporter, val scope: CoroutineScope, ) { val joinRequestHooks = JoinRequestHooks(callId, callType, eventReporter) - val wsHook = WsHook(callId, callType, eventReporter) { + val wsHook = WsHook(callId, callType, connectionFlow, scope, eventReporter) { joinRequestHooks.joinStageAttemptId } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt index 3fed642df44..47e091676a6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt @@ -16,11 +16,16 @@ package io.getstream.video.android.core.analytics +import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.events.reporting.ClientEventReporter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow internal class WsHook( val callId: String, val callType: String, + val connectionFlow: StateFlow, + val scope: CoroutineScope, val reporter: ClientEventReporter, val getJoinStageAttemptId: () -> String, ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index 59ef0ec1826..dd0ef16b1a9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -681,7 +681,7 @@ public class RtcSession internal constructor( stateJob = coroutineScope.launch { sfuConnectionModule.socketConnection.state().collect { sfuSocketState -> logger.d { - "[stateJob] SFU socket: $sfuSocketState | " + + "noob [stateJob] SFU socket: $sfuSocketState | " + "connection: ${call.state.connection.value} ($sfuName)" } _sfuSfuSocketState.value = sfuSocketState @@ -711,8 +711,8 @@ public class RtcSession internal constructor( is SfuSocketState.Disconnected.DisconnectedTemporarily -> { val strategy = sfuSocketState.reconnectStrategy val reason = "SFU:${sfuSocketState.error.message}:$strategy" - logger.w { "[stateJob] SFU sent $strategy for $sfuName" } - coroutineScope.launch { call.reconnect(strategy, reason) } + logger.w { "noob [stateJob] SFU sent $strategy for $sfuName" } + call.scope.launch { call.reconnect(strategy, reason) } } is SfuSocketState.Disconnected.WebSocketEventLost -> { @@ -883,7 +883,7 @@ public class RtcSession internal constructor( options: List? = null, telemetryModel: TelemetryModel? = null, ): SfuConnectionResult { - logger.i { "[connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } + logger.i { "noob [connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } call.callAnalyticsHooks.wsHook.onWsInitiated(sfuName, reconnectDetails != null) val request = buildJoinRequest(reconnectDetails, options) @@ -1131,7 +1131,7 @@ public class RtcSession internal constructor( private val atomicCleanup = AtomicUnitCall() fun cleanup() = atomicCleanup { - logger.i { "[cleanup] #sfu; #track; no args" } + logger.i { "noob [cleanup] #sfu; #track; no args" } coroutineScope.launch { serialProcessor.submit("cleanupSfuConnections") { @@ -1351,7 +1351,7 @@ public class RtcSession internal constructor( rejoin = { logger.d { "[createPublisher] rejoin attempt, connection state: ${call.state.connection.value}" } if (call.state.connection.value !is RealtimeConnection.Reconnecting) { - coroutineScope.launch { + call.scope.launch { // TODO Rahul, self cancelling coroutine code serialProcessor.submit("publisherRejoin") { logger.d { "[createPublisher] rejoin attempt EXECUTE, connection state: ${call.state.connection.value} " @@ -1983,11 +1983,13 @@ public class RtcSession internal constructor( // Tears down the old session after migration is confirmed (or timed out). // No sendLeaveEvent — matches JS SDK behavior (just close WS, no explicit leave for migration). internal fun finalizeMigration() { + logger.d { "noob [finalizeMigration]" } eventJob?.cancel() cleanup() } internal suspend fun prepareRejoin(reason: String) { + logger.d { "noob [prepareRejoin] reason: $reason" } val stats = call.collectStats() sendCallStats(stats) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt index 8b1316dac39..848f558a90a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt @@ -165,7 +165,7 @@ class SfuSocketConnection( } override suspend fun connect(connectData: JoinRequest) { - logger.d { "[connect] request: $connectData" } + logger.d { "noob [connect] request: $connectData" } internalSocket.connect(connectData) } From 5c125e6e130eeb2b9973a7d4650c592fe7c874b6 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Jun 2026 16:19:38 +0530 Subject: [PATCH 34/63] fix: add logic to send pc analytics only once --- .../core/analytics/CallAnalyticsHooks.kt | 15 +++-- .../PeerConnectionAnalyticsObserver.kt | 63 ++++++++++++++----- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index 7a43943fb77..7f06feb7d43 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -41,11 +41,18 @@ internal class CallAnalyticsHooks( val peerConnectionAnalyticsObserver = PeerConnectionAnalyticsObserver(scope, peerConnectionHook) fun onCallLeave(callLeaveReason: CallLeaveReason) { - val abortReason = when (callLeaveReason) { - is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE - else -> ClientEventReporter.AbortReason.CLIENT_ABORTED + val isAnyStageInProgress = + joinRequestHooks.joinStage == Stage.IN_PROGRESS || + wsHook.wsStage == Stage.IN_PROGRESS || + peerConnectionAnalyticsObserver.publisherStage == Stage.IN_PROGRESS || + peerConnectionAnalyticsObserver.subscriberStage == Stage.IN_PROGRESS + if (isAnyStageInProgress) { + val abortReason = when (callLeaveReason) { + is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE + else -> ClientEventReporter.AbortReason.CLIENT_ABORTED + } + eventReporter.abortAllInFlight(abortReason) } - eventReporter.abortAllInFlight(abortReason) } fun stopObservers() { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt index 7e267055025..3f1fc83610e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch +import org.webrtc.PeerConnection internal class PeerConnectionAnalyticsObserver( private val scope: CoroutineScope, @@ -31,36 +32,66 @@ internal class PeerConnectionAnalyticsObserver( ) { private var peerConnectionObserverJob: Job? = null + private var publisherJob: Job? = null + private var subscriberJob: Job? = null + var publisherStage = Stage.NOT_STARTED + var subscriberStage = Stage.NOT_STARTED fun observePeerConnections(session: StateFlow) { peerConnectionObserverJob?.cancel() peerConnectionObserverJob = scope.launch { - launch { + publisherJob?.cancel() + publisherJob = launch { session.filterNotNull() .flatMapLatest { it.publisher.filterNotNull() } .flatMapLatest { it.state.filterNotNull() } .collect { state -> - hook.onPeerConnectionStateChanged( - role = PeerConnectionRole.PUBLISH, - iceState = session.value?.publisher?.value?.iceState?.value, - peerConnectionState = state, - ) + publisherStage = getStage(state) + scope.launch { + hook.onPeerConnectionStateChanged( + role = PeerConnectionRole.PUBLISH, + iceState = session.value?.publisher?.value?.iceState?.value, + peerConnectionState = state, + ) + } + if (publisherStage == Stage.NOT_STARTED) { + publisherJob?.cancel() + } } - session.filterNotNull() - .flatMapLatest { it.subscriber.filterNotNull() } - .flatMapLatest { it.state.filterNotNull() } - .collect { state -> - hook.onPeerConnectionStateChanged( - role = PeerConnectionRole.SUBSCRIBE, - iceState = session.value?.subscriber?.value?.iceState?.value, - peerConnectionState = state, - ) - } + subscriberJob?.cancel() + subscriberJob = launch { + session.filterNotNull() + .flatMapLatest { it.subscriber.filterNotNull() } + .flatMapLatest { it.state.filterNotNull() } + .collect { state -> + subscriberStage = getStage(state) + scope.launch { + hook.onPeerConnectionStateChanged( + role = PeerConnectionRole.SUBSCRIBE, + iceState = session.value?.subscriber?.value?.iceState?.value, + peerConnectionState = state, + ) + } + if (subscriberStage == Stage.NOT_STARTED) { + subscriberJob?.cancel() + } + } + } } } } + private fun getStage(peerConnectionState: PeerConnection.PeerConnectionState): Stage { + return when (peerConnectionState) { + PeerConnection.PeerConnectionState.CONNECTING -> { + Stage.IN_PROGRESS + } + else -> { + Stage.NOT_STARTED + } + } + } fun stop() { peerConnectionObserverJob?.cancel() peerConnectionObserverJob = null From 83ee7299320192e3059ecc5b393b54212f1ccbf4 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Jun 2026 17:48:15 +0530 Subject: [PATCH 35/63] feat: add media permission hook --- .../getstream/video/android/core/CallState.kt | 3 ++- .../core/analytics/CallAnalyticsHooks.kt | 2 ++ .../core/analytics/JoinRequestHooks.kt | 10 ++++++- .../core/analytics/MediaPermissionHook.kt | 26 +++++++++++++++++++ .../PeerConnectionAnalyticsObserver.kt | 16 +++++++----- .../events/reporting/ClientCallEventData.kt | 1 + .../events/reporting/ClientEventReporter.kt | 19 +++++++++++++- 7 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index c5efcf6d066..351a834fa27 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -1511,7 +1511,8 @@ public class CallState( if (_ringingState.value is RingingState.Outgoing || _ringingState.value is RingingState.Incoming && client.state.activeCall.value == null) { isJoinAndRingInProgress.set(false) call.reject(reason = RejectReason.Custom(alias = REJECT_REASON_TIMEOUT)) - call.leave(CallLeaveReason.SdkDriven(cause = SdkCause.RING_TIMEOUT, message = "Outgoing call timed out with no answer")) + val leaveMessage = if (_ringingState.value is RingingState.Outgoing) "Outgoing call timed out with no answer" else "Incoming call timed out with no answer" + call.leave(CallLeaveReason.SdkDriven(cause = SdkCause.RING_TIMEOUT, message = leaveMessage)) } } else { logger.w { "[startRingingTimer] No autoCancelTimeoutMs set - call ring with no timeout" } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index 7f06feb7d43..d834d9173a0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -40,6 +40,8 @@ internal class CallAnalyticsHooks( val peerConnectionAnalyticsObserver = PeerConnectionAnalyticsObserver(scope, peerConnectionHook) + val mediaPermissionHook = MediaPermissionHook(callId, callType, eventReporter) + fun onCallLeave(callLeaveReason: CallLeaveReason) { val isAnyStageInProgress = joinRequestHooks.joinStage == Stage.IN_PROGRESS || diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index f52ca3b896c..e86278eee1f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -25,9 +25,17 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev var eventSessionId = "" var joinStage = Stage.NOT_STARTED var joinStageAttemptId = "" + + fun onJoinFunctionStart() { + joinStageAttemptId = UUID.randomUUID().toString() + eventReporter.reportSdkMethodJoinInitiated( + callType = callType, + callId = callId, + joinStageAttemptId = joinStageAttemptId, + ) + } fun onJoinRequestStart() { if (joinStage == Stage.NOT_STARTED) { - joinStageAttemptId = UUID.randomUUID().toString() eventSessionId = eventReporter.reportCoordinatorJoinInitiated( callType = callType, callId = callId, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt new file mode 100644 index 00000000000..75bf6fec20d --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.events.reporting.ClientEventReporter + +internal class MediaPermissionHook(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { + var stage = Stage.NOT_STARTED + + fun mediaPermissionStatus() { + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt index 3f1fc83610e..989e1eed72c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -54,9 +54,6 @@ internal class PeerConnectionAnalyticsObserver( peerConnectionState = state, ) } - if (publisherStage == Stage.NOT_STARTED) { - publisherJob?.cancel() - } } subscriberJob?.cancel() @@ -73,9 +70,6 @@ internal class PeerConnectionAnalyticsObserver( peerConnectionState = state, ) } - if (subscriberStage == Stage.NOT_STARTED) { - subscriberJob?.cancel() - } } } } @@ -87,11 +81,19 @@ internal class PeerConnectionAnalyticsObserver( PeerConnection.PeerConnectionState.CONNECTING -> { Stage.IN_PROGRESS } - else -> { + + PeerConnection.PeerConnectionState.FAILED, + PeerConnection.PeerConnectionState.CONNECTED, + -> { Stage.NOT_STARTED } + + else -> { + Stage.IN_PROGRESS + } } } + fun stop() { peerConnectionObserverJob?.cancel() peerConnectionObserverJob = null diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt index 1453ef9509a..ce8c81a5332 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt @@ -17,6 +17,7 @@ package io.getstream.video.android.core.events.reporting internal enum class CallEventStage(val value: String) { + JOIN_INITIATED("JoinInitiated"), COORDINATOR_JOIN("CoordinatorJoin"), WS_JOIN("WSJoin"), PEER_CONNECTION_CONNECT("PeerConnectionConnect"), diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index 2080d989c1c..5e4a554ff4e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -91,6 +91,23 @@ internal class ClientEventReporter( SFU_REQUEST_TIMEOUT("REQUEST_TIMEOUT", "SFU connection timed out"), } + internal fun reportSdkMethodJoinInitiated( + callId: String, + callType: String, + joinStageAttemptId: String, + ) { + joinStageAttemptIdMap[callId] = joinStageAttemptId + sendEvent( + buildRequest( + callId, + callType, + stage = CallEventStage.JOIN_INITIATED, + eventType = CallEventType.INITIATED, + joinStageAttemptId = joinStageAttemptId, + ), + ) + } + // --- CoordinatorJoin --- internal fun reportCoordinatorJoinInitiated( @@ -379,7 +396,7 @@ internal class ClientEventReporter( callType: String, stage: CallEventStage, eventType: CallEventType, - eventSessionId: String, + eventSessionId: String? = null, joinStageAttemptId: String, elapsedTime: Long? = null, outcome: CallEventOutcome? = null, From 47f6d42457cfd867d865e02c0cc19ba644fc25ed Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Jun 2026 18:56:58 +0530 Subject: [PATCH 36/63] fix: correct start subscriber state observer --- .../io/getstream/video/android/core/Call.kt | 1 + .../PeerConnectionAnalyticsObserver.kt | 33 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 5857c166596..1be67a972df 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -561,6 +561,7 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, callJoinInterceptor: CallJoinInterceptor? = null, ): Result { + callAnalyticsHooks.joinRequestHooks.onJoinFunctionStart() logger.d { "[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions" } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt index 989e1eed72c..aea2657c7eb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -55,23 +55,22 @@ internal class PeerConnectionAnalyticsObserver( ) } } - - subscriberJob?.cancel() - subscriberJob = launch { - session.filterNotNull() - .flatMapLatest { it.subscriber.filterNotNull() } - .flatMapLatest { it.state.filterNotNull() } - .collect { state -> - subscriberStage = getStage(state) - scope.launch { - hook.onPeerConnectionStateChanged( - role = PeerConnectionRole.SUBSCRIBE, - iceState = session.value?.subscriber?.value?.iceState?.value, - peerConnectionState = state, - ) - } + } + subscriberJob?.cancel() + subscriberJob = launch { + session.filterNotNull() + .flatMapLatest { it.subscriber.filterNotNull() } + .flatMapLatest { it.state.filterNotNull() } + .collect { state -> + subscriberStage = getStage(state) + scope.launch { + hook.onPeerConnectionStateChanged( + role = PeerConnectionRole.SUBSCRIBE, + iceState = session.value?.subscriber?.value?.iceState?.value, + peerConnectionState = state, + ) } - } + } } } } @@ -84,7 +83,7 @@ internal class PeerConnectionAnalyticsObserver( PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.CONNECTED, - -> { + -> { Stage.NOT_STARTED } From f1a9f7e250fdc25cb471a29909bc0ffd2a16c8e4 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Jun 2026 19:23:01 +0530 Subject: [PATCH 37/63] temp: add logs --- .../video/android/core/analytics/CallAnalyticsHooks.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index d834d9173a0..d4c9ec3ac21 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -16,6 +16,7 @@ package io.getstream.video.android.core.analytics +import io.getstream.log.taggedLogger import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.events.reporting.ClientEventReporter @@ -29,6 +30,7 @@ internal class CallAnalyticsHooks( val eventReporter: ClientEventReporter, val scope: CoroutineScope, ) { + val logger by taggedLogger("CallAnalyticsHooks") val joinRequestHooks = JoinRequestHooks(callId, callType, eventReporter) val wsHook = WsHook(callId, callType, connectionFlow, scope, eventReporter) { joinRequestHooks.joinStageAttemptId @@ -48,6 +50,8 @@ internal class CallAnalyticsHooks( wsHook.wsStage == Stage.IN_PROGRESS || peerConnectionAnalyticsObserver.publisherStage == Stage.IN_PROGRESS || peerConnectionAnalyticsObserver.subscriberStage == Stage.IN_PROGRESS + logger.d { "noob isAnyStageInProgress:$isAnyStageInProgress" } + if (isAnyStageInProgress) { val abortReason = when (callLeaveReason) { is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE From 3cd08119406c068bc5e9c0ed62225a705fbdde24 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Jun 2026 19:27:58 +0530 Subject: [PATCH 38/63] temp: spotless --- .../android/core/analytics/PeerConnectionAnalyticsObserver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt index aea2657c7eb..6cacd31ec78 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -83,7 +83,7 @@ internal class PeerConnectionAnalyticsObserver( PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.CONNECTED, - -> { + -> { Stage.NOT_STARTED } From ad422701152215fb5ccd30295117746a9cfc82f7 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 3 Jun 2026 02:22:52 +0530 Subject: [PATCH 39/63] feat: add coordinator ws analytics --- .../api/stream-video-android-core.api | 22 +- .../video/generated/models/ClientEvent.kt | 4 + .../video/android/core/StreamVideoClient.kt | 113 ++++---- .../core/analytics/CallAnalyticsHooks.kt | 7 +- .../PeerConnectionAnalyticsObserver.kt | 4 +- .../video/android/core/call/RtcSession.kt | 6 +- .../call/connection/StreamPeerConnection.kt | 9 + .../events/reporting/ClientCallEventData.kt | 22 +- .../events/reporting/ClientEventReporter.kt | 247 ++++++++++-------- .../core/events/reporting/TelemetryModel.kt | 42 +++ .../CoordinatorSocketStateService.kt | 14 + 11 files changed, 302 insertions(+), 188 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 8be8f5527da..050fcb0c5a0 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -2457,23 +2457,24 @@ public final class io/getstream/android/video/generated/models/ChatPreferencesRe public final class io/getstream/android/video/generated/models/ClientEvent { public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component11 ()Ljava/lang/Integer; - public final fun component12 ()Ljava/lang/String; + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component12 ()Ljava/lang/Integer; public final fun component13 ()Ljava/lang/String; public final fun component14 ()Ljava/lang/String; public final fun component15 ()Ljava/lang/String; public final fun component16 ()Ljava/lang/String; - public final fun component17 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component18 ()Ljava/lang/String; + public final fun component17 ()Ljava/lang/String; + public final fun component18 ()Lorg/threeten/bp/OffsetDateTime; public final fun component19 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/Integer; public final fun component20 ()Ljava/lang/String; public final fun component21 ()Ljava/lang/String; - public final fun component22 ()Ljava/lang/Boolean; + public final fun component22 ()Ljava/lang/String; + public final fun component23 ()Ljava/lang/Boolean; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -2481,10 +2482,11 @@ public final class io/getstream/android/video/generated/models/ClientEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCallSessionId ()Ljava/lang/String; + public final fun getCoordinatorConnectId ()Ljava/lang/String; public final fun getElapsedTime ()Ljava/lang/Integer; public final fun getEventSessionId ()Ljava/lang/String; public final fun getEventType ()Ljava/lang/String; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt index b77450c3ba1..26752a4f77c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt @@ -60,6 +60,9 @@ data class ClientEvent ( @Json(name = "join_success_id") val joinSuccessId: kotlin.String? = null, + @Json(name = "coordinator_connect_id") + val coordinatorConnectId: kotlin.String? = null, + @Json(name = "outcome") val outcome: kotlin.String? = null, @@ -119,6 +122,7 @@ data class ClientEvent ( appendIfNotNull("eventSessionId", eventSessionId) appendIfNotNull("userSessionId", userSessionId) appendIfNotNull("joinSuccessId", joinSuccessId) + appendIfNotNull("coordinatorConnectId", coordinatorConnectId) appendIfNotNull("userId", userId) appendIfNotNull("sfuId", sfuId) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index 0bbb56b86ad..fa409420b29 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -83,6 +83,7 @@ import io.getstream.result.Error import io.getstream.result.Result import io.getstream.result.Result.Failure import io.getstream.result.Result.Success +import io.getstream.video.android.core.analytics.CoordinatorAnalytics import io.getstream.video.android.core.audio.AudioExecutionContext import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.errors.VideoErrorCode @@ -219,6 +220,64 @@ internal class StreamVideoClient internal constructor( internal fun getAudioContext(): AudioExecutionContext = audioExecutionContext val socketImpl = coordinatorConnectionModule.socketConnection + var location: String? = null + val coordinatorAnalytics = CoordinatorAnalytics(scope, state.clientEventReporter) + + init { + // listen to socket events and errors + scope.launch(CoroutineName("init#coordinatorSocket.events.collect")) { + coordinatorConnectionModule.socketConnection.events().collect { + fireEvent(it) + } + } + scope.launch { + coordinatorConnectionModule.socketConnection.state().collect { + state.handleState(it) + } + } + + scope.launch { + coordinatorAnalytics + .startObserver(coordinatorConnectionModule.socketConnection.state()) + } + + scope.launch(CoroutineName("init#coordinatorSocket.errors.collect")) { + coordinatorConnectionModule.socketConnection.errors().collect { error -> + state.handleError(error.streamError) + } + } + + scope.launch(CoroutineName("init#coordinatorSocket.connectionState.collect")) { + coordinatorConnectionModule.socketConnection.state().collect { it -> + // If the socket is reconnected then we have a new connection ID. + // We need to re-watch every watched call with the new connection ID + // (otherwise the WS events will stop) + val watchedCalls = calls + if (it is VideoSocketState.Connected && watchedCalls.isNotEmpty()) { + val filter = Filters.`in`("cid", watchedCalls.values.map { it.cid }).toMap() + queryCalls(filters = filter, watch = true).also { + if (it is Failure) { + logger.e { "Failed to re-watch calls (${it.value}" } + } + } + } + } + } + + scope.launch { + /** + * Invoke the reject API only when the busy check is triggered from the video client. + * + * Network calls initiated from background notification flows may be suspended + * by the OS, so API calls are intentionally avoided in those cases. + */ + callBusyHandler.callBusyHandlerState.filterNotNull() + .filter { it.source == CallBusyHandler.CallBusyHandlerCheckerSource.VIDEO_CLIENT } + .map { it.streamCallId }.collect { streamCallId -> + call(streamCallId.type, streamCallId.id).reject(RejectReason.Busy) + } + } + } fun onCallCleanUp(call: Call) { if (enableCallUpdatesAfterLeave) { @@ -256,6 +315,7 @@ internal class StreamVideoClient internal constructor( CallLeaveReason.SdkDriven(cause = SdkCause.CLIENT_CLEANUP, message = "client-cleanup"), ) // SDK client cleanup audioExecutionContext.release() + coordinatorAnalytics.endObserver() } /** @@ -389,59 +449,6 @@ internal class StreamVideoClient internal constructor( coordinatorConnectionModule.socketConnection.connect(user) } - init { - // listen to socket events and errors - scope.launch(CoroutineName("init#coordinatorSocket.events.collect")) { - coordinatorConnectionModule.socketConnection.events().collect { - fireEvent(it) - } - } - scope.launch { - coordinatorConnectionModule.socketConnection.state().collect { - state.handleState(it) - } - } - - scope.launch(CoroutineName("init#coordinatorSocket.errors.collect")) { - coordinatorConnectionModule.socketConnection.errors().collect { error -> - state.handleError(error.streamError) - } - } - - scope.launch(CoroutineName("init#coordinatorSocket.connectionState.collect")) { - coordinatorConnectionModule.socketConnection.state().collect { it -> - // If the socket is reconnected then we have a new connection ID. - // We need to re-watch every watched call with the new connection ID - // (otherwise the WS events will stop) - val watchedCalls = calls - if (it is VideoSocketState.Connected && watchedCalls.isNotEmpty()) { - val filter = Filters.`in`("cid", watchedCalls.values.map { it.cid }).toMap() - queryCalls(filters = filter, watch = true).also { - if (it is Failure) { - logger.e { "Failed to re-watch calls (${it.value}" } - } - } - } - } - } - - scope.launch { - /** - * Invoke the reject API only when the busy check is triggered from the video client. - * - * Network calls initiated from background notification flows may be suspended - * by the OS, so API calls are intentionally avoided in those cases. - */ - callBusyHandler.callBusyHandlerState.filterNotNull() - .filter { it.source == CallBusyHandler.CallBusyHandlerCheckerSource.VIDEO_CLIENT } - .map { it.streamCallId }.collect { streamCallId -> - call(streamCallId.type, streamCallId.id).reject(RejectReason.Busy) - } - } - } - - var location: String? = null - internal suspend fun getCachedLocation(): Result { if (state.failureInjector.isEnabled(FailureKey.FAIL_LOCATION)) { return state.failureInjector.sendFailResult(FailureKey.FAIL_LOCATION) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index d4c9ec3ac21..4e7eaa29727 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -19,6 +19,7 @@ package io.getstream.video.android.core.analytics import io.getstream.log.taggedLogger import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.RealtimeConnection +import io.getstream.video.android.core.events.reporting.AnalyticsCallAbortReason import io.getstream.video.android.core.events.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @@ -54,10 +55,10 @@ internal class CallAnalyticsHooks( if (isAnyStageInProgress) { val abortReason = when (callLeaveReason) { - is CallLeaveReason.Backend -> ClientEventReporter.AbortReason.BACKEND_LEAVE - else -> ClientEventReporter.AbortReason.CLIENT_ABORTED + is CallLeaveReason.Backend -> AnalyticsCallAbortReason.BACKEND_LEAVE + else -> AnalyticsCallAbortReason.CLIENT_ABORTED } - eventReporter.abortAllInFlight(abortReason) + eventReporter.abortAllPostCallInFlight(abortReason) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt index 6cacd31ec78..ba1933852c0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -78,13 +78,13 @@ internal class PeerConnectionAnalyticsObserver( private fun getStage(peerConnectionState: PeerConnection.PeerConnectionState): Stage { return when (peerConnectionState) { PeerConnection.PeerConnectionState.CONNECTING -> { - Stage.IN_PROGRESS + Stage.IN_PROGRESS // initiated } PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.CONNECTED, -> { - Stage.NOT_STARTED + Stage.NOT_STARTED // completed } else -> { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index dd0ef16b1a9..8e6e266172b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -71,7 +71,7 @@ import io.getstream.video.android.core.events.SfuDataRequest import io.getstream.video.android.core.events.SubscriberOfferEvent import io.getstream.video.android.core.events.TrackPublishedEvent import io.getstream.video.android.core.events.TrackUnpublishedEvent -import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.events.reporting.AnalyticsFailureCodes import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.internal.module.SfuConnectionModule import io.getstream.video.android.core.model.AudioTrack @@ -934,8 +934,8 @@ public class RtcSession internal constructor( call.callAnalyticsHooks.wsHook.onWsCompleted( success = false, retryCount = telemetryModel?.retryAttempt ?: 0, - failureReason = ClientEventReporter.FailureCodes.SFU_REQUEST_TIMEOUT.message, - failureCode = ClientEventReporter.FailureCodes.SFU_REQUEST_TIMEOUT.code, + failureReason = AnalyticsFailureCodes.SFU_REQUEST_TIMEOUT.message, + failureCode = AnalyticsFailureCodes.SFU_REQUEST_TIMEOUT.code, ) sendCallStats() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnection.kt index ab79ab0b21d..116cc1fd6ac 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnection.kt @@ -96,6 +96,7 @@ open class StreamPeerConnection( // see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState internal val state = MutableStateFlow(null) internal val iceState = MutableStateFlow(null) + internal val iceError = MutableStateFlow(null) open suspend fun stats(): ComputedStats? = null @@ -173,6 +174,7 @@ open class StreamPeerConnection( this.statsTracer = StatsTracer(connection, type.toPeerType()) this.state.value = this.connection.connectionState() this.iceState.value = this.connection.iceConnectionState() + this.iceError.value = null } /** @@ -539,6 +541,12 @@ open class StreamPeerConnection( override fun onConnectionChange(newState: PeerConnection.PeerConnectionState) { logger.i { "[onConnectionChange] #sfu; #$typeTag; newState: $newState" } state.value = newState + when (newState) { + PeerConnection.PeerConnectionState.CONNECTED -> { + iceError.value = null + } + else -> {} + } tracer.trace(PeerConnectionTraceKey.ON_CONNECTION_STATE_CHANGE.value, newState.name) } @@ -613,6 +621,7 @@ open class StreamPeerConnection( override fun onIceCandidateError(event: IceCandidateErrorEvent?) { logger.e { "[onIceCandidateError] #sfu; #$typeTag; event: ${event?.stringify()}" } + iceError.value = event } override fun onSelectedCandidatePairChanged(event: CandidatePairChangeEvent?) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt index ce8c81a5332..730a50e1cbf 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt @@ -16,19 +16,27 @@ package io.getstream.video.android.core.events.reporting -internal enum class CallEventStage(val value: String) { - JOIN_INITIATED("JoinInitiated"), - COORDINATOR_JOIN("CoordinatorJoin"), - WS_JOIN("WSJoin"), - PEER_CONNECTION_CONNECT("PeerConnectionConnect"), +internal sealed interface EventStage { + val value: String + + data object CoordinatorWs : EventStage { + override val value = "CoordinatorWS" + } + + enum class Call(override val value: String) : EventStage { + JOIN_INITIATED("JoinInitiated"), + COORDINATOR_JOIN("CoordinatorJoin"), + WS_JOIN("WSJoin"), + PEER_CONNECTION_CONNECT("PeerConnectionConnect"), + } } -internal enum class CallEventType(val value: String) { +internal enum class EventType(val value: String) { INITIATED("initiated"), COMPLETED("completed"), } -internal enum class CallEventOutcome(val value: String) { +internal enum class EventOutcome(val value: String) { SUCCESS("success"), FAILURE("failure"), } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index 5e4a554ff4e..f23fb4d1d7a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -52,7 +52,7 @@ internal class ClientEventReporter( ) { private val logger by taggedLogger("ClientEventReporter") - private val inFlightSessions = ConcurrentHashMap() + private val postCallFlightSessions = ConcurrentHashMap() private val joinStageAttemptIdMap = ConcurrentHashMap() private val callSessionIdMap = ConcurrentHashMap() @@ -62,33 +62,50 @@ internal class ClientEventReporter( // Whether each PC role has ever reached CONNECTED (for was_previously_connected) private val pcEverConnected = ConcurrentHashMap() - internal data class InFlightSession( - val eventSessionId: EventSessionId, - val callId: String, - val callType: String, - val stage: CallEventStage, - val startedAtMs: Long, - val joinStageAttemptIdSnapshot: String, - val sfuId: String? = null, - val callSessionId: String? = null, - val userSessionId: String? = null, - val peerConnectionRole: PeerConnectionRole? = null, - val wasPreviouslyConnected: Boolean = false, - ) - - enum class AbortReason(val code: String, val message: String) { - CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), - BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), + internal fun reportCoordinatorWSInitiated(): String { + val coordinatorConnectId = UUID.randomUUID().toString() + val eventSessionId = UUID.randomUUID().toString() + val now = System.currentTimeMillis() + postCallFlightSessions[eventSessionId] = PreCallInFlightSession( + eventSessionId = coordinatorConnectId, + coordinatorConnectId = coordinatorConnectId, + stage = EventStage.Call.COORDINATOR_JOIN, + startedAtMs = now, + ) + sendEvent( + buildRequest( + eventSessionId = eventSessionId, + coordinatorConnectId = coordinatorConnectId, + stage = EventStage.CoordinatorWs, + eventType = EventType.INITIATED, + ), + ) + return eventSessionId } - enum class FailureCodes(val code: String, val message: String) { - CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), - BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), - NETWORK_OFFLINE("NETWORK_OFFLINE", "Device offline"), - ICE_GATHERING_FAILED("ICE_GATHERING_FAILED", "ICE gathering failed"), - ICE_CONNECTIVITY_FAILED("ICE_CONNECTIVITY_FAILED", "ICE connectivity failed"), - REQUEST_TIMEOUT("REQUEST_TIMEOUT", "Device offline"), - SFU_REQUEST_TIMEOUT("REQUEST_TIMEOUT", "SFU connection timed out"), + internal fun reportCoordinatorWSCompleted( + eventSessionId: String, + success: Boolean, + retryCount: Int? = null, + failureCode: String? = null, + failureReason: String? = null, + ) { + val session = postCallFlightSessions.remove(eventSessionId) ?: return + if (session is PreCallInFlightSession) { + val elapsedTime = System.currentTimeMillis() - session.startedAtMs + sendEvent( + buildRequest( + coordinatorConnectId = session.coordinatorConnectId, + stage = EventStage.CoordinatorWs, + outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, + retryCountAttempt = retryCount, + retryFailureCode = failureCode, + elapsedTime = elapsedTime, + retryFailureReason = failureReason, + eventType = EventType.COMPLETED, + ), + ) + } } internal fun reportSdkMethodJoinInitiated( @@ -101,8 +118,8 @@ internal class ClientEventReporter( buildRequest( callId, callType, - stage = CallEventStage.JOIN_INITIATED, - eventType = CallEventType.INITIATED, + stage = EventStage.Call.JOIN_INITIATED, + eventType = EventType.INITIATED, joinStageAttemptId = joinStageAttemptId, ), ) @@ -118,11 +135,11 @@ internal class ClientEventReporter( val eventSessionId = UUID.randomUUID().toString() joinStageAttemptIdMap[callId] = joinStageAttemptId val now = System.currentTimeMillis() - inFlightSessions[eventSessionId] = InFlightSession( + postCallFlightSessions[eventSessionId] = PostCallFlightSession( eventSessionId = eventSessionId, callId = callId, callType = callType, - stage = CallEventStage.COORDINATOR_JOIN, + stage = EventStage.Call.COORDINATOR_JOIN, startedAtMs = now, joinStageAttemptIdSnapshot = joinStageAttemptId, ) @@ -130,8 +147,8 @@ internal class ClientEventReporter( buildRequest( callId, callType, - stage = CallEventStage.COORDINATOR_JOIN, - eventType = CallEventType.INITIATED, + stage = EventStage.Call.COORDINATOR_JOIN, + eventType = EventType.INITIATED, eventSessionId = eventSessionId, joinStageAttemptId = joinStageAttemptId, ), @@ -147,25 +164,27 @@ internal class ClientEventReporter( failureCode: String? = null, // TODO Rahul, ask tomorrow callSessionId: String? = null, ) { - val session = inFlightSessions.remove(eventSessionId) ?: return - val elapsedTime = System.currentTimeMillis() - session.startedAtMs - callSessionIdMap[session.callId] = callSessionId ?: "" - sendEvent( - buildRequest( - callId = session.callId, - callType = session.callType, - stage = CallEventStage.COORDINATOR_JOIN, - eventType = CallEventType.COMPLETED, - eventSessionId = eventSessionId, - elapsedTime = elapsedTime, - outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, - retryCountAttempt = retryCount, - retryFailureReason = if (!success) failureReason else null, - retryFailureCode = if (!success) failureCode else null, - callSessionId = callSessionId, - joinStageAttemptId = session.joinStageAttemptIdSnapshot, - ), - ) + val session = postCallFlightSessions.remove(eventSessionId) ?: return + if (session is PostCallFlightSession) { + val elapsedTime = System.currentTimeMillis() - session.startedAtMs + callSessionIdMap[session.callId] = callSessionId ?: "" + sendEvent( + buildRequest( + callId = session.callId, + callType = session.callType, + stage = EventStage.Call.COORDINATOR_JOIN, + eventType = EventType.COMPLETED, + eventSessionId = eventSessionId, + elapsedTime = elapsedTime, + outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, + retryCountAttempt = retryCount, + retryFailureReason = if (!success) failureReason else null, + retryFailureCode = if (!success) failureCode else null, + callSessionId = callSessionId, + joinStageAttemptId = session.joinStageAttemptIdSnapshot, + ), + ) + } } // --- WSJoin --- @@ -180,11 +199,11 @@ internal class ClientEventReporter( val eventSessionId = UUID.randomUUID().toString() val now = System.currentTimeMillis() val callSessionId = callSessionIdMap[callId] - inFlightSessions[eventSessionId] = InFlightSession( + postCallFlightSessions[eventSessionId] = PostCallFlightSession( callId = callId, callType = callType, eventSessionId = eventSessionId, - stage = CallEventStage.WS_JOIN, + stage = EventStage.Call.WS_JOIN, startedAtMs = now, joinStageAttemptIdSnapshot = joinStageAttemptIdMap[callId] ?: "", sfuId = sfuId, @@ -195,8 +214,8 @@ internal class ClientEventReporter( buildRequest( callId = callId, callType = callType, - stage = CallEventStage.WS_JOIN, - eventType = CallEventType.INITIATED, + stage = EventStage.Call.WS_JOIN, + eventType = EventType.INITIATED, eventSessionId = eventSessionId, joinStageAttemptId = joinStageAttemptId, sfuId = sfuId, @@ -214,25 +233,27 @@ internal class ClientEventReporter( failureReason: String? = null, failureCode: String? = null, ) { - val session = inFlightSessions.remove(eventSessionId) ?: return + val session = postCallFlightSessions.remove(eventSessionId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs - sendEvent( - buildRequest( - callId = session.callId, - callType = session.callType, - stage = CallEventStage.WS_JOIN, - eventType = CallEventType.COMPLETED, - eventSessionId = eventSessionId, - elapsedTime = elapsedTime, - outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, - retryCountAttempt = retryCount, - retryFailureReason = if (!success) failureReason else null, - retryFailureCode = if (!success) failureCode else null, - sfuId = session.sfuId, - callSessionId = session.callSessionId, - joinStageAttemptId = joinStageAttemptId, - ), - ) + if (session is PostCallFlightSession) { + sendEvent( + buildRequest( + callId = session.callId, + callType = session.callType, + stage = EventStage.Call.WS_JOIN, + eventType = EventType.COMPLETED, + eventSessionId = eventSessionId, + elapsedTime = elapsedTime, + outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, + retryCountAttempt = retryCount, + retryFailureReason = if (!success) failureReason else null, + retryFailureCode = if (!success) failureCode else null, + sfuId = session.sfuId, + callSessionId = session.callSessionId, + joinStageAttemptId = joinStageAttemptId, + ), + ) + } } // --- PeerConnectionConnect (ICE state machine) --- @@ -265,11 +286,11 @@ internal class ClientEventReporter( } val eventSessionId = UUID.randomUUID().toString() val now = System.currentTimeMillis() - inFlightSessions[eventSessionId] = InFlightSession( + postCallFlightSessions[eventSessionId] = PostCallFlightSession( callId = callId, callType = callType, eventSessionId = eventSessionId, - stage = CallEventStage.PEER_CONNECTION_CONNECT, + stage = EventStage.Call.PEER_CONNECTION_CONNECT, startedAtMs = now, joinStageAttemptIdSnapshot = joinStageAttemptIdMap[callId] ?: "", peerConnectionRole = role, @@ -281,8 +302,8 @@ internal class ClientEventReporter( buildRequest( callId = callId, callType = callType, - stage = CallEventStage.PEER_CONNECTION_CONNECT, - eventType = CallEventType.INITIATED, + stage = EventStage.Call.PEER_CONNECTION_CONNECT, + eventType = EventType.INITIATED, eventSessionId = eventSessionId, joinStageAttemptId = joinStageAttemptId, peerConnection = role, @@ -323,7 +344,9 @@ internal class ClientEventReporter( ) } - else -> { /* DISCONNECTED handled by ICE restart → CHECKING */ } + else -> { + /* DISCONNECTED handled by ICE restart → CHECKING */ + } } } @@ -338,32 +361,35 @@ internal class ClientEventReporter( failureReason: String? = null, failureCode: String? = null, ) { - val session = inFlightSessions.remove(eventSessionId) ?: return + val session = postCallFlightSessions.remove(eventSessionId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs - sendEvent( - buildRequest( - callId = callId, - callType = callType, - stage = CallEventStage.PEER_CONNECTION_CONNECT, - eventType = CallEventType.COMPLETED, - eventSessionId = eventSessionId, - joinStageAttemptId = joinStageAttemptId, - elapsedTime = elapsedTime, - outcome = if (success) CallEventOutcome.SUCCESS else CallEventOutcome.FAILURE, - retryCountAttempt = 0, - retryFailureReason = if (!success) failureReason else null, - retryFailureCode = if (!success) failureCode else null, - peerConnection = session.peerConnectionRole, - wasPreviouslyConnected = session.wasPreviouslyConnected, - iceState = iceState, - peerConnectionState = peerConnectionState, - ), - ) + if (session is PostCallFlightSession) { + sendEvent( + buildRequest( + callId = callId, + callType = callType, + stage = EventStage.Call.PEER_CONNECTION_CONNECT, + eventType = EventType.COMPLETED, + eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, + elapsedTime = elapsedTime, + outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, + retryCountAttempt = 0, + retryFailureReason = if (!success) failureReason else null, + retryFailureCode = if (!success) failureCode else null, + peerConnection = session.peerConnectionRole, + wasPreviouslyConnected = session.wasPreviouslyConnected, + iceState = iceState, + peerConnectionState = peerConnectionState, + ), + ) + } } - internal fun abortAllInFlight(reason: AbortReason) { - val snapshot = inFlightSessions.values.toList() - inFlightSessions.clear() + internal fun abortAllPostCallInFlight(reason: AnalyticsCallAbortReason) { + val snapshot: List = + postCallFlightSessions.values.filterIsInstance().toList() + postCallFlightSessions.clear() activePcSessionIds.clear() val now = System.currentTimeMillis() val events = snapshot.map { session -> @@ -371,10 +397,10 @@ internal class ClientEventReporter( callId = session.callId, callType = session.callType, stage = session.stage, - eventType = CallEventType.COMPLETED, + eventType = EventType.COMPLETED, eventSessionId = session.eventSessionId, elapsedTime = now - session.startedAtMs, - outcome = CallEventOutcome.FAILURE, + outcome = EventOutcome.FAILURE, retryCountAttempt = 0, retryFailureReason = reason.message, retryFailureCode = reason.code, @@ -392,14 +418,15 @@ internal class ClientEventReporter( // --- Request builder --- private fun buildRequest( - callId: String, - callType: String, - stage: CallEventStage, - eventType: CallEventType, + callId: String? = null, + callType: String? = null, + stage: EventStage, + eventType: EventType, eventSessionId: String? = null, - joinStageAttemptId: String, + joinStageAttemptId: String? = null, + coordinatorConnectId: String? = null, elapsedTime: Long? = null, - outcome: CallEventOutcome? = null, + outcome: EventOutcome? = null, retryCountAttempt: Int? = null, retryFailureReason: String? = null, retryFailureCode: String? = null, @@ -439,7 +466,7 @@ internal class ClientEventReporter( private fun sendEvent(event: ClientEvent) { when (sendingStrategy) { - TelemetrySendingStrategy.BATCH -> { } + TelemetrySendingStrategy.BATCH -> {} TelemetrySendingStrategy.IN_PLACE -> { scope.launch { // TODO: wrap with StreamRetryPolicy when retries are added diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt index 392d594e82f..78ed84d0684 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt @@ -17,3 +17,45 @@ package io.getstream.video.android.core.events.reporting internal data class TelemetryModel(val retryAttempt: Int) + +internal enum class AnalyticsCallAbortReason(val code: String, val message: String) { + CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), + BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), +} + +internal enum class AnalyticsFailureCodes(val code: String, val message: String) { + CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), + BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), + NETWORK_OFFLINE("NETWORK_OFFLINE", "Device offline"), + ICE_GATHERING_FAILED("ICE_GATHERING_FAILED", "ICE gathering failed"), + ICE_CONNECTIVITY_FAILED("ICE_CONNECTIVITY_FAILED", "ICE connectivity failed"), + REQUEST_TIMEOUT("REQUEST_TIMEOUT", "Device offline"), + SFU_REQUEST_TIMEOUT("REQUEST_TIMEOUT", "SFU connection timed out"), +} + +internal sealed class InFlightSession( + open val stage: EventStage.Call, + open val startedAtMs: Long, + open val eventSessionId: EventSessionId, +) + +internal data class PostCallFlightSession( + val callId: String, + val callType: String, + override val eventSessionId: EventSessionId, + override val stage: EventStage.Call, + override val startedAtMs: Long, + val joinStageAttemptIdSnapshot: String, + val sfuId: String? = null, + val callSessionId: String? = null, + val userSessionId: String? = null, + val peerConnectionRole: PeerConnectionRole? = null, + val wasPreviouslyConnected: Boolean = false, +) : InFlightSession(stage, startedAtMs, eventSessionId) + +internal data class PreCallInFlightSession( + override val stage: EventStage.Call, + override val startedAtMs: Long, + override val eventSessionId: EventSessionId, + val coordinatorConnectId: String, +) : InFlightSession(stage, startedAtMs, eventSessionId) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketStateService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketStateService.kt index 0e81094d90b..af8424a5f39 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketStateService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketStateService.kt @@ -30,6 +30,13 @@ import kotlinx.coroutines.flow.StateFlow internal class CoordinatorSocketStateService(initialState: VideoSocketState = VideoSocketState.Disconnected.Stopped) { private val logger by taggedLogger("Video:SocketState") + // TODO making it companion because CoordinatorSocketConnection has public constructor + // So we cannot inject CoordinatorSocketStateService from StreamBuilder without breaking apis + internal companion object { + var retryAttempts = 0 + var lastRetryAttempts = 0 + } + suspend fun observer(onNewState: suspend (VideoSocketState) -> Unit) { stateMachine.stateFlow.collect(onNewState) } @@ -43,6 +50,7 @@ internal class CoordinatorSocketStateService(initialState: VideoSocketState = Vi connectionConf: ConnectionConf, forceReconnection: Boolean, ) { + retryAttempts += 1 logger.v { "[onReconnect] user.id: '${connectionConf.user.id}', isReconnection: ${connectionConf.isReconnection}" } @@ -63,6 +71,8 @@ internal class CoordinatorSocketStateService(initialState: VideoSocketState = Vi * @param connectionConf The [VideoSocketFactory.ConnectionConf] to be used on the new connection. */ suspend fun onConnect(connectionConf: ConnectionConf) { + lastRetryAttempts = retryAttempts + retryAttempts = 0 logger.v { "[onConnect] user.id: '${connectionConf.user.id}', isReconnection: ${connectionConf.isReconnection}" } @@ -88,6 +98,8 @@ internal class CoordinatorSocketStateService(initialState: VideoSocketState = Vi * @param connectedEvent The [ConnectedEvent] received within the WebSocket connection. */ suspend fun onConnectionEstablished(connectedEvent: ConnectedEvent) { + lastRetryAttempts = retryAttempts + retryAttempts = 0 logger.i { "[onConnected] user.id: '${connectedEvent.me.id}', connectionId: ${connectedEvent.connectionId}" } @@ -100,6 +112,8 @@ internal class CoordinatorSocketStateService(initialState: VideoSocketState = Vi * @param error The [Error.NetworkError] */ suspend fun onUnrecoverableError(error: Error.NetworkError) { + lastRetryAttempts = retryAttempts + retryAttempts = 0 logger.e { "[onUnrecoverableError] error: $error" } stateMachine.sendEvent(VideoSocketStateEvent.UnrecoverableError(error)) } From 3cdbce129437657848dc72db9670b229a187bfbe Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 3 Jun 2026 02:49:34 +0530 Subject: [PATCH 40/63] feat: add coordinator ws analytics --- .../core/analytics/CoordinatorAnalytics.kt | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt new file mode 100644 index 00000000000..d840e24e81a --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.socket.coordinator.CoordinatorSocketStateService +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketConnectionType +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +internal class CoordinatorAnalytics( + private val scope: CoroutineScope, + private val eventReporter: ClientEventReporter, +) { + + private var job: Job? = null + private var eventSessionId = "" + + fun startObserver(videoSocketStateFlow: StateFlow) { + endObserver() + job = scope.launch { + videoSocketStateFlow.collect { + when (it) { + is VideoSocketState.Connecting -> { + when (it.connectionType) { + VideoSocketConnectionType.INITIAL_CONNECTION -> { + eventSessionId = eventReporter.reportCoordinatorWSInitiated() + } + + VideoSocketConnectionType.AUTOMATIC_RECONNECTION -> {} + VideoSocketConnectionType.FORCE_RECONNECTION -> {} + } + } + + is VideoSocketState.Connected -> { + if (eventSessionId.isNotEmpty()) { + eventReporter.reportCoordinatorWSCompleted( + eventSessionId, + true, + CoordinatorSocketStateService.lastRetryAttempts, + ) + } + } + + is VideoSocketState.Disconnected.DisconnectedPermanently -> { + if (eventSessionId.isNotEmpty()) { + eventReporter.reportCoordinatorWSCompleted( + eventSessionId, + false, + CoordinatorSocketStateService.lastRetryAttempts, + ) + } + } + + else -> {} + } + } + } + } + + fun endObserver() { + job?.cancel() + job = null + } +} From 5d57cc5d13662c7234473bbf602fc0921b13bce0 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 3 Jun 2026 14:17:10 +0530 Subject: [PATCH 41/63] feat: add audio analytics, video analytics --- .../api/stream-video-android-core.api | 16 ++-- .../video/generated/models/ClientEvent.kt | 27 ++++--- .../io/getstream/video/android/core/Call.kt | 8 ++ .../android/core/analytics/AudioAnalytics.kt | 43 ++++++++++ .../core/analytics/CallAnalyticsHooks.kt | 12 ++- .../core/analytics/MediaPermissionHook.kt | 34 +++++++- .../android/core/analytics/VideoAnalytics.kt | 43 ++++++++++ .../video/android/core/analytics/WsHook.kt | 3 + .../connection/StreamPeerConnectionFactory.kt | 8 ++ .../events/reporting/ClientCallEventData.kt | 3 + .../events/reporting/ClientEventReporter.kt | 79 ++++++++++++++++++- 11 files changed, 254 insertions(+), 22 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 050fcb0c5a0..aaa2dd8ed03 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -2457,8 +2457,8 @@ public final class io/getstream/android/video/generated/models/ChatPreferencesRe public final class io/getstream/android/video/generated/models/ClientEvent { public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component10 ()Ljava/lang/String; public final fun component11 ()Lorg/threeten/bp/OffsetDateTime; @@ -2475,6 +2475,9 @@ public final class io/getstream/android/video/generated/models/ClientEvent { public final fun component21 ()Ljava/lang/String; public final fun component22 ()Ljava/lang/String; public final fun component23 ()Ljava/lang/Boolean; + public final fun component24 ()Ljava/lang/Boolean; + public final fun component25 ()Ljava/lang/Boolean; + public final fun component26 ()Ljava/lang/Boolean; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -2482,23 +2485,26 @@ public final class io/getstream/android/video/generated/models/ClientEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCallSessionId ()Ljava/lang/String; + public final fun getCameraGranted ()Ljava/lang/Boolean; public final fun getCoordinatorConnectId ()Ljava/lang/String; public final fun getElapsedTime ()Ljava/lang/Integer; public final fun getEventSessionId ()Ljava/lang/String; public final fun getEventType ()Ljava/lang/String; public final fun getIceState ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; - public final fun getJoinSuccessId ()Ljava/lang/String; + public final fun getJoinAttemptId ()Ljava/lang/String; + public final fun getMicrophoneGranted ()Ljava/lang/Boolean; public final fun getOutcome ()Ljava/lang/String; public final fun getPeerConnection ()Ljava/lang/String; public final fun getPreviouslyConnectedTimestamp ()Lorg/threeten/bp/OffsetDateTime; public final fun getRetryCountAttempt ()Ljava/lang/Integer; public final fun getRetryFailureCode ()Ljava/lang/String; public final fun getRetryFailureReason ()Ljava/lang/String; + public final fun getScreenShareGranted ()Ljava/lang/Boolean; public final fun getSdkVersion ()Ljava/lang/String; public final fun getSfuId ()Ljava/lang/String; public final fun getStage ()Ljava/lang/String; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt index 26752a4f77c..3db3e0251e0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt @@ -23,16 +23,7 @@ package io.getstream.android.video.generated.models -import kotlin.collections.List -import kotlin.collections.Map -import kotlin.collections.* -import kotlin.io.* -import com.squareup.moshi.FromJson import com.squareup.moshi.Json -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson /** * A single client-side telemetry event. When stage is CoordinatorJoin, WSJoin, or PeerConnectionConnect the event reports a join-lifecycle attempt; initiation and completion of a stage attempt share the same event_session_id. Other stage values denote generic client events. @@ -57,8 +48,8 @@ data class ClientEvent ( @Json(name = "id") val id: kotlin.String? = null, - @Json(name = "join_success_id") - val joinSuccessId: kotlin.String? = null, + @Json(name = "join_attempt_id") + val joinAttemptId: kotlin.String? = null, @Json(name = "coordinator_connect_id") val coordinatorConnectId: kotlin.String? = null, @@ -106,7 +97,14 @@ data class ClientEvent ( val userSessionId: kotlin.String? = null, @Json(name = "was_previously_connected") - val wasPreviouslyConnected: kotlin.Boolean? = null + val wasPreviouslyConnected: kotlin.Boolean? = null, + + @Json(name = "screen_share") + val screenShareGranted: kotlin.Boolean? = null, + @Json(name = "microphone") + val microphoneGranted: kotlin.Boolean? = null, + @Json(name = "camera") + val cameraGranted: kotlin.Boolean? = null ) { internal fun toLog(): String { @@ -121,7 +119,7 @@ data class ClientEvent ( appendIfNotNull("callSessionId", callSessionId) appendIfNotNull("eventSessionId", eventSessionId) appendIfNotNull("userSessionId", userSessionId) - appendIfNotNull("joinSuccessId", joinSuccessId) + appendIfNotNull("joinAttemptId", joinAttemptId) appendIfNotNull("coordinatorConnectId", coordinatorConnectId) appendIfNotNull("userId", userId) @@ -148,6 +146,9 @@ data class ClientEvent ( appendIfNotNull("sdkVersion", sdkVersion) appendIfNotNull("timestamp", timestamp) + appendIfNotNull("camera", callSessionId) + appendIfNotNull("microphone", microphoneGranted) + appendIfNotNull("screenShare", screenShareGranted) append(")") } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 1be67a972df..91ff652ebaa 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -314,6 +314,7 @@ public class Call( internal val callAnalyticsHooks = CallAnalyticsHooks( + clientImpl.context, this.id, this.type, state.connection, @@ -562,6 +563,7 @@ public class Call( callJoinInterceptor: CallJoinInterceptor? = null, ): Result { callAnalyticsHooks.joinRequestHooks.onJoinFunctionStart() + callAnalyticsHooks.mediaPermissionHook.mediaPermissionStatus() logger.d { "[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions" } @@ -583,6 +585,11 @@ public class Call( // Ensure factory is created with the current audioBitrateProfile before joining ensureFactoryMatchesAudioProfile() + peerConnectionFactory.setPlaybackSamplesReadyCallback { + scope.launch { + callAnalyticsHooks.audioAnalytics.firstAudioFrameRendered() + } + } this.state.callJoinInterceptor = callJoinInterceptor @@ -1507,6 +1514,7 @@ public class Call( ) } onRendered(videoRenderer) + callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered() } override fun onFrameResolutionChanged( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt new file mode 100644 index 00000000000..523ae356035 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.events.reporting.ClientEventReporter + +internal class AudioAnalytics( + private val callId: String, + private val callType: String, + private val clientEventReporter: ClientEventReporter, + private val onSfuId: () -> String, + val getJoinStageAttemptId: () -> String, +) { + + var eventSession: String = "" + + fun firstAudioFrameRendered() { + if (eventSession.isNotEmpty()) { + eventSession = clientEventReporter.reportFirstAudioFrameRendered( + onSfuId(), + callId, + callType, + getJoinStageAttemptId(), + ) + } + } + + fun reset() {} +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index 4e7eaa29727..eaaa2010eb6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -16,6 +16,7 @@ package io.getstream.video.android.core.analytics +import android.content.Context import io.getstream.log.taggedLogger import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.RealtimeConnection @@ -25,6 +26,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow internal class CallAnalyticsHooks( + val context: Context, val callId: String, val callType: String, val connectionFlow: StateFlow, @@ -43,7 +45,15 @@ internal class CallAnalyticsHooks( val peerConnectionAnalyticsObserver = PeerConnectionAnalyticsObserver(scope, peerConnectionHook) - val mediaPermissionHook = MediaPermissionHook(callId, callType, eventReporter) + val mediaPermissionHook = MediaPermissionHook(context, callId, callType, eventReporter) { + joinRequestHooks.joinStageAttemptId + } + val audioAnalytics = AudioAnalytics(callId, callType, eventReporter, { wsHook.sfuName }) { + joinRequestHooks.joinStageAttemptId + } + val videoAnalytics = VideoAnalytics(callId, callType, eventReporter, { wsHook.sfuName }) { + joinRequestHooks.joinStageAttemptId + } fun onCallLeave(callLeaveReason: CallLeaveReason) { val isAnyStageInProgress = diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt index 75bf6fec20d..1bf5210a233 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt @@ -16,11 +16,41 @@ package io.getstream.video.android.core.analytics +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat import io.getstream.video.android.core.events.reporting.ClientEventReporter -internal class MediaPermissionHook(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { - var stage = Stage.NOT_STARTED +internal class MediaPermissionHook( + val context: Context, + val callId: String, + val callType: String, + val eventReporter: ClientEventReporter, + val getJoinStageAttemptId: () -> String, +) { fun mediaPermissionStatus() { + eventReporter.reportMediaPermissionStatus( + callId, + callType, + getJoinStageAttemptId(), + isCameraPermissionGranted(), + isMicrophonePermissionGranted(), + ) + } + + fun isMicrophonePermissionGranted(): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.RECORD_AUDIO, + ) == PackageManager.PERMISSION_GRANTED + } + + fun isCameraPermissionGranted(): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA, + ) == PackageManager.PERMISSION_GRANTED } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt new file mode 100644 index 00000000000..4e623d7eed5 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics + +import io.getstream.video.android.core.events.reporting.ClientEventReporter + +internal class VideoAnalytics( + private val callId: String, + private val callType: String, + private val clientEventReporter: ClientEventReporter, + private val onSfuId: () -> String, + val getJoinStageAttemptId: () -> String, +) { + + var eventSession: String = "" + + fun firstVideoFrameRendered() { + if (eventSession.isNotEmpty()) { + eventSession = clientEventReporter.reportFirstVideoFrameRendered( + onSfuId(), + callId, + callType, + getJoinStageAttemptId(), + ) + } + } + + fun reset() {} +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt index 47e091676a6..6b1a4636946 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt @@ -31,8 +31,11 @@ internal class WsHook( ) { var telemetryWsEventSessionId = "" var wsStage = Stage.NOT_STARTED + + var sfuName: String = "" fun onWsInitiated(sfuName: String, wasPreviouslyConnected: Boolean) { if (wsStage == Stage.NOT_STARTED) { + this.sfuName = sfuName telemetryWsEventSessionId = reporter.reportWsJoinInitiated( callId = callId, callType = callType, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory.kt index e28647cc048..0c479c8e0ca 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory.kt @@ -88,6 +88,7 @@ public class StreamPeerConnectionFactory( private val audioLogger by taggedLogger("Call:AudioTrackCallback") private var audioSampleCallback: ((AudioSamples) -> Unit)? = null + private var playbackSamplesReadyCallback: (() -> Unit)? = null private var audioRecordDataCallback: ( (audioFormat: Int, channelCount: Int, sampleRate: Int, sampleData: ByteBuffer) -> Unit )? = null @@ -107,6 +108,10 @@ public class StreamPeerConnectionFactory( audioSampleCallback = callback } + internal fun setPlaybackSamplesReadyCallback(callback: () -> Unit) { + playbackSamplesReadyCallback = callback + } + /** * Set to get callbacks when audio input from microphone is received. * This can be example used to detect whether a person is speaking @@ -273,6 +278,9 @@ public class StreamPeerConnectionFactory( ) } } + .setPlaybackSamplesReadyCallback { + playbackSamplesReadyCallback?.invoke() + } .setUseHardwareNoiseSuppressor(useHardwareNoiseSuppressor) .setAudioRecordErrorCallback(object : JavaAudioDeviceModule.AudioRecordErrorCallback { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt index 730a50e1cbf..3e6f101163b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt @@ -28,6 +28,9 @@ internal sealed interface EventStage { COORDINATOR_JOIN("CoordinatorJoin"), WS_JOIN("WSJoin"), PEER_CONNECTION_CONNECT("PeerConnectionConnect"), + FIRST_AUDIO_FRAME_RENDERED("FirstAudioFrame"), + FIRST_VIDEO_FRAME_RENDERED("FirstVideoFrame"), + MEDIA_DEVICE_PERMISSION("MediaDevicePermission"), } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index f23fb4d1d7a..c13818f5eec 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -386,6 +386,77 @@ internal class ClientEventReporter( } } + internal fun reportFirstAudioFrameRendered( + sfuId: String, + callId: String, + callType: String, + joinStageAttemptId: String, + ): String { + val eventSessionId = UUID.randomUUID().toString() + val callSessionId = callSessionIdMap[callId] + sendEvent( + buildRequest( + callId = callId, + callType = callType, + stage = EventStage.Call.FIRST_AUDIO_FRAME_RENDERED, + eventType = EventType.INITIATED, + eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, + callSessionId = callSessionId, + sfuId = sfuId, + ), + ) + return eventSessionId + } + + internal fun reportFirstVideoFrameRendered( + sfuId: String, + callId: String, + callType: String, + joinStageAttemptId: String, + ): String { + val eventSessionId = UUID.randomUUID().toString() + val callSessionId = callSessionIdMap[callId] + sendEvent( + buildRequest( + callId = callId, + callType = callType, + stage = EventStage.Call.FIRST_VIDEO_FRAME_RENDERED, + eventType = EventType.INITIATED, + eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, + callSessionId = callSessionId, + sfuId = sfuId, + ), + ) + return eventSessionId + } + + internal fun reportMediaPermissionStatus( + callId: String, + callType: String, + joinStageAttemptId: String, + isCameraGranted: Boolean, + isMicrophoneGranted: Boolean, + ): String { + val eventSessionId = UUID.randomUUID().toString() + val callSessionId = callSessionIdMap[callId] + sendEvent( + buildRequest( + callId = callId, + callType = callType, + stage = EventStage.Call.MEDIA_DEVICE_PERMISSION, + eventType = EventType.INITIATED, + eventSessionId = eventSessionId, + joinStageAttemptId = joinStageAttemptId, + callSessionId = callSessionId, + cameraAllowed = isCameraGranted, + microphoneAllowed = isMicrophoneGranted, + ), + ) + return eventSessionId + } + internal fun abortAllPostCallInFlight(reason: AnalyticsCallAbortReason) { val snapshot: List = postCallFlightSessions.values.filterIsInstance().toList() @@ -437,9 +508,12 @@ internal class ClientEventReporter( iceState: PeerConnection.IceConnectionState? = null, peerConnectionState: PeerConnection.PeerConnectionState? = null, userSessionId: String? = null, + screenShareAllowed: Boolean? = null, + microphoneAllowed: Boolean? = null, + cameraAllowed: Boolean? = null, ): ClientEvent = ClientEvent( eventSessionId = eventSessionId, - joinSuccessId = joinStageAttemptId, + joinAttemptId = joinStageAttemptId, eventType = eventType.value, id = callId, sdkVersion = sdkVersion, @@ -460,6 +534,9 @@ internal class ClientEventReporter( sfuId = sfuId, userSessionId = userSessionId, wasPreviouslyConnected = wasPreviouslyConnected, + screenShareGranted = screenShareAllowed, + microphoneGranted = microphoneAllowed, + cameraGranted = cameraAllowed, ) // --- Delivery --- From bd2a2baa0cb755bf8587579d91a6c32deb30d71e Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 3 Jun 2026 16:18:35 +0530 Subject: [PATCH 42/63] feat: update video analytics --- .../api/stream-video-android-core.api | 10 ++++++---- .../android/video/generated/models/ClientEvent.kt | 7 +++++-- .../kotlin/io/getstream/video/android/core/Call.kt | 12 ++++++++++-- .../video/android/core/analytics/AudioAnalytics.kt | 7 ++++--- .../video/android/core/analytics/VideoAnalytics.kt | 5 +++-- .../call/connection/StreamPeerConnectionFactory.kt | 2 +- .../core/events/reporting/ClientEventReporter.kt | 4 ++++ 7 files changed, 33 insertions(+), 14 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index aaa2dd8ed03..0c94880cc3c 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -2457,8 +2457,8 @@ public final class io/getstream/android/video/generated/models/ChatPreferencesRe public final class io/getstream/android/video/generated/models/ClientEvent { public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component10 ()Ljava/lang/String; public final fun component11 ()Lorg/threeten/bp/OffsetDateTime; @@ -2478,6 +2478,7 @@ public final class io/getstream/android/video/generated/models/ClientEvent { public final fun component24 ()Ljava/lang/Boolean; public final fun component25 ()Ljava/lang/Boolean; public final fun component26 ()Ljava/lang/Boolean; + public final fun component27 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -2485,8 +2486,8 @@ public final class io/getstream/android/video/generated/models/ClientEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;)Lio/getstream/android/video/generated/models/ClientEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCallSessionId ()Ljava/lang/String; public final fun getCameraGranted ()Ljava/lang/Boolean; @@ -2509,6 +2510,7 @@ public final class io/getstream/android/video/generated/models/ClientEvent { public final fun getSfuId ()Ljava/lang/String; public final fun getStage ()Ljava/lang/String; public final fun getTimestamp ()Lorg/threeten/bp/OffsetDateTime; + public final fun getTrackId ()Ljava/lang/String; public final fun getType ()Ljava/lang/String; public final fun getUserAgent ()Ljava/lang/String; public final fun getUserId ()Ljava/lang/String; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt index 3db3e0251e0..2d4bbc683a0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt @@ -104,7 +104,9 @@ data class ClientEvent ( @Json(name = "microphone") val microphoneGranted: kotlin.Boolean? = null, @Json(name = "camera") - val cameraGranted: kotlin.Boolean? = null + val cameraGranted: kotlin.Boolean? = null, + @Json(name = "trackId") + val trackId: String? = null ) { internal fun toLog(): String { @@ -146,9 +148,10 @@ data class ClientEvent ( appendIfNotNull("sdkVersion", sdkVersion) appendIfNotNull("timestamp", timestamp) - appendIfNotNull("camera", callSessionId) + appendIfNotNull("camera", cameraGranted) appendIfNotNull("microphone", microphoneGranted) appendIfNotNull("screenShare", screenShareGranted) + appendIfNotNull("trackId", trackId) append(")") } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 91ff652ebaa..c229690f0f6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -588,6 +588,7 @@ public class Call( peerConnectionFactory.setPlaybackSamplesReadyCallback { scope.launch { callAnalyticsHooks.audioAnalytics.firstAudioFrameRendered() + peerConnectionFactory.setPlaybackSamplesReadyCallback(null) } } @@ -1500,7 +1501,7 @@ public class Call( val width = videoRenderer.measuredWidth val height = videoRenderer.measuredHeight logger.i { - "[initRenderer.onFirstFrameRendered] #sfu; #track; " + + "noob [initRenderer.onFirstFrameRendered] #sfu; #track; " + "trackType: $trackType, dimension: ($width - $height), " + "sessionId: $sessionId" } @@ -1514,7 +1515,14 @@ public class Call( ) } onRendered(videoRenderer) - callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered() + val videoTrackId = session.value?.subscriber?.value?.getTrack( + sessionId, + TrackType.TRACK_TYPE_VIDEO, + )?.asVideoTrack()?.video?.id() + + videoTrackId?.let { + callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered(videoTrackId) + } } override fun onFrameResolutionChanged( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt index 523ae356035..869d52b8254 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt @@ -17,6 +17,7 @@ package io.getstream.video.android.core.analytics import io.getstream.video.android.core.events.reporting.ClientEventReporter +import java.util.concurrent.atomic.AtomicBoolean internal class AudioAnalytics( private val callId: String, @@ -26,11 +27,11 @@ internal class AudioAnalytics( val getJoinStageAttemptId: () -> String, ) { - var eventSession: String = "" + var recordedFirstFrame: AtomicBoolean = AtomicBoolean(false) fun firstAudioFrameRendered() { - if (eventSession.isNotEmpty()) { - eventSession = clientEventReporter.reportFirstAudioFrameRendered( + if (recordedFirstFrame.compareAndSet(false, true)) { + clientEventReporter.reportFirstAudioFrameRendered( onSfuId(), callId, callType, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt index 4e623d7eed5..155ff58a9bf 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt @@ -28,13 +28,14 @@ internal class VideoAnalytics( var eventSession: String = "" - fun firstVideoFrameRendered() { - if (eventSession.isNotEmpty()) { + fun firstVideoFrameRendered(trackId: String) { + if (eventSession.isEmpty()) { eventSession = clientEventReporter.reportFirstVideoFrameRendered( onSfuId(), callId, callType, getJoinStageAttemptId(), + trackId, ) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory.kt index 0c479c8e0ca..1d1fcb5c3ee 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory.kt @@ -108,7 +108,7 @@ public class StreamPeerConnectionFactory( audioSampleCallback = callback } - internal fun setPlaybackSamplesReadyCallback(callback: () -> Unit) { + internal fun setPlaybackSamplesReadyCallback(callback: (() -> Unit)?) { playbackSamplesReadyCallback = callback } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index c13818f5eec..42d58d80674 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -414,6 +414,7 @@ internal class ClientEventReporter( callId: String, callType: String, joinStageAttemptId: String, + trackId: String, ): String { val eventSessionId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] @@ -427,6 +428,7 @@ internal class ClientEventReporter( joinStageAttemptId = joinStageAttemptId, callSessionId = callSessionId, sfuId = sfuId, + trackId = trackId, ), ) return eventSessionId @@ -511,6 +513,7 @@ internal class ClientEventReporter( screenShareAllowed: Boolean? = null, microphoneAllowed: Boolean? = null, cameraAllowed: Boolean? = null, + trackId: String? = null, ): ClientEvent = ClientEvent( eventSessionId = eventSessionId, joinAttemptId = joinStageAttemptId, @@ -537,6 +540,7 @@ internal class ClientEventReporter( screenShareGranted = screenShareAllowed, microphoneGranted = microphoneAllowed, cameraGranted = cameraAllowed, + trackId = trackId, ) // --- Delivery --- From 4c428a90b1c6a7ef5b6302408688d44f4b0c45e0 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 3 Jun 2026 21:16:58 +0530 Subject: [PATCH 43/63] chore: replace event_session_id with stage_id --- .../api/stream-video-android-core.api | 40 +++---- .../video/generated/models/ClientEvent.kt | 53 +++++---- .../core/analytics/CoordinatorAnalytics.kt | 12 +- .../core/analytics/JoinRequestHooks.kt | 12 +- .../video/android/core/analytics/WsHook.kt | 8 +- .../events/reporting/ClientEventReporter.kt | 110 +++++++++--------- .../core/events/reporting/TelemetryModel.kt | 10 +- 7 files changed, 130 insertions(+), 115 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 0c94880cc3c..672970a2b77 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -2457,58 +2457,58 @@ public final class io/getstream/android/video/generated/models/ChatPreferencesRe public final class io/getstream/android/video/generated/models/ClientEvent { public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component10 ()Ljava/lang/String; - public final fun component11 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component12 ()Ljava/lang/Integer; - public final fun component13 ()Ljava/lang/String; + public final fun component11 ()Ljava/lang/String; + public final fun component12 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component13 ()Ljava/lang/Integer; public final fun component14 ()Ljava/lang/String; public final fun component15 ()Ljava/lang/String; public final fun component16 ()Ljava/lang/String; public final fun component17 ()Ljava/lang/String; - public final fun component18 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component18 ()Ljava/lang/String; public final fun component19 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/String; public final fun component20 ()Ljava/lang/String; - public final fun component21 ()Ljava/lang/String; + public final fun component21 ()Lorg/threeten/bp/OffsetDateTime; public final fun component22 ()Ljava/lang/String; - public final fun component23 ()Ljava/lang/Boolean; - public final fun component24 ()Ljava/lang/Boolean; - public final fun component25 ()Ljava/lang/Boolean; - public final fun component26 ()Ljava/lang/Boolean; - public final fun component27 ()Ljava/lang/String; + public final fun component23 ()Ljava/lang/String; + public final fun component24 ()Ljava/lang/String; + public final fun component25 ()Ljava/lang/String; + public final fun component26 ()Ljava/lang/String; + public final fun component27 ()Ljava/lang/Boolean; public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Integer; public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/String; public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;)Lio/getstream/android/video/generated/models/ClientEvent; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/getstream/android/video/generated/models/ClientEvent; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ClientEvent;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ClientEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCallSessionId ()Ljava/lang/String; - public final fun getCameraGranted ()Ljava/lang/Boolean; + public final fun getCameraPermissionStatus ()Ljava/lang/String; public final fun getCoordinatorConnectId ()Ljava/lang/String; public final fun getElapsedTime ()Ljava/lang/Integer; - public final fun getEventSessionId ()Ljava/lang/String; public final fun getEventType ()Ljava/lang/String; public final fun getIceState ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; public final fun getJoinAttemptId ()Ljava/lang/String; - public final fun getMicrophoneGranted ()Ljava/lang/Boolean; + public final fun getMicrophonePermissionStatus ()Ljava/lang/String; public final fun getOutcome ()Ljava/lang/String; public final fun getPeerConnection ()Ljava/lang/String; public final fun getPreviouslyConnectedTimestamp ()Lorg/threeten/bp/OffsetDateTime; public final fun getRetryCountAttempt ()Ljava/lang/Integer; public final fun getRetryFailureCode ()Ljava/lang/String; public final fun getRetryFailureReason ()Ljava/lang/String; - public final fun getScreenShareGranted ()Ljava/lang/Boolean; + public final fun getScreenShareStatus ()Ljava/lang/String; public final fun getSdkVersion ()Ljava/lang/String; public final fun getSfuId ()Ljava/lang/String; public final fun getStage ()Ljava/lang/String; + public final fun getStageId ()Ljava/lang/String; public final fun getTimestamp ()Lorg/threeten/bp/OffsetDateTime; public final fun getTrackId ()Ljava/lang/String; public final fun getType ()Ljava/lang/String; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt index 2d4bbc683a0..1c9c0c5af27 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ClientEvent.kt @@ -23,22 +23,34 @@ package io.getstream.android.video.generated.models +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.* +import kotlin.io.* +import com.squareup.moshi.FromJson import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson /** - * A single client-side telemetry event. When stage is CoordinatorJoin, WSJoin, or PeerConnectionConnect the event reports a join-lifecycle attempt; initiation and completion of a stage attempt share the same event_session_id. Other stage values denote generic client events. + * A single client-side telemetry event. When stage is CoordinatorJoin, CoordinatorWS, WSJoin, or PeerConnectionConnect the event reports a join-lifecycle attempt; initiation and completion of a stage attempt share the same stage_id. FirstAudioFrame and FirstVideoFrame report media readiness and only ever carry an initiated event. MediaDevicePermission reports the result of requesting screen-share, microphone, and camera permissions. Other stage values denote generic client events. */ data class ClientEvent ( @Json(name = "call_session_id") val callSessionId: kotlin.String? = null, + @Json(name = "camera_permission_status") + val cameraPermissionStatus: kotlin.String? = null, + + @Json(name = "coordinator_connect_id") + val coordinatorConnectId: kotlin.String? = null, + @Json(name = "elapsed_time") val elapsedTime: kotlin.Int? = null, - @Json(name = "event_session_id") - val eventSessionId: kotlin.String? = null, - @Json(name = "event_type") val eventType: kotlin.String? = null, @@ -51,8 +63,8 @@ data class ClientEvent ( @Json(name = "join_attempt_id") val joinAttemptId: kotlin.String? = null, - @Json(name = "coordinator_connect_id") - val coordinatorConnectId: kotlin.String? = null, + @Json(name = "microphone_permission_status") + val microphonePermissionStatus: kotlin.String? = null, @Json(name = "outcome") val outcome: kotlin.String? = null, @@ -72,6 +84,9 @@ data class ClientEvent ( @Json(name = "retry_failure_reason") val retryFailureReason: kotlin.String? = null, + @Json(name = "screen_share_status") + val screenShareStatus: kotlin.String? = null, + @Json(name = "sdk_version") val sdkVersion: kotlin.String? = null, @@ -81,9 +96,15 @@ data class ClientEvent ( @Json(name = "stage") val stage: kotlin.String? = null, + @Json(name = "stage_id") + val stageId: kotlin.String? = null, + @Json(name = "timestamp") val timestamp: org.threeten.bp.OffsetDateTime? = null, + @Json(name = "track_id") + val trackId: kotlin.String? = null, + @Json(name = "type") val type: kotlin.String? = null, @@ -97,18 +118,8 @@ data class ClientEvent ( val userSessionId: kotlin.String? = null, @Json(name = "was_previously_connected") - val wasPreviouslyConnected: kotlin.Boolean? = null, - - @Json(name = "screen_share") - val screenShareGranted: kotlin.Boolean? = null, - @Json(name = "microphone") - val microphoneGranted: kotlin.Boolean? = null, - @Json(name = "camera") - val cameraGranted: kotlin.Boolean? = null, - @Json(name = "trackId") - val trackId: String? = null + val wasPreviouslyConnected: kotlin.Boolean? = null ) { - internal fun toLog(): String { return buildString { append("ClientEvent(") @@ -119,7 +130,7 @@ data class ClientEvent ( appendIfNotNull("outcome", outcome) appendIfNotNull("callSessionId", callSessionId) - appendIfNotNull("eventSessionId", eventSessionId) + appendIfNotNull("stageId", stageId) appendIfNotNull("userSessionId", userSessionId) appendIfNotNull("joinAttemptId", joinAttemptId) appendIfNotNull("coordinatorConnectId", coordinatorConnectId) @@ -148,9 +159,9 @@ data class ClientEvent ( appendIfNotNull("sdkVersion", sdkVersion) appendIfNotNull("timestamp", timestamp) - appendIfNotNull("camera", cameraGranted) - appendIfNotNull("microphone", microphoneGranted) - appendIfNotNull("screenShare", screenShareGranted) + appendIfNotNull("cameraPermissionStatus", cameraPermissionStatus) + appendIfNotNull("microphonePermissionStatus", microphonePermissionStatus) + appendIfNotNull("screenShareStatus", screenShareStatus) appendIfNotNull("trackId", trackId) append(")") diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt index d840e24e81a..d59a1038852 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt @@ -31,7 +31,7 @@ internal class CoordinatorAnalytics( ) { private var job: Job? = null - private var eventSessionId = "" + private var stageId = "" fun startObserver(videoSocketStateFlow: StateFlow) { endObserver() @@ -41,7 +41,7 @@ internal class CoordinatorAnalytics( is VideoSocketState.Connecting -> { when (it.connectionType) { VideoSocketConnectionType.INITIAL_CONNECTION -> { - eventSessionId = eventReporter.reportCoordinatorWSInitiated() + stageId = eventReporter.reportCoordinatorWSInitiated() } VideoSocketConnectionType.AUTOMATIC_RECONNECTION -> {} @@ -50,9 +50,9 @@ internal class CoordinatorAnalytics( } is VideoSocketState.Connected -> { - if (eventSessionId.isNotEmpty()) { + if (stageId.isNotEmpty()) { eventReporter.reportCoordinatorWSCompleted( - eventSessionId, + stageId, true, CoordinatorSocketStateService.lastRetryAttempts, ) @@ -60,9 +60,9 @@ internal class CoordinatorAnalytics( } is VideoSocketState.Disconnected.DisconnectedPermanently -> { - if (eventSessionId.isNotEmpty()) { + if (stageId.isNotEmpty()) { eventReporter.reportCoordinatorWSCompleted( - eventSessionId, + stageId, false, CoordinatorSocketStateService.lastRetryAttempts, ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index e86278eee1f..9d635db6aac 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -22,7 +22,7 @@ import java.util.UUID internal class JoinRequestHooks(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { - var eventSessionId = "" + var stageId = "" var joinStage = Stage.NOT_STARTED var joinStageAttemptId = "" @@ -36,7 +36,7 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev } fun onJoinRequestStart() { if (joinStage == Stage.NOT_STARTED) { - eventSessionId = eventReporter.reportCoordinatorJoinInitiated( + stageId = eventReporter.reportCoordinatorJoinInitiated( callType = callType, callId = callId, joinStageAttemptId = joinStageAttemptId, @@ -46,9 +46,9 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev } fun onJoinRequestSuccess(telemetryModel: TelemetryModel, currentSessionId: String) { if (joinStage == Stage.IN_PROGRESS) { - if (eventSessionId.isNotEmpty()) { + if (stageId.isNotEmpty()) { eventReporter.reportCoordinatorJoinCompleted( - eventSessionId = eventSessionId, + stageId = stageId, success = true, retryCount = telemetryModel.retryAttempt, callSessionId = currentSessionId, @@ -60,9 +60,9 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev fun onJoinRequestPermanentError(retryCount: Int, message: String) { if (joinStage == Stage.IN_PROGRESS) { - if (eventSessionId.isNotEmpty()) { + if (stageId.isNotEmpty()) { eventReporter.reportCoordinatorJoinCompleted( - eventSessionId = eventSessionId, + stageId = stageId, success = false, retryCount = retryCount, failureReason = message, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt index 6b1a4636946..5904ff5035a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt @@ -29,14 +29,14 @@ internal class WsHook( val reporter: ClientEventReporter, val getJoinStageAttemptId: () -> String, ) { - var telemetryWsEventSessionId = "" + var telemetryWsEventStageId = "" var wsStage = Stage.NOT_STARTED var sfuName: String = "" fun onWsInitiated(sfuName: String, wasPreviouslyConnected: Boolean) { if (wsStage == Stage.NOT_STARTED) { this.sfuName = sfuName - telemetryWsEventSessionId = reporter.reportWsJoinInitiated( + telemetryWsEventStageId = reporter.reportWsJoinInitiated( callId = callId, callType = callType, sfuId = sfuName, @@ -54,9 +54,9 @@ internal class WsHook( failureCode: String? = null, ) { if (wsStage == Stage.IN_PROGRESS) { - if (telemetryWsEventSessionId.isNotEmpty()) { + if (telemetryWsEventStageId.isNotEmpty()) { reporter.reportWsJoinCompleted( - eventSessionId = telemetryWsEventSessionId, + stageId = telemetryWsEventStageId, success = success, retryCount = retryCount, failureReason = failureReason, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index 42d58d80674..f42630095ef 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -32,7 +32,7 @@ import java.util.UUID import java.util.concurrent.ConcurrentHashMap import kotlin.collections.set -internal typealias EventSessionId = String +internal typealias StageId = String internal typealias CallId = String /** @@ -52,7 +52,7 @@ internal class ClientEventReporter( ) { private val logger by taggedLogger("ClientEventReporter") - private val postCallFlightSessions = ConcurrentHashMap() + private val postCallFlightSessions = ConcurrentHashMap() private val joinStageAttemptIdMap = ConcurrentHashMap() private val callSessionIdMap = ConcurrentHashMap() @@ -64,33 +64,33 @@ internal class ClientEventReporter( internal fun reportCoordinatorWSInitiated(): String { val coordinatorConnectId = UUID.randomUUID().toString() - val eventSessionId = UUID.randomUUID().toString() + val stageId = UUID.randomUUID().toString() val now = System.currentTimeMillis() - postCallFlightSessions[eventSessionId] = PreCallInFlightSession( - eventSessionId = coordinatorConnectId, + postCallFlightSessions[stageId] = PreCallInFlightSession( + stageId = coordinatorConnectId, coordinatorConnectId = coordinatorConnectId, stage = EventStage.Call.COORDINATOR_JOIN, startedAtMs = now, ) sendEvent( buildRequest( - eventSessionId = eventSessionId, + stageId = stageId, coordinatorConnectId = coordinatorConnectId, stage = EventStage.CoordinatorWs, eventType = EventType.INITIATED, ), ) - return eventSessionId + return stageId } internal fun reportCoordinatorWSCompleted( - eventSessionId: String, + stageId: String, success: Boolean, retryCount: Int? = null, failureCode: String? = null, failureReason: String? = null, ) { - val session = postCallFlightSessions.remove(eventSessionId) ?: return + val session = postCallFlightSessions.remove(stageId) ?: return if (session is PreCallInFlightSession) { val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( @@ -132,11 +132,11 @@ internal class ClientEventReporter( callType: String, joinStageAttemptId: String, ): String { - val eventSessionId = UUID.randomUUID().toString() + val stageId = UUID.randomUUID().toString() joinStageAttemptIdMap[callId] = joinStageAttemptId val now = System.currentTimeMillis() - postCallFlightSessions[eventSessionId] = PostCallFlightSession( - eventSessionId = eventSessionId, + postCallFlightSessions[stageId] = PostCallFlightSession( + stageId = stageId, callId = callId, callType = callType, stage = EventStage.Call.COORDINATOR_JOIN, @@ -149,22 +149,22 @@ internal class ClientEventReporter( callType, stage = EventStage.Call.COORDINATOR_JOIN, eventType = EventType.INITIATED, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, ), ) - return eventSessionId + return stageId } internal fun reportCoordinatorJoinCompleted( - eventSessionId: String, + stageId: String, success: Boolean, retryCount: Int, failureReason: String? = null, failureCode: String? = null, // TODO Rahul, ask tomorrow callSessionId: String? = null, ) { - val session = postCallFlightSessions.remove(eventSessionId) ?: return + val session = postCallFlightSessions.remove(stageId) ?: return if (session is PostCallFlightSession) { val elapsedTime = System.currentTimeMillis() - session.startedAtMs callSessionIdMap[session.callId] = callSessionId ?: "" @@ -174,7 +174,7 @@ internal class ClientEventReporter( callType = session.callType, stage = EventStage.Call.COORDINATOR_JOIN, eventType = EventType.COMPLETED, - eventSessionId = eventSessionId, + stageId = stageId, elapsedTime = elapsedTime, outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, retryCountAttempt = retryCount, @@ -196,13 +196,13 @@ internal class ClientEventReporter( joinStageAttemptId: String, wasPreviouslyConnected: Boolean, ): String { - val eventSessionId = UUID.randomUUID().toString() + val stageId = UUID.randomUUID().toString() val now = System.currentTimeMillis() val callSessionId = callSessionIdMap[callId] - postCallFlightSessions[eventSessionId] = PostCallFlightSession( + postCallFlightSessions[stageId] = PostCallFlightSession( callId = callId, callType = callType, - eventSessionId = eventSessionId, + stageId = stageId, stage = EventStage.Call.WS_JOIN, startedAtMs = now, joinStageAttemptIdSnapshot = joinStageAttemptIdMap[callId] ?: "", @@ -216,24 +216,24 @@ internal class ClientEventReporter( callType = callType, stage = EventStage.Call.WS_JOIN, eventType = EventType.INITIATED, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, sfuId = sfuId, wasPreviouslyConnected = wasPreviouslyConnected, ), ) - return eventSessionId + return stageId } internal fun reportWsJoinCompleted( - eventSessionId: String, + stageId: String, joinStageAttemptId: String, success: Boolean, retryCount: Int, failureReason: String? = null, failureCode: String? = null, ) { - val session = postCallFlightSessions.remove(eventSessionId) ?: return + val session = postCallFlightSessions.remove(stageId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs if (session is PostCallFlightSession) { sendEvent( @@ -242,7 +242,7 @@ internal class ClientEventReporter( callType = session.callType, stage = EventStage.Call.WS_JOIN, eventType = EventType.COMPLETED, - eventSessionId = eventSessionId, + stageId = stageId, elapsedTime = elapsedTime, outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, retryCountAttempt = retryCount, @@ -275,7 +275,7 @@ internal class ClientEventReporter( completePeerConnectionSession( callId = callId, callType = callType, - eventSessionId = oldId, + stageId = oldId, joinStageAttemptId = joinStageAttemptId, success = false, iceState = iceState, @@ -284,12 +284,12 @@ internal class ClientEventReporter( failureCode = "ICE_CONNECTIVITY_FAILED", ) } - val eventSessionId = UUID.randomUUID().toString() + val stageId = UUID.randomUUID().toString() val now = System.currentTimeMillis() - postCallFlightSessions[eventSessionId] = PostCallFlightSession( + postCallFlightSessions[stageId] = PostCallFlightSession( callId = callId, callType = callType, - eventSessionId = eventSessionId, + stageId = stageId, stage = EventStage.Call.PEER_CONNECTION_CONNECT, startedAtMs = now, joinStageAttemptIdSnapshot = joinStageAttemptIdMap[callId] ?: "", @@ -297,14 +297,14 @@ internal class ClientEventReporter( wasPreviouslyConnected = wasPrev, callSessionId = callSessionIdMap[callId], ) - activePcSessionIds[role] = eventSessionId + activePcSessionIds[role] = stageId sendEvent( buildRequest( callId = callId, callType = callType, stage = EventStage.Call.PEER_CONNECTION_CONNECT, eventType = EventType.INITIATED, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, peerConnection = role, wasPreviouslyConnected = wasPrev, @@ -316,12 +316,12 @@ internal class ClientEventReporter( } PeerConnection.IceConnectionState.CONNECTED -> { - val eventSessionId = activePcSessionIds.remove(role) ?: return + val stageId = activePcSessionIds.remove(role) ?: return pcEverConnected[role] = true completePeerConnectionSession( callId = callId, callType = callType, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, success = true, iceState = iceState, @@ -330,11 +330,11 @@ internal class ClientEventReporter( } PeerConnection.IceConnectionState.FAILED -> { - val eventSessionId = activePcSessionIds.remove(role) ?: return + val stageId = activePcSessionIds.remove(role) ?: return completePeerConnectionSession( callId = callId, callType = callType, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, success = false, iceState = iceState, @@ -353,7 +353,7 @@ internal class ClientEventReporter( private fun completePeerConnectionSession( callId: String, callType: String, - eventSessionId: String, + stageId: String, joinStageAttemptId: String, success: Boolean, iceState: PeerConnection.IceConnectionState, @@ -361,7 +361,7 @@ internal class ClientEventReporter( failureReason: String? = null, failureCode: String? = null, ) { - val session = postCallFlightSessions.remove(eventSessionId) ?: return + val session = postCallFlightSessions.remove(stageId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs if (session is PostCallFlightSession) { sendEvent( @@ -370,7 +370,7 @@ internal class ClientEventReporter( callType = callType, stage = EventStage.Call.PEER_CONNECTION_CONNECT, eventType = EventType.COMPLETED, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, elapsedTime = elapsedTime, outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, @@ -392,7 +392,7 @@ internal class ClientEventReporter( callType: String, joinStageAttemptId: String, ): String { - val eventSessionId = UUID.randomUUID().toString() + val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] sendEvent( buildRequest( @@ -400,13 +400,13 @@ internal class ClientEventReporter( callType = callType, stage = EventStage.Call.FIRST_AUDIO_FRAME_RENDERED, eventType = EventType.INITIATED, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, callSessionId = callSessionId, sfuId = sfuId, ), ) - return eventSessionId + return stageId } internal fun reportFirstVideoFrameRendered( @@ -416,7 +416,7 @@ internal class ClientEventReporter( joinStageAttemptId: String, trackId: String, ): String { - val eventSessionId = UUID.randomUUID().toString() + val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] sendEvent( buildRequest( @@ -424,14 +424,14 @@ internal class ClientEventReporter( callType = callType, stage = EventStage.Call.FIRST_VIDEO_FRAME_RENDERED, eventType = EventType.INITIATED, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, callSessionId = callSessionId, sfuId = sfuId, trackId = trackId, ), ) - return eventSessionId + return stageId } internal fun reportMediaPermissionStatus( @@ -441,7 +441,7 @@ internal class ClientEventReporter( isCameraGranted: Boolean, isMicrophoneGranted: Boolean, ): String { - val eventSessionId = UUID.randomUUID().toString() + val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] sendEvent( buildRequest( @@ -449,14 +449,14 @@ internal class ClientEventReporter( callType = callType, stage = EventStage.Call.MEDIA_DEVICE_PERMISSION, eventType = EventType.INITIATED, - eventSessionId = eventSessionId, + stageId = stageId, joinStageAttemptId = joinStageAttemptId, callSessionId = callSessionId, cameraAllowed = isCameraGranted, microphoneAllowed = isMicrophoneGranted, ), ) - return eventSessionId + return stageId } internal fun abortAllPostCallInFlight(reason: AnalyticsCallAbortReason) { @@ -471,7 +471,7 @@ internal class ClientEventReporter( callType = session.callType, stage = session.stage, eventType = EventType.COMPLETED, - eventSessionId = session.eventSessionId, + stageId = session.stageId, elapsedTime = now - session.startedAtMs, outcome = EventOutcome.FAILURE, retryCountAttempt = 0, @@ -495,7 +495,7 @@ internal class ClientEventReporter( callType: String? = null, stage: EventStage, eventType: EventType, - eventSessionId: String? = null, + stageId: String? = null, joinStageAttemptId: String? = null, coordinatorConnectId: String? = null, elapsedTime: Long? = null, @@ -515,7 +515,7 @@ internal class ClientEventReporter( cameraAllowed: Boolean? = null, trackId: String? = null, ): ClientEvent = ClientEvent( - eventSessionId = eventSessionId, + stageId = stageId, joinAttemptId = joinStageAttemptId, eventType = eventType.value, id = callId, @@ -537,12 +537,16 @@ internal class ClientEventReporter( sfuId = sfuId, userSessionId = userSessionId, wasPreviouslyConnected = wasPreviouslyConnected, - screenShareGranted = screenShareAllowed, - microphoneGranted = microphoneAllowed, - cameraGranted = cameraAllowed, + screenShareStatus = getPermissionStatusText(screenShareAllowed), + microphonePermissionStatus = getPermissionStatusText(microphoneAllowed), + cameraPermissionStatus = getPermissionStatusText(cameraAllowed), trackId = trackId, ) + fun getPermissionStatusText(allowed: Boolean?): String? { + return if (allowed == true) "GRANTED" else if (allowed == false) "NOT_GRANTED" else null + } + // --- Delivery --- private fun sendEvent(event: ClientEvent) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt index 78ed84d0684..3b4bd9e9c54 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt @@ -36,13 +36,13 @@ internal enum class AnalyticsFailureCodes(val code: String, val message: String) internal sealed class InFlightSession( open val stage: EventStage.Call, open val startedAtMs: Long, - open val eventSessionId: EventSessionId, + open val stageId: StageId, ) internal data class PostCallFlightSession( val callId: String, val callType: String, - override val eventSessionId: EventSessionId, + override val stageId: StageId, override val stage: EventStage.Call, override val startedAtMs: Long, val joinStageAttemptIdSnapshot: String, @@ -51,11 +51,11 @@ internal data class PostCallFlightSession( val userSessionId: String? = null, val peerConnectionRole: PeerConnectionRole? = null, val wasPreviouslyConnected: Boolean = false, -) : InFlightSession(stage, startedAtMs, eventSessionId) +) : InFlightSession(stage, startedAtMs, stageId) internal data class PreCallInFlightSession( override val stage: EventStage.Call, override val startedAtMs: Long, - override val eventSessionId: EventSessionId, + override val stageId: StageId, val coordinatorConnectId: String, -) : InFlightSession(stage, startedAtMs, eventSessionId) +) : InFlightSession(stage, startedAtMs, stageId) From e0169e95b491e4a86a5bc8d9bc82d98d9c7c9db2 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 3 Jun 2026 21:34:47 +0530 Subject: [PATCH 44/63] chore: send coordinator id --- .../video/android/core/analytics/CoordinatorAnalytics.kt | 2 +- .../android/core/events/reporting/ClientEventReporter.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt index d59a1038852..4b00d378cf4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt @@ -33,7 +33,7 @@ internal class CoordinatorAnalytics( private var job: Job? = null private var stageId = "" - fun startObserver(videoSocketStateFlow: StateFlow) { + internal fun startObserver(videoSocketStateFlow: StateFlow) { endObserver() job = scope.launch { videoSocketStateFlow.collect { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt index f42630095ef..c17807d91e3 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt @@ -61,9 +61,9 @@ internal class ClientEventReporter( // Whether each PC role has ever reached CONNECTED (for was_previously_connected) private val pcEverConnected = ConcurrentHashMap() - + private var coordinatorConnectId = "" internal fun reportCoordinatorWSInitiated(): String { - val coordinatorConnectId = UUID.randomUUID().toString() + this.coordinatorConnectId = UUID.randomUUID().toString() val stageId = UUID.randomUUID().toString() val now = System.currentTimeMillis() postCallFlightSessions[stageId] = PreCallInFlightSession( @@ -541,6 +541,7 @@ internal class ClientEventReporter( microphonePermissionStatus = getPermissionStatusText(microphoneAllowed), cameraPermissionStatus = getPermissionStatusText(cameraAllowed), trackId = trackId, + coordinatorConnectId = this.coordinatorConnectId, ) fun getPermissionStatusText(allowed: Boolean?): String? { From 291671f5a46b4e8e03c8047f91989562ee0f8fb7 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 11:44:09 +0530 Subject: [PATCH 45/63] chore: replace event session id with stage id --- .../video/android/core/analytics/VideoAnalytics.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt index 155ff58a9bf..96b7c016411 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt @@ -26,11 +26,11 @@ internal class VideoAnalytics( val getJoinStageAttemptId: () -> String, ) { - var eventSession: String = "" + var stageId: String = "" fun firstVideoFrameRendered(trackId: String) { - if (eventSession.isEmpty()) { - eventSession = clientEventReporter.reportFirstVideoFrameRendered( + if (stageId.isEmpty()) { + stageId = clientEventReporter.reportFirstVideoFrameRendered( onSfuId(), callId, callType, From 699e6c50274b13feb769e674bc72b8ed7aab1b0c Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 12:13:22 +0530 Subject: [PATCH 46/63] fix: fix sending events for firstVideoFrameRendered --- .../kotlin/io/getstream/video/android/core/Call.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index c229690f0f6..2522ca5dd19 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -1515,13 +1515,15 @@ public class Call( ) } onRendered(videoRenderer) - val videoTrackId = session.value?.subscriber?.value?.getTrack( - sessionId, - TrackType.TRACK_TYPE_VIDEO, - )?.asVideoTrack()?.video?.id() + if (trackType == TrackType.TRACK_TYPE_VIDEO && sessionId != this@Call.sessionId) { + val videoTrackId = session.value?.subscriber?.value?.getTrack( + sessionId, + TrackType.TRACK_TYPE_VIDEO, + )?.asVideoTrack()?.video?.id() - videoTrackId?.let { - callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered(videoTrackId) + videoTrackId?.let { + callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered(it) + } } } From 732b71b07bdbbc9e1ca75879ffff8a8555b730a9 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 12:21:05 +0530 Subject: [PATCH 47/63] fix: fix sending events for firstVideoFrameRendered --- .../io/getstream/video/android/core/Call.kt | 16 ++++----- .../android/core/analytics/VideoAnalytics.kt | 34 ++++++++++++++----- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 2522ca5dd19..c5a808b427f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -1515,16 +1515,12 @@ public class Call( ) } onRendered(videoRenderer) - if (trackType == TrackType.TRACK_TYPE_VIDEO && sessionId != this@Call.sessionId) { - val videoTrackId = session.value?.subscriber?.value?.getTrack( - sessionId, - TrackType.TRACK_TYPE_VIDEO, - )?.asVideoTrack()?.video?.id() - - videoTrackId?.let { - callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered(it) - } - } + callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered( + trackType, + session.value, + sessionId, + this@Call.sessionId, + ) } override fun onFrameResolutionChanged( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt index 96b7c016411..080651ebea4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt @@ -16,7 +16,9 @@ package io.getstream.video.android.core.analytics +import io.getstream.video.android.core.call.RtcSession import io.getstream.video.android.core.events.reporting.ClientEventReporter +import stream.video.sfu.models.TrackType internal class VideoAnalytics( private val callId: String, @@ -28,15 +30,29 @@ internal class VideoAnalytics( var stageId: String = "" - fun firstVideoFrameRendered(trackId: String) { - if (stageId.isEmpty()) { - stageId = clientEventReporter.reportFirstVideoFrameRendered( - onSfuId(), - callId, - callType, - getJoinStageAttemptId(), - trackId, - ) + fun firstVideoFrameRendered( + trackType: TrackType, + rtcSession: RtcSession?, + videoSessionId: String, + callSessionId: String, + ) { + if (trackType == TrackType.TRACK_TYPE_VIDEO && videoSessionId != callSessionId) { + val videoTrackId = rtcSession?.subscriber?.value?.getTrack( + videoSessionId, + TrackType.TRACK_TYPE_VIDEO, + )?.asVideoTrack()?.video?.id() + + videoTrackId?.let { + if (stageId.isEmpty()) { + stageId = clientEventReporter.reportFirstVideoFrameRendered( + onSfuId(), + callId, + callType, + getJoinStageAttemptId(), + videoTrackId, + ) + } + } } } From af82b293714329469187b5d4ea73f3cca285afcb Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 12:57:35 +0530 Subject: [PATCH 48/63] fix: fix sending events for firstVideoFrameRendered for view and screen-share --- .../io/getstream/video/android/core/Call.kt | 2 + .../android/core/analytics/VideoAnalytics.kt | 39 ++++++++++++------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index c5a808b427f..8f4ed5a385d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -1517,6 +1517,8 @@ public class Call( onRendered(videoRenderer) callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered( trackType, + width, + height, session.value, sessionId, this@Call.sessionId, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt index 080651ebea4..02ae04ac21c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt @@ -16,6 +16,7 @@ package io.getstream.video.android.core.analytics +import android.util.Log import io.getstream.video.android.core.call.RtcSession import io.getstream.video.android.core.events.reporting.ClientEventReporter import stream.video.sfu.models.TrackType @@ -32,27 +33,37 @@ internal class VideoAnalytics( fun firstVideoFrameRendered( trackType: TrackType, + width: Int, + height: Int, rtcSession: RtcSession?, videoSessionId: String, callSessionId: String, ) { - if (trackType == TrackType.TRACK_TYPE_VIDEO && videoSessionId != callSessionId) { - val videoTrackId = rtcSession?.subscriber?.value?.getTrack( - videoSessionId, - TrackType.TRACK_TYPE_VIDEO, - )?.asVideoTrack()?.video?.id() - - videoTrackId?.let { - if (stageId.isEmpty()) { - stageId = clientEventReporter.reportFirstVideoFrameRendered( - onSfuId(), - callId, - callType, - getJoinStageAttemptId(), - videoTrackId, + when (trackType) { + TrackType.TRACK_TYPE_VIDEO, TrackType.TRACK_TYPE_SCREEN_SHARE -> { + if (videoSessionId != callSessionId) { + val videoTrackId = rtcSession?.subscriber?.value?.getTrack( + videoSessionId, + TrackType.TRACK_TYPE_VIDEO, + )?.asVideoTrack()?.video?.id() + Log.d( + "VideoAnalytics", + "noob [firstVideoFrameRendered]: $trackType, w:$width, h:$height, videoTrackId:$videoTrackId, videoSessionId:$videoSessionId, callSessionId:$callSessionId", ) + videoTrackId?.let { + if (stageId.isEmpty()) { + stageId = clientEventReporter.reportFirstVideoFrameRendered( + onSfuId(), + callId, + callType, + getJoinStageAttemptId(), + videoTrackId, + ) + } + } } } + else -> {} } } From 270089045df8d47e0ba39a2d780033f2ad6809b0 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 16:16:58 +0530 Subject: [PATCH 49/63] fix: fix the logic of resending of events after re-join --- .../io/getstream/video/android/core/Call.kt | 1 - .../android/core/analytics/AudioAnalytics.kt | 4 +- .../core/analytics/CallAnalyticsHooks.kt | 13 ++++-- .../core/analytics/JoinRequestHooks.kt | 8 +++- .../PeerConnectionAnalyticsObserver.kt | 25 +++++++++-- .../core/analytics/PeerConnectionHook.kt | 44 ------------------- .../android/core/analytics/VideoAnalytics.kt | 4 +- 7 files changed, 44 insertions(+), 55 deletions(-) delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 8f4ed5a385d..9c09826382c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -588,7 +588,6 @@ public class Call( peerConnectionFactory.setPlaybackSamplesReadyCallback { scope.launch { callAnalyticsHooks.audioAnalytics.firstAudioFrameRendered() - peerConnectionFactory.setPlaybackSamplesReadyCallback(null) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt index 869d52b8254..c1ef8e3491d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt @@ -40,5 +40,7 @@ internal class AudioAnalytics( } } - fun reset() {} + fun reset() { + recordedFirstFrame.set(false) + } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt index eaaa2010eb6..5fd28c0ce67 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt @@ -34,17 +34,17 @@ internal class CallAnalyticsHooks( val scope: CoroutineScope, ) { val logger by taggedLogger("CallAnalyticsHooks") - val joinRequestHooks = JoinRequestHooks(callId, callType, eventReporter) + val joinRequestHooks = JoinRequestHooks(callId, callType, eventReporter) { + resetAfterJoinSuccess() + } val wsHook = WsHook(callId, callType, connectionFlow, scope, eventReporter) { joinRequestHooks.joinStageAttemptId } - val peerConnectionHook = PeerConnectionHook(callId, callType, eventReporter) { + val peerConnectionAnalyticsObserver = PeerConnectionAnalyticsObserver(callId, callType, scope,eventReporter){ joinRequestHooks.joinStageAttemptId } - val peerConnectionAnalyticsObserver = PeerConnectionAnalyticsObserver(scope, peerConnectionHook) - val mediaPermissionHook = MediaPermissionHook(context, callId, callType, eventReporter) { joinRequestHooks.joinStageAttemptId } @@ -55,6 +55,11 @@ internal class CallAnalyticsHooks( joinRequestHooks.joinStageAttemptId } + fun resetAfterJoinSuccess() { + audioAnalytics.reset() + videoAnalytics.reset() + } + fun onCallLeave(callLeaveReason: CallLeaveReason) { val isAnyStageInProgress = joinRequestHooks.joinStage == Stage.IN_PROGRESS || diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index 9d635db6aac..37ab7015b80 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -20,7 +20,12 @@ import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.events.reporting.TelemetryModel import java.util.UUID -internal class JoinRequestHooks(val callId: String, val callType: String, val eventReporter: ClientEventReporter) { +internal class JoinRequestHooks( + val callId: String, + val callType: String, + val eventReporter: ClientEventReporter, + val onJoinSuccess: () -> Unit +) { var stageId = "" var joinStage = Stage.NOT_STARTED @@ -53,6 +58,7 @@ internal class JoinRequestHooks(val callId: String, val callType: String, val ev retryCount = telemetryModel.retryAttempt, callSessionId = currentSessionId, ) + onJoinSuccess() } resetStage() } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt index ba1933852c0..318912f7713 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -17,6 +17,7 @@ package io.getstream.video.android.core.analytics import io.getstream.video.android.core.call.RtcSession +import io.getstream.video.android.core.events.reporting.ClientEventReporter import io.getstream.video.android.core.events.reporting.PeerConnectionRole import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -27,8 +28,11 @@ import kotlinx.coroutines.launch import org.webrtc.PeerConnection internal class PeerConnectionAnalyticsObserver( + val callId:String, + val callType:String, private val scope: CoroutineScope, - private val hook: PeerConnectionHook, + val reporter: ClientEventReporter, + val getJoinStageAttemptId: () -> String, ) { private var peerConnectionObserverJob: Job? = null @@ -48,7 +52,7 @@ internal class PeerConnectionAnalyticsObserver( .collect { state -> publisherStage = getStage(state) scope.launch { - hook.onPeerConnectionStateChanged( + onPeerConnectionStateChanged( role = PeerConnectionRole.PUBLISH, iceState = session.value?.publisher?.value?.iceState?.value, peerConnectionState = state, @@ -64,7 +68,7 @@ internal class PeerConnectionAnalyticsObserver( .collect { state -> subscriberStage = getStage(state) scope.launch { - hook.onPeerConnectionStateChanged( + onPeerConnectionStateChanged( role = PeerConnectionRole.SUBSCRIBE, iceState = session.value?.subscriber?.value?.iceState?.value, peerConnectionState = state, @@ -93,6 +97,21 @@ internal class PeerConnectionAnalyticsObserver( } } + internal fun onPeerConnectionStateChanged( + role: PeerConnectionRole, + iceState: PeerConnection.IceConnectionState?, + peerConnectionState: PeerConnection.PeerConnectionState?, + ) { + reporter.onPeerConnectionStateChanged( + callId = callId, + callType = callType, + role = role, + iceState = iceState, + peerConnectionState = peerConnectionState, + joinStageAttemptId = getJoinStageAttemptId.invoke(), + ) + } + fun stop() { peerConnectionObserverJob?.cancel() peerConnectionObserverJob = null diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt deleted file mode 100644 index e5a5248f8fc..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionHook.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * 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. - */ - -package io.getstream.video.android.core.analytics - -import io.getstream.video.android.core.events.reporting.ClientEventReporter -import io.getstream.video.android.core.events.reporting.PeerConnectionRole -import org.webrtc.PeerConnection - -internal class PeerConnectionHook( - val callId: String, - val callType: String, - val reporter: ClientEventReporter, - val getJoinStageAttemptId: () -> String, -) { - - internal fun onPeerConnectionStateChanged( - role: PeerConnectionRole, - iceState: PeerConnection.IceConnectionState?, - peerConnectionState: PeerConnection.PeerConnectionState?, - ) { - reporter.onPeerConnectionStateChanged( - callId = callId, - callType = callType, - role = role, - iceState = iceState, - peerConnectionState = peerConnectionState, - joinStageAttemptId = getJoinStageAttemptId.invoke(), - ) - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt index 02ae04ac21c..9d755a97a30 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt @@ -67,5 +67,7 @@ internal class VideoAnalytics( } } - fun reset() {} + fun reset() { + stageId = "" + } } From b9a57fb79af036d66608fbcc2f6cb4702138ca95 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 16:20:10 +0530 Subject: [PATCH 50/63] chore: change name --- .../io/getstream/video/android/core/Call.kt | 32 +++++++++---------- ...csHooks.kt => CallAnalyticsCoordinator.kt} | 2 +- .../video/android/core/call/RtcSession.kt | 8 ++--- 3 files changed, 21 insertions(+), 21 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{CallAnalyticsHooks.kt => CallAnalyticsCoordinator.kt} (98%) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 9c09826382c..e8a5d695da2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -60,7 +60,7 @@ import io.getstream.result.Result import io.getstream.result.Result.Failure import io.getstream.result.Result.Success import io.getstream.result.flatMap -import io.getstream.video.android.core.analytics.CallAnalyticsHooks +import io.getstream.video.android.core.analytics.CallAnalyticsCoordinator import io.getstream.video.android.core.audio.StreamAudioDevice import io.getstream.video.android.core.call.FastReconnectResult import io.getstream.video.android.core.call.RtcSession @@ -312,8 +312,8 @@ public class Call( _peerConnectionFactory = value } - internal val callAnalyticsHooks = - CallAnalyticsHooks( + internal val callAnalyticsCoordinator = + CallAnalyticsCoordinator( clientImpl.context, this.id, this.type, @@ -562,8 +562,8 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, callJoinInterceptor: CallJoinInterceptor? = null, ): Result { - callAnalyticsHooks.joinRequestHooks.onJoinFunctionStart() - callAnalyticsHooks.mediaPermissionHook.mediaPermissionStatus() + callAnalyticsCoordinator.joinRequestHooks.onJoinFunctionStart() + callAnalyticsCoordinator.mediaPermissionHook.mediaPermissionStatus() logger.d { "[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions" } @@ -587,7 +587,7 @@ public class Call( ensureFactoryMatchesAudioProfile() peerConnectionFactory.setPlaybackSamplesReadyCallback { scope.launch { - callAnalyticsHooks.audioAnalytics.firstAudioFrameRendered() + callAnalyticsCoordinator.audioAnalytics.firstAudioFrameRendered() } } @@ -635,7 +635,7 @@ public class Call( logger.e { "Join failed with error $result" } if (isPermanentError(result.value)) { state._connection.value = RealtimeConnection.Failed(result.value) - callAnalyticsHooks.joinRequestHooks.onJoinRequestPermanentError( + callAnalyticsCoordinator.joinRequestHooks.onJoinRequestPermanentError( retryCount, result.value.message, ) @@ -649,7 +649,7 @@ public class Call( session.value = null val errorMessage = "Join failed after 3 retries" state._connection.value = RealtimeConnection.Failed(errorMessage) - callAnalyticsHooks.joinRequestHooks.onJoinRequestRetryExhausted(retryCount, errorMessage) + callAnalyticsCoordinator.joinRequestHooks.onJoinRequestRetryExhausted(retryCount, errorMessage) return Failure(value = Error.GenericError(errorMessage)) } @@ -797,8 +797,8 @@ public class Call( } } monitorPublisherPCStateJob?.cancel() - callAnalyticsHooks.peerConnectionAnalyticsObserver.stop() - callAnalyticsHooks.peerConnectionAnalyticsObserver.observePeerConnections(session) + callAnalyticsCoordinator.peerConnectionAnalyticsObserver.stop() + callAnalyticsCoordinator.peerConnectionAnalyticsObserver.observePeerConnections(session) monitorPublisherPCStateJob = scope.launch { session .filterNotNull() @@ -1070,7 +1070,7 @@ public class Call( if (state.connection.value is RealtimeConnection.ReconnectingFailed) { logger.w { "[reconnect] All recovery attempts exhausted — leaving call ($reason)" } - callAnalyticsHooks.joinRequestHooks.onJoinRequestRetryExhausted( + callAnalyticsCoordinator.joinRequestHooks.onJoinRequestRetryExhausted( loopIteration, "All recovery attempts exhausted — leaving call ($reason)", ) @@ -1312,7 +1312,7 @@ public class Call( private fun internalLeave(reason: CallLeaveReason) = atomicLeave { monitorSubscriberPCStateJob?.cancel() monitorPublisherPCStateJob?.cancel() - callAnalyticsHooks.stopObservers() + callAnalyticsCoordinator.stopObservers() monitorPublisherPCStateJob = null monitorSubscriberPCStateJob = null leaveTimeoutAfterDisconnect?.cancel() @@ -1351,7 +1351,7 @@ public class Call( clientImpl.scope.launch { val leaveReason = "[reason=${reason::class.simpleName}, message=${reason.message}]" - callAnalyticsHooks.onCallLeave(reason) + callAnalyticsCoordinator.onCallLeave(reason) safeCall { session.value?.sfuTracer?.trace("leave-call", leaveReason) @@ -1514,7 +1514,7 @@ public class Call( ) } onRendered(videoRenderer) - callAnalyticsHooks.videoAnalytics.firstVideoFrameRendered( + callAnalyticsCoordinator.videoAnalytics.firstVideoFrameRendered( trackType, width, height, @@ -1904,7 +1904,7 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, telemetryModel: TelemetryModel, ): Result { - callAnalyticsHooks.joinRequestHooks.onJoinRequestStart() + callAnalyticsCoordinator.joinRequestHooks.onJoinRequestStart() val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( type, id, @@ -1922,7 +1922,7 @@ public class Call( hintHighScaleLivestreamPublisher = hintHighScaleLivestreamPublisher, ) result.onSuccess { - callAnalyticsHooks.joinRequestHooks.onJoinRequestSuccess( + callAnalyticsCoordinator.joinRequestHooks.onJoinRequestSuccess( telemetryModel, it.call.currentSessionId, ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt similarity index 98% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt index 5fd28c0ce67..3b55c6f3173 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt @@ -25,7 +25,7 @@ import io.getstream.video.android.core.events.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow -internal class CallAnalyticsHooks( +internal class CallAnalyticsCoordinator( val context: Context, val callId: String, val callType: String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index 8e6e266172b..f2ad18c6e43 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -884,7 +884,7 @@ public class RtcSession internal constructor( telemetryModel: TelemetryModel? = null, ): SfuConnectionResult { logger.i { "noob [connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } - call.callAnalyticsHooks.wsHook.onWsInitiated(sfuName, reconnectDetails != null) + call.callAnalyticsCoordinator.wsHook.onWsInitiated(sfuName, reconnectDetails != null) val request = buildJoinRequest(reconnectDetails, options) sfuTracer.trace( @@ -902,7 +902,7 @@ public class RtcSession internal constructor( } return when (terminalState) { is SfuSocketState.Connected -> { - call.callAnalyticsHooks.wsHook.onWsCompleted( + call.callAnalyticsCoordinator.wsHook.onWsCompleted( success = true, retryCount = 0, ) @@ -920,7 +920,7 @@ public class RtcSession internal constructor( } logger.w { "[connectInternal] $msg" } sfuTracer.trace("connect-failed", msg) - call.callAnalyticsHooks.wsHook.onWsCompleted( + call.callAnalyticsCoordinator.wsHook.onWsCompleted( success = false, retryCount = telemetryModel?.retryAttempt ?: 0, failureReason = msg, @@ -931,7 +931,7 @@ public class RtcSession internal constructor( } else -> { sfuTracer.trace("connect-failed", "Connection timed out") - call.callAnalyticsHooks.wsHook.onWsCompleted( + call.callAnalyticsCoordinator.wsHook.onWsCompleted( success = false, retryCount = telemetryModel?.retryAttempt ?: 0, failureReason = AnalyticsFailureCodes.SFU_REQUEST_TIMEOUT.message, From 5fb286ee6665375d0f57df80e7dc703f1430017c Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 16:21:31 +0530 Subject: [PATCH 51/63] chore: spotless & apiDump --- .../main/kotlin/io/getstream/video/android/core/Call.kt | 5 ++++- .../android/core/analytics/CallAnalyticsCoordinator.kt | 7 ++++--- .../video/android/core/analytics/JoinRequestHooks.kt | 2 +- .../core/analytics/PeerConnectionAnalyticsObserver.kt | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index e8a5d695da2..50cf31aa65d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -649,7 +649,10 @@ public class Call( session.value = null val errorMessage = "Join failed after 3 retries" state._connection.value = RealtimeConnection.Failed(errorMessage) - callAnalyticsCoordinator.joinRequestHooks.onJoinRequestRetryExhausted(retryCount, errorMessage) + callAnalyticsCoordinator.joinRequestHooks.onJoinRequestRetryExhausted( + retryCount, + errorMessage, + ) return Failure(value = Error.GenericError(errorMessage)) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt index 3b55c6f3173..e9cc3e3456e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt @@ -41,9 +41,10 @@ internal class CallAnalyticsCoordinator( joinRequestHooks.joinStageAttemptId } - val peerConnectionAnalyticsObserver = PeerConnectionAnalyticsObserver(callId, callType, scope,eventReporter){ - joinRequestHooks.joinStageAttemptId - } + val peerConnectionAnalyticsObserver = + PeerConnectionAnalyticsObserver(callId, callType, scope, eventReporter) { + joinRequestHooks.joinStageAttemptId + } val mediaPermissionHook = MediaPermissionHook(context, callId, callType, eventReporter) { joinRequestHooks.joinStageAttemptId diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index 37ab7015b80..957a311af5d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -24,7 +24,7 @@ internal class JoinRequestHooks( val callId: String, val callType: String, val eventReporter: ClientEventReporter, - val onJoinSuccess: () -> Unit + val onJoinSuccess: () -> Unit, ) { var stageId = "" diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt index 318912f7713..9d725ea9438 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -28,8 +28,8 @@ import kotlinx.coroutines.launch import org.webrtc.PeerConnection internal class PeerConnectionAnalyticsObserver( - val callId:String, - val callType:String, + val callId: String, + val callType: String, private val scope: CoroutineScope, val reporter: ClientEventReporter, val getJoinStageAttemptId: () -> String, From b5a3a0180b30039d29146f2360376f7e5d456e23 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 18:46:10 +0530 Subject: [PATCH 52/63] fix: Fix first audio frame --- .../io/getstream/video/android/core/Call.kt | 6 +- .../android/core/analytics/AudioAnalytics.kt | 84 +++++++++++++++++-- .../analytics/CallAnalyticsCoordinator.kt | 3 + 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 3d8f0b40dd0..c76b34a4cfb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -322,6 +322,7 @@ public class Call( this.id, this.type, state.connection, + state.participants, client.state.clientEventReporter, scope, ) @@ -589,11 +590,6 @@ public class Call( // Ensure factory is created with the current audioBitrateProfile before joining ensureFactoryMatchesAudioProfile() - peerConnectionFactory.setPlaybackSamplesReadyCallback { - scope.launch { - callAnalyticsCoordinator.audioAnalytics.firstAudioFrameRendered() - } - } this.state.callJoinInterceptor = callJoinInterceptor diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt index c1ef8e3491d..cfb9c248bfe 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt @@ -16,9 +16,22 @@ package io.getstream.video.android.core.analytics +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.ParticipantState import io.getstream.video.android.core.events.reporting.ClientEventReporter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.launch +import org.webrtc.AudioTrackSink +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean +private typealias TrackId = String internal class AudioAnalytics( private val callId: String, private val callType: String, @@ -27,20 +40,75 @@ internal class AudioAnalytics( val getJoinStageAttemptId: () -> String, ) { + val logger by taggedLogger("AudioAnalytics") var recordedFirstFrame: AtomicBoolean = AtomicBoolean(false) - fun firstAudioFrameRendered() { - if (recordedFirstFrame.compareAndSet(false, true)) { - clientEventReporter.reportFirstAudioFrameRendered( - onSfuId(), - callId, - callType, - getJoinStageAttemptId(), - ) + private val trackSinks = + ConcurrentHashMap>() + private var observeJob: Job? = null + + fun observeParticipantsForFirstRemoteAudioFrame( + participants: StateFlow>, + scope: CoroutineScope, + ) { + observeJob?.cancel() + observeJob = scope.launch { + participants + .flatMapLatest { list -> + val audioTrackFlows = list + .filter { !it.isLocal } + .map { it.audioTrack.filterNotNull() } + if (audioTrackFlows.isEmpty()) { + emptyFlow() + } else { + merge(*audioTrackFlows.toTypedArray()) + } + } + .collect { modelAudioTrack -> + if (trackSinks.containsKey(modelAudioTrack.streamId)) return@collect + val webRtcTrack = modelAudioTrack.audio + val sink = AudioTrackSink { audioData, bitsPerSample, sampleRate, numberOfChannels, numberOfFrames, _ -> + // onData fires on the WebRTC native audio thread every ~10ms. + // Rules: + // - No logging (I/O on a real-time thread) + // - No removeSink (deadlocks the sink-list lock WebRTC holds here) + // - No blocking calls + // CAS here so exactly ONE coroutine is ever launched, then + // hand off all real work (reporting + cleanup) to the coroutine. + if (numberOfFrames > 0 && sampleRate > 0 && numberOfChannels > 0 && audioData.hasRemaining()) { + scope.launch { + if (recordedFirstFrame.compareAndSet(false, true)) { + reportAndCleanup() + } + } + } + } + trackSinks[modelAudioTrack.streamId] = Pair(webRtcTrack, sink) + webRtcTrack.addSink(sink) + } } } + // Called from a coroutine — safe to do I/O, removeSink, and job cancellation here. + private fun reportAndCleanup() { + clientEventReporter.reportFirstAudioFrameRendered( + onSfuId(), + callId, + callType, + getJoinStageAttemptId(), + ) + trackSinks.forEach { (_, pair) -> pair.first.removeSink(pair.second) } + trackSinks.clear() + observeJob?.cancel() + observeJob = null + } + fun reset() { + logger.d { "noob [reset]" } recordedFirstFrame.set(false) + trackSinks.forEach { (_, pair) -> pair.first.removeSink(pair.second) } + trackSinks.clear() + observeJob?.cancel() + observeJob = null } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt index e9cc3e3456e..fa531337b5f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt @@ -19,6 +19,7 @@ package io.getstream.video.android.core.analytics import android.content.Context import io.getstream.log.taggedLogger import io.getstream.video.android.core.CallLeaveReason +import io.getstream.video.android.core.ParticipantState import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.events.reporting.AnalyticsCallAbortReason import io.getstream.video.android.core.events.reporting.ClientEventReporter @@ -30,6 +31,7 @@ internal class CallAnalyticsCoordinator( val callId: String, val callType: String, val connectionFlow: StateFlow, + val participants: StateFlow>, val eventReporter: ClientEventReporter, val scope: CoroutineScope, ) { @@ -59,6 +61,7 @@ internal class CallAnalyticsCoordinator( fun resetAfterJoinSuccess() { audioAnalytics.reset() videoAnalytics.reset() + audioAnalytics.observeParticipantsForFirstRemoteAudioFrame(participants, scope) } fun onCallLeave(callLeaveReason: CallLeaveReason) { From 1c89a96cd863deedad7ee5fb6db19bcf3fdf163c Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 18:50:02 +0530 Subject: [PATCH 53/63] fix: spotless --- .../android/core/analytics/AudioAnalytics.kt | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt index cfb9c248bfe..5e7839e18de 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt @@ -67,22 +67,23 @@ internal class AudioAnalytics( .collect { modelAudioTrack -> if (trackSinks.containsKey(modelAudioTrack.streamId)) return@collect val webRtcTrack = modelAudioTrack.audio - val sink = AudioTrackSink { audioData, bitsPerSample, sampleRate, numberOfChannels, numberOfFrames, _ -> - // onData fires on the WebRTC native audio thread every ~10ms. - // Rules: - // - No logging (I/O on a real-time thread) - // - No removeSink (deadlocks the sink-list lock WebRTC holds here) - // - No blocking calls - // CAS here so exactly ONE coroutine is ever launched, then - // hand off all real work (reporting + cleanup) to the coroutine. - if (numberOfFrames > 0 && sampleRate > 0 && numberOfChannels > 0 && audioData.hasRemaining()) { - scope.launch { - if (recordedFirstFrame.compareAndSet(false, true)) { - reportAndCleanup() + val sink = + AudioTrackSink { audioData, bitsPerSample, sampleRate, numberOfChannels, numberOfFrames, _ -> + // onData fires on the WebRTC native audio thread every ~10ms. + // Rules: + // - No logging (I/O on a real-time thread) + // - No removeSink (deadlocks the sink-list lock WebRTC holds here) + // - No blocking calls + // CAS here so exactly ONE coroutine is ever launched, then + // hand off all real work (reporting + cleanup) to the coroutine. + if (numberOfFrames > 0 && sampleRate > 0 && numberOfChannels > 0 && audioData.hasRemaining()) { + scope.launch { + if (recordedFirstFrame.compareAndSet(false, true)) { + reportAndCleanup() + } } } } - } trackSinks[modelAudioTrack.streamId] = Pair(webRtcTrack, sink) webRtcTrack.addSink(sink) } From 1f6d32469156523d6cf1e9067a70a7c013e21352 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 18:57:31 +0530 Subject: [PATCH 54/63] chore: move files --- .../src/main/kotlin/io/getstream/video/android/core/Call.kt | 2 +- .../kotlin/io/getstream/video/android/core/ClientState.kt | 2 +- .../getstream/video/android/core/analytics/AudioAnalytics.kt | 2 +- .../video/android/core/analytics/CallAnalyticsCoordinator.kt | 4 ++-- .../video/android/core/analytics/CoordinatorAnalytics.kt | 2 +- .../video/android/core/analytics/JoinRequestHooks.kt | 4 ++-- .../video/android/core/analytics/MediaPermissionHook.kt | 2 +- .../android/core/analytics/PeerConnectionAnalyticsObserver.kt | 4 ++-- .../getstream/video/android/core/analytics/VideoAnalytics.kt | 2 +- .../io/getstream/video/android/core/analytics/WsHook.kt | 2 +- .../{events => analytics}/reporting/ClientCallEventData.kt | 2 +- .../{events => analytics}/reporting/ClientEventReporter.kt | 2 +- .../reporting/ClientEventReporterErrorMappers.kt | 2 +- .../core/{events => analytics}/reporting/TelemetryModel.kt | 2 +- .../kotlin/io/getstream/video/android/core/call/RtcSession.kt | 4 ++-- 15 files changed, 19 insertions(+), 19 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/{events => analytics}/reporting/ClientCallEventData.kt (96%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/{events => analytics}/reporting/ClientEventReporter.kt (99%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/{events => analytics}/reporting/ClientEventReporterErrorMappers.kt (95%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/{events => analytics}/reporting/TelemetryModel.kt (97%) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index c76b34a4cfb..23d9a945ea4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -61,6 +61,7 @@ import io.getstream.result.Result.Failure import io.getstream.result.Result.Success import io.getstream.result.flatMap import io.getstream.video.android.core.analytics.CallAnalyticsCoordinator +import io.getstream.video.android.core.analytics.reporting.TelemetryModel import io.getstream.video.android.core.audio.StreamAudioDevice import io.getstream.video.android.core.call.FastReconnectResult import io.getstream.video.android.core.call.RtcSession @@ -77,7 +78,6 @@ import io.getstream.video.android.core.closedcaptions.ClosedCaptionsSettings import io.getstream.video.android.core.events.GoAwayEvent import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener -import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.internal.InternalStreamVideoApi import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.model.AudioTrack diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index bec2443ed74..71449ce0198 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -23,7 +23,7 @@ import io.getstream.android.video.generated.models.ConnectedEvent import io.getstream.android.video.generated.models.VideoEvent import io.getstream.log.taggedLogger import io.getstream.result.Error -import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import io.getstream.video.android.core.faultinjector.FailureInjector import io.getstream.video.android.core.faultinjector.NoOpFailureInjector import io.getstream.video.android.core.header.HeadersUtil diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt index 5e7839e18de..3575f2553cd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt @@ -18,7 +18,7 @@ package io.getstream.video.android.core.analytics import io.getstream.log.taggedLogger import io.getstream.video.android.core.ParticipantState -import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.StateFlow diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt index fa531337b5f..f1621bc07c5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt @@ -21,8 +21,8 @@ import io.getstream.log.taggedLogger import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.ParticipantState import io.getstream.video.android.core.RealtimeConnection -import io.getstream.video.android.core.events.reporting.AnalyticsCallAbortReason -import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.AnalyticsCallAbortReason +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt index 4b00d378cf4..63b9a548ddf 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt @@ -16,7 +16,7 @@ package io.getstream.video.android.core.analytics -import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import io.getstream.video.android.core.socket.coordinator.CoordinatorSocketStateService import io.getstream.video.android.core.socket.coordinator.state.VideoSocketConnectionType import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt index 957a311af5d..270d5c3331b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt @@ -16,8 +16,8 @@ package io.getstream.video.android.core.analytics -import io.getstream.video.android.core.events.reporting.ClientEventReporter -import io.getstream.video.android.core.events.reporting.TelemetryModel +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.TelemetryModel import java.util.UUID internal class JoinRequestHooks( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt index 1bf5210a233..4f918bcb035 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt @@ -20,7 +20,7 @@ import android.Manifest import android.content.Context import android.content.pm.PackageManager import androidx.core.content.ContextCompat -import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter internal class MediaPermissionHook( val context: Context, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt index 9d725ea9438..d0311e4b788 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt @@ -16,9 +16,9 @@ package io.getstream.video.android.core.analytics +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.PeerConnectionRole import io.getstream.video.android.core.call.RtcSession -import io.getstream.video.android.core.events.reporting.ClientEventReporter -import io.getstream.video.android.core.events.reporting.PeerConnectionRole import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.StateFlow diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt index 9d755a97a30..d310414358a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt @@ -17,8 +17,8 @@ package io.getstream.video.android.core.analytics import android.util.Log +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import io.getstream.video.android.core.call.RtcSession -import io.getstream.video.android.core.events.reporting.ClientEventReporter import stream.video.sfu.models.TrackType internal class VideoAnalytics( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt index 5904ff5035a..129a0d4a5d2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt @@ -17,7 +17,7 @@ package io.getstream.video.android.core.analytics import io.getstream.video.android.core.RealtimeConnection -import io.getstream.video.android.core.events.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientCallEventData.kt similarity index 96% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientCallEventData.kt index 3e6f101163b..0dbc0967df7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientCallEventData.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientCallEventData.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.events.reporting +package io.getstream.video.android.core.analytics.reporting internal sealed interface EventStage { val value: String diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt similarity index 99% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt index c17807d91e3..a9c54277ff4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.events.reporting +package io.getstream.video.android.core.analytics.reporting import io.getstream.android.video.generated.apis.ProductvideoApi import io.getstream.android.video.generated.models.ClientEvent diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporterErrorMappers.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporterErrorMappers.kt similarity index 95% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporterErrorMappers.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporterErrorMappers.kt index c1ff623f9c7..d719bb3bfb9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/ClientEventReporterErrorMappers.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporterErrorMappers.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.events.reporting +package io.getstream.video.android.core.analytics.reporting import io.getstream.video.android.core.ReconnectOutcome diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/TelemetryModel.kt similarity index 97% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/TelemetryModel.kt index 3b4bd9e9c54..8c73539819c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/reporting/TelemetryModel.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/TelemetryModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.events.reporting +package io.getstream.video.android.core.analytics.reporting internal data class TelemetryModel(val retryAttempt: Int) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index ca534f71cf2..eea34fe0e88 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -45,6 +45,8 @@ import io.getstream.video.android.core.MediaManagerImpl import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.analytics.reporting.AnalyticsFailureCodes +import io.getstream.video.android.core.analytics.reporting.TelemetryModel import io.getstream.video.android.core.call.connection.Publisher import io.getstream.video.android.core.call.connection.StreamPeerConnection import io.getstream.video.android.core.call.connection.Subscriber @@ -71,8 +73,6 @@ import io.getstream.video.android.core.events.SfuDataRequest import io.getstream.video.android.core.events.SubscriberOfferEvent import io.getstream.video.android.core.events.TrackPublishedEvent import io.getstream.video.android.core.events.TrackUnpublishedEvent -import io.getstream.video.android.core.events.reporting.AnalyticsFailureCodes -import io.getstream.video.android.core.events.reporting.TelemetryModel import io.getstream.video.android.core.internal.module.SfuConnectionModule import io.getstream.video.android.core.model.AudioTrack import io.getstream.video.android.core.model.IceCandidate From 4b9a26bd7b6aa8842473b7ab8224d6ad1929fc1c Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 19:03:32 +0530 Subject: [PATCH 55/63] chore: refactor client event reporter --- .../analytics/reporting/ClientEventFactory.kt | 84 +++++++++++++++++ .../reporting/ClientEventReporter.kt | 90 ++++--------------- 2 files changed, 100 insertions(+), 74 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt new file mode 100644 index 00000000000..e273e1369cd --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting + +import io.getstream.android.video.generated.models.ClientEvent +import io.getstream.video.android.core.StreamVideo +import org.threeten.bp.OffsetDateTime +import org.threeten.bp.ZoneOffset +import org.webrtc.PeerConnection + +internal class ClientEventFactory(val sdkVersion: String, val userAgent: () -> String, val getCoordinatorId: () -> String) { + + fun buildRequest( + callId: String? = null, + callType: String? = null, + stage: EventStage, + eventType: EventType, + stageId: String? = null, + joinStageAttemptId: String? = null, + coordinatorConnectId: String? = null, + elapsedTime: Long? = null, + outcome: EventOutcome? = null, + retryCountAttempt: Int? = null, + retryFailureReason: String? = null, + retryFailureCode: String? = null, + callSessionId: String? = null, + sfuId: String? = null, + peerConnection: PeerConnectionRole? = null, + wasPreviouslyConnected: Boolean? = null, + iceState: PeerConnection.IceConnectionState? = null, + peerConnectionState: PeerConnection.PeerConnectionState? = null, + userSessionId: String? = null, + screenShareAllowed: Boolean? = null, + microphoneAllowed: Boolean? = null, + cameraAllowed: Boolean? = null, + trackId: String? = null, + ): ClientEvent = ClientEvent( + stageId = stageId, + joinAttemptId = joinStageAttemptId, + eventType = eventType.value, + id = callId, + sdkVersion = sdkVersion, + stage = stage.value, + timestamp = OffsetDateTime.now(ZoneOffset.UTC), + type = callType, + userAgent = userAgent.invoke().take(512), + userId = StreamVideo.Companion.instanceOrNull()?.userId, + callSessionId = callSessionId, + elapsedTime = elapsedTime?.toInt(), + iceState = iceState?.name, + outcome = outcome?.value, + peerConnection = peerConnection?.value, + previouslyConnectedTimestamp = null, + retryCountAttempt = retryCountAttempt, + retryFailureCode = retryFailureCode, + retryFailureReason = retryFailureReason, + sfuId = sfuId, + userSessionId = userSessionId, + wasPreviouslyConnected = wasPreviouslyConnected, + screenShareStatus = getPermissionStatusText(screenShareAllowed), + microphonePermissionStatus = getPermissionStatusText(microphoneAllowed), + cameraPermissionStatus = getPermissionStatusText(cameraAllowed), + trackId = trackId, + coordinatorConnectId = getCoordinatorId(), + ) + + fun getPermissionStatusText(allowed: Boolean?): String? { + return if (allowed == true) "GRANTED" else if (allowed == false) "NOT_GRANTED" else null + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt index a9c54277ff4..be29c85cd19 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt @@ -20,13 +20,10 @@ import io.getstream.android.video.generated.apis.ProductvideoApi import io.getstream.android.video.generated.models.ClientEvent import io.getstream.android.video.generated.models.ReportClientEventRequest import io.getstream.log.taggedLogger -import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import org.threeten.bp.OffsetDateTime -import org.threeten.bp.ZoneOffset import org.webrtc.PeerConnection import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -51,6 +48,9 @@ internal class ClientEventReporter( private val scope: CoroutineScope = UserScope(ClientScope()), ) { private val logger by taggedLogger("ClientEventReporter") + private val clientEventFactory = ClientEventFactory(sdkVersion, userAgent) { + this.coordinatorConnectId + } private val postCallFlightSessions = ConcurrentHashMap() private val joinStageAttemptIdMap = ConcurrentHashMap() @@ -73,7 +73,7 @@ internal class ClientEventReporter( startedAtMs = now, ) sendEvent( - buildRequest( + clientEventFactory.buildRequest( stageId = stageId, coordinatorConnectId = coordinatorConnectId, stage = EventStage.CoordinatorWs, @@ -94,7 +94,7 @@ internal class ClientEventReporter( if (session is PreCallInFlightSession) { val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( - buildRequest( + clientEventFactory.buildRequest( coordinatorConnectId = session.coordinatorConnectId, stage = EventStage.CoordinatorWs, outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, @@ -115,7 +115,7 @@ internal class ClientEventReporter( ) { joinStageAttemptIdMap[callId] = joinStageAttemptId sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId, callType, stage = EventStage.Call.JOIN_INITIATED, @@ -144,7 +144,7 @@ internal class ClientEventReporter( joinStageAttemptIdSnapshot = joinStageAttemptId, ) sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId, callType, stage = EventStage.Call.COORDINATOR_JOIN, @@ -169,7 +169,7 @@ internal class ClientEventReporter( val elapsedTime = System.currentTimeMillis() - session.startedAtMs callSessionIdMap[session.callId] = callSessionId ?: "" sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId = session.callId, callType = session.callType, stage = EventStage.Call.COORDINATOR_JOIN, @@ -211,7 +211,7 @@ internal class ClientEventReporter( callSessionId = callSessionId, ) sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId = callId, callType = callType, stage = EventStage.Call.WS_JOIN, @@ -237,7 +237,7 @@ internal class ClientEventReporter( val elapsedTime = System.currentTimeMillis() - session.startedAtMs if (session is PostCallFlightSession) { sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId = session.callId, callType = session.callType, stage = EventStage.Call.WS_JOIN, @@ -299,7 +299,7 @@ internal class ClientEventReporter( ) activePcSessionIds[role] = stageId sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId = callId, callType = callType, stage = EventStage.Call.PEER_CONNECTION_CONNECT, @@ -365,7 +365,7 @@ internal class ClientEventReporter( val elapsedTime = System.currentTimeMillis() - session.startedAtMs if (session is PostCallFlightSession) { sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId = callId, callType = callType, stage = EventStage.Call.PEER_CONNECTION_CONNECT, @@ -395,7 +395,7 @@ internal class ClientEventReporter( val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId = callId, callType = callType, stage = EventStage.Call.FIRST_AUDIO_FRAME_RENDERED, @@ -419,7 +419,7 @@ internal class ClientEventReporter( val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId = callId, callType = callType, stage = EventStage.Call.FIRST_VIDEO_FRAME_RENDERED, @@ -444,7 +444,7 @@ internal class ClientEventReporter( val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] sendEvent( - buildRequest( + clientEventFactory.buildRequest( callId = callId, callType = callType, stage = EventStage.Call.MEDIA_DEVICE_PERMISSION, @@ -466,7 +466,7 @@ internal class ClientEventReporter( activePcSessionIds.clear() val now = System.currentTimeMillis() val events = snapshot.map { session -> - buildRequest( + clientEventFactory.buildRequest( callId = session.callId, callType = session.callType, stage = session.stage, @@ -490,64 +490,6 @@ internal class ClientEventReporter( // --- Request builder --- - private fun buildRequest( - callId: String? = null, - callType: String? = null, - stage: EventStage, - eventType: EventType, - stageId: String? = null, - joinStageAttemptId: String? = null, - coordinatorConnectId: String? = null, - elapsedTime: Long? = null, - outcome: EventOutcome? = null, - retryCountAttempt: Int? = null, - retryFailureReason: String? = null, - retryFailureCode: String? = null, - callSessionId: String? = null, - sfuId: String? = null, - peerConnection: PeerConnectionRole? = null, - wasPreviouslyConnected: Boolean? = null, - iceState: PeerConnection.IceConnectionState? = null, - peerConnectionState: PeerConnection.PeerConnectionState? = null, - userSessionId: String? = null, - screenShareAllowed: Boolean? = null, - microphoneAllowed: Boolean? = null, - cameraAllowed: Boolean? = null, - trackId: String? = null, - ): ClientEvent = ClientEvent( - stageId = stageId, - joinAttemptId = joinStageAttemptId, - eventType = eventType.value, - id = callId, - sdkVersion = sdkVersion, - stage = stage.value, - timestamp = OffsetDateTime.now(ZoneOffset.UTC), - type = callType, - userAgent = userAgent.invoke().take(512), - userId = StreamVideo.instanceOrNull()?.userId, - callSessionId = callSessionId, - elapsedTime = elapsedTime?.toInt(), - iceState = iceState?.name, - outcome = outcome?.value, - peerConnection = peerConnection?.value, - previouslyConnectedTimestamp = null, - retryCountAttempt = retryCountAttempt, - retryFailureCode = retryFailureCode, - retryFailureReason = retryFailureReason, - sfuId = sfuId, - userSessionId = userSessionId, - wasPreviouslyConnected = wasPreviouslyConnected, - screenShareStatus = getPermissionStatusText(screenShareAllowed), - microphonePermissionStatus = getPermissionStatusText(microphoneAllowed), - cameraPermissionStatus = getPermissionStatusText(cameraAllowed), - trackId = trackId, - coordinatorConnectId = this.coordinatorConnectId, - ) - - fun getPermissionStatusText(allowed: Boolean?): String? { - return if (allowed == true) "GRANTED" else if (allowed == false) "NOT_GRANTED" else null - } - // --- Delivery --- private fun sendEvent(event: ClientEvent) { From a16cccce2dd016edc7028022e97957e4b7a9055d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 19:04:53 +0530 Subject: [PATCH 56/63] chore: refactor client event reporter --- .../android/core/analytics/reporting/ClientEventFactory.kt | 1 - .../android/core/analytics/reporting/ClientEventReporter.kt | 2 -- 2 files changed, 3 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt index e273e1369cd..453097da1a8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt @@ -31,7 +31,6 @@ internal class ClientEventFactory(val sdkVersion: String, val userAgent: () -> S eventType: EventType, stageId: String? = null, joinStageAttemptId: String? = null, - coordinatorConnectId: String? = null, elapsedTime: Long? = null, outcome: EventOutcome? = null, retryCountAttempt: Int? = null, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt index be29c85cd19..afc328c0b6a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt @@ -75,7 +75,6 @@ internal class ClientEventReporter( sendEvent( clientEventFactory.buildRequest( stageId = stageId, - coordinatorConnectId = coordinatorConnectId, stage = EventStage.CoordinatorWs, eventType = EventType.INITIATED, ), @@ -95,7 +94,6 @@ internal class ClientEventReporter( val elapsedTime = System.currentTimeMillis() - session.startedAtMs sendEvent( clientEventFactory.buildRequest( - coordinatorConnectId = session.coordinatorConnectId, stage = EventStage.CoordinatorWs, outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, retryCountAttempt = retryCount, From 5e31613e748bd104920ec6c81fbf19091738d47a Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 19:17:14 +0530 Subject: [PATCH 57/63] chore: refactor client event reporter --- .../video/android/core/ClientState.kt | 7 +- .../reporting/ClientEventReporter.kt | 70 ++++------------- .../core/analytics/reporting/EventSender.kt | 75 +++++++++++++++++++ .../analytics/reporting/PendingEventStore.kt | 67 +++++++++++++++++ 4 files changed, 162 insertions(+), 57 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/EventSender.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index 71449ce0198..df842b958ed 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -24,6 +24,8 @@ import io.getstream.android.video.generated.models.VideoEvent import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.analytics.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.ImmediateEventSender +import io.getstream.video.android.core.analytics.reporting.InMemoryPendingEventStore import io.getstream.video.android.core.faultinjector.FailureInjector import io.getstream.video.android.core.faultinjector.NoOpFailureInjector import io.getstream.video.android.core.header.HeadersUtil @@ -93,7 +95,10 @@ class ClientState(private val client: StreamVideo) { private val serviceLauncher = ServiceLauncher(client.context) internal val clientEventReporter = ClientEventReporter( - streamVideoClient.coordinatorConnectionModule.api, + sender = ImmediateEventSender( + api = streamVideoClient.coordinatorConnectionModule.api, + pendingStore = InMemoryPendingEventStore(), + ), userAgent = { HeadersUtil().buildSdkTrackingHeaders() }, sdkVersion = BuildConfig.STREAM_VIDEO_VERSION, ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt index afc328c0b6a..b70f062df70 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt @@ -16,14 +16,8 @@ package io.getstream.video.android.core.analytics.reporting -import io.getstream.android.video.generated.apis.ProductvideoApi import io.getstream.android.video.generated.models.ClientEvent -import io.getstream.android.video.generated.models.ReportClientEventRequest import io.getstream.log.taggedLogger -import io.getstream.video.android.core.socket.common.scope.ClientScope -import io.getstream.video.android.core.socket.common.scope.UserScope -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import org.webrtc.PeerConnection import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -38,14 +32,10 @@ internal typealias CallId = String * [ClientEvent.retryFailureCode] : Ask clarification */ -internal enum class TelemetrySendingStrategy { IN_PLACE, BATCH } - internal class ClientEventReporter( - private val api: ProductvideoApi, + private val sender: EventSender, private val userAgent: () -> String, private val sdkVersion: String, - private val sendingStrategy: TelemetrySendingStrategy = TelemetrySendingStrategy.IN_PLACE, - private val scope: CoroutineScope = UserScope(ClientScope()), ) { private val logger by taggedLogger("ClientEventReporter") private val clientEventFactory = ClientEventFactory(sdkVersion, userAgent) { @@ -72,7 +62,7 @@ internal class ClientEventReporter( stage = EventStage.Call.COORDINATOR_JOIN, startedAtMs = now, ) - sendEvent( + sender.send( clientEventFactory.buildRequest( stageId = stageId, stage = EventStage.CoordinatorWs, @@ -92,7 +82,7 @@ internal class ClientEventReporter( val session = postCallFlightSessions.remove(stageId) ?: return if (session is PreCallInFlightSession) { val elapsedTime = System.currentTimeMillis() - session.startedAtMs - sendEvent( + sender.send( clientEventFactory.buildRequest( stage = EventStage.CoordinatorWs, outcome = if (success) EventOutcome.SUCCESS else EventOutcome.FAILURE, @@ -112,7 +102,7 @@ internal class ClientEventReporter( joinStageAttemptId: String, ) { joinStageAttemptIdMap[callId] = joinStageAttemptId - sendEvent( + sender.send( clientEventFactory.buildRequest( callId, callType, @@ -141,7 +131,7 @@ internal class ClientEventReporter( startedAtMs = now, joinStageAttemptIdSnapshot = joinStageAttemptId, ) - sendEvent( + sender.send( clientEventFactory.buildRequest( callId, callType, @@ -166,7 +156,7 @@ internal class ClientEventReporter( if (session is PostCallFlightSession) { val elapsedTime = System.currentTimeMillis() - session.startedAtMs callSessionIdMap[session.callId] = callSessionId ?: "" - sendEvent( + sender.send( clientEventFactory.buildRequest( callId = session.callId, callType = session.callType, @@ -208,7 +198,7 @@ internal class ClientEventReporter( wasPreviouslyConnected = wasPreviouslyConnected, callSessionId = callSessionId, ) - sendEvent( + sender.send( clientEventFactory.buildRequest( callId = callId, callType = callType, @@ -234,7 +224,7 @@ internal class ClientEventReporter( val session = postCallFlightSessions.remove(stageId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs if (session is PostCallFlightSession) { - sendEvent( + sender.send( clientEventFactory.buildRequest( callId = session.callId, callType = session.callType, @@ -296,7 +286,7 @@ internal class ClientEventReporter( callSessionId = callSessionIdMap[callId], ) activePcSessionIds[role] = stageId - sendEvent( + sender.send( clientEventFactory.buildRequest( callId = callId, callType = callType, @@ -362,7 +352,7 @@ internal class ClientEventReporter( val session = postCallFlightSessions.remove(stageId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs if (session is PostCallFlightSession) { - sendEvent( + sender.send( clientEventFactory.buildRequest( callId = callId, callType = callType, @@ -392,7 +382,7 @@ internal class ClientEventReporter( ): String { val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] - sendEvent( + sender.send( clientEventFactory.buildRequest( callId = callId, callType = callType, @@ -416,7 +406,7 @@ internal class ClientEventReporter( ): String { val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] - sendEvent( + sender.send( clientEventFactory.buildRequest( callId = callId, callType = callType, @@ -441,7 +431,7 @@ internal class ClientEventReporter( ): String { val stageId = UUID.randomUUID().toString() val callSessionId = callSessionIdMap[callId] - sendEvent( + sender.send( clientEventFactory.buildRequest( callId = callId, callType = callType, @@ -483,39 +473,7 @@ internal class ClientEventReporter( joinStageAttemptId = session.joinStageAttemptIdSnapshot, ) } - sendEvents(events) + sender.sendAll(events) } - // --- Request builder --- - - // --- Delivery --- - - private fun sendEvent(event: ClientEvent) { - when (sendingStrategy) { - TelemetrySendingStrategy.BATCH -> {} - TelemetrySendingStrategy.IN_PLACE -> { - scope.launch { - // TODO: wrap with StreamRetryPolicy when retries are added - runCatching { - logger.d { event.toLog() } - api.reportClientCallEvent(ReportClientEventRequest(arrayListOf(event))) - }.onFailure { e -> - logger.w { "[sendEvent] Failed to send client event: ${e.message}" } - } - } - } - } - } - - private fun sendEvents(events: List) { - scope.launch { - // TODO: wrap with StreamRetryPolicy when retries are added - runCatching { - logger.d { events.map { it.toLog() }.joinToString { "," } } - api.reportClientCallEvent(ReportClientEventRequest(events)) - }.onFailure { e -> - logger.w { "[sendEvent] Failed to send client event: ${e.message}" } - } - } - } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/EventSender.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/EventSender.kt new file mode 100644 index 00000000000..f79daad4517 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/EventSender.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting + +import io.getstream.android.video.generated.apis.ProductvideoApi +import io.getstream.android.video.generated.models.ClientEvent +import io.getstream.android.video.generated.models.ReportClientEventRequest +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.socket.common.scope.ClientScope +import io.getstream.video.android.core.socket.common.scope.UserScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * Responsible for delivering telemetry [ClientEvent]s to the backend. + * Failed events are handed to a [PendingEventStore] and can be retried via [retryPending]. + */ +internal interface EventSender { + fun send(event: ClientEvent) + fun sendAll(events: List) + + /** + * Retries any events that previously failed to send. + * Call this when connectivity is restored or on a suitable recovery point. + */ + fun retryPending() +} + +/** + * Sends each event immediately in a coroutine. + * On network failure the events are saved to [pendingStore] for later retry. + */ +internal class ImmediateEventSender( + private val api: ProductvideoApi, + private val scope: CoroutineScope = UserScope(ClientScope()), + private val pendingStore: PendingEventStore = InMemoryPendingEventStore(), +) : EventSender { + + private val logger by taggedLogger("ImmediateEventSender") + + override fun send(event: ClientEvent) = sendAll(listOf(event)) + + override fun sendAll(events: List) { + if (events.isEmpty()) return + scope.launch { + runCatching { + logger.d { events.joinToString(",") { it.toLog() } } + api.reportClientCallEvent(ReportClientEventRequest(events)) + }.onFailure { e -> + logger.w { "[sendAll] Failed — saving ${events.size} event(s) for retry: ${e.message}" } + pendingStore.save(events) + } + } + } + + override fun retryPending() { + if (pendingStore.isEmpty()) return + val pending = pendingStore.loadAndClear() + sendAll(pending) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt new file mode 100644 index 00000000000..f67112726c9 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting + +import io.getstream.android.video.generated.models.ClientEvent +import java.util.concurrent.CopyOnWriteArrayList + +/** + * Storage for telemetry events that failed to send. + * Implementations may store events in-memory (lost on process kill) + * or on disk (survives process restarts). + */ +internal interface PendingEventStore { + fun save(events: List) + fun loadAndClear(): List + fun isEmpty(): Boolean +} + +/** + * In-memory implementation. Events are retained within the same process session + * and lost if the process is killed before retry. + */ +internal class InMemoryPendingEventStore : PendingEventStore { + private val queue = CopyOnWriteArrayList() + + override fun save(events: List) { + queue.addAll(events) + } + + override fun loadAndClear(): List = queue.toList().also { queue.clear() } + + override fun isEmpty(): Boolean = queue.isEmpty() +} + +/** + * File-based implementation placeholder. + * Implement when disk persistence for failed events is required. + */ +internal class FilePendingEventStore : PendingEventStore { + override fun save(events: List) { + // TODO: serialize events to disk + } + + override fun loadAndClear(): List { + // TODO: deserialize from disk and delete the file + return emptyList() + } + + override fun isEmpty(): Boolean { + // TODO: check if file exists and is non-empty + return true + } +} From 93d60cd6561c7a2b8a538bcce1ef6412a60cc9a9 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 19:18:05 +0530 Subject: [PATCH 58/63] chore: spotless --- .../reporting/ClientEventReporter.kt | 1 - .../analytics/reporting/PendingEventStore.kt | 20 ------------------- 2 files changed, 21 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt index b70f062df70..e5160682e5b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt @@ -475,5 +475,4 @@ internal class ClientEventReporter( } sender.sendAll(events) } - } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt index f67112726c9..8c8bad06271 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt @@ -45,23 +45,3 @@ internal class InMemoryPendingEventStore : PendingEventStore { override fun isEmpty(): Boolean = queue.isEmpty() } - -/** - * File-based implementation placeholder. - * Implement when disk persistence for failed events is required. - */ -internal class FilePendingEventStore : PendingEventStore { - override fun save(events: List) { - // TODO: serialize events to disk - } - - override fun loadAndClear(): List { - // TODO: deserialize from disk and delete the file - return emptyList() - } - - override fun isEmpty(): Boolean { - // TODO: check if file exists and is non-empty - return true - } -} From 489816f1a223fc562ca0c3613a757a49ccff90cd Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 4 Jun 2026 19:25:33 +0530 Subject: [PATCH 59/63] chore: send missing call_session_id --- .../android/core/analytics/reporting/ClientEventReporter.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt index e5160682e5b..06c165243bd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt @@ -351,10 +351,12 @@ internal class ClientEventReporter( ) { val session = postCallFlightSessions.remove(stageId) ?: return val elapsedTime = System.currentTimeMillis() - session.startedAtMs + val callSessionId = callSessionIdMap[callId] if (session is PostCallFlightSession) { sender.send( clientEventFactory.buildRequest( callId = callId, + callSessionId = callSessionId, callType = callType, stage = EventStage.Call.PEER_CONNECTION_CONNECT, eventType = EventType.COMPLETED, From 927166cbc434765073a8b13c4ef2df5caf462ce5 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 5 Jun 2026 14:25:33 +0530 Subject: [PATCH 60/63] chore: rename files, introduce datasource --- .../io/getstream/video/android/core/Call.kt | 20 ++--- .../video/android/core/ClientState.kt | 4 +- .../video/android/core/StreamVideoClient.kt | 4 +- .../analytics/CallAnalyticsCoordinator.kt | 58 +++++++------ .../AudioObserver.kt} | 7 +- .../CoordinatorSocketObserver.kt} | 4 +- .../JoinRequestObserver.kt} | 7 +- .../MediaPermissionObserver.kt} | 4 +- .../PeerConnectionObserver.kt} | 5 +- .../SfuSocketObserver.kt} | 6 +- .../VideoObserver.kt} | 6 +- .../core/analytics/reporting/EventSender.kt | 14 ++-- .../FileBasedPendingEventDataSource.kt | 81 +++++++++++++++++++ .../PendingEventDataSource.kt} | 6 +- .../SynchronizedPendingEventDataSource.kt | 50 ++++++++++++ .../video/android/core/call/RtcSession.kt | 11 ++- 16 files changed, 219 insertions(+), 68 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{AudioAnalytics.kt => observer/AudioObserver.kt} (97%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{CoordinatorAnalytics.kt => observer/CoordinatorSocketObserver.kt} (96%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{JoinRequestHooks.kt => observer/JoinRequestObserver.kt} (94%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{MediaPermissionHook.kt => observer/MediaPermissionObserver.kt} (94%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{PeerConnectionAnalyticsObserver.kt => observer/PeerConnectionObserver.kt} (96%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{WsHook.kt => observer/SfuSocketObserver.kt} (94%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{VideoAnalytics.kt => observer/VideoObserver.kt} (95%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/FileBasedPendingEventDataSource.kt rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/{PendingEventStore.kt => datasource/PendingEventDataSource.kt} (88%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/SynchronizedPendingEventDataSource.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 23d9a945ea4..a2c396d99f5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -567,8 +567,8 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, callJoinInterceptor: CallJoinInterceptor? = null, ): Result { - callAnalyticsCoordinator.joinRequestHooks.onJoinFunctionStart() - callAnalyticsCoordinator.mediaPermissionHook.mediaPermissionStatus() + callAnalyticsCoordinator.joinRequestObserver.onJoinFunctionStart() + callAnalyticsCoordinator.mediaPermissionObserver.mediaPermissionStatus() logger.d { "[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions" } @@ -635,7 +635,7 @@ public class Call( logger.e { "Join failed with error $result" } if (isPermanentError(result.value)) { state._connection.value = RealtimeConnection.Failed(result.value) - callAnalyticsCoordinator.joinRequestHooks.onJoinRequestPermanentError( + callAnalyticsCoordinator.joinRequestObserver.onJoinRequestPermanentError( retryCount, result.value.message, ) @@ -649,7 +649,7 @@ public class Call( session.value = null val errorMessage = "Join failed after 3 retries" state._connection.value = RealtimeConnection.Failed(errorMessage) - callAnalyticsCoordinator.joinRequestHooks.onJoinRequestRetryExhausted( + callAnalyticsCoordinator.joinRequestObserver.onJoinRequestRetryExhausted( retryCount, errorMessage, ) @@ -800,8 +800,8 @@ public class Call( } } monitorPublisherPCStateJob?.cancel() - callAnalyticsCoordinator.peerConnectionAnalyticsObserver.stop() - callAnalyticsCoordinator.peerConnectionAnalyticsObserver.observePeerConnections(session) + callAnalyticsCoordinator.peerConnectionObserver.stop() + callAnalyticsCoordinator.peerConnectionObserver.observePeerConnections(session) monitorPublisherPCStateJob = scope.launch { session .filterNotNull() @@ -1073,7 +1073,7 @@ public class Call( if (state.connection.value is RealtimeConnection.ReconnectingFailed) { logger.w { "[reconnect] All recovery attempts exhausted — leaving call ($reason)" } - callAnalyticsCoordinator.joinRequestHooks.onJoinRequestRetryExhausted( + callAnalyticsCoordinator.joinRequestObserver.onJoinRequestRetryExhausted( loopIteration, "All recovery attempts exhausted — leaving call ($reason)", ) @@ -1517,7 +1517,7 @@ public class Call( ) } onRendered(videoRenderer) - callAnalyticsCoordinator.videoAnalytics.firstVideoFrameRendered( + callAnalyticsCoordinator.videoObserver.firstVideoFrameRendered( trackType, width, height, @@ -1905,7 +1905,7 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, telemetryModel: TelemetryModel, ): Result { - callAnalyticsCoordinator.joinRequestHooks.onJoinRequestStart() + callAnalyticsCoordinator.joinRequestObserver.onJoinRequestStart() val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( type, id, @@ -1923,7 +1923,7 @@ public class Call( hintHighScaleLivestreamPublisher = hintHighScaleLivestreamPublisher, ) result.onSuccess { - callAnalyticsCoordinator.joinRequestHooks.onJoinRequestSuccess( + callAnalyticsCoordinator.joinRequestObserver.onJoinRequestSuccess( telemetryModel, it.call.currentSessionId, ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index df842b958ed..7f8ed347504 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -25,7 +25,7 @@ import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import io.getstream.video.android.core.analytics.reporting.ImmediateEventSender -import io.getstream.video.android.core.analytics.reporting.InMemoryPendingEventStore +import io.getstream.video.android.core.analytics.reporting.datasource.InMemoryPendingEventDataSource import io.getstream.video.android.core.faultinjector.FailureInjector import io.getstream.video.android.core.faultinjector.NoOpFailureInjector import io.getstream.video.android.core.header.HeadersUtil @@ -97,7 +97,7 @@ class ClientState(private val client: StreamVideo) { ClientEventReporter( sender = ImmediateEventSender( api = streamVideoClient.coordinatorConnectionModule.api, - pendingStore = InMemoryPendingEventStore(), + dataSource = InMemoryPendingEventDataSource(), ), userAgent = { HeadersUtil().buildSdkTrackingHeaders() }, sdkVersion = BuildConfig.STREAM_VIDEO_VERSION, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index fa409420b29..b08b1031954 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -83,7 +83,7 @@ import io.getstream.result.Error import io.getstream.result.Result import io.getstream.result.Result.Failure import io.getstream.result.Result.Success -import io.getstream.video.android.core.analytics.CoordinatorAnalytics +import io.getstream.video.android.core.analytics.observer.CoordinatorSocketObserver import io.getstream.video.android.core.audio.AudioExecutionContext import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.errors.VideoErrorCode @@ -221,7 +221,7 @@ internal class StreamVideoClient internal constructor( val socketImpl = coordinatorConnectionModule.socketConnection var location: String? = null - val coordinatorAnalytics = CoordinatorAnalytics(scope, state.clientEventReporter) + val coordinatorAnalytics = CoordinatorSocketObserver(scope, state.clientEventReporter) init { // listen to socket events and errors diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt index f1621bc07c5..245fd29bf77 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt @@ -21,6 +21,12 @@ import io.getstream.log.taggedLogger import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.ParticipantState import io.getstream.video.android.core.RealtimeConnection +import io.getstream.video.android.core.analytics.observer.AudioObserver +import io.getstream.video.android.core.analytics.observer.JoinRequestObserver +import io.getstream.video.android.core.analytics.observer.MediaPermissionObserver +import io.getstream.video.android.core.analytics.observer.PeerConnectionObserver +import io.getstream.video.android.core.analytics.observer.SfuSocketObserver +import io.getstream.video.android.core.analytics.observer.VideoObserver import io.getstream.video.android.core.analytics.reporting.AnalyticsCallAbortReason import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope @@ -36,40 +42,42 @@ internal class CallAnalyticsCoordinator( val scope: CoroutineScope, ) { val logger by taggedLogger("CallAnalyticsHooks") - val joinRequestHooks = JoinRequestHooks(callId, callType, eventReporter) { + + val joinRequestObserver = JoinRequestObserver(callId, callType, eventReporter) { resetAfterJoinSuccess() } - val wsHook = WsHook(callId, callType, connectionFlow, scope, eventReporter) { - joinRequestHooks.joinStageAttemptId - } - - val peerConnectionAnalyticsObserver = - PeerConnectionAnalyticsObserver(callId, callType, scope, eventReporter) { - joinRequestHooks.joinStageAttemptId + val sfuSocketObserver = + SfuSocketObserver(callId, callType, connectionFlow, scope, eventReporter) { + joinRequestObserver.joinStageAttemptId } - - val mediaPermissionHook = MediaPermissionHook(context, callId, callType, eventReporter) { - joinRequestHooks.joinStageAttemptId - } - val audioAnalytics = AudioAnalytics(callId, callType, eventReporter, { wsHook.sfuName }) { - joinRequestHooks.joinStageAttemptId - } - val videoAnalytics = VideoAnalytics(callId, callType, eventReporter, { wsHook.sfuName }) { - joinRequestHooks.joinStageAttemptId + val peerConnectionObserver = PeerConnectionObserver(callId, callType, scope, eventReporter) { + joinRequestObserver.joinStageAttemptId } + val mediaPermissionObserver = + MediaPermissionObserver(context, callId, callType, eventReporter) { + joinRequestObserver.joinStageAttemptId + } + val audioObserver = + AudioObserver(callId, callType, eventReporter, { sfuSocketObserver.sfuName }) { + joinRequestObserver.joinStageAttemptId + } + val videoObserver = + VideoObserver(callId, callType, eventReporter, { sfuSocketObserver.sfuName }) { + joinRequestObserver.joinStageAttemptId + } fun resetAfterJoinSuccess() { - audioAnalytics.reset() - videoAnalytics.reset() - audioAnalytics.observeParticipantsForFirstRemoteAudioFrame(participants, scope) + audioObserver.reset() + videoObserver.reset() + audioObserver.observeParticipantsForFirstRemoteAudioFrame(participants, scope) } fun onCallLeave(callLeaveReason: CallLeaveReason) { val isAnyStageInProgress = - joinRequestHooks.joinStage == Stage.IN_PROGRESS || - wsHook.wsStage == Stage.IN_PROGRESS || - peerConnectionAnalyticsObserver.publisherStage == Stage.IN_PROGRESS || - peerConnectionAnalyticsObserver.subscriberStage == Stage.IN_PROGRESS + joinRequestObserver.joinStage == Stage.IN_PROGRESS || + sfuSocketObserver.wsStage == Stage.IN_PROGRESS || + peerConnectionObserver.publisherStage == Stage.IN_PROGRESS || + peerConnectionObserver.subscriberStage == Stage.IN_PROGRESS logger.d { "noob isAnyStageInProgress:$isAnyStageInProgress" } if (isAnyStageInProgress) { @@ -82,6 +90,6 @@ internal class CallAnalyticsCoordinator( } fun stopObservers() { - peerConnectionAnalyticsObserver.stop() + peerConnectionObserver.stop() } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/AudioObserver.kt similarity index 97% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/AudioObserver.kt index 3575f2553cd..f435eb772fc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/AudioAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/AudioObserver.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics +package io.getstream.video.android.core.analytics.observer import io.getstream.log.taggedLogger import io.getstream.video.android.core.ParticipantState @@ -32,7 +32,8 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean private typealias TrackId = String -internal class AudioAnalytics( + +internal class AudioObserver( private val callId: String, private val callType: String, private val clientEventReporter: ClientEventReporter, @@ -40,7 +41,7 @@ internal class AudioAnalytics( val getJoinStageAttemptId: () -> String, ) { - val logger by taggedLogger("AudioAnalytics") + val logger by taggedLogger("AudioObserver") var recordedFirstFrame: AtomicBoolean = AtomicBoolean(false) private val trackSinks = diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/CoordinatorSocketObserver.kt similarity index 96% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/CoordinatorSocketObserver.kt index 63b9a548ddf..90009b9cf3c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CoordinatorAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/CoordinatorSocketObserver.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics +package io.getstream.video.android.core.analytics.observer import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import io.getstream.video.android.core.socket.coordinator.CoordinatorSocketStateService @@ -25,7 +25,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -internal class CoordinatorAnalytics( +internal class CoordinatorSocketObserver( private val scope: CoroutineScope, private val eventReporter: ClientEventReporter, ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt similarity index 94% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt index 270d5c3331b..424e33cc1dc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/JoinRequestHooks.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt @@ -14,13 +14,14 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics +package io.getstream.video.android.core.analytics.observer +import io.getstream.video.android.core.analytics.Stage import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import io.getstream.video.android.core.analytics.reporting.TelemetryModel import java.util.UUID -internal class JoinRequestHooks( +internal class JoinRequestObserver( val callId: String, val callType: String, val eventReporter: ClientEventReporter, @@ -39,6 +40,7 @@ internal class JoinRequestHooks( joinStageAttemptId = joinStageAttemptId, ) } + fun onJoinRequestStart() { if (joinStage == Stage.NOT_STARTED) { stageId = eventReporter.reportCoordinatorJoinInitiated( @@ -49,6 +51,7 @@ internal class JoinRequestHooks( joinStage = Stage.IN_PROGRESS } } + fun onJoinRequestSuccess(telemetryModel: TelemetryModel, currentSessionId: String) { if (joinStage == Stage.IN_PROGRESS) { if (stageId.isNotEmpty()) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/MediaPermissionObserver.kt similarity index 94% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/MediaPermissionObserver.kt index 4f918bcb035..f44996841bb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/MediaPermissionHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/MediaPermissionObserver.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics +package io.getstream.video.android.core.analytics.observer import android.Manifest import android.content.Context @@ -22,7 +22,7 @@ import android.content.pm.PackageManager import androidx.core.content.ContextCompat import io.getstream.video.android.core.analytics.reporting.ClientEventReporter -internal class MediaPermissionHook( +internal class MediaPermissionObserver( val context: Context, val callId: String, val callType: String, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/PeerConnectionObserver.kt similarity index 96% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/PeerConnectionObserver.kt index d0311e4b788..6fee6346286 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/PeerConnectionAnalyticsObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/PeerConnectionObserver.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics +package io.getstream.video.android.core.analytics.observer +import io.getstream.video.android.core.analytics.Stage import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import io.getstream.video.android.core.analytics.reporting.PeerConnectionRole import io.getstream.video.android.core.call.RtcSession @@ -27,7 +28,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch import org.webrtc.PeerConnection -internal class PeerConnectionAnalyticsObserver( +internal class PeerConnectionObserver( val callId: String, val callType: String, private val scope: CoroutineScope, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/SfuSocketObserver.kt similarity index 94% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/SfuSocketObserver.kt index 129a0d4a5d2..8390a31d50d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/WsHook.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/SfuSocketObserver.kt @@ -14,14 +14,15 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics +package io.getstream.video.android.core.analytics.observer import io.getstream.video.android.core.RealtimeConnection +import io.getstream.video.android.core.analytics.Stage import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow -internal class WsHook( +internal class SfuSocketObserver( val callId: String, val callType: String, val connectionFlow: StateFlow, @@ -33,6 +34,7 @@ internal class WsHook( var wsStage = Stage.NOT_STARTED var sfuName: String = "" + fun onWsInitiated(sfuName: String, wasPreviouslyConnected: Boolean) { if (wsStage == Stage.NOT_STARTED) { this.sfuName = sfuName diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/VideoObserver.kt similarity index 95% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/VideoObserver.kt index d310414358a..e7be0a74eaf 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/VideoAnalytics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/VideoObserver.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics +package io.getstream.video.android.core.analytics.observer import android.util.Log import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import io.getstream.video.android.core.call.RtcSession import stream.video.sfu.models.TrackType -internal class VideoAnalytics( +internal class VideoObserver( private val callId: String, private val callType: String, private val clientEventReporter: ClientEventReporter, @@ -47,7 +47,7 @@ internal class VideoAnalytics( TrackType.TRACK_TYPE_VIDEO, )?.asVideoTrack()?.video?.id() Log.d( - "VideoAnalytics", + "VideoObserver", "noob [firstVideoFrameRendered]: $trackType, w:$width, h:$height, videoTrackId:$videoTrackId, videoSessionId:$videoSessionId, callSessionId:$callSessionId", ) videoTrackId?.let { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/EventSender.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/EventSender.kt index f79daad4517..9971f875697 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/EventSender.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/EventSender.kt @@ -20,6 +20,8 @@ import io.getstream.android.video.generated.apis.ProductvideoApi import io.getstream.android.video.generated.models.ClientEvent import io.getstream.android.video.generated.models.ReportClientEventRequest import io.getstream.log.taggedLogger +import io.getstream.video.android.core.analytics.reporting.datasource.InMemoryPendingEventDataSource +import io.getstream.video.android.core.analytics.reporting.datasource.PendingEventDataSource import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope import kotlinx.coroutines.CoroutineScope @@ -27,7 +29,7 @@ import kotlinx.coroutines.launch /** * Responsible for delivering telemetry [ClientEvent]s to the backend. - * Failed events are handed to a [PendingEventStore] and can be retried via [retryPending]. + * Failed events are handed to a [PendingEventDataSource] and can be retried via [retryPending]. */ internal interface EventSender { fun send(event: ClientEvent) @@ -42,12 +44,12 @@ internal interface EventSender { /** * Sends each event immediately in a coroutine. - * On network failure the events are saved to [pendingStore] for later retry. + * On network failure the events are saved to [dataSource] for later retry. */ internal class ImmediateEventSender( private val api: ProductvideoApi, private val scope: CoroutineScope = UserScope(ClientScope()), - private val pendingStore: PendingEventStore = InMemoryPendingEventStore(), + private val dataSource: PendingEventDataSource = InMemoryPendingEventDataSource(), ) : EventSender { private val logger by taggedLogger("ImmediateEventSender") @@ -62,14 +64,14 @@ internal class ImmediateEventSender( api.reportClientCallEvent(ReportClientEventRequest(events)) }.onFailure { e -> logger.w { "[sendAll] Failed — saving ${events.size} event(s) for retry: ${e.message}" } - pendingStore.save(events) + dataSource.save(events) } } } override fun retryPending() { - if (pendingStore.isEmpty()) return - val pending = pendingStore.loadAndClear() + if (dataSource.isEmpty()) return + val pending = dataSource.loadAndClear() sendAll(pending) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/FileBasedPendingEventDataSource.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/FileBasedPendingEventDataSource.kt new file mode 100644 index 00000000000..79a3447ca24 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/FileBasedPendingEventDataSource.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting.datasource + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import io.getstream.android.video.generated.infrastructure.Serializer +import io.getstream.android.video.generated.models.ClientEvent +import io.getstream.log.taggedLogger +import java.io.File + +/** + * Persists pending telemetry events to a single NDJSON file across process restarts. + * + * [loadAndClear] reads up to [batchSize] events and removes them from the file. + * The lock only covers the file read/write itself — not the network call — so the + * blocked window is microseconds, not seconds. + * + * This class is not thread-safe on its own; wrap with [SynchronizedPendingEventDataSource]. + */ +internal class FileBasedPendingEventDataSource( + storageDir: File, + moshi: Moshi = Serializer.moshi, + private val batchSize: Int = DEFAULT_BATCH_SIZE, +) : PendingEventDataSource { + + private val logger by taggedLogger("FileBasedPendingEventDataSource") + + private val file = File(storageDir, "pending_events.ndjson") + private val adapter: JsonAdapter = moshi.adapter(ClientEvent::class.java) + + override fun save(events: List) { + if (events.isEmpty()) return + try { + if (file.parentFile?.exists() == false) file.parentFile?.mkdirs() + val lines = events.joinToString(separator = "\n", postfix = "\n") { adapter.toJson(it) } + file.appendText(lines) + } catch (e: Exception) { + logger.w { "[save] Failed to persist ${events.size} event(s): ${e.message}" } + } + } + + override fun loadAndClear(): List { + if (!file.exists() || file.length() == 0L) return emptyList() + return try { + val allLines = file.readLines().filter { it.isNotBlank() } + val batch = allLines.take(batchSize) + val remaining = allLines.drop(batchSize) + if (remaining.isEmpty()) { + file.delete() + } else { + file.writeText(remaining.joinToString(separator = "\n", postfix = "\n")) + } + logger.d { "[loadAndClear] batch=${batch.size}, remaining=${remaining.size}" } + batch.mapNotNull { line -> runCatching { adapter.fromJson(line) }.getOrNull() } + } catch (e: Exception) { + logger.w { "[loadAndClear] Failed: ${e.message}" } + emptyList() + } + } + + override fun isEmpty(): Boolean = !file.exists() || file.length() == 0L + + companion object { + const val DEFAULT_BATCH_SIZE = 10 + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/PendingEventDataSource.kt similarity index 88% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/PendingEventDataSource.kt index 8c8bad06271..fbac606f9bd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/PendingEventStore.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/PendingEventDataSource.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics.reporting +package io.getstream.video.android.core.analytics.reporting.datasource import io.getstream.android.video.generated.models.ClientEvent import java.util.concurrent.CopyOnWriteArrayList @@ -24,7 +24,7 @@ import java.util.concurrent.CopyOnWriteArrayList * Implementations may store events in-memory (lost on process kill) * or on disk (survives process restarts). */ -internal interface PendingEventStore { +internal interface PendingEventDataSource { fun save(events: List) fun loadAndClear(): List fun isEmpty(): Boolean @@ -34,7 +34,7 @@ internal interface PendingEventStore { * In-memory implementation. Events are retained within the same process session * and lost if the process is killed before retry. */ -internal class InMemoryPendingEventStore : PendingEventStore { +internal class InMemoryPendingEventDataSource : PendingEventDataSource { private val queue = CopyOnWriteArrayList() override fun save(events: List) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/SynchronizedPendingEventDataSource.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/SynchronizedPendingEventDataSource.kt new file mode 100644 index 00000000000..d3cb1afa550 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/datasource/SynchronizedPendingEventDataSource.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting.datasource + +import io.getstream.android.video.generated.models.ClientEvent +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +/** + * Decorator that adds thread-safety to any [PendingEventDataSource]. + * + * Concurrency is the only concern here — all actual storage logic lives in [delegate]. + * + * Usage: + * ``` + * SynchronizedPendingEventDataSource(FileBasedPendingEventDataSource(storageDir)) + * SynchronizedPendingEventDataSource(InMemoryPendingEventDataSource()) + * ``` + * + * [save] and [loadAndClear] acquire a write lock because they mutate state. + * [isEmpty] acquires a read lock so multiple callers can check concurrently + * without blocking each other. + */ +internal class SynchronizedPendingEventDataSource( + private val delegate: PendingEventDataSource, +) : PendingEventDataSource { + + private val lock = ReentrantReadWriteLock() + + override fun save(events: List) = lock.write { delegate.save(events) } + + override fun loadAndClear(): List = lock.write { delegate.loadAndClear() } + + override fun isEmpty(): Boolean = lock.read { delegate.isEmpty() } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index eea34fe0e88..55cb87f9e36 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -884,7 +884,10 @@ public class RtcSession internal constructor( telemetryModel: TelemetryModel? = null, ): SfuConnectionResult { logger.i { "noob [connectInternal] #sfu; #track; reconnect=${reconnectDetails?.strategy}" } - call.callAnalyticsCoordinator.wsHook.onWsInitiated(sfuName, reconnectDetails != null) + call.callAnalyticsCoordinator.sfuSocketObserver.onWsInitiated( + sfuName, + reconnectDetails != null, + ) val request = buildJoinRequest(reconnectDetails, options) sfuTracer.trace( @@ -902,7 +905,7 @@ public class RtcSession internal constructor( } return when (terminalState) { is SfuSocketState.Connected -> { - call.callAnalyticsCoordinator.wsHook.onWsCompleted( + call.callAnalyticsCoordinator.sfuSocketObserver.onWsCompleted( success = true, retryCount = 0, ) @@ -920,7 +923,7 @@ public class RtcSession internal constructor( } logger.w { "[connectInternal] $msg" } sfuTracer.trace("connect-failed", msg) - call.callAnalyticsCoordinator.wsHook.onWsCompleted( + call.callAnalyticsCoordinator.sfuSocketObserver.onWsCompleted( success = false, retryCount = telemetryModel?.retryAttempt ?: 0, failureReason = msg, @@ -931,7 +934,7 @@ public class RtcSession internal constructor( } else -> { sfuTracer.trace("connect-failed", "Connection timed out") - call.callAnalyticsCoordinator.wsHook.onWsCompleted( + call.callAnalyticsCoordinator.sfuSocketObserver.onWsCompleted( success = false, retryCount = telemetryModel?.retryAttempt ?: 0, failureReason = AnalyticsFailureCodes.SFU_REQUEST_TIMEOUT.message, From 2f8117754a30898b1a2be56cae80a4dad597ca91 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 5 Jun 2026 14:37:43 +0530 Subject: [PATCH 61/63] chore: refactor models and packages in analytics --- .../api/stream-video-android-core.api | 8 ------ .../io/getstream/video/android/core/Call.kt | 2 +- .../analytics/CallAnalyticsCoordinator.kt | 3 ++- .../analytics/observer/JoinRequestObserver.kt | 4 +-- .../observer/PeerConnectionObserver.kt | 4 +-- .../analytics/observer/SfuSocketObserver.kt | 2 +- .../analytics/{ => observer/model}/Stage.kt | 4 +-- .../observer/model/TelemetryModel.kt | 19 +++++++++++++ .../analytics/reporting/ClientEventFactory.kt | 4 +++ .../reporting/ClientEventReporter.kt | 13 ++++++--- .../model/AnalyticsCallAbortReason.kt | 22 +++++++++++++++ .../reporting/model/AnalyticsFailureCodes.kt | 27 +++++++++++++++++++ .../analytics/reporting/model/EventOutcome.kt | 22 +++++++++++++++ .../EventStage.kt} | 17 +----------- .../analytics/reporting/model/EventType.kt | 22 +++++++++++++++ .../InFlightSession.kt} | 20 +++----------- .../reporting/model/PeerConnectionRole.kt | 22 +++++++++++++++ .../video/android/core/call/RtcSession.kt | 4 +-- 18 files changed, 164 insertions(+), 55 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/{ => observer/model}/Stage.kt (83%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/model/TelemetryModel.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/AnalyticsCallAbortReason.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/AnalyticsFailureCodes.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventOutcome.kt rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/{ClientCallEventData.kt => model/EventStage.kt} (75%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventType.kt rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/{TelemetryModel.kt => model/InFlightSession.kt} (61%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/PeerConnectionRole.kt diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 10ced630c58..b5bfcad33a8 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -9889,14 +9889,6 @@ public final class io/getstream/video/android/core/StreamVideoConfigDefault : io public fun getJoinOnAcceptedByCallee ()Z } -public final class io/getstream/video/android/core/analytics/Stage : java/lang/Enum { - public static final field IN_PROGRESS Lio/getstream/video/android/core/analytics/Stage; - public static final field NOT_STARTED Lio/getstream/video/android/core/analytics/Stage; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/analytics/Stage; - public static fun values ()[Lio/getstream/video/android/core/analytics/Stage; -} - public abstract interface class io/getstream/video/android/core/api/SignalServerService { public abstract fun iceRestart (Lstream/video/sfu/signal/ICERestartRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun iceTrickle (Lstream/video/sfu/models/ICETrickle;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index a2c396d99f5..5b83f26d595 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -61,7 +61,7 @@ import io.getstream.result.Result.Failure import io.getstream.result.Result.Success import io.getstream.result.flatMap import io.getstream.video.android.core.analytics.CallAnalyticsCoordinator -import io.getstream.video.android.core.analytics.reporting.TelemetryModel +import io.getstream.video.android.core.analytics.observer.model.TelemetryModel import io.getstream.video.android.core.audio.StreamAudioDevice import io.getstream.video.android.core.call.FastReconnectResult import io.getstream.video.android.core.call.RtcSession diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt index 245fd29bf77..e20361eacc4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt @@ -27,8 +27,9 @@ import io.getstream.video.android.core.analytics.observer.MediaPermissionObserve import io.getstream.video.android.core.analytics.observer.PeerConnectionObserver import io.getstream.video.android.core.analytics.observer.SfuSocketObserver import io.getstream.video.android.core.analytics.observer.VideoObserver -import io.getstream.video.android.core.analytics.reporting.AnalyticsCallAbortReason +import io.getstream.video.android.core.analytics.observer.model.Stage import io.getstream.video.android.core.analytics.reporting.ClientEventReporter +import io.getstream.video.android.core.analytics.reporting.model.AnalyticsCallAbortReason import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt index 424e33cc1dc..e360943ff33 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt @@ -16,9 +16,9 @@ package io.getstream.video.android.core.analytics.observer -import io.getstream.video.android.core.analytics.Stage +import io.getstream.video.android.core.analytics.observer.model.Stage +import io.getstream.video.android.core.analytics.observer.model.TelemetryModel import io.getstream.video.android.core.analytics.reporting.ClientEventReporter -import io.getstream.video.android.core.analytics.reporting.TelemetryModel import java.util.UUID internal class JoinRequestObserver( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/PeerConnectionObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/PeerConnectionObserver.kt index 6fee6346286..a46415b2957 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/PeerConnectionObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/PeerConnectionObserver.kt @@ -16,9 +16,9 @@ package io.getstream.video.android.core.analytics.observer -import io.getstream.video.android.core.analytics.Stage +import io.getstream.video.android.core.analytics.observer.model.Stage import io.getstream.video.android.core.analytics.reporting.ClientEventReporter -import io.getstream.video.android.core.analytics.reporting.PeerConnectionRole +import io.getstream.video.android.core.analytics.reporting.model.PeerConnectionRole import io.getstream.video.android.core.call.RtcSession import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/SfuSocketObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/SfuSocketObserver.kt index 8390a31d50d..972fe7fcc81 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/SfuSocketObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/SfuSocketObserver.kt @@ -17,7 +17,7 @@ package io.getstream.video.android.core.analytics.observer import io.getstream.video.android.core.RealtimeConnection -import io.getstream.video.android.core.analytics.Stage +import io.getstream.video.android.core.analytics.observer.model.Stage import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/model/Stage.kt similarity index 83% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/model/Stage.kt index 9eba7a58c08..e5c0892a312 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/Stage.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/model/Stage.kt @@ -14,6 +14,6 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics +package io.getstream.video.android.core.analytics.observer.model -enum class Stage { NOT_STARTED, IN_PROGRESS } +internal enum class Stage { NOT_STARTED, IN_PROGRESS } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/model/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/model/TelemetryModel.kt new file mode 100644 index 00000000000..b3010efa674 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/model/TelemetryModel.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.observer.model + +internal data class TelemetryModel(val retryAttempt: Int) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt index 453097da1a8..aa7bea25473 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventFactory.kt @@ -18,6 +18,10 @@ package io.getstream.video.android.core.analytics.reporting import io.getstream.android.video.generated.models.ClientEvent import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.analytics.reporting.model.EventOutcome +import io.getstream.video.android.core.analytics.reporting.model.EventStage +import io.getstream.video.android.core.analytics.reporting.model.EventType +import io.getstream.video.android.core.analytics.reporting.model.PeerConnectionRole import org.threeten.bp.OffsetDateTime import org.threeten.bp.ZoneOffset import org.webrtc.PeerConnection diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt index 06c165243bd..ea0f906bb13 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt @@ -18,14 +18,21 @@ package io.getstream.video.android.core.analytics.reporting import io.getstream.android.video.generated.models.ClientEvent import io.getstream.log.taggedLogger +import io.getstream.video.android.core.analytics.reporting.model.AnalyticsCallAbortReason +import io.getstream.video.android.core.analytics.reporting.model.CallId +import io.getstream.video.android.core.analytics.reporting.model.EventOutcome +import io.getstream.video.android.core.analytics.reporting.model.EventStage +import io.getstream.video.android.core.analytics.reporting.model.EventType +import io.getstream.video.android.core.analytics.reporting.model.InFlightSession +import io.getstream.video.android.core.analytics.reporting.model.PeerConnectionRole +import io.getstream.video.android.core.analytics.reporting.model.PostCallFlightSession +import io.getstream.video.android.core.analytics.reporting.model.PreCallInFlightSession +import io.getstream.video.android.core.analytics.reporting.model.StageId import org.webrtc.PeerConnection import java.util.UUID import java.util.concurrent.ConcurrentHashMap import kotlin.collections.set -internal typealias StageId = String -internal typealias CallId = String - /** * TODO * [ClientEvent.previouslyConnectedTimestamp] : Ask clarification diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/AnalyticsCallAbortReason.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/AnalyticsCallAbortReason.kt new file mode 100644 index 00000000000..201209a76dd --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/AnalyticsCallAbortReason.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting.model + +internal enum class AnalyticsCallAbortReason(val code: String, val message: String) { + CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), + BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/AnalyticsFailureCodes.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/AnalyticsFailureCodes.kt new file mode 100644 index 00000000000..a7e3c46f831 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/AnalyticsFailureCodes.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting.model + +internal enum class AnalyticsFailureCodes(val code: String, val message: String) { + CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), + BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), + NETWORK_OFFLINE("NETWORK_OFFLINE", "Device offline"), + ICE_GATHERING_FAILED("ICE_GATHERING_FAILED", "ICE gathering failed"), + ICE_CONNECTIVITY_FAILED("ICE_CONNECTIVITY_FAILED", "ICE connectivity failed"), + REQUEST_TIMEOUT("REQUEST_TIMEOUT", "Device offline"), + SFU_REQUEST_TIMEOUT("REQUEST_TIMEOUT", "SFU connection timed out"), +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventOutcome.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventOutcome.kt new file mode 100644 index 00000000000..8f96f4b0301 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventOutcome.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting.model + +internal enum class EventOutcome(val value: String) { + SUCCESS("success"), + FAILURE("failure"), +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientCallEventData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventStage.kt similarity index 75% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientCallEventData.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventStage.kt index 0dbc0967df7..dfb959dbb60 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientCallEventData.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventStage.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics.reporting +package io.getstream.video.android.core.analytics.reporting.model internal sealed interface EventStage { val value: String @@ -33,18 +33,3 @@ internal sealed interface EventStage { MEDIA_DEVICE_PERMISSION("MediaDevicePermission"), } } - -internal enum class EventType(val value: String) { - INITIATED("initiated"), - COMPLETED("completed"), -} - -internal enum class EventOutcome(val value: String) { - SUCCESS("success"), - FAILURE("failure"), -} - -internal enum class PeerConnectionRole(val value: String) { - PUBLISH("publish"), - SUBSCRIBE("subscribe"), -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventType.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventType.kt new file mode 100644 index 00000000000..5e262a9fea5 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/EventType.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting.model + +internal enum class EventType(val value: String) { + INITIATED("initiated"), + COMPLETED("completed"), +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/TelemetryModel.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/InFlightSession.kt similarity index 61% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/TelemetryModel.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/InFlightSession.kt index 8c73539819c..743dd479709 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/TelemetryModel.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/InFlightSession.kt @@ -14,24 +14,10 @@ * limitations under the License. */ -package io.getstream.video.android.core.analytics.reporting +package io.getstream.video.android.core.analytics.reporting.model -internal data class TelemetryModel(val retryAttempt: Int) - -internal enum class AnalyticsCallAbortReason(val code: String, val message: String) { - CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), - BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), -} - -internal enum class AnalyticsFailureCodes(val code: String, val message: String) { - CLIENT_ABORTED("CLIENT_ABORTED", "Aborted: user left during retry"), - BACKEND_LEAVE("BACKEND_LEAVE", "Aborted: backend ended call during connect"), - NETWORK_OFFLINE("NETWORK_OFFLINE", "Device offline"), - ICE_GATHERING_FAILED("ICE_GATHERING_FAILED", "ICE gathering failed"), - ICE_CONNECTIVITY_FAILED("ICE_CONNECTIVITY_FAILED", "ICE connectivity failed"), - REQUEST_TIMEOUT("REQUEST_TIMEOUT", "Device offline"), - SFU_REQUEST_TIMEOUT("REQUEST_TIMEOUT", "SFU connection timed out"), -} +internal typealias StageId = String +internal typealias CallId = String internal sealed class InFlightSession( open val stage: EventStage.Call, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/PeerConnectionRole.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/PeerConnectionRole.kt new file mode 100644 index 00000000000..f955d072da9 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/model/PeerConnectionRole.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * 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. + */ + +package io.getstream.video.android.core.analytics.reporting.model + +internal enum class PeerConnectionRole(val value: String) { + PUBLISH("publish"), + SUBSCRIBE("subscribe"), +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index 55cb87f9e36..72cb4b97ca2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -45,8 +45,8 @@ import io.getstream.video.android.core.MediaManagerImpl import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoClient -import io.getstream.video.android.core.analytics.reporting.AnalyticsFailureCodes -import io.getstream.video.android.core.analytics.reporting.TelemetryModel +import io.getstream.video.android.core.analytics.observer.model.TelemetryModel +import io.getstream.video.android.core.analytics.reporting.model.AnalyticsFailureCodes import io.getstream.video.android.core.call.connection.Publisher import io.getstream.video.android.core.call.connection.StreamPeerConnection import io.getstream.video.android.core.call.connection.Subscriber From 7edfd08bea49f5377e1aeefcaa8c6b390281db80 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 5 Jun 2026 14:40:25 +0530 Subject: [PATCH 62/63] chore: rename files --- .../io/getstream/video/android/core/Call.kt | 12 ++++++------ .../core/analytics/CallAnalyticsCoordinator.kt | 16 ++++++++-------- .../{JoinRequestObserver.kt => JoinObserver.kt} | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/{JoinRequestObserver.kt => JoinObserver.kt} (98%) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 5b83f26d595..5a6aebfac9d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -567,7 +567,7 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, callJoinInterceptor: CallJoinInterceptor? = null, ): Result { - callAnalyticsCoordinator.joinRequestObserver.onJoinFunctionStart() + callAnalyticsCoordinator.joinObserver.onJoinFunctionStart() callAnalyticsCoordinator.mediaPermissionObserver.mediaPermissionStatus() logger.d { "[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions" @@ -635,7 +635,7 @@ public class Call( logger.e { "Join failed with error $result" } if (isPermanentError(result.value)) { state._connection.value = RealtimeConnection.Failed(result.value) - callAnalyticsCoordinator.joinRequestObserver.onJoinRequestPermanentError( + callAnalyticsCoordinator.joinObserver.onJoinRequestPermanentError( retryCount, result.value.message, ) @@ -649,7 +649,7 @@ public class Call( session.value = null val errorMessage = "Join failed after 3 retries" state._connection.value = RealtimeConnection.Failed(errorMessage) - callAnalyticsCoordinator.joinRequestObserver.onJoinRequestRetryExhausted( + callAnalyticsCoordinator.joinObserver.onJoinRequestRetryExhausted( retryCount, errorMessage, ) @@ -1073,7 +1073,7 @@ public class Call( if (state.connection.value is RealtimeConnection.ReconnectingFailed) { logger.w { "[reconnect] All recovery attempts exhausted — leaving call ($reason)" } - callAnalyticsCoordinator.joinRequestObserver.onJoinRequestRetryExhausted( + callAnalyticsCoordinator.joinObserver.onJoinRequestRetryExhausted( loopIteration, "All recovery attempts exhausted — leaving call ($reason)", ) @@ -1905,7 +1905,7 @@ public class Call( hintHighScaleLivestreamPublisher: Boolean? = null, telemetryModel: TelemetryModel, ): Result { - callAnalyticsCoordinator.joinRequestObserver.onJoinRequestStart() + callAnalyticsCoordinator.joinObserver.onJoinRequestStart() val migratingFromList = migratingFromList ?: getFailedSfuIdsSnapshot().takeIf { it.isNotEmpty() } val result = clientImpl.joinCall( type, id, @@ -1923,7 +1923,7 @@ public class Call( hintHighScaleLivestreamPublisher = hintHighScaleLivestreamPublisher, ) result.onSuccess { - callAnalyticsCoordinator.joinRequestObserver.onJoinRequestSuccess( + callAnalyticsCoordinator.joinObserver.onJoinRequestSuccess( telemetryModel, it.call.currentSessionId, ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt index e20361eacc4..73ed2400e6e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/CallAnalyticsCoordinator.kt @@ -22,7 +22,7 @@ import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.ParticipantState import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.analytics.observer.AudioObserver -import io.getstream.video.android.core.analytics.observer.JoinRequestObserver +import io.getstream.video.android.core.analytics.observer.JoinObserver import io.getstream.video.android.core.analytics.observer.MediaPermissionObserver import io.getstream.video.android.core.analytics.observer.PeerConnectionObserver import io.getstream.video.android.core.analytics.observer.SfuSocketObserver @@ -44,27 +44,27 @@ internal class CallAnalyticsCoordinator( ) { val logger by taggedLogger("CallAnalyticsHooks") - val joinRequestObserver = JoinRequestObserver(callId, callType, eventReporter) { + val joinObserver = JoinObserver(callId, callType, eventReporter) { resetAfterJoinSuccess() } val sfuSocketObserver = SfuSocketObserver(callId, callType, connectionFlow, scope, eventReporter) { - joinRequestObserver.joinStageAttemptId + joinObserver.joinStageAttemptId } val peerConnectionObserver = PeerConnectionObserver(callId, callType, scope, eventReporter) { - joinRequestObserver.joinStageAttemptId + joinObserver.joinStageAttemptId } val mediaPermissionObserver = MediaPermissionObserver(context, callId, callType, eventReporter) { - joinRequestObserver.joinStageAttemptId + joinObserver.joinStageAttemptId } val audioObserver = AudioObserver(callId, callType, eventReporter, { sfuSocketObserver.sfuName }) { - joinRequestObserver.joinStageAttemptId + joinObserver.joinStageAttemptId } val videoObserver = VideoObserver(callId, callType, eventReporter, { sfuSocketObserver.sfuName }) { - joinRequestObserver.joinStageAttemptId + joinObserver.joinStageAttemptId } fun resetAfterJoinSuccess() { @@ -75,7 +75,7 @@ internal class CallAnalyticsCoordinator( fun onCallLeave(callLeaveReason: CallLeaveReason) { val isAnyStageInProgress = - joinRequestObserver.joinStage == Stage.IN_PROGRESS || + joinObserver.joinStage == Stage.IN_PROGRESS || sfuSocketObserver.wsStage == Stage.IN_PROGRESS || peerConnectionObserver.publisherStage == Stage.IN_PROGRESS || peerConnectionObserver.subscriberStage == Stage.IN_PROGRESS diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinObserver.kt similarity index 98% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinObserver.kt index e360943ff33..1768f51e039 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinRequestObserver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/observer/JoinObserver.kt @@ -21,7 +21,7 @@ import io.getstream.video.android.core.analytics.observer.model.TelemetryModel import io.getstream.video.android.core.analytics.reporting.ClientEventReporter import java.util.UUID -internal class JoinRequestObserver( +internal class JoinObserver( val callId: String, val callType: String, val eventReporter: ClientEventReporter, From bdf34cd2b78a62c50194584049c58cceb31d700e Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 5 Jun 2026 17:28:41 +0530 Subject: [PATCH 63/63] chore: send missing call session id --- .../android/core/analytics/reporting/ClientEventReporter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt index ea0f906bb13..74ab658d32c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/analytics/reporting/ClientEventReporter.kt @@ -215,6 +215,7 @@ internal class ClientEventReporter( joinStageAttemptId = joinStageAttemptId, sfuId = sfuId, wasPreviouslyConnected = wasPreviouslyConnected, + callSessionId = callSessionId, ), ) return stageId