From 0ba709c512f0f96792e2483070afd37f8e75ecc1 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 7 Nov 2025 14:26:49 +0530 Subject: [PATCH 01/47] feat: add video moderation --- .../video/android/ui/call/CallScreen.kt | 4 + .../ui/moderation/ModerationUiState.kt | 35 +++ .../ModerationWarningUiContainer.kt | 204 ++++++++++++++++ .../android/ui/moderation/ModerationsTheme.kt | 104 ++++++++ .../api/stream-video-android-core.api | 227 +++++++++++++++--- .../models/CallModerationBlurEvent.kt | 2 +- .../models/CallModerationWarningEvent.kt | 2 +- .../generated/models/CallStatsLocation.kt | 62 +++++ .../models/CallStatsParticipantSession.kt | 29 ++- .../generated/models/MetricDescriptor.kt | 50 ++++ .../video/generated/models/MetricThreshold.kt | 56 +++++ .../models/ParticipantSeriesPublisherStats.kt | 9 + .../ParticipantSeriesSubscriberStats.kt | 11 +- .../models/ParticipantSeriesTrackMetrics.kt | 17 +- .../models/ParticipantSeriesUserStats.kt | 11 +- .../getstream/video/android/core/CallState.kt | 12 +- .../moderation/CallModerationConstants.kt | 23 ++ .../core/moderation/ModerationBlurConfig.kt | 24 ++ .../core/moderation/ModerationManager.kt | 54 +++++ .../moderation/ModerationWarningConfig.kt | 25 ++ .../stream-video-android-filters-video.api | 55 ++++- .../android/filters/video/BlurIntensity.kt | 32 +++ .../video/BlurredBackgroundVideoFilter.kt | 10 - .../filters/video/SimpleBlurVideoFilter.kt | 43 ++++ .../api/stream-video-android-ui-compose.api | 16 +- .../build.gradle.kts | 2 + .../components/call/activecall/CallContent.kt | 34 ++- .../android/ui/common/StreamCallActivity.kt | 9 + 28 files changed, 1102 insertions(+), 60 deletions(-) create mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationUiState.kt create mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt create mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsLocation.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/MetricDescriptor.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/MetricThreshold.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationManager.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt create mode 100644 stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt create mode 100644 stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/SimpleBlurVideoFilter.kt diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt index a6289028654..6b986fb7da4 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt @@ -132,6 +132,7 @@ import io.getstream.video.android.ui.closedcaptions.ClosedCaptionsDefaults import io.getstream.video.android.ui.menu.SettingsMenu import io.getstream.video.android.ui.menu.VideoFilter import io.getstream.video.android.ui.menu.availableVideoFilters +import io.getstream.video.android.ui.moderation.ModerationWarningUiContainer import io.getstream.video.android.util.config.AppConfig import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay @@ -569,6 +570,9 @@ fun CallScreen( ) } }, + moderationWarningUi = { call -> + ModerationWarningUiContainer(call) + }, ) if (orientation == Configuration.ORIENTATION_LANDSCAPE) { StreamIconToggleButton( diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationUiState.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationUiState.kt new file mode 100644 index 00000000000..fd36effb467 --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationUiState.kt @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package io.getstream.video.android.ui.moderation + +sealed class ModerationUiState { + + data object Visible : ModerationUiState() + + data object NotVisible : ModerationUiState() + +// public fun TranscriptionSettingsResponse.ClosedCaptionMode.toClosedCaptionUiState(): ModerationUiState { +// return when (this) { +// is TranscriptionSettingsResponse.ClosedCaptionMode.Available, +// is TranscriptionSettingsResponse.ClosedCaptionMode.AutoOn, +// -> +// Available +// else -> +// UnAvailable +// } +// } +} diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt new file mode 100644 index 00000000000..b4f58c67b0b --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt @@ -0,0 +1,204 @@ +/* + * 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. + */ + +package io.getstream.video.android.ui.moderation + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.EaseInCubic +import androidx.compose.animation.core.EaseOutCubic +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.moderation.ModerationText +import io.getstream.video.android.core.moderation.ModerationWarningAnimationConfig +import kotlinx.coroutines.delay + +@Composable +internal fun ModerationWarningUiContainer( + call: Call, + config: ModerationThemeConfig = ModerationDefaults.config, +) { + if (LocalInspectionMode.current) { + Box( + modifier = Modifier + .fillMaxSize() + .offset(y = config.yOffset) + .padding(horizontal = config.horizontalMargin), + + contentAlignment = Alignment.BottomCenter, + ) { + ModerationWarningUiContentDemo() + } + } else { + val moderationWarning by call.state.moderationWarning.collectAsStateWithLifecycle() + // Track which warning was already shown + var lastShownId by rememberSaveable { mutableStateOf(null) } + + // Only show UI if we have a new warning + val currentWarning = moderationWarning?.takeIf { + val id = "${it.callCid}_${it.createdAt}" + if (id != lastShownId) { + lastShownId = id + true + } else { + false + } + } + + if (currentWarning != null) { + Box( + modifier = Modifier + .fillMaxSize() + .offset(y = config.yOffset) + .padding(horizontal = config.horizontalMargin), + contentAlignment = Alignment.BottomCenter, + ) { + val moderationText = ModerationText("Warning", currentWarning.message) + ModerationUi(config, moderationText) + } + } + } +} + +@Composable +internal fun ModerationUi(config: ModerationThemeConfig, moderationText: ModerationText) { + SlideInOutMessage(moderationText, ModerationWarningAnimationConfig()) +} + +@Composable +fun SlideInOutMessage( + moderationText: ModerationText, + moderationWarningAnimationConfig: ModerationWarningAnimationConfig, +) { + var visible by remember { mutableStateOf(false) } + + // Trigger visibility when composable enters composition + LaunchedEffect(Unit) { + visible = true + delay(moderationWarningAnimationConfig.displayTime) // visible for 3 seconds + visible = false + } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter, + ) { + AnimatedVisibility( + visible = visible, + enter = slideInVertically( + initialOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 500, easing = EaseOutCubic), + ) + fadeIn( + animationSpec = tween(durationMillis = 500), + ), + exit = slideOutVertically( + targetOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 500, easing = EaseInCubic), + ) + fadeOut( + animationSpec = tween(durationMillis = 500), + ), + ) { + ModerationWarningUiContent(moderationText) + } + } +} + +@Composable +internal fun ModerationWarningUiContent(moderationText: ModerationText) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + Color.White, + shape = RoundedCornerShape( + topStart = 8.dp, + bottomStart = 8.dp, + topEnd = 8.dp, + bottomEnd = 8.dp, + ), + ), + contentAlignment = Alignment.TopStart, + ) { + Row( + modifier = Modifier.height(IntrinsicSize.Min), + ) { + // Orange column on the left + Box( + modifier = Modifier + .width(8.dp) + .fillMaxHeight() + .background( + Color(0xFFFFA500), + shape = RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp), + ), + ) + + Spacer(modifier = Modifier.width(12.dp)) + + Column(Modifier.padding(vertical = 12.dp)) { + Text( + text = moderationText.title, + color = Color.Black, + fontSize = 16.sp, + ) + Text( + text = moderationText.message, + color = Color.Gray, + fontSize = 16.sp, + ) + } + } + } +} + +@Preview +@Composable +internal fun ModerationWarningUiContentDemo() { + ModerationWarningUiContent(ModerationText("Warning title", "Warning Message")) +} diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt new file mode 100644 index 00000000000..1136efcf390 --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt @@ -0,0 +1,104 @@ +/* + * 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. + */ + +package io.getstream.video.android.ui.moderation + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import io.getstream.video.android.compose.theme.StreamColors + +/** + * Provides default configurations for the Closed Captions UI. + * + * The [ModerationDefaults] object contains a predefined instance of [ModerationThemeConfig], + * which serves as the default styling and behavior configuration for the closed captions UI. + * Developers can use this default configuration or provide a custom one to override specific values. + */ + +public object ModerationDefaults { + /** + * The default configuration for closed captions, defining layout, styling, and behavior. + * + * - `yOffset`: Vertical offset for positioning the closed captions container. + * - `horizontalMargin`: Horizontal margin around the container. + * - `boxAlpha`: Opacity of the background box containing the captions. + * - `boxPadding`: Padding inside the background box. + * - `textColor`: Color used for the caption text. + * - `roundedCornerShape`: The corner radius of the background box. + */ + public val config: ModerationThemeConfig = ModerationThemeConfig( + yOffset = -100.dp, + horizontalMargin = 16.dp, + boxAlpha = 0.5f, + boxPadding = 12.dp, + textColor = Color.Black, + roundedCornerShape = RoundedCornerShape(16.dp), + ) + + @Composable + public fun streamModerationThemeConfig(): ModerationThemeConfig { + val colors = StreamColors.defaultColors() + return config.copy( + backgroundColor = colors.baseSheetPrimary, + textColor = colors.basePrimary, + ) + } +} + +/** + * Defines the configuration for Closed Captions UI, allowing customization of its layout, styling, and behavior. + * + * This configuration can be used to style the closed captions container and its contents. Developers can + * customize the appearance by overriding specific values as needed. + * + * @param yOffset Vertical offset for the closed captions container. Negative values move the container upwards. + * @param horizontalMargin Horizontal margin around the container. + * @param boxAlpha Background opacity of the closed captions container, where `0.0f` is fully transparent + * and `1.0f` is fully opaque. + * @param boxPadding Padding inside the background box of the closed captions container. + * @param backgroundColor Color used for rendering the background box of the closed captions container. + * @param textColor Color used for rendering the caption text. + * Must be less than or equal to [ClosedCaptionsConfig.maxCaptions] to ensure consistency. + * @param roundedCornerShape A shape used for the caption container. + * + * Example Usage: + * ``` + * val customConfig = ClosedCaptionsThemeConfig( + * yOffset = -100.dp, + * horizontalMargin = 20.dp, + * boxAlpha = 0.7f, + * boxPadding = 16.dp, + * backgroundColor = Color.Black, + * speakerColor = Color.Cyan, + * textColor = Color.Green, + * maxVisibleCaptions = 5, + * roundedCornerShape = RounderCornerShape(12.dp), + * ) + * ``` + */ +public data class ModerationThemeConfig( + val yOffset: Dp = -50.dp, + val horizontalMargin: Dp = 0.dp, + val boxAlpha: Float = 1f, + val boxPadding: Dp = 0.dp, + val backgroundColor: Color = Color.Black, + val textColor: Color = Color.White, + val roundedCornerShape: Shape? = null, +) 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 7371ff834fd..ec9ab7427b6 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -1653,6 +1653,31 @@ public final class io/getstream/android/video/generated/models/CallStateResponse public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/CallStatsLocation { + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Ljava/lang/Float;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 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/Float; + public final fun component6 ()Ljava/lang/Float; + public final fun component7 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/String;)Lio/getstream/android/video/generated/models/CallStatsLocation; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/CallStatsLocation;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Float;Ljava/lang/Float;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsLocation; + public fun equals (Ljava/lang/Object;)Z + public final fun getAccuracyRadiusMeters ()Ljava/lang/Integer; + public final fun getCity ()Ljava/lang/String; + public final fun getContinent ()Ljava/lang/String; + public final fun getCountry ()Ljava/lang/String; + public final fun getLatitude ()Ljava/lang/Float; + public final fun getLongitude ()Ljava/lang/Float; + public final fun getSubdivision ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/CallStatsParticipant { public fun (Ljava/lang/String;Ljava/util/List;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/util/List;)V public synthetic fun (Ljava/lang/String;Ljava/util/List;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -1691,26 +1716,44 @@ public final class io/getstream/android/video/generated/models/CallStatsParticip } public final class io/getstream/android/video/generated/models/CallStatsParticipantSession { - public fun (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;)V - public synthetic fun (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;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/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;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 component12 ()Ljava/lang/String; + public final fun component13 ()Ljava/lang/String; + public final fun component14 ()Lorg/threeten/bp/OffsetDateTime; + public final fun component15 ()Ljava/lang/String; + public final fun component16 ()Ljava/lang/String; + public final fun component17 ()Lio/getstream/android/video/generated/models/CallStatsLocation; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lio/getstream/android/video/generated/models/PublishedTrackFlags; - public final fun component4 ()Ljava/lang/Integer; - public final fun component5 ()Lorg/threeten/bp/OffsetDateTime; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Lorg/threeten/bp/OffsetDateTime; + 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/String; public final fun component8 ()Ljava/lang/String; - public final fun copy (ZLjava/lang/String;Lio/getstream/android/video/generated/models/PublishedTrackFlags;Ljava/lang/Integer;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;)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/Integer;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/CallStatsParticipantSession; + 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;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;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; public final fun getCqScore ()Ljava/lang/Integer; + public final fun getCurrentIp ()Ljava/lang/String; + 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 getLocation ()Lio/getstream/android/video/generated/models/CallStatsLocation; public final fun getPublishedTracks ()Lio/getstream/android/video/generated/models/PublishedTrackFlags; public final fun getPublisherType ()Ljava/lang/String; + public final fun getSdk ()Ljava/lang/String; + public final fun getSdkVersion ()Ljava/lang/String; public final fun getStartedAt ()Lorg/threeten/bp/OffsetDateTime; public final fun getUnifiedSessionId ()Ljava/lang/String; public final fun getUserSessionId ()Ljava/lang/String; + public final fun getWebrtcVersion ()Ljava/lang/String; public fun hashCode ()I public final fun isLive ()Z public fun toString ()Ljava/lang/String; @@ -3571,6 +3614,42 @@ public final class io/getstream/android/video/generated/models/MessageStatsRespo public fun toString ()Ljava/lang/String; } +public final class io/getstream/android/video/generated/models/MetricDescriptor { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (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 copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/android/video/generated/models/MetricDescriptor; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/MetricDescriptor;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/MetricDescriptor; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getLabel ()Ljava/lang/String; + public final fun getUnit ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/video/generated/models/MetricThreshold { + public fun (Ljava/lang/String;Ljava/lang/String;FLjava/lang/String;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;FLjava/lang/String;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 ()F + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/String;FLjava/lang/String;Ljava/lang/Integer;)Lio/getstream/android/video/generated/models/MetricThreshold; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/MetricThreshold;Ljava/lang/String;Ljava/lang/String;FLjava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/MetricThreshold; + public fun equals (Ljava/lang/Object;)Z + public final fun getLevel ()Ljava/lang/String; + public final fun getOperator ()Ljava/lang/String; + public final fun getValue ()F + public final fun getValueUnit ()Ljava/lang/String; + public final fun getWindowSeconds ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/android/video/generated/models/MuteUsersRequest { public fun ()V public fun (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;)V @@ -3945,14 +4024,20 @@ public final class io/getstream/android/video/generated/models/ParticipantReport public final class io/getstream/android/video/generated/models/ParticipantSeriesPublisherStats { public fun ()V - public fun (Ljava/util/Map;Ljava/util/Map;)V - public synthetic fun (Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/Map; + public fun (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/util/Map;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ParticipantSeriesPublisherStats; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ParticipantSeriesPublisherStats;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ParticipantSeriesPublisherStats; + public final fun component3 ()Ljava/util/Map; + public final fun component4 ()Ljava/util/Map; + public final fun component5 ()Ljava/util/Map; + public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ParticipantSeriesPublisherStats; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ParticipantSeriesPublisherStats;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ParticipantSeriesPublisherStats; public fun equals (Ljava/lang/Object;)Z public final fun getGlobal ()Ljava/util/Map; + public final fun getGlobalMeta ()Ljava/util/Map; + public final fun getGlobalMetricsOrder ()Ljava/util/List; + public final fun getGlobalThresholds ()Ljava/util/Map; public final fun getTracks ()Ljava/util/Map; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -3960,14 +4045,20 @@ public final class io/getstream/android/video/generated/models/ParticipantSeries public final class io/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats { public fun ()V - public fun (Ljava/util/List;Ljava/util/Map;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V + public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/util/List;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/util/Map; + public final fun component4 ()Ljava/util/Map; + public final fun component5 ()Ljava/util/Map; + public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats;Ljava/util/List;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats; public fun equals (Ljava/lang/Object;)Z public final fun getGlobal ()Ljava/util/Map; + public final fun getGlobalMeta ()Ljava/util/Map; + public final fun getGlobalMetricsOrder ()Ljava/util/List; + public final fun getGlobalThresholds ()Ljava/util/Map; public final fun getSubscriptions ()Ljava/util/List; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -4009,17 +4100,27 @@ public final class io/getstream/android/video/generated/models/ParticipantSeries } public final class io/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics { - public fun (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/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/List;Ljava/util/Map;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/List;Ljava/util/Map;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/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/util/List; + public final fun component7 ()Ljava/util/Map; + public final fun component8 ()Ljava/util/Map; + public final fun component9 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics; public fun equals (Ljava/lang/Object;)Z + public final fun getCodec ()Ljava/lang/String; public final fun getLabel ()Ljava/lang/String; public final fun getMetrics ()Ljava/util/Map; + public final fun getMetricsMeta ()Ljava/util/Map; + public final fun getMetricsOrder ()Ljava/util/List; + public final fun getRid ()Ljava/lang/String; + public final fun getThresholds ()Ljava/util/Map; public final fun getTrackId ()Ljava/lang/String; public final fun getTrackType ()Ljava/lang/String; public fun hashCode ()I @@ -4028,13 +4129,19 @@ public final class io/getstream/android/video/generated/models/ParticipantSeries public final class io/getstream/android/video/generated/models/ParticipantSeriesUserStats { public fun ()V - public fun (Ljava/util/Map;)V - public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/Map; - public final fun copy (Ljava/util/Map;)Lio/getstream/android/video/generated/models/ParticipantSeriesUserStats; - public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ParticipantSeriesUserStats;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ParticipantSeriesUserStats; + public fun (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/util/Map; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lio/getstream/android/video/generated/models/ParticipantSeriesUserStats; + public static synthetic fun copy$default (Lio/getstream/android/video/generated/models/ParticipantSeriesUserStats;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/android/video/generated/models/ParticipantSeriesUserStats; public fun equals (Ljava/lang/Object;)Z public final fun getMetrics ()Ljava/util/Map; + public final fun getMetricsMeta ()Ljava/util/Map; + public final fun getMetricsOrder ()Ljava/util/List; + public final fun getThresholds ()Ljava/util/Map; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -7334,6 +7441,8 @@ public final class io/getstream/video/android/core/CallState { public final fun getMe ()Lkotlinx/coroutines/flow/StateFlow; public final fun getMember (Ljava/lang/String;)Lio/getstream/video/android/core/MemberState; public final fun getMembers ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getModerationBlur ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getModerationWarning ()Lkotlinx/coroutines/flow/StateFlow; public final fun getOrCreateParticipant (Ljava/lang/String;Ljava/lang/String;ZLstream/video/sfu/models/ParticipantSource;)Lio/getstream/video/android/core/ParticipantState; public static synthetic fun getOrCreateParticipant$default (Lio/getstream/video/android/core/CallState;Ljava/lang/String;Ljava/lang/String;ZLstream/video/sfu/models/ParticipantSource;ILjava/lang/Object;)Lio/getstream/video/android/core/ParticipantState; public final fun getOwnCapabilities ()Lkotlinx/coroutines/flow/StateFlow; @@ -7368,6 +7477,7 @@ public final class io/getstream/video/android/core/CallState { public final fun markSpeakingAsMuted ()V public final fun pin (Ljava/lang/String;Ljava/lang/String;)V public final fun replaceParticipants (Ljava/util/List;)V + public final fun resetModeration ()V public final fun unpin (Ljava/lang/String;)V public final fun updateFromResponse (Lio/getstream/android/video/generated/models/CallResponse;)V public final fun updateFromResponse (Lio/getstream/android/video/generated/models/CallStateResponseFields;)V @@ -11661,6 +11771,67 @@ public final class io/getstream/video/android/core/model/VisibilityOnScreenState public static fun values ()[Lio/getstream/video/android/core/model/VisibilityOnScreenState; } +public final class io/getstream/video/android/core/moderation/CallModerationConstants { + public static final field DEFAULT_BLUR_AUTO_DISMISS_TIME_MS J + public static final field DEFAULT_MODERATION_DISPLAY_TIME_MS J + public static final field INSTANCE Lio/getstream/video/android/core/moderation/CallModerationConstants; + public static final field POLICY_VIOLATION Ljava/lang/String; +} + +public final class io/getstream/video/android/core/moderation/ModerationBlurConfig { + public fun ()V + public fun (JI)V + public synthetic fun (JIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()J + public final fun component2 ()I + public final fun copy (JI)Lio/getstream/video/android/core/moderation/ModerationBlurConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/moderation/ModerationBlurConfig;JIILjava/lang/Object;)Lio/getstream/video/android/core/moderation/ModerationBlurConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getBlurIntensity ()I + public final fun getVisibilityDurationMs ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/moderation/ModerationManager { + public fun ()V + public final fun getModerationBlur ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getModerationWarning ()Lkotlinx/coroutines/flow/StateFlow; + public final fun handleEvent (Lio/getstream/android/video/generated/models/VideoEvent;)V + public final fun resetModerationBlur ()V + public final fun resetModerationWarning ()V +} + +public final class io/getstream/video/android/core/moderation/ModerationText { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/video/android/core/moderation/ModerationText; + public static synthetic fun copy$default (Lio/getstream/video/android/core/moderation/ModerationText;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/video/android/core/moderation/ModerationText; + public fun equals (Ljava/lang/Object;)Z + public final fun getMessage ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/moderation/ModerationWarningAnimationConfig { + public fun ()V + public fun (JII)V + public synthetic fun (JIIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()J + public final fun component2 ()I + public final fun component3 ()I + public final fun copy (JII)Lio/getstream/video/android/core/moderation/ModerationWarningAnimationConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/moderation/ModerationWarningAnimationConfig;JIIILjava/lang/Object;)Lio/getstream/video/android/core/moderation/ModerationWarningAnimationConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getDisplayTime ()J + public final fun getSlideInDuration ()I + public final fun getSlideOutDuration ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public class io/getstream/video/android/core/notifications/DefaultNotificationHandler : io/getstream/android/push/permissions/NotificationPermissionHandler, io/getstream/video/android/core/notifications/NotificationHandler { public static final field Companion Lio/getstream/video/android/core/notifications/DefaultNotificationHandler$Companion; public fun (Landroid/app/Application;Lio/getstream/android/push/permissions/NotificationPermissionHandler;ZI)V diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallModerationBlurEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallModerationBlurEvent.kt index dddde332b6b..00fed1eb552 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallModerationBlurEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallModerationBlurEvent.kt @@ -35,7 +35,7 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * This event is sent when a moderation blur action is applied to a user's video stream */ data class CallModerationBlurEvent ( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallModerationWarningEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallModerationWarningEvent.kt index af51d10f73c..744173a55d1 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallModerationWarningEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallModerationWarningEvent.kt @@ -35,7 +35,7 @@ import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson /** - * + * This event is sent when a moderation warning is issued to a user */ data class CallModerationWarningEvent ( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsLocation.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsLocation.kt new file mode 100644 index 00000000000..7bc0fdf153b --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/CallStatsLocation.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 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 CallStatsLocation ( + @Json(name = "accuracy_radius_meters") + val accuracyRadiusMeters: kotlin.Int? = null, + + @Json(name = "city") + val city: kotlin.String? = null, + + @Json(name = "continent") + val continent: kotlin.String? = null, + + @Json(name = "country") + val country: kotlin.String? = null, + + @Json(name = "latitude") + val latitude: kotlin.Float? = null, + + @Json(name = "longitude") + val longitude: kotlin.Float? = null, + + @Json(name = "subdivision") + val subdivision: kotlin.String? = 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 0511059ed38..2e1db44842e 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 @@ -48,18 +48,45 @@ data class CallStatsParticipantSession ( @Json(name = "published_tracks") val publishedTracks: io.getstream.android.video.generated.models.PublishedTrackFlags, + @Json(name = "browser") + val browser: kotlin.String? = null, + + @Json(name = "browser_version") + val browserVersion: kotlin.String? = null, + @Json(name = "cq_score") val cqScore: kotlin.Int? = null, + @Json(name = "current_ip") + val currentIp: kotlin.String? = null, + + @Json(name = "current_sfu") + val currentSfu: kotlin.String? = null, + + @Json(name = "distance_to_sfu_kilometers") + val distanceToSfuKilometers: kotlin.Float? = null, + @Json(name = "ended_at") val endedAt: org.threeten.bp.OffsetDateTime? = null, @Json(name = "publisher_type") val publisherType: kotlin.String? = null, + @Json(name = "sdk") + val sdk: kotlin.String? = null, + + @Json(name = "sdk_version") + val sdkVersion: kotlin.String? = null, + @Json(name = "started_at") val startedAt: org.threeten.bp.OffsetDateTime? = null, @Json(name = "unified_session_id") - val unifiedSessionId: kotlin.String? = null + val unifiedSessionId: kotlin.String? = null, + + @Json(name = "webrtc_version") + val webrtcVersion: kotlin.String? = null, + + @Json(name = "location") + val location: io.getstream.android.video.generated.models.CallStatsLocation? = null ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/MetricDescriptor.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/MetricDescriptor.kt new file mode 100644 index 00000000000..ba114a81c0c --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/MetricDescriptor.kt @@ -0,0 +1,50 @@ +/* + * 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 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 MetricDescriptor ( + @Json(name = "label") + val label: kotlin.String, + + @Json(name = "description") + val description: kotlin.String? = null, + + @Json(name = "unit") + val unit: kotlin.String? = null +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/MetricThreshold.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/MetricThreshold.kt new file mode 100644 index 00000000000..6a60cea77ce --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/MetricThreshold.kt @@ -0,0 +1,56 @@ +/* + * 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 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 MetricThreshold ( + @Json(name = "level") + val level: kotlin.String, + + @Json(name = "operator") + val operator: kotlin.String, + + @Json(name = "value") + val value: kotlin.Float, + + @Json(name = "value_unit") + val valueUnit: kotlin.String? = null, + + @Json(name = "window_seconds") + val windowSeconds: kotlin.Int? = null +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesPublisherStats.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesPublisherStats.kt index 697a2b43e1b..0844b90167e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesPublisherStats.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesPublisherStats.kt @@ -39,9 +39,18 @@ import com.squareup.moshi.ToJson */ data class ParticipantSeriesPublisherStats ( + @Json(name = "global_metrics_order") + val globalMetricsOrder: kotlin.collections.List? = emptyList(), + @Json(name = "global") val global: kotlin.collections.Map>>? = emptyMap(), + @Json(name = "global_meta") + val globalMeta: kotlin.collections.Map? = emptyMap(), + + @Json(name = "global_thresholds") + val globalThresholds: kotlin.collections.Map>? = emptyMap(), + @Json(name = "tracks") val tracks: kotlin.collections.Map>? = emptyMap() ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats.kt index 671d845e960..c825f6179aa 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesSubscriberStats.kt @@ -39,9 +39,18 @@ import com.squareup.moshi.ToJson */ data class ParticipantSeriesSubscriberStats ( + @Json(name = "global_metrics_order") + val globalMetricsOrder: kotlin.collections.List? = emptyList(), + @Json(name = "subscriptions") val subscriptions: kotlin.collections.List? = emptyList(), @Json(name = "global") - val global: kotlin.collections.Map>>? = emptyMap() + val global: kotlin.collections.Map>>? = emptyMap(), + + @Json(name = "global_meta") + val globalMeta: kotlin.collections.Map? = emptyMap(), + + @Json(name = "global_thresholds") + val globalThresholds: kotlin.collections.Map>? = emptyMap() ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics.kt index 4270745694d..c95063be5cc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesTrackMetrics.kt @@ -42,12 +42,27 @@ data class ParticipantSeriesTrackMetrics ( @Json(name = "track_id") val trackId: kotlin.String, + @Json(name = "codec") + val codec: kotlin.String? = null, + @Json(name = "label") val label: kotlin.String? = null, + @Json(name = "rid") + val rid: kotlin.String? = null, + @Json(name = "track_type") val trackType: kotlin.String? = null, + @Json(name = "metrics_order") + val metricsOrder: kotlin.collections.List? = emptyList(), + @Json(name = "metrics") - val metrics: kotlin.collections.Map>>? = emptyMap() + val metrics: kotlin.collections.Map>>? = emptyMap(), + + @Json(name = "metrics_meta") + val metricsMeta: kotlin.collections.Map? = emptyMap(), + + @Json(name = "thresholds") + val thresholds: kotlin.collections.Map>? = emptyMap() ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesUserStats.kt b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesUserStats.kt index 0bac4fb1efe..1a276663eeb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesUserStats.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/android/video/generated/models/ParticipantSeriesUserStats.kt @@ -39,6 +39,15 @@ import com.squareup.moshi.ToJson */ data class ParticipantSeriesUserStats ( + @Json(name = "metrics_order") + val metricsOrder: kotlin.collections.List? = emptyList(), + @Json(name = "metrics") - val metrics: kotlin.collections.Map>>? = emptyMap() + val metrics: kotlin.collections.Map>>? = emptyMap(), + + @Json(name = "metrics_meta") + val metricsMeta: kotlin.collections.Map? = emptyMap(), + + @Json(name = "thresholds") + val thresholds: kotlin.collections.Map>? = emptyMap() ) 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 478d1c136f9..d879401c9eb 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 @@ -33,6 +33,8 @@ import io.getstream.android.video.generated.models.CallMemberAddedEvent import io.getstream.android.video.generated.models.CallMemberRemovedEvent import io.getstream.android.video.generated.models.CallMemberUpdatedEvent import io.getstream.android.video.generated.models.CallMemberUpdatedPermissionEvent +import io.getstream.android.video.generated.models.CallModerationBlurEvent +import io.getstream.android.video.generated.models.CallModerationWarningEvent import io.getstream.android.video.generated.models.CallParticipantResponse import io.getstream.android.video.generated.models.CallReactionEvent import io.getstream.android.video.generated.models.CallRecordingStartedEvent @@ -80,7 +82,6 @@ import io.getstream.log.taggedLogger import io.getstream.result.Result import io.getstream.video.android.core.call.RtcSession import io.getstream.video.android.core.closedcaptions.ClosedCaptionManager -import io.getstream.video.android.core.closedcaptions.ClosedCaptionsSettings import io.getstream.video.android.core.events.AudioLevelChangedEvent import io.getstream.video.android.core.events.CallEndedSfuEvent import io.getstream.video.android.core.events.ChangePublishQualityEvent @@ -106,6 +107,7 @@ import io.getstream.video.android.core.model.Reaction import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.core.model.ScreenSharingSession import io.getstream.video.android.core.model.VisibilityOnScreenState +import io.getstream.video.android.core.moderation.ModerationManager import io.getstream.video.android.core.notifications.IncomingNotificationData import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository import io.getstream.video.android.core.permission.PermissionRequest @@ -686,6 +688,10 @@ public class CallState( */ val ccMode: StateFlow = closedCaptionManager.ccMode + private val moderationManager = ModerationManager() + val moderationWarning: StateFlow = moderationManager.moderationWarning + val moderationBlur: StateFlow = moderationManager.moderationBlur + private val pendingParticipantsJoined = ConcurrentHashMap() /** @@ -1619,6 +1625,10 @@ public class CallState( fun updateNotification(notification: Notification) { atomicNotification.set(notification) } + + fun resetModeration() { + moderationManager.resetModerationBlur() + } } private fun MemberResponse.toMemberState(): MemberState { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt new file mode 100644 index 00000000000..ff98fa2fcac --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt @@ -0,0 +1,23 @@ +/* + * 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. + */ + +package io.getstream.video.android.core.moderation + +object CallModerationConstants { + const val POLICY_VIOLATION = "PolicyViolationModeration" + const val DEFAULT_MODERATION_DISPLAY_TIME_MS = 5_000L + const val DEFAULT_BLUR_AUTO_DISMISS_TIME_MS = 10_000L +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt new file mode 100644 index 00000000000..80392a726bd --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt @@ -0,0 +1,24 @@ +/* + * 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. + */ + +package io.getstream.video.android.core.moderation + +import io.getstream.video.android.core.moderation.CallModerationConstants.DEFAULT_BLUR_AUTO_DISMISS_TIME_MS + +data class ModerationBlurConfig( + val visibilityDurationMs: Long = DEFAULT_BLUR_AUTO_DISMISS_TIME_MS, + val blurIntensity: Int = 40, +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationManager.kt new file mode 100644 index 00000000000..8b15c54c453 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationManager.kt @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package io.getstream.video.android.core.moderation + +import io.getstream.android.video.generated.models.CallModerationBlurEvent +import io.getstream.android.video.generated.models.CallModerationWarningEvent +import io.getstream.android.video.generated.models.VideoEvent +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class ModerationManager { + + private val _moderationWarning: MutableStateFlow = + MutableStateFlow(null) + val moderationWarning: StateFlow = _moderationWarning.asStateFlow() + + private val _moderationBlur: MutableStateFlow = + MutableStateFlow(null) + val moderationBlur: StateFlow = _moderationBlur.asStateFlow() + + fun resetModerationWarning() { + _moderationWarning.value = null + } + + fun resetModerationBlur() { + _moderationBlur.value = null + } + + fun handleEvent(videoEvent: VideoEvent) { + when (videoEvent) { + is CallModerationWarningEvent -> { + _moderationWarning.value = videoEvent + } + is CallModerationBlurEvent -> { + _moderationBlur.value = videoEvent + } + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt new file mode 100644 index 00000000000..a70b0896a2c --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package io.getstream.video.android.core.moderation + +data class ModerationWarningAnimationConfig( + val displayTime: Long = CallModerationConstants.DEFAULT_MODERATION_DISPLAY_TIME_MS, + val slideInDuration: Int = 500, + val slideOutDuration: Int = 500, +) + +data class ModerationText(val title: String, val message: String) diff --git a/stream-video-android-filters-video/api/stream-video-android-filters-video.api b/stream-video-android-filters-video/api/stream-video-android-filters-video.api index 561ce407059..9b37a1170c3 100644 --- a/stream-video-android-filters-video/api/stream-video-android-filters-video.api +++ b/stream-video-android-filters-video/api/stream-video-android-filters-video.api @@ -1,11 +1,45 @@ -public final class io/getstream/video/android/filters/video/BlurIntensity : java/lang/Enum { - public static final field HEAVY Lio/getstream/video/android/filters/video/BlurIntensity; - public static final field LIGHT Lio/getstream/video/android/filters/video/BlurIntensity; - public static final field MEDIUM Lio/getstream/video/android/filters/video/BlurIntensity; - public static fun getEntries ()Lkotlin/enums/EnumEntries; +public abstract class io/getstream/video/android/filters/video/BlurIntensity { + public synthetic fun (ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getRadius ()I - public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/filters/video/BlurIntensity; - public static fun values ()[Lio/getstream/video/android/filters/video/BlurIntensity; +} + +public final class io/getstream/video/android/filters/video/BlurIntensity$CUSTOM : io/getstream/video/android/filters/video/BlurIntensity { + public fun (I)V + public final fun component1 ()I + public final fun copy (I)Lio/getstream/video/android/filters/video/BlurIntensity$CUSTOM; + public static synthetic fun copy$default (Lio/getstream/video/android/filters/video/BlurIntensity$CUSTOM;IILjava/lang/Object;)Lio/getstream/video/android/filters/video/BlurIntensity$CUSTOM; + public fun equals (Ljava/lang/Object;)Z + public final fun getCustomRadius ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/filters/video/BlurIntensity$HEAVY : io/getstream/video/android/filters/video/BlurIntensity { + public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$HEAVY; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/filters/video/BlurIntensity$LIGHT : io/getstream/video/android/filters/video/BlurIntensity { + public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$LIGHT; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/filters/video/BlurIntensity$MEDIUM : io/getstream/video/android/filters/video/BlurIntensity { + public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$MEDIUM; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/filters/video/BlurIntensity$ULTRA : io/getstream/video/android/filters/video/BlurIntensity { + public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$ULTRA; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter : io/getstream/video/android/core/call/video/BitmapVideoFilter { @@ -15,6 +49,13 @@ public final class io/getstream/video/android/filters/video/BlurredBackgroundVid public fun applyFilter (Landroid/graphics/Bitmap;)V } +public final class io/getstream/video/android/filters/video/SimpleBlurVideoFilter : io/getstream/video/android/core/call/video/BitmapVideoFilter { + public fun ()V + public fun (Lio/getstream/video/android/filters/video/BlurIntensity;)V + public synthetic fun (Lio/getstream/video/android/filters/video/BlurIntensity;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun applyFilter (Landroid/graphics/Bitmap;)V +} + public final class io/getstream/video/android/filters/video/VirtualBackgroundVideoFilter : io/getstream/video/android/core/call/video/BitmapVideoFilter { public fun (Landroid/content/Context;ID)V public synthetic fun (Landroid/content/Context;IDILkotlin/jvm/internal/DefaultConstructorMarker;)V diff --git a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt new file mode 100644 index 00000000000..ebdb55219cd --- /dev/null +++ b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package io.getstream.video.android.filters.video + +import androidx.annotation.Keep + +/** + * The intensity of the background blur effect. Used in [BlurredBackgroundVideoFilter]. + * The class names are in capital letter to maintain backward-compatibility + */ +@Keep +public sealed class BlurIntensity(public val radius: Int) { + public data object LIGHT : BlurIntensity(7) + public data object MEDIUM : BlurIntensity(11) + public data object HEAVY : BlurIntensity(16) + public data object ULTRA : BlurIntensity(40) + public data class CUSTOM(val customRadius: Int) : BlurIntensity(customRadius) +} diff --git a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter.kt b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter.kt index 4d969c7c92e..c0323d38304 100644 --- a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter.kt +++ b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter.kt @@ -85,14 +85,4 @@ public class BlurredBackgroundVideoFilter( } } -/** - * The intensity of the background blur effect. Used in [BlurredBackgroundVideoFilter]. - */ -@Keep -public enum class BlurIntensity(public val radius: Int) { - LIGHT(7), - MEDIUM(11), - HEAVY(16), -} - private const val DEFAULT_FOREGROUND_THRESHOLD: Double = 0.999 // 1 is max confidence that pixel is in the foreground diff --git a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/SimpleBlurVideoFilter.kt b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/SimpleBlurVideoFilter.kt new file mode 100644 index 00000000000..c64158e0282 --- /dev/null +++ b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/SimpleBlurVideoFilter.kt @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package io.getstream.video.android.filters.video + +import android.graphics.Bitmap +import android.graphics.Canvas +import androidx.annotation.Keep +import com.google.android.renderscript.Toolkit +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.call.video.BitmapVideoFilter + +@Keep +public class SimpleBlurVideoFilter( + private val blurIntensity: BlurIntensity = BlurIntensity.ULTRA, +) : BitmapVideoFilter() { + + private val logger by taggedLogger("SimpleBlurVideoFilter") + + override fun applyFilter(videoFrameBitmap: Bitmap) { + // Blur the entire frame + val timeBeforeBlur = System.currentTimeMillis() + val blurredBitmap = Toolkit.blur(videoFrameBitmap, blurIntensity.radius) + logger.d { "Time taken to blur image : ${System.currentTimeMillis() - timeBeforeBlur}" } + + // Copy the blurred result back to the original bitmap + val canvas = Canvas(videoFrameBitmap) + canvas.drawBitmap(blurredBitmap, 0f, 0f, null) + } +} diff --git a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api index a213bf82dd7..f466016f18c 100644 --- a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api +++ b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api @@ -1057,7 +1057,7 @@ public final class io/getstream/video/android/compose/ui/components/call/activec } public final class io/getstream/video/android/compose/ui/components/call/activecall/CallContentKt { - public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/core/pip/PictureInPictureConfiguration;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V + public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/core/pip/PictureInPictureConfiguration;Lkotlin/jvm/functions/Function3;ZLio/getstream/video/android/core/moderation/ModerationBlurConfig;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V } @@ -1074,25 +1074,27 @@ public final class io/getstream/video/android/compose/ui/components/call/activec public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/call/activecall/ComposableSingletons$CallContentKt; public static field lambda-1 Lkotlin/jvm/functions/Function6; public static field lambda-10 Lkotlin/jvm/functions/Function2; + public static field lambda-11 Lkotlin/jvm/functions/Function2; public static field lambda-2 Lkotlin/jvm/functions/Function3; public static field lambda-3 Lkotlin/jvm/functions/Function3; public static field lambda-4 Lkotlin/jvm/functions/Function3; - public static field lambda-5 Lkotlin/jvm/functions/Function6; - public static field lambda-6 Lkotlin/jvm/functions/Function3; + public static field lambda-5 Lkotlin/jvm/functions/Function3; + public static field lambda-6 Lkotlin/jvm/functions/Function6; public static field lambda-7 Lkotlin/jvm/functions/Function3; public static field lambda-8 Lkotlin/jvm/functions/Function3; - public static field lambda-9 Lkotlin/jvm/functions/Function2; + public static field lambda-9 Lkotlin/jvm/functions/Function3; public fun ()V public final fun getLambda-1$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; public final fun getLambda-10$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-11$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda-2$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-3$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-4$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-5$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; - public final fun getLambda-6$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-5$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-6$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; public final fun getLambda-7$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-8$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-9$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-9$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; } public final class io/getstream/video/android/compose/ui/components/call/activecall/internal/ComposableSingletons$InviteUsersDialogKt { diff --git a/stream-video-android-ui-compose/build.gradle.kts b/stream-video-android-ui-compose/build.gradle.kts index 52f8d78b45d..f1803fa9c32 100644 --- a/stream-video-android-ui-compose/build.gradle.kts +++ b/stream-video-android-ui-compose/build.gradle.kts @@ -77,4 +77,6 @@ dependencies { // preview compileOnly(project(":stream-video-android-previewdata")) testImplementation(project(":stream-video-android-previewdata")) + + implementation(project(":stream-video-android-filters-video")) } 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 cea9cfaf28a..7f7bebcd90d 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 @@ -35,10 +35,12 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -73,9 +75,13 @@ import io.getstream.video.android.compose.ui.components.video.VideoRenderer import io.getstream.video.android.core.Call import io.getstream.video.android.core.ParticipantState import io.getstream.video.android.core.call.state.CallAction +import io.getstream.video.android.core.moderation.ModerationBlurConfig import io.getstream.video.android.core.pip.PictureInPictureConfiguration +import io.getstream.video.android.filters.video.BlurIntensity +import io.getstream.video.android.filters.video.SimpleBlurVideoFilter import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall +import kotlinx.coroutines.delay /** * Represents the UI in an Active call that shows participants and their video, as well as some @@ -97,6 +103,7 @@ import io.getstream.video.android.mock.previewCall * @param pictureInPictureConfiguration User can provide Picture-In-Picture configuration. * @param pictureInPictureContent Content shown when the user enters Picture in Picture mode, if it's been enabled in the app. * @param closedCaptionUi You can pass your composable lambda here to render Closed Captions + * @param moderationWarningUi Todo Rahul */ @Composable public fun CallContent( @@ -153,12 +160,16 @@ public fun CallContent( PictureInPictureConfiguration(true), pictureInPictureContent: @Composable (Call) -> Unit = { DefaultPictureInPictureContent(it) }, enableDiagnostics: Boolean = false, + moderationBlurConfig: ModerationBlurConfig = ModerationBlurConfig(), closedCaptionUi: @Composable (Call) -> Unit = {}, + moderationWarningUi: @Composable (Call) -> Unit = {}, ) { val context = LocalContext.current val orientation = LocalConfiguration.current.orientation val isInPictureInPicture = rememberIsInPipMode() + + DefaultPermissionHandler(videoPermission = permissions) MediaPiPLifecycle(call = call, pictureInPictureConfiguration) @@ -234,11 +245,32 @@ public fun CallContent( } } closedCaptionUi(call) + ModerationVideoBlur(call, moderationBlurConfig) + moderationWarningUi(call) }, ) } } +@Composable +internal fun ModerationVideoBlur(call: Call, moderationBlurConfig: ModerationBlurConfig) { + val moderationBlur by call.state.moderationBlur.collectAsStateWithLifecycle() + var isVideoBlur by rememberSaveable { mutableStateOf(false) } + if (moderationBlur != null) { + LaunchedEffect(Unit) { + if (!isVideoBlur) { + call.videoFilter = + SimpleBlurVideoFilter(blurIntensity = BlurIntensity.CUSTOM(moderationBlurConfig.blurIntensity)) + isVideoBlur = true + delay(moderationBlurConfig.visibilityDurationMs) + call.videoFilter = null + call.state.resetModeration() + isVideoBlur = false + } + } + } +} + @Deprecated("Use CallContent with pictureInPictureConfiguration argument") @Composable public fun CallContent( @@ -300,7 +332,7 @@ public fun CallContent( call, modifier, layout, permissions, onBackPressed, onCallAction, appBarContent, style, videoRenderer, floatingVideoRenderer, videoContent, videoOverlayContent, controlsContent, PictureInPictureConfiguration(enableInPictureInPicture), - pictureInPictureContent, enableDiagnostics, closedCaptionUi, + pictureInPictureContent, enableDiagnostics, ModerationBlurConfig(), closedCaptionUi, ) } 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 1f0e8a3e335..781cec8d282 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 @@ -58,6 +58,7 @@ import io.getstream.video.android.core.call.state.ToggleSpeakerphone import io.getstream.video.android.core.events.CallEndedSfuEvent import io.getstream.video.android.core.events.ParticipantLeftEvent import io.getstream.video.android.core.model.RejectReason +import io.getstream.video.android.core.moderation.CallModerationConstants import io.getstream.video.android.core.notifications.NotificationHandler import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallController import io.getstream.video.android.model.StreamCallId @@ -1012,6 +1013,14 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper public open fun onCallEvent(call: Call, event: VideoEvent) { when (event) { is CallEndedEvent, is CallEndedSfuEvent, is CallSessionEndedEvent, is LocalCallMissedEvent -> { + when (event) { + is CallEndedEvent -> { + if (event.reason == CallModerationConstants.POLICY_VIOLATION) { + // TODO Rahul + } + } + else -> {} + } // In any case finish the activity, the call is done for leave(call, onSuccess = onSuccessFinish, onError = onErrorFinish) } From 0eed60358bd19d0ca6a01fc173ceb9ebf0fbf62f Mon Sep 17 00:00:00 2001 From: rahullohra Date: Fri, 7 Nov 2025 14:30:14 +0530 Subject: [PATCH 02/47] feat: add video moderation --- .../compose/ui/components/call/activecall/CallContent.kt | 2 -- 1 file changed, 2 deletions(-) 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 7f7bebcd90d..d89194664a2 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 @@ -168,8 +168,6 @@ public fun CallContent( val orientation = LocalConfiguration.current.orientation val isInPictureInPicture = rememberIsInPipMode() - - DefaultPermissionHandler(videoPermission = permissions) MediaPiPLifecycle(call = call, pictureInPictureConfiguration) From ca00cec2d7464e2404ee30b7e11145d97e5ca33b Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 11 Nov 2025 17:48:20 +0530 Subject: [PATCH 03/47] feat: add video moderation --- .../kotlin/io/getstream/video/android/App.kt | 40 ++++++++ .../data/model/PolicyViolationUiData.kt | 19 ++++ .../io/getstream/video/android/ui/Dialog.kt | 94 +++++++++++++++++++ .../video/android/ui/join/CallJoinScreen.kt | 9 ++ .../ModerationWarningUiContainer.kt | 66 ++++++------- .../android/ui/moderation/ModerationsTheme.kt | 74 +++------------ .../video/android/util/config/AppConfig.kt | 6 ++ demo-app/src/main/res/values/strings.xml | 1 + .../api/stream-video-android-core.api | 14 +-- .../io/getstream/video/android/core/Call.kt | 2 + .../getstream/video/android/core/CallState.kt | 28 +++++- .../moderation/CallModerationConstants.kt | 1 + .../core/moderation/ModerationBlurConfig.kt | 1 - .../core/moderation/ModerationManager.kt | 54 ----------- .../android/filters/video/BlurIntensity.kt | 3 +- .../components/call/activecall/CallContent.kt | 11 ++- .../src/main/res/values/strings.xml | 3 + 17 files changed, 260 insertions(+), 166 deletions(-) create mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/data/model/PolicyViolationUiData.kt create mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/Dialog.kt delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationManager.kt 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 25bb70f165c..bf5d70b9b70 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 @@ -19,9 +19,22 @@ package io.getstream.video.android import android.app.Application import android.content.Context import dagger.hilt.android.HiltAndroidApp +import io.getstream.android.video.generated.models.CallEndedEvent +import io.getstream.video.android.compose.R +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.moderation.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.util.StreamVideoInitHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @HiltAndroidApp @@ -52,6 +65,33 @@ class App : Application() { useRandomUserAsFallback = false, ) } + + observePolicyViolation() + } + + internal var policyViolationUiData: MutableStateFlow = + MutableStateFlow(null) + + private fun observePolicyViolation() { + CoroutineScope(Dispatchers.Default).launch { + StreamVideo.instanceState + .flatMapLatest { instance -> + instance?.state?.activeCall ?: flowOf(null) + }.filterNotNull() + .collectLatest { call -> + call.events.collectLatest { event -> + if (event is CallEndedEvent) { + if (event.reason == CallModerationConstants.POLICY_VIOLATION) { + policyViolationUiData.value = PolicyViolationUiData( + getString(R.string.stream_default_policy_violation_title), + getString(R.string.stream_default_policy_violation_message), + getString(R.string.stream_default_policy_violation_action_button), + ) + } + } + } + } + } } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/model/PolicyViolationUiData.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/model/PolicyViolationUiData.kt new file mode 100644 index 00000000000..5dfaa437e52 --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/model/PolicyViolationUiData.kt @@ -0,0 +1,19 @@ +/* + * 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. + */ + +package io.getstream.video.android.data.model + +internal data class PolicyViolationUiData(val title: String, val message: String, val actionButtonText: String) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/Dialog.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/Dialog.kt new file mode 100644 index 00000000000..21a776a4c2a --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/Dialog.kt @@ -0,0 +1,94 @@ +/* + * 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. + */ + +package io.getstream.video.android.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import io.getstream.video.android.compose.theme.VideoTheme +import io.getstream.video.android.compose.ui.components.base.StreamButton + +@Composable +fun SingleButtonDialog( + title: String, + message: String, + buttonText: String, + onButtonClick: () -> Unit, +) { + Dialog( + onDismissRequest = { /* No-op → prevents outside dismiss */ }, + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color.White, shape = RoundedCornerShape(12.dp)) + .padding(24.dp), + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + color = VideoTheme.colors.baseSheetPrimary, + fontWeight = FontWeight.Bold, + text = title, + fontSize = 18.sp, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = message, + fontSize = 14.sp, + color = Color.Gray, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(24.dp)) + StreamButton( + style = VideoTheme.styles.buttonStyles.secondaryButtonStyle(), + text = buttonText, + onClick = onButtonClick, + ) + } + } + } +} + +@Preview +@Composable +fun SingleButtonDialogDemo() { + VideoTheme { + SingleButtonDialog( + "Policy Violation Detected", + "We have to end your call as we have detected policy violation", + "OK", + ) { } + } +} 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 15e980460e3..b0d90051b96 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 @@ -90,6 +90,7 @@ import androidx.compose.ui.window.PopupPositionProvider import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.getstream.video.android.R +import io.getstream.video.android.app import io.getstream.video.android.compose.theme.VideoTheme import io.getstream.video.android.compose.ui.components.avatar.UserAvatar import io.getstream.video.android.compose.ui.components.base.StreamButton @@ -101,6 +102,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.SingleButtonDialog import io.getstream.video.android.util.config.AppConfig import io.getstream.video.android.util.config.types.StreamEnvironment @@ -173,6 +175,13 @@ fun CallJoinScreen( navigateUpToLogin.invoke(callJoinViewModel.autoLogInAfterLogOut) } } + val appContext = LocalContext.current.applicationContext + val policyViolation by appContext.app.policyViolationUiData.collectAsStateWithLifecycle() + policyViolation?.let { + SingleButtonDialog(it.title, it.message, it.actionButtonText) { + appContext.app.policyViolationUiData.value = null + } + } } @Composable diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt index b4f58c67b0b..361943712da 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt @@ -29,7 +29,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -44,16 +43,18 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.getstream.video.android.R import io.getstream.video.android.core.Call import io.getstream.video.android.core.moderation.ModerationText import io.getstream.video.android.core.moderation.ModerationWarningAnimationConfig @@ -62,7 +63,9 @@ import kotlinx.coroutines.delay @Composable internal fun ModerationWarningUiContainer( call: Call, - config: ModerationThemeConfig = ModerationDefaults.config, + config: ModerationThemeConfig = ModerationDefaults.defaultTheme, + moderationWarningAnimationConfig: ModerationWarningAnimationConfig = + ModerationWarningAnimationConfig(), ) { if (LocalInspectionMode.current) { Box( @@ -77,21 +80,7 @@ internal fun ModerationWarningUiContainer( } } else { val moderationWarning by call.state.moderationWarning.collectAsStateWithLifecycle() - // Track which warning was already shown - var lastShownId by rememberSaveable { mutableStateOf(null) } - - // Only show UI if we have a new warning - val currentWarning = moderationWarning?.takeIf { - val id = "${it.callCid}_${it.createdAt}" - if (id != lastShownId) { - lastShownId = id - true - } else { - false - } - } - - if (currentWarning != null) { + moderationWarning?.let { Box( modifier = Modifier .fillMaxSize() @@ -99,22 +88,30 @@ internal fun ModerationWarningUiContainer( .padding(horizontal = config.horizontalMargin), contentAlignment = Alignment.BottomCenter, ) { - val moderationText = ModerationText("Warning", currentWarning.message) - ModerationUi(config, moderationText) + val moderationText = ModerationText( + LocalContext.current.getString(R.string.stream_moderation_warning_title), + it.message, + ) + ModerationUi(config, moderationWarningAnimationConfig, moderationText) } } } } @Composable -internal fun ModerationUi(config: ModerationThemeConfig, moderationText: ModerationText) { - SlideInOutMessage(moderationText, ModerationWarningAnimationConfig()) +internal fun ModerationUi( + moderationThemeConfig: ModerationThemeConfig, + moderationWarningAnimationConfig: ModerationWarningAnimationConfig, + moderationText: ModerationText, +) { + SlideInOutMessage(moderationThemeConfig, moderationWarningAnimationConfig, moderationText) } @Composable fun SlideInOutMessage( - moderationText: ModerationText, + moderationThemeConfig: ModerationThemeConfig, moderationWarningAnimationConfig: ModerationWarningAnimationConfig, + moderationText: ModerationText, ) { var visible by remember { mutableStateOf(false) } @@ -144,13 +141,16 @@ fun SlideInOutMessage( animationSpec = tween(durationMillis = 500), ), ) { - ModerationWarningUiContent(moderationText) + ModerationWarningUiContent(moderationThemeConfig, moderationText) } } } @Composable -internal fun ModerationWarningUiContent(moderationText: ModerationText) { +internal fun ModerationWarningUiContent( + moderationThemeConfig: ModerationThemeConfig, + moderationText: ModerationText, +) { Box( modifier = Modifier .fillMaxWidth() @@ -171,7 +171,7 @@ internal fun ModerationWarningUiContent(moderationText: ModerationText) { // Orange column on the left Box( modifier = Modifier - .width(8.dp) + .width(12.dp) .fillMaxHeight() .background( Color(0xFFFFA500), @@ -179,17 +179,16 @@ internal fun ModerationWarningUiContent(moderationText: ModerationText) { ), ) - Spacer(modifier = Modifier.width(12.dp)) - - Column(Modifier.padding(vertical = 12.dp)) { + Column(Modifier.padding(vertical = 12.dp, horizontal = 12.dp)) { Text( text = moderationText.title, - color = Color.Black, + fontWeight = FontWeight.Bold, + color = moderationThemeConfig.titleColor, fontSize = 16.sp, ) Text( text = moderationText.message, - color = Color.Gray, + color = moderationThemeConfig.messageColor, fontSize = 16.sp, ) } @@ -200,5 +199,8 @@ internal fun ModerationWarningUiContent(moderationText: ModerationText) { @Preview @Composable internal fun ModerationWarningUiContentDemo() { - ModerationWarningUiContent(ModerationText("Warning title", "Warning Message")) + ModerationWarningUiContent( + ModerationThemeConfig(), + ModerationText("Warning title", "Warning Message"), + ) } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt index 1136efcf390..23afbab752b 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt @@ -17,88 +17,38 @@ package io.getstream.video.android.ui.moderation import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import io.getstream.video.android.compose.theme.StreamColors /** - * Provides default configurations for the Closed Captions UI. + * Provides default configurations for the Moderation Warning UI. * * The [ModerationDefaults] object contains a predefined instance of [ModerationThemeConfig], - * which serves as the default styling and behavior configuration for the closed captions UI. - * Developers can use this default configuration or provide a custom one to override specific values. + * which serves as the default styling configuration for the Moderation Warning UI. */ public object ModerationDefaults { - /** - * The default configuration for closed captions, defining layout, styling, and behavior. - * - * - `yOffset`: Vertical offset for positioning the closed captions container. - * - `horizontalMargin`: Horizontal margin around the container. - * - `boxAlpha`: Opacity of the background box containing the captions. - * - `boxPadding`: Padding inside the background box. - * - `textColor`: Color used for the caption text. - * - `roundedCornerShape`: The corner radius of the background box. - */ - public val config: ModerationThemeConfig = ModerationThemeConfig( - yOffset = -100.dp, - horizontalMargin = 16.dp, - boxAlpha = 0.5f, - boxPadding = 12.dp, - textColor = Color.Black, - roundedCornerShape = RoundedCornerShape(16.dp), - ) - - @Composable - public fun streamModerationThemeConfig(): ModerationThemeConfig { - val colors = StreamColors.defaultColors() - return config.copy( - backgroundColor = colors.baseSheetPrimary, - textColor = colors.basePrimary, - ) - } + public val defaultTheme: ModerationThemeConfig = ModerationThemeConfig() } /** - * Defines the configuration for Closed Captions UI, allowing customization of its layout, styling, and behavior. - * - * This configuration can be used to style the closed captions container and its contents. Developers can - * customize the appearance by overriding specific values as needed. + * Defines the configuration for Moderation Warning UI, allowing customization of its layout & styling * * @param yOffset Vertical offset for the closed captions container. Negative values move the container upwards. * @param horizontalMargin Horizontal margin around the container. - * @param boxAlpha Background opacity of the closed captions container, where `0.0f` is fully transparent - * and `1.0f` is fully opaque. - * @param boxPadding Padding inside the background box of the closed captions container. * @param backgroundColor Color used for rendering the background box of the closed captions container. - * @param textColor Color used for rendering the caption text. - * Must be less than or equal to [ClosedCaptionsConfig.maxCaptions] to ensure consistency. + * @param titleColor Color used for rendering the caption text. + * @param messageColor Color used for rendering the caption text. * @param roundedCornerShape A shape used for the caption container. * - * Example Usage: - * ``` - * val customConfig = ClosedCaptionsThemeConfig( - * yOffset = -100.dp, - * horizontalMargin = 20.dp, - * boxAlpha = 0.7f, - * boxPadding = 16.dp, - * backgroundColor = Color.Black, - * speakerColor = Color.Cyan, - * textColor = Color.Green, - * maxVisibleCaptions = 5, - * roundedCornerShape = RounderCornerShape(12.dp), - * ) - * ``` */ public data class ModerationThemeConfig( - val yOffset: Dp = -50.dp, - val horizontalMargin: Dp = 0.dp, - val boxAlpha: Float = 1f, - val boxPadding: Dp = 0.dp, - val backgroundColor: Color = Color.Black, - val textColor: Color = Color.White, - val roundedCornerShape: Shape? = null, + val yOffset: Dp = -100.dp, + val horizontalMargin: Dp = 16.dp, + val backgroundColor: Color = Color.White, + val titleColor: Color = Color.Black, + val messageColor: Color = Color.Gray, + val roundedCornerShape: Shape? = RoundedCornerShape(16.dp), ) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt index b6cdf4d0d30..dc31f5484fa 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt @@ -74,6 +74,12 @@ object AppConfig { displayName = "Pronto Staging", sharelink = "https://pronto-staging.getstream.io/join/", ), + StreamEnvironment( + env = "video-moderation", + aliases = emptyList(), + displayName = "Video Moderation", + sharelink = "https://pronto.getstream.io/join/", + ), ) val currentEnvironment = MutableStateFlow(availableEnvironments.default(BuildConfig.FLAVOR)) diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index d0edf9b6dbe..1cd42e73aef 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -56,6 +56,7 @@ Please consider installing the update.\nIt contains important features or bug fixes. App update failed. Try again later You are offline. Check your internet connection. + Warning %s is typing %s and %d more are typing 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 ec9ab7427b6..e3d02c9cbe1 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -11773,6 +11773,7 @@ public final class io/getstream/video/android/core/model/VisibilityOnScreenState public final class io/getstream/video/android/core/moderation/CallModerationConstants { public static final field DEFAULT_BLUR_AUTO_DISMISS_TIME_MS J + public static final field DEFAULT_MODERATION_AUTO_DISMISS_TIME_MS J public static final field DEFAULT_MODERATION_DISPLAY_TIME_MS J public static final field INSTANCE Lio/getstream/video/android/core/moderation/CallModerationConstants; public static final field POLICY_VIOLATION Ljava/lang/String; @@ -11780,23 +11781,22 @@ public final class io/getstream/video/android/core/moderation/CallModerationCons public final class io/getstream/video/android/core/moderation/ModerationBlurConfig { public fun ()V - public fun (JI)V - public synthetic fun (JIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (J)V + public synthetic fun (JILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J - public final fun component2 ()I - public final fun copy (JI)Lio/getstream/video/android/core/moderation/ModerationBlurConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/moderation/ModerationBlurConfig;JIILjava/lang/Object;)Lio/getstream/video/android/core/moderation/ModerationBlurConfig; + public final fun copy (J)Lio/getstream/video/android/core/moderation/ModerationBlurConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/moderation/ModerationBlurConfig;JILjava/lang/Object;)Lio/getstream/video/android/core/moderation/ModerationBlurConfig; public fun equals (Ljava/lang/Object;)Z - public final fun getBlurIntensity ()I public final fun getVisibilityDurationMs ()J public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class io/getstream/video/android/core/moderation/ModerationManager { - public fun ()V + public fun (Lkotlinx/coroutines/CoroutineScope;)V public final fun getModerationBlur ()Lkotlinx/coroutines/flow/StateFlow; public final fun getModerationWarning ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getScope ()Lkotlinx/coroutines/CoroutineScope; public final fun handleEvent (Lio/getstream/android/video/generated/models/VideoEvent;)V public final fun resetModerationBlur ()V public final fun resetModerationWarning ()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 9e91764aee6..833d8b2c285 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 @@ -95,6 +95,7 @@ import io.getstream.webrtc.android.ui.VideoTextureViewRenderer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -1345,6 +1346,7 @@ public class Call( supervisorJob.children.forEach { it.join() } supervisorJob.cancel() } + scope.cancel() } suspend fun ring(): Result { 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 d879401c9eb..0a72ddbb181 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 @@ -107,7 +107,7 @@ import io.getstream.video.android.core.model.Reaction import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.core.model.ScreenSharingSession import io.getstream.video.android.core.model.VisibilityOnScreenState -import io.getstream.video.android.core.moderation.ModerationManager +import io.getstream.video.android.core.moderation.CallModerationConstants import io.getstream.video.android.core.notifications.IncomingNotificationData import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository import io.getstream.video.android.core.permission.PermissionRequest @@ -688,9 +688,13 @@ public class CallState( */ val ccMode: StateFlow = closedCaptionManager.ccMode - private val moderationManager = ModerationManager() - val moderationWarning: StateFlow = moderationManager.moderationWarning - val moderationBlur: StateFlow = moderationManager.moderationBlur + private val _moderationWarning: MutableStateFlow = + MutableStateFlow(null) + val moderationWarning: StateFlow = _moderationWarning.asStateFlow() + + private val _moderationBlur: MutableStateFlow = + MutableStateFlow(null) + val moderationBlur: StateFlow = _moderationBlur.asStateFlow() private val pendingParticipantsJoined = ConcurrentHashMap() @@ -1131,6 +1135,20 @@ public class CallState( is ClosedCaptionEvent, is CallClosedCaptionsStoppedEvent, -> closedCaptionManager.handleEvent(event) + is CallModerationWarningEvent -> { + _moderationWarning.value = event + scope.launch { + delay(CallModerationConstants.DEFAULT_MODERATION_AUTO_DISMISS_TIME_MS) + _moderationWarning.value = null + } + } + is CallModerationBlurEvent -> { + _moderationBlur.value = event + scope.launch { + delay(CallModerationConstants.DEFAULT_BLUR_AUTO_DISMISS_TIME_MS) + _moderationWarning.value = null + } + } } } @@ -1627,7 +1645,7 @@ public class CallState( } fun resetModeration() { - moderationManager.resetModerationBlur() + _moderationBlur.value = null } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt index ff98fa2fcac..f90f4a6cae8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt @@ -19,5 +19,6 @@ package io.getstream.video.android.core.moderation object CallModerationConstants { const val POLICY_VIOLATION = "PolicyViolationModeration" const val DEFAULT_MODERATION_DISPLAY_TIME_MS = 5_000L + const val DEFAULT_MODERATION_AUTO_DISMISS_TIME_MS = DEFAULT_MODERATION_DISPLAY_TIME_MS + 3_000L const val DEFAULT_BLUR_AUTO_DISMISS_TIME_MS = 10_000L } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt index 80392a726bd..3688bf65c1c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt @@ -20,5 +20,4 @@ import io.getstream.video.android.core.moderation.CallModerationConstants.DEFAUL data class ModerationBlurConfig( val visibilityDurationMs: Long = DEFAULT_BLUR_AUTO_DISMISS_TIME_MS, - val blurIntensity: Int = 40, ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationManager.kt deleted file mode 100644 index 8b15c54c453..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationManager.kt +++ /dev/null @@ -1,54 +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. - */ - -package io.getstream.video.android.core.moderation - -import io.getstream.android.video.generated.models.CallModerationBlurEvent -import io.getstream.android.video.generated.models.CallModerationWarningEvent -import io.getstream.android.video.generated.models.VideoEvent -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow - -class ModerationManager { - - private val _moderationWarning: MutableStateFlow = - MutableStateFlow(null) - val moderationWarning: StateFlow = _moderationWarning.asStateFlow() - - private val _moderationBlur: MutableStateFlow = - MutableStateFlow(null) - val moderationBlur: StateFlow = _moderationBlur.asStateFlow() - - fun resetModerationWarning() { - _moderationWarning.value = null - } - - fun resetModerationBlur() { - _moderationBlur.value = null - } - - fun handleEvent(videoEvent: VideoEvent) { - when (videoEvent) { - is CallModerationWarningEvent -> { - _moderationWarning.value = videoEvent - } - is CallModerationBlurEvent -> { - _moderationBlur.value = videoEvent - } - } - } -} diff --git a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt index ebdb55219cd..ca0ff5a7e20 100644 --- a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt +++ b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt @@ -27,6 +27,5 @@ public sealed class BlurIntensity(public val radius: Int) { public data object LIGHT : BlurIntensity(7) public data object MEDIUM : BlurIntensity(11) public data object HEAVY : BlurIntensity(16) - public data object ULTRA : BlurIntensity(40) - public data class CUSTOM(val customRadius: Int) : BlurIntensity(customRadius) + public data object ULTRA : BlurIntensity(25) } 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 d89194664a2..c2ef125a14d 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 @@ -175,7 +175,11 @@ public fun CallContent( BackHandler { if (pictureInPictureConfiguration.enable) { try { - enterPictureInPicture(context = context, call = call, pictureInPictureConfiguration) + enterPictureInPicture( + context = context, + call = call, + pictureInPictureConfiguration, + ) } catch (e: Exception) { StreamLog.e(tag = "CallContent") { e.stackTraceToString() } call.leave() @@ -228,7 +232,8 @@ public fun CallContent( }, ) { BoxWithConstraints { - val contentSize = IntSize(constraints.maxWidth, constraints.maxHeight) + val contentSize = + IntSize(constraints.maxWidth, constraints.maxHeight) CompositionLocalProvider(LocalVideoContentSize provides contentSize) { videoContent.invoke(this@Row, call) @@ -258,7 +263,7 @@ internal fun ModerationVideoBlur(call: Call, moderationBlurConfig: ModerationBlu LaunchedEffect(Unit) { if (!isVideoBlur) { call.videoFilter = - SimpleBlurVideoFilter(blurIntensity = BlurIntensity.CUSTOM(moderationBlurConfig.blurIntensity)) + SimpleBlurVideoFilter(blurIntensity = BlurIntensity.ULTRA) isVideoBlur = true delay(moderationBlurConfig.visibilityDurationMs) call.videoFilter = null diff --git a/stream-video-android-ui-compose/src/main/res/values/strings.xml b/stream-video-android-ui-compose/src/main/res/values/strings.xml index 1cd0f3caf1a..e82db45c129 100644 --- a/stream-video-android-ui-compose/src/main/res/values/strings.xml +++ b/stream-video-android-ui-compose/src/main/res/values/strings.xml @@ -20,4 +20,7 @@ Please grant the necessary permissions. Settings Not now + Policy Violated + We have to end your call as we have detected policy violation + Ok From 4ac180ec204a0f788929fc3b4273b757b171aa4d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 11 Nov 2025 17:49:06 +0530 Subject: [PATCH 04/47] feat: add video moderation --- .../api/stream-video-android-core.api | 10 ---------- .../api/stream-video-android-filters-video.api | 11 ----------- 2 files changed, 21 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 e3d02c9cbe1..2fde61dd335 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -11792,16 +11792,6 @@ public final class io/getstream/video/android/core/moderation/ModerationBlurConf public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/moderation/ModerationManager { - public fun (Lkotlinx/coroutines/CoroutineScope;)V - public final fun getModerationBlur ()Lkotlinx/coroutines/flow/StateFlow; - public final fun getModerationWarning ()Lkotlinx/coroutines/flow/StateFlow; - public final fun getScope ()Lkotlinx/coroutines/CoroutineScope; - public final fun handleEvent (Lio/getstream/android/video/generated/models/VideoEvent;)V - public final fun resetModerationBlur ()V - public final fun resetModerationWarning ()V -} - public final class io/getstream/video/android/core/moderation/ModerationText { public fun (Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; diff --git a/stream-video-android-filters-video/api/stream-video-android-filters-video.api b/stream-video-android-filters-video/api/stream-video-android-filters-video.api index 9b37a1170c3..550e3a41fe2 100644 --- a/stream-video-android-filters-video/api/stream-video-android-filters-video.api +++ b/stream-video-android-filters-video/api/stream-video-android-filters-video.api @@ -3,17 +3,6 @@ public abstract class io/getstream/video/android/filters/video/BlurIntensity { public final fun getRadius ()I } -public final class io/getstream/video/android/filters/video/BlurIntensity$CUSTOM : io/getstream/video/android/filters/video/BlurIntensity { - public fun (I)V - public final fun component1 ()I - public final fun copy (I)Lio/getstream/video/android/filters/video/BlurIntensity$CUSTOM; - public static synthetic fun copy$default (Lio/getstream/video/android/filters/video/BlurIntensity$CUSTOM;IILjava/lang/Object;)Lio/getstream/video/android/filters/video/BlurIntensity$CUSTOM; - public fun equals (Ljava/lang/Object;)Z - public final fun getCustomRadius ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class io/getstream/video/android/filters/video/BlurIntensity$HEAVY : io/getstream/video/android/filters/video/BlurIntensity { public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$HEAVY; public fun equals (Ljava/lang/Object;)Z From ae13786b4e1b691cce44f5bb36944279fa7f8034 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 11 Nov 2025 18:26:10 +0530 Subject: [PATCH 05/47] Make ModerationWarningUiContainer as public api --- .../video/android/ui/call/CallScreen.kt | 2 +- .../ui/moderation/ModerationUiState.kt | 35 ------------------ demo-app/src/main/res/values/strings.xml | 1 - .../moderation/ModerationWarningConfig.kt | 17 ++++++++- .../api/stream-video-android-ui-compose.api | 37 +++++++++++++++++++ .../components/call/activecall/CallContent.kt | 2 +- .../ModerationWarningUiContainer.kt | 17 +++++---- .../call}/moderation/ModerationsTheme.kt | 4 +- .../src/main/res/values/strings.xml | 3 +- 9 files changed, 69 insertions(+), 49 deletions(-) delete mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationUiState.kt rename {demo-app/src/main/kotlin/io/getstream/video/android/ui => stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call}/moderation/ModerationWarningUiContainer.kt (93%) rename {demo-app/src/main/kotlin/io/getstream/video/android/ui => stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call}/moderation/ModerationsTheme.kt (92%) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt index 6b986fb7da4..72ccd12f535 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt @@ -105,6 +105,7 @@ import io.getstream.video.android.compose.ui.components.call.controls.actions.To import io.getstream.video.android.compose.ui.components.call.controls.actions.ToggleCameraAction import io.getstream.video.android.compose.ui.components.call.controls.actions.ToggleMicrophoneAction import io.getstream.video.android.compose.ui.components.call.controls.actions.ToggleSettingsAction +import io.getstream.video.android.compose.ui.components.call.moderation.ModerationWarningUiContainer import io.getstream.video.android.compose.ui.components.call.pinning.ParticipantAction import io.getstream.video.android.compose.ui.components.call.pinning.ParticipantActions import io.getstream.video.android.compose.ui.components.call.renderer.FloatingParticipantVideo @@ -132,7 +133,6 @@ import io.getstream.video.android.ui.closedcaptions.ClosedCaptionsDefaults import io.getstream.video.android.ui.menu.SettingsMenu import io.getstream.video.android.ui.menu.VideoFilter import io.getstream.video.android.ui.menu.availableVideoFilters -import io.getstream.video.android.ui.moderation.ModerationWarningUiContainer import io.getstream.video.android.util.config.AppConfig import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationUiState.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationUiState.kt deleted file mode 100644 index fd36effb467..00000000000 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationUiState.kt +++ /dev/null @@ -1,35 +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. - */ - -package io.getstream.video.android.ui.moderation - -sealed class ModerationUiState { - - data object Visible : ModerationUiState() - - data object NotVisible : ModerationUiState() - -// public fun TranscriptionSettingsResponse.ClosedCaptionMode.toClosedCaptionUiState(): ModerationUiState { -// return when (this) { -// is TranscriptionSettingsResponse.ClosedCaptionMode.Available, -// is TranscriptionSettingsResponse.ClosedCaptionMode.AutoOn, -// -> -// Available -// else -> -// UnAvailable -// } -// } -} diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index 1cd42e73aef..d0edf9b6dbe 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -56,7 +56,6 @@ Please consider installing the update.\nIt contains important features or bug fixes. App update failed. Try again later You are offline. Check your internet connection. - Warning %s is typing %s and %d more are typing diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt index a70b0896a2c..8db29dd8a4d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt @@ -16,10 +16,25 @@ package io.getstream.video.android.core.moderation +/** + * Configuration for the animation and visibility behavior of the Moderation Warning UI. + * + * @param displayTime The duration (in milliseconds) for which the moderation warning UI remains visible. + * @param slideInDuration The duration (in milliseconds) of the slide-in animation when the warning UI appears. + * @param slideOutDuration The duration (in milliseconds) of the slide-out animation when the warning UI disappears. + * + * See [io.getstream.video.android.compose.ui.components.call.moderation.ModerationUi] for implementation details. + */ data class ModerationWarningAnimationConfig( val displayTime: Long = CallModerationConstants.DEFAULT_MODERATION_DISPLAY_TIME_MS, val slideInDuration: Int = 500, - val slideOutDuration: Int = 500, + val slideOutDuration: Int = slideInDuration, ) +/** + * Defines the textual content displayed in the Moderation Warning UI. + * + * @param title The title text shown at the top of the moderation warning. + * @param message The message text displayed below the title, providing additional context. + */ data class ModerationText(val title: String, val message: String) diff --git a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api index f466016f18c..5cc2c296403 100644 --- a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api +++ b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api @@ -1296,6 +1296,43 @@ public final class io/getstream/video/android/compose/ui/components/call/lobby/L public static final fun buildDefaultLobbyControlActions (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function1;ZZLandroidx/compose/runtime/Composer;II)Ljava/util/List; } +public final class io/getstream/video/android/compose/ui/components/call/moderation/ModerationDefaults { + public static final field $stable I + public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationDefaults; + public final fun getDefaultTheme ()Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig; +} + +public final class io/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig { + public static final field $stable I + public synthetic fun (FFJJJJFLandroidx/compose/ui/graphics/Shape;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FFJJJJFLandroidx/compose/ui/graphics/Shape;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-D9Ej5fM ()F + public final fun component2-D9Ej5fM ()F + public final fun component3-0d7_KjU ()J + public final fun component4-0d7_KjU ()J + public final fun component5-0d7_KjU ()J + public final fun component6-0d7_KjU ()J + public final fun component7-D9Ej5fM ()F + public final fun component8 ()Landroidx/compose/ui/graphics/Shape; + public final fun copy-KylZ79E (FFJJJJFLandroidx/compose/ui/graphics/Shape;)Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig; + public static synthetic fun copy-KylZ79E$default (Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig;FFJJJJFLandroidx/compose/ui/graphics/Shape;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getBackgroundColor-0d7_KjU ()J + public final fun getHorizontalMargin-D9Ej5fM ()F + public final fun getMessageColor-0d7_KjU ()J + public final fun getRoundedCornerShape ()Landroidx/compose/ui/graphics/Shape; + public final fun getTitleColor-0d7_KjU ()J + public final fun getWarningStripColor-0d7_KjU ()J + public final fun getWarningStripWidth-D9Ej5fM ()F + public final fun getYOffset-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainerKt { + public static final fun ModerationWarningUiContainer (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig;Lio/getstream/video/android/core/moderation/ModerationWarningAnimationConfig;Lio/getstream/video/android/core/moderation/ModerationText;Landroidx/compose/runtime/Composer;II)V +} + public final class io/getstream/video/android/compose/ui/components/call/pinning/ComposableSingletons$ParticipantActionsKt { public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/call/pinning/ComposableSingletons$ParticipantActionsKt; public static field lambda-1 Lkotlin/jvm/functions/Function3; 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 c2ef125a14d..3703891b941 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 @@ -103,7 +103,7 @@ import kotlinx.coroutines.delay * @param pictureInPictureConfiguration User can provide Picture-In-Picture configuration. * @param pictureInPictureContent Content shown when the user enters Picture in Picture mode, if it's been enabled in the app. * @param closedCaptionUi You can pass your composable lambda here to render Closed Captions - * @param moderationWarningUi Todo Rahul + * @param moderationWarningUi Pass your composable lambda here to render Moderation Warning UI */ @Composable public fun CallContent( diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt similarity index 93% rename from demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt rename to stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt index 361943712da..0c2336a4dc0 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.ui.moderation +package io.getstream.video.android.compose.ui.components.call.moderation import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.EaseInCubic @@ -54,18 +54,19 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import io.getstream.video.android.R +import io.getstream.video.android.compose.R import io.getstream.video.android.core.Call import io.getstream.video.android.core.moderation.ModerationText import io.getstream.video.android.core.moderation.ModerationWarningAnimationConfig import kotlinx.coroutines.delay @Composable -internal fun ModerationWarningUiContainer( +public fun ModerationWarningUiContainer( call: Call, config: ModerationThemeConfig = ModerationDefaults.defaultTheme, moderationWarningAnimationConfig: ModerationWarningAnimationConfig = ModerationWarningAnimationConfig(), + moderationText: ModerationText? = null, ) { if (LocalInspectionMode.current) { Box( @@ -88,11 +89,11 @@ internal fun ModerationWarningUiContainer( .padding(horizontal = config.horizontalMargin), contentAlignment = Alignment.BottomCenter, ) { - val moderationText = ModerationText( + val finalModerationText = moderationText ?: ModerationText( LocalContext.current.getString(R.string.stream_moderation_warning_title), it.message, ) - ModerationUi(config, moderationWarningAnimationConfig, moderationText) + ModerationUi(config, moderationWarningAnimationConfig, finalModerationText) } } } @@ -108,7 +109,7 @@ internal fun ModerationUi( } @Composable -fun SlideInOutMessage( +private fun SlideInOutMessage( moderationThemeConfig: ModerationThemeConfig, moderationWarningAnimationConfig: ModerationWarningAnimationConfig, moderationText: ModerationText, @@ -171,10 +172,10 @@ internal fun ModerationWarningUiContent( // Orange column on the left Box( modifier = Modifier - .width(12.dp) + .width(moderationThemeConfig.warningStripWidth) .fillMaxHeight() .background( - Color(0xFFFFA500), + moderationThemeConfig.warningStripColor, shape = RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp), ), ) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt similarity index 92% rename from demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt rename to stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt index 23afbab752b..a7a032de5f4 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.ui.moderation +package io.getstream.video.android.compose.ui.components.call.moderation import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.graphics.Color @@ -50,5 +50,7 @@ public data class ModerationThemeConfig( val backgroundColor: Color = Color.White, val titleColor: Color = Color.Black, val messageColor: Color = Color.Gray, + val warningStripColor: Color = Color(0xFFFFA500), + val warningStripWidth: Dp = 12.dp, val roundedCornerShape: Shape? = RoundedCornerShape(16.dp), ) diff --git a/stream-video-android-ui-compose/src/main/res/values/strings.xml b/stream-video-android-ui-compose/src/main/res/values/strings.xml index e82db45c129..78cd5750e96 100644 --- a/stream-video-android-ui-compose/src/main/res/values/strings.xml +++ b/stream-video-android-ui-compose/src/main/res/values/strings.xml @@ -22,5 +22,6 @@ Not now Policy Violated We have to end your call as we have detected policy violation - Ok + OK + Warning From d97cf3085b866b45b470c7a82e1ff7e601cb80cd Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 00:59:05 +0530 Subject: [PATCH 06/47] remove dependency of stream-video-android-filters-video from android-ui-compose --- .../video/android/ui/call/CallScreen.kt | 22 ++++++++++ .../api/stream-video-android-ui-compose.api | 16 ++++---- .../build.gradle.kts | 2 - .../components/call/activecall/CallContent.kt | 41 ++++++------------- 4 files changed, 43 insertions(+), 38 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt index 72ccd12f535..8d1e92370ed 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt @@ -118,9 +118,12 @@ import io.getstream.video.android.core.Call import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.call.state.ChooseLayout import io.getstream.video.android.core.model.PreferredVideoResolution +import io.getstream.video.android.core.moderation.ModerationBlurConfig import io.getstream.video.android.core.pip.PictureInPictureConfiguration import io.getstream.video.android.core.utils.isEnabled +import io.getstream.video.android.filters.video.BlurIntensity import io.getstream.video.android.filters.video.BlurredBackgroundVideoFilter +import io.getstream.video.android.filters.video.SimpleBlurVideoFilter import io.getstream.video.android.filters.video.VirtualBackgroundVideoFilter import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall @@ -570,6 +573,9 @@ fun CallScreen( ) } }, + moderationBlurUi = { call -> + ModerationVideoBlur(call, ModerationBlurConfig()) + }, moderationWarningUi = { call -> ModerationWarningUiContainer(call) }, @@ -930,6 +936,22 @@ private fun BadNetworkLabel( } } +@Composable +internal fun ModerationVideoBlur(call: Call, moderationBlurConfig: ModerationBlurConfig) { + var isVideoBlur by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(Unit) { + if (!isVideoBlur) { + call.videoFilter = + SimpleBlurVideoFilter(blurIntensity = BlurIntensity.ULTRA) + isVideoBlur = true + delay(moderationBlurConfig.visibilityDurationMs) + call.videoFilter = null + call.state.resetModeration() + isVideoBlur = false + } + } +} + @Composable fun isTablet(): Boolean { val configuration = LocalConfiguration.current diff --git a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api index dc031416c1b..f062c396a7e 100644 --- a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api +++ b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api @@ -1057,7 +1057,7 @@ public final class io/getstream/video/android/compose/ui/components/call/activec } public final class io/getstream/video/android/compose/ui/components/call/activecall/CallContentKt { - public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/core/pip/PictureInPictureConfiguration;Lkotlin/jvm/functions/Function3;ZLio/getstream/video/android/core/moderation/ModerationBlurConfig;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V + public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/core/pip/PictureInPictureConfiguration;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V } @@ -1073,26 +1073,28 @@ public final class io/getstream/video/android/compose/ui/components/call/activec public final class io/getstream/video/android/compose/ui/components/call/activecall/ComposableSingletons$CallContentKt { public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/call/activecall/ComposableSingletons$CallContentKt; public static field lambda-1 Lkotlin/jvm/functions/Function6; - public static field lambda-10 Lkotlin/jvm/functions/Function2; + public static field lambda-10 Lkotlin/jvm/functions/Function3; public static field lambda-11 Lkotlin/jvm/functions/Function2; + public static field lambda-12 Lkotlin/jvm/functions/Function2; public static field lambda-2 Lkotlin/jvm/functions/Function3; public static field lambda-3 Lkotlin/jvm/functions/Function3; public static field lambda-4 Lkotlin/jvm/functions/Function3; public static field lambda-5 Lkotlin/jvm/functions/Function3; - public static field lambda-6 Lkotlin/jvm/functions/Function6; - public static field lambda-7 Lkotlin/jvm/functions/Function3; + public static field lambda-6 Lkotlin/jvm/functions/Function3; + public static field lambda-7 Lkotlin/jvm/functions/Function6; public static field lambda-8 Lkotlin/jvm/functions/Function3; public static field lambda-9 Lkotlin/jvm/functions/Function3; public fun ()V public final fun getLambda-1$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; - public final fun getLambda-10$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-10$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-11$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-12$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda-2$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-3$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-4$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-5$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-6$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; - public final fun getLambda-7$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-6$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-7$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; public final fun getLambda-8$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-9$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; } diff --git a/stream-video-android-ui-compose/build.gradle.kts b/stream-video-android-ui-compose/build.gradle.kts index c757e782673..769372e00fc 100644 --- a/stream-video-android-ui-compose/build.gradle.kts +++ b/stream-video-android-ui-compose/build.gradle.kts @@ -78,8 +78,6 @@ dependencies { compileOnly(project(":stream-video-android-previewdata")) testImplementation(project(":stream-video-android-previewdata")) - implementation(project(":stream-video-android-filters-video")) - // unit tests testImplementation(libs.junit) testImplementation(libs.truth) 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 3703891b941..6153b9127f1 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 @@ -35,12 +35,10 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -75,13 +73,9 @@ import io.getstream.video.android.compose.ui.components.video.VideoRenderer import io.getstream.video.android.core.Call import io.getstream.video.android.core.ParticipantState import io.getstream.video.android.core.call.state.CallAction -import io.getstream.video.android.core.moderation.ModerationBlurConfig import io.getstream.video.android.core.pip.PictureInPictureConfiguration -import io.getstream.video.android.filters.video.BlurIntensity -import io.getstream.video.android.filters.video.SimpleBlurVideoFilter import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall -import kotlinx.coroutines.delay /** * Represents the UI in an Active call that shows participants and their video, as well as some @@ -160,8 +154,8 @@ public fun CallContent( PictureInPictureConfiguration(true), pictureInPictureContent: @Composable (Call) -> Unit = { DefaultPictureInPictureContent(it) }, enableDiagnostics: Boolean = false, - moderationBlurConfig: ModerationBlurConfig = ModerationBlurConfig(), closedCaptionUi: @Composable (Call) -> Unit = {}, + moderationBlurUi: @Composable (Call) -> Unit = {}, moderationWarningUi: @Composable (Call) -> Unit = {}, ) { val context = LocalContext.current @@ -248,32 +242,21 @@ public fun CallContent( } } closedCaptionUi(call) - ModerationVideoBlur(call, moderationBlurConfig) - moderationWarningUi(call) + + val moderationBlur by call.state.moderationBlur.collectAsStateWithLifecycle() + moderationBlur?.let { + moderationBlurUi(call) + } + + val moderationWarning by call.state.moderationWarning.collectAsStateWithLifecycle() + moderationWarning?.let { + moderationWarningUi(call) + } }, ) } } -@Composable -internal fun ModerationVideoBlur(call: Call, moderationBlurConfig: ModerationBlurConfig) { - val moderationBlur by call.state.moderationBlur.collectAsStateWithLifecycle() - var isVideoBlur by rememberSaveable { mutableStateOf(false) } - if (moderationBlur != null) { - LaunchedEffect(Unit) { - if (!isVideoBlur) { - call.videoFilter = - SimpleBlurVideoFilter(blurIntensity = BlurIntensity.ULTRA) - isVideoBlur = true - delay(moderationBlurConfig.visibilityDurationMs) - call.videoFilter = null - call.state.resetModeration() - isVideoBlur = false - } - } - } -} - @Deprecated("Use CallContent with pictureInPictureConfiguration argument") @Composable public fun CallContent( @@ -335,7 +318,7 @@ public fun CallContent( call, modifier, layout, permissions, onBackPressed, onCallAction, appBarContent, style, videoRenderer, floatingVideoRenderer, videoContent, videoOverlayContent, controlsContent, PictureInPictureConfiguration(enableInPictureInPicture), - pictureInPictureContent, enableDiagnostics, ModerationBlurConfig(), closedCaptionUi, + pictureInPictureContent, enableDiagnostics, closedCaptionUi, ) } From 86807690907ba4fc62b7b9e88d2cbb0a35d17db1 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 02:34:47 +0530 Subject: [PATCH 07/47] remove all public-apis --- .../kotlin/io/getstream/video/android/App.kt | 2 +- .../video/android/ui/call/CallScreen.kt | 77 ++++++++++++++----- .../ui}/moderation/CallModerationConstants.kt | 2 +- .../ui}/moderation/ModerationBlurConfig.kt | 4 +- .../ui}/moderation/ModerationWarningConfig.kt | 4 +- .../ModerationWarningUiContainer.kt | 34 +++----- .../ui}/moderation/ModerationsTheme.kt | 6 +- .../api/stream-video-android-core.api | 54 ------------- .../getstream/video/android/core/CallState.kt | 30 +------- .../api/stream-video-android-ui-compose.api | 59 +++----------- .../components/call/activecall/CallContent.kt | 22 +----- .../android/ui/common/StreamCallActivity.kt | 9 --- 12 files changed, 90 insertions(+), 213 deletions(-) rename {stream-video-android-core/src/main/kotlin/io/getstream/video/android/core => demo-app/src/main/kotlin/io/getstream/video/android/ui}/moderation/CallModerationConstants.kt (94%) rename {stream-video-android-core/src/main/kotlin/io/getstream/video/android/core => demo-app/src/main/kotlin/io/getstream/video/android/ui}/moderation/ModerationBlurConfig.kt (82%) rename {stream-video-android-core/src/main/kotlin/io/getstream/video/android/core => demo-app/src/main/kotlin/io/getstream/video/android/ui}/moderation/ModerationWarningConfig.kt (90%) rename {stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call => demo-app/src/main/kotlin/io/getstream/video/android/ui}/moderation/ModerationWarningUiContainer.kt (84%) rename {stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call => demo-app/src/main/kotlin/io/getstream/video/android/ui}/moderation/ModerationsTheme.kt (93%) 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 bf5d70b9b70..c54050f327e 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 @@ -22,10 +22,10 @@ import dagger.hilt.android.HiltAndroidApp import io.getstream.android.video.generated.models.CallEndedEvent import io.getstream.video.android.compose.R import io.getstream.video.android.core.StreamVideo -import io.getstream.video.android.core.moderation.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.moderation.CallModerationConstants import io.getstream.video.android.util.StreamVideoInitHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt index 8d1e92370ed..98c19b74624 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt @@ -83,6 +83,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Popup import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.getstream.android.video.generated.models.CallModerationBlurEvent +import io.getstream.android.video.generated.models.CallModerationWarningEvent import io.getstream.android.video.generated.models.OwnCapability import io.getstream.android.video.generated.models.TranscriptionSettingsResponse import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState @@ -105,7 +107,6 @@ import io.getstream.video.android.compose.ui.components.call.controls.actions.To import io.getstream.video.android.compose.ui.components.call.controls.actions.ToggleCameraAction import io.getstream.video.android.compose.ui.components.call.controls.actions.ToggleMicrophoneAction import io.getstream.video.android.compose.ui.components.call.controls.actions.ToggleSettingsAction -import io.getstream.video.android.compose.ui.components.call.moderation.ModerationWarningUiContainer import io.getstream.video.android.compose.ui.components.call.pinning.ParticipantAction import io.getstream.video.android.compose.ui.components.call.pinning.ParticipantActions import io.getstream.video.android.compose.ui.components.call.renderer.FloatingParticipantVideo @@ -118,7 +119,6 @@ import io.getstream.video.android.core.Call import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.call.state.ChooseLayout import io.getstream.video.android.core.model.PreferredVideoResolution -import io.getstream.video.android.core.moderation.ModerationBlurConfig import io.getstream.video.android.core.pip.PictureInPictureConfiguration import io.getstream.video.android.core.utils.isEnabled import io.getstream.video.android.filters.video.BlurIntensity @@ -136,6 +136,12 @@ import io.getstream.video.android.ui.closedcaptions.ClosedCaptionsDefaults import io.getstream.video.android.ui.menu.SettingsMenu import io.getstream.video.android.ui.menu.VideoFilter import io.getstream.video.android.ui.menu.availableVideoFilters +import io.getstream.video.android.ui.moderation.CallModerationConstants +import io.getstream.video.android.ui.moderation.ModerationBlurConfig +import io.getstream.video.android.ui.moderation.ModerationDefaults +import io.getstream.video.android.ui.moderation.ModerationText +import io.getstream.video.android.ui.moderation.ModerationWarningAnimationConfig +import io.getstream.video.android.ui.moderation.ModerationWarningUiContainer import io.getstream.video.android.util.config.AppConfig import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay @@ -573,12 +579,6 @@ fun CallScreen( ) } }, - moderationBlurUi = { call -> - ModerationVideoBlur(call, ModerationBlurConfig()) - }, - moderationWarningUi = { call -> - ModerationWarningUiContainer(call) - }, ) if (orientation == Configuration.ORIENTATION_LANDSCAPE) { StreamIconToggleButton( @@ -601,6 +601,8 @@ fun CallScreen( } } } + ModerationBlurRootUi(call) + ModerationWarningRootUi(call) } }, updateUnreadCount = { unreadCount = it }, @@ -937,17 +939,54 @@ private fun BadNetworkLabel( } @Composable -internal fun ModerationVideoBlur(call: Call, moderationBlurConfig: ModerationBlurConfig) { - var isVideoBlur by rememberSaveable { mutableStateOf(false) } - LaunchedEffect(Unit) { - if (!isVideoBlur) { - call.videoFilter = - SimpleBlurVideoFilter(blurIntensity = BlurIntensity.ULTRA) - isVideoBlur = true - delay(moderationBlurConfig.visibilityDurationMs) - call.videoFilter = null - call.state.resetModeration() - isVideoBlur = false +private fun ModerationWarningRootUi(call: Call) { + var warningEvent by remember { mutableStateOf(null) } + val moderationThemeConfig = ModerationDefaults.defaultTheme + val moderationWarningAnimationConfig = ModerationWarningAnimationConfig() + + LaunchedEffect(call) { + call.events.collect { event -> + when (event) { + is CallModerationWarningEvent -> { + warningEvent = event + delay(CallModerationConstants.DEFAULT_MODERATION_AUTO_DISMISS_TIME_MS) + warningEvent = null // auto-dismiss after config duration + } + } + } + } + warningEvent?.let { event -> + val moderationText = ModerationText( + LocalContext.current.getString( + io.getstream.video.android.compose.R.string.stream_moderation_warning_title, + ), + event.message, + ) + ModerationWarningUiContainer( + call, + moderationThemeConfig, + moderationWarningAnimationConfig, + moderationText, + ) + } +} + +@Composable +private fun ModerationBlurRootUi(call: Call) { + var blurEvent by remember { mutableStateOf(null) } + val moderationBlurConfig = ModerationBlurConfig() + LaunchedEffect(call) { + call.events.collect { event -> + when (event) { + is CallModerationBlurEvent -> { + blurEvent = event + call.videoFilter = + SimpleBlurVideoFilter(blurIntensity = BlurIntensity.ULTRA) + delay(moderationBlurConfig.visibilityDurationMs) + blurEvent = null // auto-dismiss after config duration + call.videoFilter = null + } + } } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt similarity index 94% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt rename to demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt index f90f4a6cae8..45a3e6b476b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/CallModerationConstants.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.moderation +package io.getstream.video.android.ui.moderation object CallModerationConstants { const val POLICY_VIOLATION = "PolicyViolationModeration" diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt similarity index 82% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt rename to demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt index 3688bf65c1c..2a0c78a45cf 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationBlurConfig.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.getstream.video.android.core.moderation +package io.getstream.video.android.ui.moderation -import io.getstream.video.android.core.moderation.CallModerationConstants.DEFAULT_BLUR_AUTO_DISMISS_TIME_MS +import io.getstream.video.android.ui.moderation.CallModerationConstants.DEFAULT_BLUR_AUTO_DISMISS_TIME_MS data class ModerationBlurConfig( val visibilityDurationMs: Long = DEFAULT_BLUR_AUTO_DISMISS_TIME_MS, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt similarity index 90% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt rename to demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt index 8db29dd8a4d..10e8c8546de 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderation/ModerationWarningConfig.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.moderation +package io.getstream.video.android.ui.moderation /** * Configuration for the animation and visibility behavior of the Moderation Warning UI. @@ -23,7 +23,7 @@ package io.getstream.video.android.core.moderation * @param slideInDuration The duration (in milliseconds) of the slide-in animation when the warning UI appears. * @param slideOutDuration The duration (in milliseconds) of the slide-out animation when the warning UI disappears. * - * See [io.getstream.video.android.compose.ui.components.call.moderation.ModerationUi] for implementation details. + * See [io.getstream.video.android.ui.moderation.ModerationUi] for implementation details. */ data class ModerationWarningAnimationConfig( val displayTime: Long = CallModerationConstants.DEFAULT_MODERATION_DISPLAY_TIME_MS, diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt similarity index 84% rename from stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt rename to demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt index 0c2336a4dc0..93b90006503 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.compose.ui.components.call.moderation +package io.getstream.video.android.ui.moderation import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.EaseInCubic @@ -47,26 +47,21 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import io.getstream.video.android.compose.R import io.getstream.video.android.core.Call -import io.getstream.video.android.core.moderation.ModerationText -import io.getstream.video.android.core.moderation.ModerationWarningAnimationConfig import kotlinx.coroutines.delay @Composable -public fun ModerationWarningUiContainer( +internal fun ModerationWarningUiContainer( call: Call, config: ModerationThemeConfig = ModerationDefaults.defaultTheme, moderationWarningAnimationConfig: ModerationWarningAnimationConfig = ModerationWarningAnimationConfig(), - moderationText: ModerationText? = null, + moderationText: ModerationText, ) { if (LocalInspectionMode.current) { Box( @@ -80,21 +75,14 @@ public fun ModerationWarningUiContainer( ModerationWarningUiContentDemo() } } else { - val moderationWarning by call.state.moderationWarning.collectAsStateWithLifecycle() - moderationWarning?.let { - Box( - modifier = Modifier - .fillMaxSize() - .offset(y = config.yOffset) - .padding(horizontal = config.horizontalMargin), - contentAlignment = Alignment.BottomCenter, - ) { - val finalModerationText = moderationText ?: ModerationText( - LocalContext.current.getString(R.string.stream_moderation_warning_title), - it.message, - ) - ModerationUi(config, moderationWarningAnimationConfig, finalModerationText) - } + Box( + modifier = Modifier + .fillMaxSize() + .offset(y = config.yOffset) + .padding(horizontal = config.horizontalMargin), + contentAlignment = Alignment.BottomCenter, + ) { + ModerationUi(config, moderationWarningAnimationConfig, moderationText) } } } diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt similarity index 93% rename from stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt rename to demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt index a7a032de5f4..329337878fb 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.compose.ui.components.call.moderation +package io.getstream.video.android.ui.moderation import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.graphics.Color @@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp * which serves as the default styling configuration for the Moderation Warning UI. */ -public object ModerationDefaults { +internal object ModerationDefaults { public val defaultTheme: ModerationThemeConfig = ModerationThemeConfig() } @@ -44,7 +44,7 @@ public object ModerationDefaults { * @param roundedCornerShape A shape used for the caption container. * */ -public data class ModerationThemeConfig( +internal data class ModerationThemeConfig( val yOffset: Dp = -100.dp, val horizontalMargin: Dp = 16.dp, val backgroundColor: Color = Color.White, 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 92d9dd4f821..9de13a87af4 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -7441,8 +7441,6 @@ public final class io/getstream/video/android/core/CallState { public final fun getMe ()Lkotlinx/coroutines/flow/StateFlow; public final fun getMember (Ljava/lang/String;)Lio/getstream/video/android/core/MemberState; public final fun getMembers ()Lkotlinx/coroutines/flow/StateFlow; - public final fun getModerationBlur ()Lkotlinx/coroutines/flow/StateFlow; - public final fun getModerationWarning ()Lkotlinx/coroutines/flow/StateFlow; public final fun getOrCreateParticipant (Ljava/lang/String;Ljava/lang/String;ZLstream/video/sfu/models/ParticipantSource;)Lio/getstream/video/android/core/ParticipantState; public static synthetic fun getOrCreateParticipant$default (Lio/getstream/video/android/core/CallState;Ljava/lang/String;Ljava/lang/String;ZLstream/video/sfu/models/ParticipantSource;ILjava/lang/Object;)Lio/getstream/video/android/core/ParticipantState; public final fun getOwnCapabilities ()Lkotlinx/coroutines/flow/StateFlow; @@ -7477,7 +7475,6 @@ public final class io/getstream/video/android/core/CallState { public final fun markSpeakingAsMuted ()V public final fun pin (Ljava/lang/String;Ljava/lang/String;)V public final fun replaceParticipants (Ljava/util/List;)V - public final fun resetModeration ()V public final fun unpin (Ljava/lang/String;)V public final fun updateFromResponse (Lio/getstream/android/video/generated/models/CallResponse;)V public final fun updateFromResponse (Lio/getstream/android/video/generated/models/CallStateResponseFields;)V @@ -11782,57 +11779,6 @@ public final class io/getstream/video/android/core/model/VisibilityOnScreenState public static fun values ()[Lio/getstream/video/android/core/model/VisibilityOnScreenState; } -public final class io/getstream/video/android/core/moderation/CallModerationConstants { - public static final field DEFAULT_BLUR_AUTO_DISMISS_TIME_MS J - public static final field DEFAULT_MODERATION_AUTO_DISMISS_TIME_MS J - public static final field DEFAULT_MODERATION_DISPLAY_TIME_MS J - public static final field INSTANCE Lio/getstream/video/android/core/moderation/CallModerationConstants; - public static final field POLICY_VIOLATION Ljava/lang/String; -} - -public final class io/getstream/video/android/core/moderation/ModerationBlurConfig { - public fun ()V - public fun (J)V - public synthetic fun (JILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()J - public final fun copy (J)Lio/getstream/video/android/core/moderation/ModerationBlurConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/moderation/ModerationBlurConfig;JILjava/lang/Object;)Lio/getstream/video/android/core/moderation/ModerationBlurConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getVisibilityDurationMs ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/moderation/ModerationText { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/video/android/core/moderation/ModerationText; - public static synthetic fun copy$default (Lio/getstream/video/android/core/moderation/ModerationText;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/video/android/core/moderation/ModerationText; - public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; - public final fun getTitle ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/moderation/ModerationWarningAnimationConfig { - public fun ()V - public fun (JII)V - public synthetic fun (JIIILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()J - public final fun component2 ()I - public final fun component3 ()I - public final fun copy (JII)Lio/getstream/video/android/core/moderation/ModerationWarningAnimationConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/moderation/ModerationWarningAnimationConfig;JIIILjava/lang/Object;)Lio/getstream/video/android/core/moderation/ModerationWarningAnimationConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getDisplayTime ()J - public final fun getSlideInDuration ()I - public final fun getSlideOutDuration ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public class io/getstream/video/android/core/notifications/DefaultNotificationHandler : io/getstream/android/push/permissions/NotificationPermissionHandler, io/getstream/video/android/core/notifications/NotificationHandler { public static final field Companion Lio/getstream/video/android/core/notifications/DefaultNotificationHandler$Companion; public fun (Landroid/app/Application;Lio/getstream/android/push/permissions/NotificationPermissionHandler;ZI)V 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 0a72ddbb181..478d1c136f9 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 @@ -33,8 +33,6 @@ import io.getstream.android.video.generated.models.CallMemberAddedEvent import io.getstream.android.video.generated.models.CallMemberRemovedEvent import io.getstream.android.video.generated.models.CallMemberUpdatedEvent import io.getstream.android.video.generated.models.CallMemberUpdatedPermissionEvent -import io.getstream.android.video.generated.models.CallModerationBlurEvent -import io.getstream.android.video.generated.models.CallModerationWarningEvent import io.getstream.android.video.generated.models.CallParticipantResponse import io.getstream.android.video.generated.models.CallReactionEvent import io.getstream.android.video.generated.models.CallRecordingStartedEvent @@ -82,6 +80,7 @@ import io.getstream.log.taggedLogger import io.getstream.result.Result import io.getstream.video.android.core.call.RtcSession import io.getstream.video.android.core.closedcaptions.ClosedCaptionManager +import io.getstream.video.android.core.closedcaptions.ClosedCaptionsSettings import io.getstream.video.android.core.events.AudioLevelChangedEvent import io.getstream.video.android.core.events.CallEndedSfuEvent import io.getstream.video.android.core.events.ChangePublishQualityEvent @@ -107,7 +106,6 @@ import io.getstream.video.android.core.model.Reaction import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.core.model.ScreenSharingSession import io.getstream.video.android.core.model.VisibilityOnScreenState -import io.getstream.video.android.core.moderation.CallModerationConstants import io.getstream.video.android.core.notifications.IncomingNotificationData import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository import io.getstream.video.android.core.permission.PermissionRequest @@ -688,14 +686,6 @@ public class CallState( */ val ccMode: StateFlow = closedCaptionManager.ccMode - private val _moderationWarning: MutableStateFlow = - MutableStateFlow(null) - val moderationWarning: StateFlow = _moderationWarning.asStateFlow() - - private val _moderationBlur: MutableStateFlow = - MutableStateFlow(null) - val moderationBlur: StateFlow = _moderationBlur.asStateFlow() - private val pendingParticipantsJoined = ConcurrentHashMap() /** @@ -1135,20 +1125,6 @@ public class CallState( is ClosedCaptionEvent, is CallClosedCaptionsStoppedEvent, -> closedCaptionManager.handleEvent(event) - is CallModerationWarningEvent -> { - _moderationWarning.value = event - scope.launch { - delay(CallModerationConstants.DEFAULT_MODERATION_AUTO_DISMISS_TIME_MS) - _moderationWarning.value = null - } - } - is CallModerationBlurEvent -> { - _moderationBlur.value = event - scope.launch { - delay(CallModerationConstants.DEFAULT_BLUR_AUTO_DISMISS_TIME_MS) - _moderationWarning.value = null - } - } } } @@ -1643,10 +1619,6 @@ public class CallState( fun updateNotification(notification: Notification) { atomicNotification.set(notification) } - - fun resetModeration() { - _moderationBlur.value = null - } } private fun MemberResponse.toMemberState(): MemberState { diff --git a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api index f062c396a7e..da7b0415932 100644 --- a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api +++ b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api @@ -1057,7 +1057,7 @@ public final class io/getstream/video/android/compose/ui/components/call/activec } public final class io/getstream/video/android/compose/ui/components/call/activecall/CallContentKt { - public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/core/pip/PictureInPictureConfiguration;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V + public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/core/pip/PictureInPictureConfiguration;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V } @@ -1073,30 +1073,26 @@ public final class io/getstream/video/android/compose/ui/components/call/activec public final class io/getstream/video/android/compose/ui/components/call/activecall/ComposableSingletons$CallContentKt { public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/call/activecall/ComposableSingletons$CallContentKt; public static field lambda-1 Lkotlin/jvm/functions/Function6; - public static field lambda-10 Lkotlin/jvm/functions/Function3; - public static field lambda-11 Lkotlin/jvm/functions/Function2; - public static field lambda-12 Lkotlin/jvm/functions/Function2; + public static field lambda-10 Lkotlin/jvm/functions/Function2; public static field lambda-2 Lkotlin/jvm/functions/Function3; public static field lambda-3 Lkotlin/jvm/functions/Function3; public static field lambda-4 Lkotlin/jvm/functions/Function3; - public static field lambda-5 Lkotlin/jvm/functions/Function3; + public static field lambda-5 Lkotlin/jvm/functions/Function6; public static field lambda-6 Lkotlin/jvm/functions/Function3; - public static field lambda-7 Lkotlin/jvm/functions/Function6; + public static field lambda-7 Lkotlin/jvm/functions/Function3; public static field lambda-8 Lkotlin/jvm/functions/Function3; - public static field lambda-9 Lkotlin/jvm/functions/Function3; + public static field lambda-9 Lkotlin/jvm/functions/Function2; public fun ()V public final fun getLambda-1$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; - public final fun getLambda-10$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-11$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; - public final fun getLambda-12$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-10$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda-2$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-3$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-4$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-5$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-5$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; public final fun getLambda-6$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-7$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; + public final fun getLambda-7$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-8$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-9$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-9$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; } public final class io/getstream/video/android/compose/ui/components/call/activecall/internal/ComposableSingletons$InviteUsersDialogKt { @@ -1310,43 +1306,6 @@ public final class io/getstream/video/android/compose/ui/components/call/lobby/L public static final fun buildDefaultLobbyControlActions (Lio/getstream/video/android/core/Call;Lkotlin/jvm/functions/Function1;ZZZLandroidx/compose/runtime/Composer;II)Ljava/util/List; } -public final class io/getstream/video/android/compose/ui/components/call/moderation/ModerationDefaults { - public static final field $stable I - public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationDefaults; - public final fun getDefaultTheme ()Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig; -} - -public final class io/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig { - public static final field $stable I - public synthetic fun (FFJJJJFLandroidx/compose/ui/graphics/Shape;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (FFJJJJFLandroidx/compose/ui/graphics/Shape;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1-D9Ej5fM ()F - public final fun component2-D9Ej5fM ()F - public final fun component3-0d7_KjU ()J - public final fun component4-0d7_KjU ()J - public final fun component5-0d7_KjU ()J - public final fun component6-0d7_KjU ()J - public final fun component7-D9Ej5fM ()F - public final fun component8 ()Landroidx/compose/ui/graphics/Shape; - public final fun copy-KylZ79E (FFJJJJFLandroidx/compose/ui/graphics/Shape;)Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig; - public static synthetic fun copy-KylZ79E$default (Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig;FFJJJJFLandroidx/compose/ui/graphics/Shape;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getBackgroundColor-0d7_KjU ()J - public final fun getHorizontalMargin-D9Ej5fM ()F - public final fun getMessageColor-0d7_KjU ()J - public final fun getRoundedCornerShape ()Landroidx/compose/ui/graphics/Shape; - public final fun getTitleColor-0d7_KjU ()J - public final fun getWarningStripColor-0d7_KjU ()J - public final fun getWarningStripWidth-D9Ej5fM ()F - public final fun getYOffset-D9Ej5fM ()F - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainerKt { - public static final fun ModerationWarningUiContainer (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/compose/ui/components/call/moderation/ModerationThemeConfig;Lio/getstream/video/android/core/moderation/ModerationWarningAnimationConfig;Lio/getstream/video/android/core/moderation/ModerationText;Landroidx/compose/runtime/Composer;II)V -} - public final class io/getstream/video/android/compose/ui/components/call/pinning/ComposableSingletons$ParticipantActionsKt { public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/call/pinning/ComposableSingletons$ParticipantActionsKt; public static field lambda-1 Lkotlin/jvm/functions/Function3; 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 6153b9127f1..cea9cfaf28a 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 @@ -97,7 +97,6 @@ import io.getstream.video.android.mock.previewCall * @param pictureInPictureConfiguration User can provide Picture-In-Picture configuration. * @param pictureInPictureContent Content shown when the user enters Picture in Picture mode, if it's been enabled in the app. * @param closedCaptionUi You can pass your composable lambda here to render Closed Captions - * @param moderationWarningUi Pass your composable lambda here to render Moderation Warning UI */ @Composable public fun CallContent( @@ -155,8 +154,6 @@ public fun CallContent( pictureInPictureContent: @Composable (Call) -> Unit = { DefaultPictureInPictureContent(it) }, enableDiagnostics: Boolean = false, closedCaptionUi: @Composable (Call) -> Unit = {}, - moderationBlurUi: @Composable (Call) -> Unit = {}, - moderationWarningUi: @Composable (Call) -> Unit = {}, ) { val context = LocalContext.current val orientation = LocalConfiguration.current.orientation @@ -169,11 +166,7 @@ public fun CallContent( BackHandler { if (pictureInPictureConfiguration.enable) { try { - enterPictureInPicture( - context = context, - call = call, - pictureInPictureConfiguration, - ) + enterPictureInPicture(context = context, call = call, pictureInPictureConfiguration) } catch (e: Exception) { StreamLog.e(tag = "CallContent") { e.stackTraceToString() } call.leave() @@ -226,8 +219,7 @@ public fun CallContent( }, ) { BoxWithConstraints { - val contentSize = - IntSize(constraints.maxWidth, constraints.maxHeight) + val contentSize = IntSize(constraints.maxWidth, constraints.maxHeight) CompositionLocalProvider(LocalVideoContentSize provides contentSize) { videoContent.invoke(this@Row, call) @@ -242,16 +234,6 @@ public fun CallContent( } } closedCaptionUi(call) - - val moderationBlur by call.state.moderationBlur.collectAsStateWithLifecycle() - moderationBlur?.let { - moderationBlurUi(call) - } - - val moderationWarning by call.state.moderationWarning.collectAsStateWithLifecycle() - moderationWarning?.let { - moderationWarningUi(call) - } }, ) } 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 781cec8d282..1f0e8a3e335 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 @@ -58,7 +58,6 @@ import io.getstream.video.android.core.call.state.ToggleSpeakerphone import io.getstream.video.android.core.events.CallEndedSfuEvent import io.getstream.video.android.core.events.ParticipantLeftEvent import io.getstream.video.android.core.model.RejectReason -import io.getstream.video.android.core.moderation.CallModerationConstants import io.getstream.video.android.core.notifications.NotificationHandler import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallController import io.getstream.video.android.model.StreamCallId @@ -1013,14 +1012,6 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper public open fun onCallEvent(call: Call, event: VideoEvent) { when (event) { is CallEndedEvent, is CallEndedSfuEvent, is CallSessionEndedEvent, is LocalCallMissedEvent -> { - when (event) { - is CallEndedEvent -> { - if (event.reason == CallModerationConstants.POLICY_VIOLATION) { - // TODO Rahul - } - } - else -> {} - } // In any case finish the activity, the call is done for leave(call, onSuccess = onSuccessFinish, onError = onErrorFinish) } From 983f1bb909b007e5e95b5958092fb88e5f485204 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 02:55:53 +0530 Subject: [PATCH 08/47] revert BlurIntensity enum --- .../android/filters/video/BlurIntensity.kt | 31 ------------------- .../video/BlurredBackgroundVideoFilter.kt | 11 +++++++ 2 files changed, 11 insertions(+), 31 deletions(-) delete mode 100644 stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt diff --git a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt deleted file mode 100644 index ca0ff5a7e20..00000000000 --- a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurIntensity.kt +++ /dev/null @@ -1,31 +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. - */ - -package io.getstream.video.android.filters.video - -import androidx.annotation.Keep - -/** - * The intensity of the background blur effect. Used in [BlurredBackgroundVideoFilter]. - * The class names are in capital letter to maintain backward-compatibility - */ -@Keep -public sealed class BlurIntensity(public val radius: Int) { - public data object LIGHT : BlurIntensity(7) - public data object MEDIUM : BlurIntensity(11) - public data object HEAVY : BlurIntensity(16) - public data object ULTRA : BlurIntensity(25) -} diff --git a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter.kt b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter.kt index c0323d38304..7ad082f138b 100644 --- a/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter.kt +++ b/stream-video-android-filters-video/src/main/kotlin/io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter.kt @@ -85,4 +85,15 @@ public class BlurredBackgroundVideoFilter( } } +/** + * The intensity of the background blur effect. Used in [BlurredBackgroundVideoFilter]. + */ +@Keep +public enum class BlurIntensity(public val radius: Int) { + LIGHT(7), + MEDIUM(11), + HEAVY(16), + ULTRA(25), +} + private const val DEFAULT_FOREGROUND_THRESHOLD: Double = 0.999 // 1 is max confidence that pixel is in the foreground From 71b533223be9f723ad43bcae25b7edef1c1f64c8 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 02:58:28 +0530 Subject: [PATCH 09/47] revert BlurIntensity enum --- .../stream-video-android-filters-video.api | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/stream-video-android-filters-video/api/stream-video-android-filters-video.api b/stream-video-android-filters-video/api/stream-video-android-filters-video.api index 550e3a41fe2..233c0962feb 100644 --- a/stream-video-android-filters-video/api/stream-video-android-filters-video.api +++ b/stream-video-android-filters-video/api/stream-video-android-filters-video.api @@ -1,34 +1,12 @@ -public abstract class io/getstream/video/android/filters/video/BlurIntensity { - public synthetic fun (ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class io/getstream/video/android/filters/video/BlurIntensity : java/lang/Enum { + public static final field HEAVY Lio/getstream/video/android/filters/video/BlurIntensity; + public static final field LIGHT Lio/getstream/video/android/filters/video/BlurIntensity; + public static final field MEDIUM Lio/getstream/video/android/filters/video/BlurIntensity; + public static final field ULTRA Lio/getstream/video/android/filters/video/BlurIntensity; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public final fun getRadius ()I -} - -public final class io/getstream/video/android/filters/video/BlurIntensity$HEAVY : io/getstream/video/android/filters/video/BlurIntensity { - public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$HEAVY; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/filters/video/BlurIntensity$LIGHT : io/getstream/video/android/filters/video/BlurIntensity { - public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$LIGHT; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/filters/video/BlurIntensity$MEDIUM : io/getstream/video/android/filters/video/BlurIntensity { - public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$MEDIUM; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/filters/video/BlurIntensity$ULTRA : io/getstream/video/android/filters/video/BlurIntensity { - public static final field INSTANCE Lio/getstream/video/android/filters/video/BlurIntensity$ULTRA; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/filters/video/BlurIntensity; + public static fun values ()[Lio/getstream/video/android/filters/video/BlurIntensity; } public final class io/getstream/video/android/filters/video/BlurredBackgroundVideoFilter : io/getstream/video/android/core/call/video/BitmapVideoFilter { From 91ef36532573df0cd4286174fc0119924fad8d34 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 03:08:06 +0530 Subject: [PATCH 10/47] revert changes in strings.xml --- demo-app/src/main/kotlin/io/getstream/video/android/App.kt | 7 +++---- .../io/getstream/video/android/ui/call/CallScreen.kt | 4 +--- demo-app/src/main/res/values/strings.xml | 5 +++++ .../src/main/res/values/strings.xml | 4 ---- 4 files changed, 9 insertions(+), 11 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 c54050f327e..4f6b1bafadd 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 @@ -20,7 +20,6 @@ import android.app.Application import android.content.Context import dagger.hilt.android.HiltAndroidApp import io.getstream.android.video.generated.models.CallEndedEvent -import io.getstream.video.android.compose.R import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.data.model.PolicyViolationUiData import io.getstream.video.android.datastore.delegate.StreamUserDataStore @@ -83,9 +82,9 @@ class App : Application() { if (event is CallEndedEvent) { if (event.reason == CallModerationConstants.POLICY_VIOLATION) { policyViolationUiData.value = PolicyViolationUiData( - getString(R.string.stream_default_policy_violation_title), - getString(R.string.stream_default_policy_violation_message), - getString(R.string.stream_default_policy_violation_action_button), + getString(R.string.policy_violation_title), + getString(R.string.policy_violation_message), + getString(R.string.policy_violation_action_button), ) } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt index 98c19b74624..3685c14ea8c 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt @@ -957,9 +957,7 @@ private fun ModerationWarningRootUi(call: Call) { } warningEvent?.let { event -> val moderationText = ModerationText( - LocalContext.current.getString( - io.getstream.video.android.compose.R.string.stream_moderation_warning_title, - ), + LocalContext.current.getString(R.string.moderation_warning_title), event.message, ) ModerationWarningUiContainer( diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index d0edf9b6dbe..8fd99a9348a 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -56,6 +56,11 @@ Please consider installing the update.\nIt contains important features or bug fixes. App update failed. Try again later You are offline. Check your internet connection. + Policy Violated + We have to end your call as we have detected policy violation + OK + Warning + %s is typing %s and %d more are typing diff --git a/stream-video-android-ui-compose/src/main/res/values/strings.xml b/stream-video-android-ui-compose/src/main/res/values/strings.xml index 78cd5750e96..1cd0f3caf1a 100644 --- a/stream-video-android-ui-compose/src/main/res/values/strings.xml +++ b/stream-video-android-ui-compose/src/main/res/values/strings.xml @@ -20,8 +20,4 @@ Please grant the necessary permissions. Settings Not now - Policy Violated - We have to end your call as we have detected policy violation - OK - Warning From 1f55bbcc5da8ce249caf78ebe3f6c33bad3ec445 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 16:45:57 +0530 Subject: [PATCH 11/47] sdk will render moderation warning ui and will blur the video --- .../video/android/ui/call/CallScreen.kt | 63 ---- .../ui/moderation/CallModerationConstants.kt | 3 - .../ui/moderation/ModerationBlurConfig.kt | 10 +- .../ui/moderation/ModerationWarningConfig.kt | 48 +-- .../ModerationWarningUiContainer.kt | 312 ++++++++---------- .../android/ui/moderation/ModerationsTheme.kt | 76 ++--- .../android/util/StreamVideoInitHelper.kt | 6 + .../api/stream-video-android-core.api | 42 ++- .../core/moderations/ModerationConfig.kt | 27 ++ .../internal/service/CallServiceConfig.kt | 18 + .../service/CallServiceConfigBuilder.kt | 27 ++ .../components/call/activecall/CallContent.kt | 66 ++++ .../moderation/ModerationWarningConfig.kt | 40 +++ .../ModerationWarningUiContainer.kt | 204 ++++++++++++ .../call/moderation/ModerationsTheme.kt | 56 ++++ .../src/main/res/values/strings.xml | 2 + 16 files changed, 690 insertions(+), 310 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt create mode 100644 stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningConfig.kt create mode 100644 stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt create mode 100644 stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt index 3685c14ea8c..a6289028654 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt @@ -83,8 +83,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Popup import androidx.lifecycle.compose.collectAsStateWithLifecycle -import io.getstream.android.video.generated.models.CallModerationBlurEvent -import io.getstream.android.video.generated.models.CallModerationWarningEvent import io.getstream.android.video.generated.models.OwnCapability import io.getstream.android.video.generated.models.TranscriptionSettingsResponse import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState @@ -121,9 +119,7 @@ import io.getstream.video.android.core.call.state.ChooseLayout import io.getstream.video.android.core.model.PreferredVideoResolution import io.getstream.video.android.core.pip.PictureInPictureConfiguration import io.getstream.video.android.core.utils.isEnabled -import io.getstream.video.android.filters.video.BlurIntensity import io.getstream.video.android.filters.video.BlurredBackgroundVideoFilter -import io.getstream.video.android.filters.video.SimpleBlurVideoFilter import io.getstream.video.android.filters.video.VirtualBackgroundVideoFilter import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall @@ -136,12 +132,6 @@ import io.getstream.video.android.ui.closedcaptions.ClosedCaptionsDefaults import io.getstream.video.android.ui.menu.SettingsMenu import io.getstream.video.android.ui.menu.VideoFilter import io.getstream.video.android.ui.menu.availableVideoFilters -import io.getstream.video.android.ui.moderation.CallModerationConstants -import io.getstream.video.android.ui.moderation.ModerationBlurConfig -import io.getstream.video.android.ui.moderation.ModerationDefaults -import io.getstream.video.android.ui.moderation.ModerationText -import io.getstream.video.android.ui.moderation.ModerationWarningAnimationConfig -import io.getstream.video.android.ui.moderation.ModerationWarningUiContainer import io.getstream.video.android.util.config.AppConfig import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay @@ -601,8 +591,6 @@ fun CallScreen( } } } - ModerationBlurRootUi(call) - ModerationWarningRootUi(call) } }, updateUnreadCount = { unreadCount = it }, @@ -938,57 +926,6 @@ private fun BadNetworkLabel( } } -@Composable -private fun ModerationWarningRootUi(call: Call) { - var warningEvent by remember { mutableStateOf(null) } - val moderationThemeConfig = ModerationDefaults.defaultTheme - val moderationWarningAnimationConfig = ModerationWarningAnimationConfig() - - LaunchedEffect(call) { - call.events.collect { event -> - when (event) { - is CallModerationWarningEvent -> { - warningEvent = event - delay(CallModerationConstants.DEFAULT_MODERATION_AUTO_DISMISS_TIME_MS) - warningEvent = null // auto-dismiss after config duration - } - } - } - } - warningEvent?.let { event -> - val moderationText = ModerationText( - LocalContext.current.getString(R.string.moderation_warning_title), - event.message, - ) - ModerationWarningUiContainer( - call, - moderationThemeConfig, - moderationWarningAnimationConfig, - moderationText, - ) - } -} - -@Composable -private fun ModerationBlurRootUi(call: Call) { - var blurEvent by remember { mutableStateOf(null) } - val moderationBlurConfig = ModerationBlurConfig() - LaunchedEffect(call) { - call.events.collect { event -> - when (event) { - is CallModerationBlurEvent -> { - blurEvent = event - call.videoFilter = - SimpleBlurVideoFilter(blurIntensity = BlurIntensity.ULTRA) - delay(moderationBlurConfig.visibilityDurationMs) - blurEvent = null // auto-dismiss after config duration - call.videoFilter = null - } - } - } - } -} - @Composable fun isTablet(): Boolean { val configuration = LocalConfiguration.current diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt index 45a3e6b476b..b2fb6664944 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt @@ -18,7 +18,4 @@ package io.getstream.video.android.ui.moderation object CallModerationConstants { const val POLICY_VIOLATION = "PolicyViolationModeration" - const val DEFAULT_MODERATION_DISPLAY_TIME_MS = 5_000L - const val DEFAULT_MODERATION_AUTO_DISMISS_TIME_MS = DEFAULT_MODERATION_DISPLAY_TIME_MS + 3_000L - const val DEFAULT_BLUR_AUTO_DISMISS_TIME_MS = 10_000L } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt index 2a0c78a45cf..9132fa0bc39 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt @@ -16,8 +16,8 @@ package io.getstream.video.android.ui.moderation -import io.getstream.video.android.ui.moderation.CallModerationConstants.DEFAULT_BLUR_AUTO_DISMISS_TIME_MS - -data class ModerationBlurConfig( - val visibilityDurationMs: Long = DEFAULT_BLUR_AUTO_DISMISS_TIME_MS, -) +// import io.getstream.video.android.compose.ui.components.call.moderation.CallModerationConstants.DEFAULT_BLUR_AUTO_DISMISS_TIME_MS +// +// data class ModerationBlurConfig( +// val visibilityDurationMs: Long = DEFAULT_BLUR_AUTO_DISMISS_TIME_MS, +// ) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt index 10e8c8546de..f4a154e7d3f 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt @@ -15,26 +15,28 @@ */ package io.getstream.video.android.ui.moderation - -/** - * Configuration for the animation and visibility behavior of the Moderation Warning UI. - * - * @param displayTime The duration (in milliseconds) for which the moderation warning UI remains visible. - * @param slideInDuration The duration (in milliseconds) of the slide-in animation when the warning UI appears. - * @param slideOutDuration The duration (in milliseconds) of the slide-out animation when the warning UI disappears. - * - * See [io.getstream.video.android.ui.moderation.ModerationUi] for implementation details. - */ -data class ModerationWarningAnimationConfig( - val displayTime: Long = CallModerationConstants.DEFAULT_MODERATION_DISPLAY_TIME_MS, - val slideInDuration: Int = 500, - val slideOutDuration: Int = slideInDuration, -) - -/** - * Defines the textual content displayed in the Moderation Warning UI. - * - * @param title The title text shown at the top of the moderation warning. - * @param message The message text displayed below the title, providing additional context. - */ -data class ModerationText(val title: String, val message: String) +// +// import io.getstream.video.android.compose.ui.components.call.moderation.CallModerationConstants +// +// /** +// * Configuration for the animation and visibility behavior of the Moderation Warning UI. +// * +// * @param displayTime The duration (in milliseconds) for which the moderation warning UI remains visible. +// * @param slideInDuration The duration (in milliseconds) of the slide-in animation when the warning UI appears. +// * @param slideOutDuration The duration (in milliseconds) of the slide-out animation when the warning UI disappears. +// * +// * See [io.getstream.video.android.ui.moderation.ModerationUi] for implementation details. +// */ +// data class ModerationWarningAnimationConfig( +// val displayTime: Long = CallModerationConstants.DEFAULT_MODERATION_DISPLAY_TIME_MS, +// val slideInDuration: Int = 500, +// val slideOutDuration: Int = slideInDuration, +// ) +// +// /** +// * Defines the textual content displayed in the Moderation Warning UI. +// * +// * @param title The title text shown at the top of the moderation warning. +// * @param message The message text displayed below the title, providing additional context. +// */ +// data class ModerationText(val title: String, val message: String) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt index 93b90006503..5b0ebeb001e 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt @@ -16,180 +16,144 @@ package io.getstream.video.android.ui.moderation -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.EaseInCubic -import androidx.compose.animation.core.EaseOutCubic -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -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.graphics.Color -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import io.getstream.video.android.core.Call -import kotlinx.coroutines.delay -@Composable -internal fun ModerationWarningUiContainer( - call: Call, - config: ModerationThemeConfig = ModerationDefaults.defaultTheme, - moderationWarningAnimationConfig: ModerationWarningAnimationConfig = - ModerationWarningAnimationConfig(), - moderationText: ModerationText, -) { - if (LocalInspectionMode.current) { - Box( - modifier = Modifier - .fillMaxSize() - .offset(y = config.yOffset) - .padding(horizontal = config.horizontalMargin), - - contentAlignment = Alignment.BottomCenter, - ) { - ModerationWarningUiContentDemo() - } - } else { - Box( - modifier = Modifier - .fillMaxSize() - .offset(y = config.yOffset) - .padding(horizontal = config.horizontalMargin), - contentAlignment = Alignment.BottomCenter, - ) { - ModerationUi(config, moderationWarningAnimationConfig, moderationText) - } - } -} - -@Composable -internal fun ModerationUi( - moderationThemeConfig: ModerationThemeConfig, - moderationWarningAnimationConfig: ModerationWarningAnimationConfig, - moderationText: ModerationText, -) { - SlideInOutMessage(moderationThemeConfig, moderationWarningAnimationConfig, moderationText) -} - -@Composable -private fun SlideInOutMessage( - moderationThemeConfig: ModerationThemeConfig, - moderationWarningAnimationConfig: ModerationWarningAnimationConfig, - moderationText: ModerationText, -) { - var visible by remember { mutableStateOf(false) } - - // Trigger visibility when composable enters composition - LaunchedEffect(Unit) { - visible = true - delay(moderationWarningAnimationConfig.displayTime) // visible for 3 seconds - visible = false - } - - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.BottomCenter, - ) { - AnimatedVisibility( - visible = visible, - enter = slideInVertically( - initialOffsetY = { fullHeight -> fullHeight }, - animationSpec = tween(durationMillis = 500, easing = EaseOutCubic), - ) + fadeIn( - animationSpec = tween(durationMillis = 500), - ), - exit = slideOutVertically( - targetOffsetY = { fullHeight -> fullHeight }, - animationSpec = tween(durationMillis = 500, easing = EaseInCubic), - ) + fadeOut( - animationSpec = tween(durationMillis = 500), - ), - ) { - ModerationWarningUiContent(moderationThemeConfig, moderationText) - } - } -} - -@Composable -internal fun ModerationWarningUiContent( - moderationThemeConfig: ModerationThemeConfig, - moderationText: ModerationText, -) { - Box( - modifier = Modifier - .fillMaxWidth() - .background( - Color.White, - shape = RoundedCornerShape( - topStart = 8.dp, - bottomStart = 8.dp, - topEnd = 8.dp, - bottomEnd = 8.dp, - ), - ), - contentAlignment = Alignment.TopStart, - ) { - Row( - modifier = Modifier.height(IntrinsicSize.Min), - ) { - // Orange column on the left - Box( - modifier = Modifier - .width(moderationThemeConfig.warningStripWidth) - .fillMaxHeight() - .background( - moderationThemeConfig.warningStripColor, - shape = RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp), - ), - ) - - Column(Modifier.padding(vertical = 12.dp, horizontal = 12.dp)) { - Text( - text = moderationText.title, - fontWeight = FontWeight.Bold, - color = moderationThemeConfig.titleColor, - fontSize = 16.sp, - ) - Text( - text = moderationText.message, - color = moderationThemeConfig.messageColor, - fontSize = 16.sp, - ) - } - } - } -} - -@Preview -@Composable -internal fun ModerationWarningUiContentDemo() { - ModerationWarningUiContent( - ModerationThemeConfig(), - ModerationText("Warning title", "Warning Message"), - ) -} +// @Composable +// internal fun ModerationWarningUiContainer( +// call: Call, +// config: ModerationThemeConfig = ModerationDefaults.defaultTheme, +// moderationWarningAnimationConfig: ModerationWarningAnimationConfig = +// ModerationWarningAnimationConfig(), +// moderationText: ModerationText, +// ) { +// if (LocalInspectionMode.current) { +// Box( +// modifier = Modifier +// .fillMaxSize() +// .offset(y = config.yOffset) +// .padding(horizontal = config.horizontalMargin), +// +// contentAlignment = Alignment.BottomCenter, +// ) { +// ModerationWarningUiContentDemo() +// } +// } else { +// Box( +// modifier = Modifier +// .fillMaxSize() +// .offset(y = config.yOffset) +// .padding(horizontal = config.horizontalMargin), +// contentAlignment = Alignment.BottomCenter, +// ) { +// ModerationUi(config, moderationWarningAnimationConfig, moderationText) +// } +// } +// } +// +// @Composable +// internal fun ModerationUi( +// moderationThemeConfig: ModerationThemeConfig, +// moderationWarningAnimationConfig: ModerationWarningAnimationConfig, +// moderationText: ModerationText, +// ) { +// SlideInOutMessage(moderationThemeConfig, moderationWarningAnimationConfig, moderationText) +// } +// +// @Composable +// private fun SlideInOutMessage( +// moderationThemeConfig: ModerationThemeConfig, +// moderationWarningAnimationConfig: ModerationWarningAnimationConfig, +// moderationText: ModerationText, +// ) { +// var visible by remember { mutableStateOf(false) } +// +// // Trigger visibility when composable enters composition +// LaunchedEffect(Unit) { +// visible = true +// delay(moderationWarningAnimationConfig.displayTime) // visible for 3 seconds +// visible = false +// } +// +// Box( +// modifier = Modifier.fillMaxSize(), +// contentAlignment = Alignment.BottomCenter, +// ) { +// AnimatedVisibility( +// visible = visible, +// enter = slideInVertically( +// initialOffsetY = { fullHeight -> fullHeight }, +// animationSpec = tween(durationMillis = 500, easing = EaseOutCubic), +// ) + fadeIn( +// animationSpec = tween(durationMillis = 500), +// ), +// exit = slideOutVertically( +// targetOffsetY = { fullHeight -> fullHeight }, +// animationSpec = tween(durationMillis = 500, easing = EaseInCubic), +// ) + fadeOut( +// animationSpec = tween(durationMillis = 500), +// ), +// ) { +// ModerationWarningUiContent(moderationThemeConfig, moderationText) +// } +// } +// } +// +// @Composable +// internal fun ModerationWarningUiContent( +// moderationThemeConfig: ModerationThemeConfig, +// moderationText: ModerationText, +// ) { +// Box( +// modifier = Modifier +// .fillMaxWidth() +// .background( +// Color.White, +// shape = RoundedCornerShape( +// topStart = 8.dp, +// bottomStart = 8.dp, +// topEnd = 8.dp, +// bottomEnd = 8.dp, +// ), +// ), +// contentAlignment = Alignment.TopStart, +// ) { +// Row( +// modifier = Modifier.height(IntrinsicSize.Min), +// ) { +// // Orange column on the left +// Box( +// modifier = Modifier +// .width(moderationThemeConfig.warningStripWidth) +// .fillMaxHeight() +// .background( +// moderationThemeConfig.warningStripColor, +// shape = RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp), +// ), +// ) +// +// Column(Modifier.padding(vertical = 12.dp, horizontal = 12.dp)) { +// Text( +// text = moderationText.title, +// fontWeight = FontWeight.Bold, +// color = moderationThemeConfig.titleColor, +// fontSize = 16.sp, +// ) +// Text( +// text = moderationText.message, +// color = moderationThemeConfig.messageColor, +// fontSize = 16.sp, +// ) +// } +// } +// } +// } +// +// @Preview +// @Composable +// internal fun ModerationWarningUiContentDemo() { +// ModerationWarningUiContent( +// ModerationThemeConfig(), +// ModerationText("Warning title", "Warning Message"), +// ) +// } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt index 329337878fb..999d4f49040 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt @@ -16,41 +16,41 @@ package io.getstream.video.android.ui.moderation -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -/** - * Provides default configurations for the Moderation Warning UI. - * - * The [ModerationDefaults] object contains a predefined instance of [ModerationThemeConfig], - * which serves as the default styling configuration for the Moderation Warning UI. - */ - -internal object ModerationDefaults { - public val defaultTheme: ModerationThemeConfig = ModerationThemeConfig() -} - -/** - * Defines the configuration for Moderation Warning UI, allowing customization of its layout & styling - * - * @param yOffset Vertical offset for the closed captions container. Negative values move the container upwards. - * @param horizontalMargin Horizontal margin around the container. - * @param backgroundColor Color used for rendering the background box of the closed captions container. - * @param titleColor Color used for rendering the caption text. - * @param messageColor Color used for rendering the caption text. - * @param roundedCornerShape A shape used for the caption container. - * - */ -internal data class ModerationThemeConfig( - val yOffset: Dp = -100.dp, - val horizontalMargin: Dp = 16.dp, - val backgroundColor: Color = Color.White, - val titleColor: Color = Color.Black, - val messageColor: Color = Color.Gray, - val warningStripColor: Color = Color(0xFFFFA500), - val warningStripWidth: Dp = 12.dp, - val roundedCornerShape: Shape? = RoundedCornerShape(16.dp), -) +// import androidx.compose.foundation.shape.RoundedCornerShape +// import androidx.compose.ui.graphics.Color +// import androidx.compose.ui.graphics.Shape +// import androidx.compose.ui.unit.Dp +// import androidx.compose.ui.unit.dp +// +// /** +// * Provides default configurations for the Moderation Warning UI. +// * +// * The [ModerationDefaults] object contains a predefined instance of [ModerationThemeConfig], +// * which serves as the default styling configuration for the Moderation Warning UI. +// */ +// +// internal object ModerationDefaults { +// public val defaultTheme: ModerationThemeConfig = ModerationThemeConfig() +// } +// +// /** +// * Defines the configuration for Moderation Warning UI, allowing customization of its layout & styling +// * +// * @param yOffset Vertical offset for the closed captions container. Negative values move the container upwards. +// * @param horizontalMargin Horizontal margin around the container. +// * @param backgroundColor Color used for rendering the background box of the closed captions container. +// * @param titleColor Color used for rendering the caption text. +// * @param messageColor Color used for rendering the caption text. +// * @param roundedCornerShape A shape used for the caption container. +// * +// */ +// internal data class ModerationThemeConfig( +// val yOffset: Dp = -100.dp, +// val horizontalMargin: Dp = 16.dp, +// val backgroundColor: Color = Color.White, +// val titleColor: Color = Color.Black, +// val messageColor: Color = Color.Gray, +// val warningStripColor: Color = Color(0xFFFFA500), +// val warningStripWidth: Dp = 12.dp, +// val roundedCornerShape: Shape? = RoundedCornerShape(16.dp), +// ) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index c924c3f7d0c..742a31ff4ac 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -40,6 +40,8 @@ import io.getstream.video.android.core.notifications.NotificationConfig import io.getstream.video.android.core.notifications.handlers.CompatibilityStreamNotificationHandler import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations +import io.getstream.video.android.core.notifications.internal.service.ModerationBlurConfig +import io.getstream.video.android.core.notifications.internal.service.ModerationWarningConfig import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.core.sounds.enableRingingCallVibrationConfig @@ -211,6 +213,10 @@ object StreamVideoInitHelper { callServiceConfigRegistry.apply { register(DefaultCallConfigurations.getLivestreamGuestCallServiceConfig()) register(CallType.AudioCall.name) { enableTelecom(true) } + register(CallType.AnyMarker.name) { + setModerationBlurConfig(ModerationBlurConfig(true, 10_000L)) + setModerationWarningConfig(ModerationWarningConfig(true, 5_000L, "Warning")) + } } return StreamVideoBuilder( 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 9de13a87af4..0e3cbea68ee 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -11779,6 +11779,34 @@ public final class io/getstream/video/android/core/model/VisibilityOnScreenState public static fun values ()[Lio/getstream/video/android/core/model/VisibilityOnScreenState; } +public final class io/getstream/video/android/core/moderations/ModerationBlurConfig { + public fun (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)V + public final fun component1 ()Z + public final fun component2 ()J + public final fun component3 ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter; + public final fun copy (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)Lio/getstream/video/android/core/moderations/ModerationBlurConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/moderations/ModerationBlurConfig;ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;ILjava/lang/Object;)Lio/getstream/video/android/core/moderations/ModerationBlurConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getBitmapVideoFilter ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter; + public final fun getBlurDuration ()J + public final fun getEnable ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/moderations/ModerationWarningConfig { + public fun (ZJ)V + public final fun component1 ()Z + public final fun component2 ()J + public final fun copy (ZJ)Lio/getstream/video/android/core/moderations/ModerationWarningConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;ZJILjava/lang/Object;)Lio/getstream/video/android/core/moderations/ModerationWarningConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getDisplayTime ()J + public final fun getEnable ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public class io/getstream/video/android/core/notifications/DefaultNotificationHandler : io/getstream/android/push/permissions/NotificationPermissionHandler, io/getstream/video/android/core/notifications/NotificationHandler { public static final field Companion Lio/getstream/video/android/core/notifications/DefaultNotificationHandler$Companion; public fun (Landroid/app/Application;Lio/getstream/android/push/permissions/NotificationPermissionHandler;ZI)V @@ -12214,19 +12242,23 @@ public final class io/getstream/video/android/core/notifications/internal/receiv public final class io/getstream/video/android/core/notifications/internal/service/CallServiceConfig { public fun ()V - public fun (ZILjava/util/Map;Ljava/lang/Class;Z)V - public synthetic fun (ZILjava/util/Map;Ljava/lang/Class;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/ModerationBlurConfig;)V + public synthetic fun (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/ModerationBlurConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z public final fun component2 ()I public final fun component3 ()Ljava/util/Map; public final fun component4 ()Ljava/lang/Class; public final fun component5 ()Z - public final fun copy (ZILjava/util/Map;Ljava/lang/Class;Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZILjava/util/Map;Ljava/lang/Class;ZILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun component6 ()Lio/getstream/video/android/core/moderations/ModerationWarningConfig; + public final fun component7 ()Lio/getstream/video/android/core/moderations/ModerationBlurConfig; + public final fun copy (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/ModerationBlurConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/ModerationBlurConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; public fun equals (Ljava/lang/Object;)Z public final fun getAudioUsage ()I public final fun getCallServicePerType ()Ljava/util/Map; public final fun getEnableTelecom ()Z + public final fun getModerationBlurConfig ()Lio/getstream/video/android/core/moderations/ModerationBlurConfig; + public final fun getModerationWarningConfig ()Lio/getstream/video/android/core/moderations/ModerationWarningConfig; public final fun getRunCallServiceInForeground ()Z public final fun getServiceClass ()Ljava/lang/Class; public fun hashCode ()I @@ -12238,6 +12270,8 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final fun build ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; public final fun enableTelecom (Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setAudioUsage (I)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; + public final fun setModerationBlurConfig (Lio/getstream/video/android/core/moderations/ModerationBlurConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; + public final fun setModerationWarningConfig (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setRunCallServiceInForeground (Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setServiceClass (Ljava/lang/Class;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt new file mode 100644 index 00000000000..638d2be264e --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt @@ -0,0 +1,27 @@ +/* + * 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. + */ + +package io.getstream.video.android.core.moderations + +import io.getstream.video.android.core.call.video.BitmapVideoFilter + +data class ModerationWarningConfig(val enable: Boolean, val displayTime: Long) + +data class ModerationBlurConfig( + val enable: Boolean, + val blurDuration: Long, + val bitmapVideoFilter: BitmapVideoFilter, +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt index c7b21f4ce80..f9fe18d9b51 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt @@ -16,7 +16,11 @@ package io.getstream.video.android.core.notifications.internal.service +import android.graphics.Bitmap import android.media.AudioAttributes +import io.getstream.video.android.core.call.video.BitmapVideoFilter +import io.getstream.video.android.core.moderations.ModerationBlurConfig +import io.getstream.video.android.core.moderations.ModerationWarningConfig import io.getstream.video.android.model.StreamCallId // Constants @@ -47,6 +51,20 @@ public data class CallServiceConfig( ), val serviceClass: Class<*> = CallService::class.java, val enableTelecom: Boolean = false, + val moderationWarningConfig: ModerationWarningConfig = ModerationWarningConfig( + true, + 5_000L, + ), + val moderationBlurConfig: ModerationBlurConfig = ModerationBlurConfig( + true, + 10_000L, + object : + BitmapVideoFilter() { + override fun applyFilter(videoFrameBitmap: Bitmap) { + TODO("Not yet implemented") + } + }, + ), ) /** diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt index b76b7cc4bf9..fe0785783cc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt @@ -16,13 +16,30 @@ package io.getstream.video.android.core.notifications.internal.service +import android.graphics.Bitmap import android.media.AudioAttributes +import io.getstream.video.android.core.call.video.BitmapVideoFilter +import io.getstream.video.android.core.moderations.ModerationBlurConfig +import io.getstream.video.android.core.moderations.ModerationWarningConfig class CallServiceConfigBuilder { private var serviceClass: Class<*> = CallService::class.java private var runCallServiceInForeground: Boolean = true private var audioUsage: Int = AudioAttributes.USAGE_VOICE_COMMUNICATION private var enableTelecom: Boolean = false + private var moderationWarningConfig: ModerationWarningConfig = + ModerationWarningConfig(true, 5_000L) + private var moderationBlurConfig: ModerationBlurConfig = + ModerationBlurConfig( + true, + 10_000L, + object : + BitmapVideoFilter() { + override fun applyFilter(videoFrameBitmap: Bitmap) { + TODO("Not yet implemented") + } + }, + ) fun setServiceClass(serviceClass: Class<*>): CallServiceConfigBuilder = apply { this.serviceClass = serviceClass @@ -40,12 +57,22 @@ class CallServiceConfigBuilder { this.enableTelecom = enableTelecom } + fun setModerationWarningConfig(moderationWarningConfig: ModerationWarningConfig): CallServiceConfigBuilder = apply { + this.moderationWarningConfig = moderationWarningConfig + } + + fun setModerationBlurConfig(moderationBlurConfig: ModerationBlurConfig): CallServiceConfigBuilder = apply { + this.moderationBlurConfig = moderationBlurConfig + } + fun build(): CallServiceConfig { return CallServiceConfig( serviceClass = serviceClass, runCallServiceInForeground = runCallServiceInForeground, audioUsage = audioUsage, enableTelecom = enableTelecom, + moderationWarningConfig = moderationWarningConfig, + moderationBlurConfig = moderationBlurConfig, ) } } 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 cea9cfaf28a..4a7daead3c5 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 @@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -51,6 +52,8 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.coerceAtLeast import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.getstream.android.video.generated.models.CallModerationBlurEvent +import io.getstream.android.video.generated.models.CallModerationWarningEvent import io.getstream.log.StreamLog import io.getstream.video.android.compose.lifecycle.MediaPiPLifecycle import io.getstream.video.android.compose.permission.VideoPermissionsState @@ -63,6 +66,8 @@ import io.getstream.video.android.compose.ui.components.call.activecall.internal import io.getstream.video.android.compose.ui.components.call.controls.ControlActions import io.getstream.video.android.compose.ui.components.call.controls.actions.DefaultOnCallActionHandler import io.getstream.video.android.compose.ui.components.call.diagnostics.CallDiagnosticsContent +import io.getstream.video.android.compose.ui.components.call.moderation.DefaultModerationWarningUiContainer +import io.getstream.video.android.compose.ui.components.call.moderation.ModerationWarningAnimationConfig import io.getstream.video.android.compose.ui.components.call.renderer.LayoutType import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantVideo import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantsLayout @@ -72,10 +77,13 @@ import io.getstream.video.android.compose.ui.components.call.renderer.internal.L import io.getstream.video.android.compose.ui.components.video.VideoRenderer import io.getstream.video.android.core.Call import io.getstream.video.android.core.ParticipantState +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 import io.getstream.video.android.core.pip.PictureInPictureConfiguration import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall +import kotlinx.coroutines.delay /** * Represents the UI in an Active call that shows participants and their video, as well as some @@ -154,6 +162,16 @@ public fun CallContent( pictureInPictureContent: @Composable (Call) -> Unit = { DefaultPictureInPictureContent(it) }, enableDiagnostics: Boolean = false, closedCaptionUi: @Composable (Call) -> Unit = {}, + moderationBlurUi: @Composable (Call) -> Unit = {}, + moderationWarningUi: @Composable (Call, String?) -> Unit = { _, message -> + val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() + val displayTime = callServiceConfig.moderationWarningConfig.displayTime + DefaultModerationWarningUiContainer( + call, + message, + moderationWarningAnimationConfig = ModerationWarningAnimationConfig(displayTime), + ) + }, ) { val context = LocalContext.current val orientation = LocalConfiguration.current.orientation @@ -234,11 +252,59 @@ public fun CallContent( } } closedCaptionUi(call) + ModerationBlurUi(call, moderationBlurUi) + ModerationWarningRootUi(call, moderationWarningUi) }, ) } } +@Composable +private fun ModerationBlurUi(call: Call, moderationBlurUi: @Composable (Call) -> Unit) { + var blurEvent by remember { mutableStateOf(null) } + val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() + LaunchedEffect(call) { + call.events.collect { event -> + when (event) { + is CallModerationBlurEvent -> { + blurEvent = event + call.videoFilter = callServiceConfig.moderationBlurConfig.bitmapVideoFilter + delay(callServiceConfig.moderationBlurConfig.blurDuration) + blurEvent = null // auto-dismiss after config duration + call.videoFilter = null + } + } + } + } + blurEvent?.let { + moderationBlurUi(call) + } +} + +@Composable +private fun ModerationWarningRootUi( + call: Call, + moderationWarningUi: @Composable (Call, String?) -> Unit, +) { + var warningEvent by remember { mutableStateOf(null) } + val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() + + LaunchedEffect(call) { + call.events.collect { event -> + when (event) { + is CallModerationWarningEvent -> { + warningEvent = event + delay(callServiceConfig.moderationWarningConfig.displayTime) + warningEvent = null // auto-dismiss after config duration + } + } + } + } + warningEvent?.let { event -> + moderationWarningUi(call, event.message) + } +} + @Deprecated("Use CallContent with pictureInPictureConfiguration argument") @Composable public fun CallContent( diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningConfig.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningConfig.kt new file mode 100644 index 00000000000..861170ddf4c --- /dev/null +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningConfig.kt @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package io.getstream.video.android.compose.ui.components.call.moderation + +/** + * Configuration for the animation and visibility behavior of the Moderation Warning UI. + * + * @param displayTime The duration (in milliseconds) for which the moderation warning UI remains visible. + * @param slideInDuration The duration (in milliseconds) of the slide-in animation when the warning UI appears. + * @param slideOutDuration The duration (in milliseconds) of the slide-out animation when the warning UI disappears. + * + * See [io.getstream.video.android.compose.ui.components.call.moderation.ModerationUi] for implementation details. + */ +internal data class ModerationWarningAnimationConfig( + val displayTime: Long = 5_000L, + val slideInDuration: Int = 500, + val slideOutDuration: Int = slideInDuration, +) + +/** + * Defines the textual content displayed in the Moderation Warning UI. + * + * @param title The title text shown at the top of the moderation warning. + * @param message The message text displayed below the title, providing additional context. + */ +internal data class ModerationText(val title: String, val message: String) diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt new file mode 100644 index 00000000000..15076f27448 --- /dev/null +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationWarningUiContainer.kt @@ -0,0 +1,204 @@ +/* + * 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. + */ + +package io.getstream.video.android.compose.ui.components.call.moderation + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.EaseInCubic +import androidx.compose.animation.core.EaseOutCubic +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +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.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.getstream.video.android.compose.R +import io.getstream.video.android.core.Call +import io.getstream.video.android.ui.moderation.ModerationDefaults +import io.getstream.video.android.ui.moderation.ModerationThemeConfig +import kotlinx.coroutines.delay + +@Composable +internal fun DefaultModerationWarningUiContainer( + call: Call, + message: String? = null, + config: ModerationThemeConfig = ModerationDefaults.defaultTheme, + moderationWarningAnimationConfig: ModerationWarningAnimationConfig = + ModerationWarningAnimationConfig(), +) { + if (LocalInspectionMode.current) { + Box( + modifier = Modifier + .fillMaxSize() + .offset(y = config.yOffset) + .padding(horizontal = config.horizontalMargin), + + contentAlignment = Alignment.BottomCenter, + ) { + ModerationWarningUiContentDemo() + } + } else { + Box( + modifier = Modifier + .fillMaxSize() + .offset(y = config.yOffset) + .padding(horizontal = config.horizontalMargin), + contentAlignment = Alignment.BottomCenter, + ) { + val defaultTitle = LocalContext.current.getString( + R.string.stream_default_moderation_warning_title, + ) + val defaultMessage = message ?: LocalContext.current.getString(R.string.stream_default_moderation_warning_message) + val moderationText = ModerationText(defaultTitle, defaultMessage) + ModerationUi(config, moderationWarningAnimationConfig, moderationText) + } + } +} + +@Composable +internal fun ModerationUi( + moderationThemeConfig: ModerationThemeConfig, + moderationWarningAnimationConfig: ModerationWarningAnimationConfig, + moderationText: ModerationText, +) { + SlideInOutMessage(moderationThemeConfig, moderationWarningAnimationConfig, moderationText) +} + +@Composable +private fun SlideInOutMessage( + moderationThemeConfig: ModerationThemeConfig, + moderationWarningAnimationConfig: ModerationWarningAnimationConfig, + moderationText: ModerationText, +) { + var visible by remember { mutableStateOf(false) } + + // Trigger visibility when composable enters composition + LaunchedEffect(Unit) { + visible = true + delay(moderationWarningAnimationConfig.displayTime) // visible for 3 seconds + visible = false + } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter, + ) { + AnimatedVisibility( + visible = visible, + enter = slideInVertically( + initialOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 500, easing = EaseOutCubic), + ) + fadeIn( + animationSpec = tween(durationMillis = 500), + ), + exit = slideOutVertically( + targetOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 500, easing = EaseInCubic), + ) + fadeOut( + animationSpec = tween(durationMillis = 500), + ), + ) { + ModerationWarningUiContent(moderationThemeConfig, moderationText) + } + } +} + +@Composable +internal fun ModerationWarningUiContent( + moderationThemeConfig: ModerationThemeConfig, + moderationText: ModerationText, +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + Color.White, + shape = RoundedCornerShape( + topStart = 8.dp, + bottomStart = 8.dp, + topEnd = 8.dp, + bottomEnd = 8.dp, + ), + ), + contentAlignment = Alignment.TopStart, + ) { + Row( + modifier = Modifier.height(IntrinsicSize.Min), + ) { + // Orange column on the left + Box( + modifier = Modifier + .width(moderationThemeConfig.warningStripWidth) + .fillMaxHeight() + .background( + moderationThemeConfig.warningStripColor, + shape = RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp), + ), + ) + + Column(Modifier.padding(vertical = 12.dp, horizontal = 12.dp)) { + Text( + text = moderationText.title, + fontWeight = FontWeight.Bold, + color = moderationThemeConfig.titleColor, + fontSize = 16.sp, + ) + Text( + text = moderationText.message, + color = moderationThemeConfig.messageColor, + fontSize = 16.sp, + ) + } + } + } +} + +@Preview +@Composable +internal fun ModerationWarningUiContentDemo() { + ModerationWarningUiContent( + ModerationThemeConfig(), + ModerationText("Warning title", "Warning Message"), + ) +} diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt new file mode 100644 index 00000000000..329337878fb --- /dev/null +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/moderation/ModerationsTheme.kt @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package io.getstream.video.android.ui.moderation + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Provides default configurations for the Moderation Warning UI. + * + * The [ModerationDefaults] object contains a predefined instance of [ModerationThemeConfig], + * which serves as the default styling configuration for the Moderation Warning UI. + */ + +internal object ModerationDefaults { + public val defaultTheme: ModerationThemeConfig = ModerationThemeConfig() +} + +/** + * Defines the configuration for Moderation Warning UI, allowing customization of its layout & styling + * + * @param yOffset Vertical offset for the closed captions container. Negative values move the container upwards. + * @param horizontalMargin Horizontal margin around the container. + * @param backgroundColor Color used for rendering the background box of the closed captions container. + * @param titleColor Color used for rendering the caption text. + * @param messageColor Color used for rendering the caption text. + * @param roundedCornerShape A shape used for the caption container. + * + */ +internal data class ModerationThemeConfig( + val yOffset: Dp = -100.dp, + val horizontalMargin: Dp = 16.dp, + val backgroundColor: Color = Color.White, + val titleColor: Color = Color.Black, + val messageColor: Color = Color.Gray, + val warningStripColor: Color = Color(0xFFFFA500), + val warningStripWidth: Dp = 12.dp, + val roundedCornerShape: Shape? = RoundedCornerShape(16.dp), +) diff --git a/stream-video-android-ui-compose/src/main/res/values/strings.xml b/stream-video-android-ui-compose/src/main/res/values/strings.xml index 1cd0f3caf1a..c6251f82d40 100644 --- a/stream-video-android-ui-compose/src/main/res/values/strings.xml +++ b/stream-video-android-ui-compose/src/main/res/values/strings.xml @@ -20,4 +20,6 @@ Please grant the necessary permissions. Settings Not now + Warning + We have detected policy violation From 01a6a6b06a69a65609ea05a34a3db57a4dea5842 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 16:53:31 +0530 Subject: [PATCH 12/47] sdk will render moderation warning ui and will blur the video --- .../kotlin/io/getstream/video/android/App.kt | 2 +- .../ui/moderation/ModerationBlurConfig.kt | 23 --- .../ui/moderation/ModerationWarningConfig.kt | 42 ----- .../ModerationWarningUiContainer.kt | 159 ------------------ .../android/ui/moderation/ModerationsTheme.kt | 56 ------ .../api/stream-video-android-core.api | 13 ++ .../getstream/video/android/core/CallState.kt | 3 + .../moderations}/CallModerationConstants.kt | 2 +- .../core/moderations/ModerationManager.kt | 39 +++++ .../api/stream-video-android-ui-compose.api | 16 +- .../components/call/activecall/CallContent.kt | 4 +- 11 files changed, 68 insertions(+), 291 deletions(-) delete mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt delete mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt delete mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt delete mode 100644 demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt rename {demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation => stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations}/CallModerationConstants.kt (93%) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt 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 4f6b1bafadd..d7ebb5f800e 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 @@ -21,10 +21,10 @@ import android.content.Context import dagger.hilt.android.HiltAndroidApp import io.getstream.android.video.generated.models.CallEndedEvent import io.getstream.video.android.core.StreamVideo +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.moderation.CallModerationConstants import io.getstream.video.android.util.StreamVideoInitHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt deleted file mode 100644 index 9132fa0bc39..00000000000 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationBlurConfig.kt +++ /dev/null @@ -1,23 +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. - */ - -package io.getstream.video.android.ui.moderation - -// import io.getstream.video.android.compose.ui.components.call.moderation.CallModerationConstants.DEFAULT_BLUR_AUTO_DISMISS_TIME_MS -// -// data class ModerationBlurConfig( -// val visibilityDurationMs: Long = DEFAULT_BLUR_AUTO_DISMISS_TIME_MS, -// ) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt deleted file mode 100644 index f4a154e7d3f..00000000000 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningConfig.kt +++ /dev/null @@ -1,42 +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. - */ - -package io.getstream.video.android.ui.moderation -// -// import io.getstream.video.android.compose.ui.components.call.moderation.CallModerationConstants -// -// /** -// * Configuration for the animation and visibility behavior of the Moderation Warning UI. -// * -// * @param displayTime The duration (in milliseconds) for which the moderation warning UI remains visible. -// * @param slideInDuration The duration (in milliseconds) of the slide-in animation when the warning UI appears. -// * @param slideOutDuration The duration (in milliseconds) of the slide-out animation when the warning UI disappears. -// * -// * See [io.getstream.video.android.ui.moderation.ModerationUi] for implementation details. -// */ -// data class ModerationWarningAnimationConfig( -// val displayTime: Long = CallModerationConstants.DEFAULT_MODERATION_DISPLAY_TIME_MS, -// val slideInDuration: Int = 500, -// val slideOutDuration: Int = slideInDuration, -// ) -// -// /** -// * Defines the textual content displayed in the Moderation Warning UI. -// * -// * @param title The title text shown at the top of the moderation warning. -// * @param message The message text displayed below the title, providing additional context. -// */ -// data class ModerationText(val title: String, val message: String) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt deleted file mode 100644 index 5b0ebeb001e..00000000000 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationWarningUiContainer.kt +++ /dev/null @@ -1,159 +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. - */ - -package io.getstream.video.android.ui.moderation - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue - -// @Composable -// internal fun ModerationWarningUiContainer( -// call: Call, -// config: ModerationThemeConfig = ModerationDefaults.defaultTheme, -// moderationWarningAnimationConfig: ModerationWarningAnimationConfig = -// ModerationWarningAnimationConfig(), -// moderationText: ModerationText, -// ) { -// if (LocalInspectionMode.current) { -// Box( -// modifier = Modifier -// .fillMaxSize() -// .offset(y = config.yOffset) -// .padding(horizontal = config.horizontalMargin), -// -// contentAlignment = Alignment.BottomCenter, -// ) { -// ModerationWarningUiContentDemo() -// } -// } else { -// Box( -// modifier = Modifier -// .fillMaxSize() -// .offset(y = config.yOffset) -// .padding(horizontal = config.horizontalMargin), -// contentAlignment = Alignment.BottomCenter, -// ) { -// ModerationUi(config, moderationWarningAnimationConfig, moderationText) -// } -// } -// } -// -// @Composable -// internal fun ModerationUi( -// moderationThemeConfig: ModerationThemeConfig, -// moderationWarningAnimationConfig: ModerationWarningAnimationConfig, -// moderationText: ModerationText, -// ) { -// SlideInOutMessage(moderationThemeConfig, moderationWarningAnimationConfig, moderationText) -// } -// -// @Composable -// private fun SlideInOutMessage( -// moderationThemeConfig: ModerationThemeConfig, -// moderationWarningAnimationConfig: ModerationWarningAnimationConfig, -// moderationText: ModerationText, -// ) { -// var visible by remember { mutableStateOf(false) } -// -// // Trigger visibility when composable enters composition -// LaunchedEffect(Unit) { -// visible = true -// delay(moderationWarningAnimationConfig.displayTime) // visible for 3 seconds -// visible = false -// } -// -// Box( -// modifier = Modifier.fillMaxSize(), -// contentAlignment = Alignment.BottomCenter, -// ) { -// AnimatedVisibility( -// visible = visible, -// enter = slideInVertically( -// initialOffsetY = { fullHeight -> fullHeight }, -// animationSpec = tween(durationMillis = 500, easing = EaseOutCubic), -// ) + fadeIn( -// animationSpec = tween(durationMillis = 500), -// ), -// exit = slideOutVertically( -// targetOffsetY = { fullHeight -> fullHeight }, -// animationSpec = tween(durationMillis = 500, easing = EaseInCubic), -// ) + fadeOut( -// animationSpec = tween(durationMillis = 500), -// ), -// ) { -// ModerationWarningUiContent(moderationThemeConfig, moderationText) -// } -// } -// } -// -// @Composable -// internal fun ModerationWarningUiContent( -// moderationThemeConfig: ModerationThemeConfig, -// moderationText: ModerationText, -// ) { -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .background( -// Color.White, -// shape = RoundedCornerShape( -// topStart = 8.dp, -// bottomStart = 8.dp, -// topEnd = 8.dp, -// bottomEnd = 8.dp, -// ), -// ), -// contentAlignment = Alignment.TopStart, -// ) { -// Row( -// modifier = Modifier.height(IntrinsicSize.Min), -// ) { -// // Orange column on the left -// Box( -// modifier = Modifier -// .width(moderationThemeConfig.warningStripWidth) -// .fillMaxHeight() -// .background( -// moderationThemeConfig.warningStripColor, -// shape = RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp), -// ), -// ) -// -// Column(Modifier.padding(vertical = 12.dp, horizontal = 12.dp)) { -// Text( -// text = moderationText.title, -// fontWeight = FontWeight.Bold, -// color = moderationThemeConfig.titleColor, -// fontSize = 16.sp, -// ) -// Text( -// text = moderationText.message, -// color = moderationThemeConfig.messageColor, -// fontSize = 16.sp, -// ) -// } -// } -// } -// } -// -// @Preview -// @Composable -// internal fun ModerationWarningUiContentDemo() { -// ModerationWarningUiContent( -// ModerationThemeConfig(), -// ModerationText("Warning title", "Warning Message"), -// ) -// } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt deleted file mode 100644 index 999d4f49040..00000000000 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/ModerationsTheme.kt +++ /dev/null @@ -1,56 +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. - */ - -package io.getstream.video.android.ui.moderation - -// import androidx.compose.foundation.shape.RoundedCornerShape -// import androidx.compose.ui.graphics.Color -// import androidx.compose.ui.graphics.Shape -// import androidx.compose.ui.unit.Dp -// import androidx.compose.ui.unit.dp -// -// /** -// * Provides default configurations for the Moderation Warning UI. -// * -// * The [ModerationDefaults] object contains a predefined instance of [ModerationThemeConfig], -// * which serves as the default styling configuration for the Moderation Warning UI. -// */ -// -// internal object ModerationDefaults { -// public val defaultTheme: ModerationThemeConfig = ModerationThemeConfig() -// } -// -// /** -// * Defines the configuration for Moderation Warning UI, allowing customization of its layout & styling -// * -// * @param yOffset Vertical offset for the closed captions container. Negative values move the container upwards. -// * @param horizontalMargin Horizontal margin around the container. -// * @param backgroundColor Color used for rendering the background box of the closed captions container. -// * @param titleColor Color used for rendering the caption text. -// * @param messageColor Color used for rendering the caption text. -// * @param roundedCornerShape A shape used for the caption container. -// * -// */ -// internal data class ModerationThemeConfig( -// val yOffset: Dp = -100.dp, -// val horizontalMargin: Dp = 16.dp, -// val backgroundColor: Color = Color.White, -// val titleColor: Color = Color.Black, -// val messageColor: Color = Color.Gray, -// val warningStripColor: Color = Color(0xFFFFA500), -// val warningStripWidth: Dp = 12.dp, -// val roundedCornerShape: Shape? = RoundedCornerShape(16.dp), -// ) 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 0e3cbea68ee..6c532af3242 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -7441,6 +7441,7 @@ public final class io/getstream/video/android/core/CallState { public final fun getMe ()Lkotlinx/coroutines/flow/StateFlow; public final fun getMember (Ljava/lang/String;)Lio/getstream/video/android/core/MemberState; public final fun getMembers ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getModerationManager ()Lio/getstream/video/android/core/moderations/ModerationManager; public final fun getOrCreateParticipant (Ljava/lang/String;Ljava/lang/String;ZLstream/video/sfu/models/ParticipantSource;)Lio/getstream/video/android/core/ParticipantState; public static synthetic fun getOrCreateParticipant$default (Lio/getstream/video/android/core/CallState;Ljava/lang/String;Ljava/lang/String;ZLstream/video/sfu/models/ParticipantSource;ILjava/lang/Object;)Lio/getstream/video/android/core/ParticipantState; public final fun getOwnCapabilities ()Lkotlinx/coroutines/flow/StateFlow; @@ -11779,6 +11780,11 @@ public final class io/getstream/video/android/core/model/VisibilityOnScreenState public static fun values ()[Lio/getstream/video/android/core/model/VisibilityOnScreenState; } +public final class io/getstream/video/android/core/moderations/CallModerationConstants { + public static final field INSTANCE Lio/getstream/video/android/core/moderations/CallModerationConstants; + public static final field POLICY_VIOLATION Ljava/lang/String; +} + public final class io/getstream/video/android/core/moderations/ModerationBlurConfig { public fun (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)V public final fun component1 ()Z @@ -11794,6 +11800,13 @@ public final class io/getstream/video/android/core/moderations/ModerationBlurCon public fun toString ()Ljava/lang/String; } +public final class io/getstream/video/android/core/moderations/ModerationManager { + public fun (Lio/getstream/video/android/core/Call;)V + public final fun disableVideoModeration ()V + public final fun enableVideoModeration (Lio/getstream/video/android/core/call/video/BitmapVideoFilter;)V + public static synthetic fun enableVideoModeration$default (Lio/getstream/video/android/core/moderations/ModerationManager;Lio/getstream/video/android/core/call/video/BitmapVideoFilter;ILjava/lang/Object;)V +} + public final class io/getstream/video/android/core/moderations/ModerationWarningConfig { public fun (ZJ)V public final fun component1 ()Z 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 478d1c136f9..e5c90d08f22 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 @@ -106,6 +106,7 @@ import io.getstream.video.android.core.model.Reaction import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.core.model.ScreenSharingSession import io.getstream.video.android.core.model.VisibilityOnScreenState +import io.getstream.video.android.core.moderations.ModerationManager import io.getstream.video.android.core.notifications.IncomingNotificationData import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository import io.getstream.video.android.core.permission.PermissionRequest @@ -660,6 +661,8 @@ public class CallState( */ internal val closedCaptionManager = ClosedCaptionManager() + public val moderationManager = ModerationManager(call) + /** * Tracks whether closed captioning is currently active for the call. * True if captioning is ongoing, false otherwise. diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/CallModerationConstants.kt similarity index 93% rename from demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/CallModerationConstants.kt index b2fb6664944..65b0e26db04 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/moderation/CallModerationConstants.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/CallModerationConstants.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.ui.moderation +package io.getstream.video.android.core.moderations object CallModerationConstants { const val POLICY_VIOLATION = "PolicyViolationModeration" diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt new file mode 100644 index 00000000000..683ea0f8f15 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package io.getstream.video.android.core.moderations + +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.call.video.BitmapVideoFilter +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig + +class ModerationManager(private val call: Call) { + fun enableVideoModeration(bitmapVideoFilter: BitmapVideoFilter? = null) { + if (bitmapVideoFilter != null) { + call.videoFilter = bitmapVideoFilter + } else { + val callServiceConfig = + StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) + ?: CallServiceConfig() + call.videoFilter = callServiceConfig.moderationBlurConfig.bitmapVideoFilter + } + } + + fun disableVideoModeration() { + call.videoFilter = null + } +} diff --git a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api index da7b0415932..b33dd86df52 100644 --- a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api +++ b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api @@ -1057,7 +1057,7 @@ public final class io/getstream/video/android/compose/ui/components/call/activec } public final class io/getstream/video/android/compose/ui/components/call/activecall/CallContentKt { - public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/core/pip/PictureInPictureConfiguration;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V + public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/core/pip/PictureInPictureConfiguration;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;III)V public static final fun CallContent (Lio/getstream/video/android/core/Call;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/LayoutType;Lio/getstream/video/android/compose/permission/VideoPermissionsState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V } @@ -1074,25 +1074,27 @@ public final class io/getstream/video/android/compose/ui/components/call/activec public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/call/activecall/ComposableSingletons$CallContentKt; public static field lambda-1 Lkotlin/jvm/functions/Function6; public static field lambda-10 Lkotlin/jvm/functions/Function2; + public static field lambda-11 Lkotlin/jvm/functions/Function2; public static field lambda-2 Lkotlin/jvm/functions/Function3; public static field lambda-3 Lkotlin/jvm/functions/Function3; public static field lambda-4 Lkotlin/jvm/functions/Function3; - public static field lambda-5 Lkotlin/jvm/functions/Function6; - public static field lambda-6 Lkotlin/jvm/functions/Function3; + public static field lambda-5 Lkotlin/jvm/functions/Function3; + public static field lambda-6 Lkotlin/jvm/functions/Function6; public static field lambda-7 Lkotlin/jvm/functions/Function3; public static field lambda-8 Lkotlin/jvm/functions/Function3; - public static field lambda-9 Lkotlin/jvm/functions/Function2; + public static field lambda-9 Lkotlin/jvm/functions/Function3; public fun ()V public final fun getLambda-1$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; public final fun getLambda-10$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-11$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda-2$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-3$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-4$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-5$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; - public final fun getLambda-6$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-5$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-6$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function6; public final fun getLambda-7$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-8$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-9$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-9$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3; } public final class io/getstream/video/android/compose/ui/components/call/activecall/internal/ComposableSingletons$InviteUsersDialogKt { 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 4a7daead3c5..0d975ddfbea 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 @@ -268,10 +268,10 @@ private fun ModerationBlurUi(call: Call, moderationBlurUi: @Composable (Call) -> when (event) { is CallModerationBlurEvent -> { blurEvent = event - call.videoFilter = callServiceConfig.moderationBlurConfig.bitmapVideoFilter + call.state.moderationManager.enableVideoModeration() delay(callServiceConfig.moderationBlurConfig.blurDuration) blurEvent = null // auto-dismiss after config duration - call.videoFilter = null + call.state.moderationManager.disableVideoModeration() } } } From fd96aa03a9b2fc770335d306acf76e77144b6821 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 17:08:12 +0530 Subject: [PATCH 13/47] sdk will render moderation warning ui and will blur the video --- .../getstream/video/android/util/StreamVideoInitHelper.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 742a31ff4ac..c39c2e69782 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -34,14 +34,14 @@ import io.getstream.video.android.core.StreamVideoBuilder import io.getstream.video.android.core.call.CallType import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi import io.getstream.video.android.core.logging.LoggingLevel +import io.getstream.video.android.core.moderations.ModerationBlurConfig +import io.getstream.video.android.core.moderations.ModerationWarningConfig import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver import io.getstream.video.android.core.notifications.DefaultStreamIntentResolver import io.getstream.video.android.core.notifications.NotificationConfig import io.getstream.video.android.core.notifications.handlers.CompatibilityStreamNotificationHandler import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations -import io.getstream.video.android.core.notifications.internal.service.ModerationBlurConfig -import io.getstream.video.android.core.notifications.internal.service.ModerationWarningConfig import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.core.sounds.enableRingingCallVibrationConfig @@ -215,7 +215,7 @@ object StreamVideoInitHelper { register(CallType.AudioCall.name) { enableTelecom(true) } register(CallType.AnyMarker.name) { setModerationBlurConfig(ModerationBlurConfig(true, 10_000L)) - setModerationWarningConfig(ModerationWarningConfig(true, 5_000L, "Warning")) + setModerationWarningConfig(ModerationWarningConfig(true, 5_000L)) } } From 900a783ec6fc49fa01f33a467a68f592227993f0 Mon Sep 17 00:00:00 2001 From: Aleksandar Apostolov Date: Wed, 12 Nov 2025 11:43:09 +0100 Subject: [PATCH 14/47] Add default moderation filter --- .../video/DefaultModerationVideoFilter.kt | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt new file mode 100644 index 00000000000..5b6bb4873b4 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt @@ -0,0 +1,245 @@ +package io.getstream.video.android.core.call.video + +import android.graphics.Bitmap +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * Uses a stack blur to aggressively obscure each frame without requiring RenderEffect/RenderScript. + */ +internal class DefaultModerationVideoFilter( + private val blurRadius: Int = DEFAULT_RADIUS, +) : BitmapVideoFilter() { + + private val stackBlurProcessor = StackBlurProcessor() + + override fun applyFilter(videoFrameBitmap: Bitmap) { + val radius = blurRadius.coerceIn(MIN_RADIUS, MAX_RADIUS) + if (radius <= 0 || videoFrameBitmap.width == 0 || videoFrameBitmap.height == 0) return + + stackBlurProcessor.blur(videoFrameBitmap, radius) + } + + private class StackBlurProcessor { + private var pixelBuffer = IntArray(0) + private var red = IntArray(0) + private var green = IntArray(0) + private var blue = IntArray(0) + private var vMin = IntArray(0) + + fun blur(bitmap: Bitmap, radius: Int) { + val width = bitmap.width + val height = bitmap.height + val wm = width - 1 + val hm = height - 1 + val size = width * height + + ensureCapacity(size, max(width, height)) + + bitmap.getPixels(pixelBuffer, 0, width, 0, 0, width, height) + + val div = radius + radius + 1 + val radiusPlus1 = radius + 1 + val divsum = (div + 1) shr 1 + val divsumSquared = divsum * divsum + val dv = IntArray(256 * divsumSquared) { it / divsumSquared } + + val stack = IntArray(div * 3) + var stackPointer: Int + var stackStart: Int + var sirOffset: Int + var rbs: Int + + var yi = 0 + var yw = 0 + + for (y in 0 until height) { + var rsum = 0 + var gsum = 0 + var bsum = 0 + var rinsum = 0 + var ginsum = 0 + var binsum = 0 + var routsum = 0 + var goutsum = 0 + var boutsum = 0 + + for (i in -radius..radius) { + val p = pixelBuffer[yi + min(wm, max(i, 0))] + sirOffset = (i + radius) * 3 + stack[sirOffset] = p shr 16 and 0xff + stack[sirOffset + 1] = p shr 8 and 0xff + stack[sirOffset + 2] = p and 0xff + rbs = radiusPlus1 - abs(i) + rsum += stack[sirOffset] * rbs + gsum += stack[sirOffset + 1] * rbs + bsum += stack[sirOffset + 2] * rbs + if (i > 0) { + rinsum += stack[sirOffset] + ginsum += stack[sirOffset + 1] + binsum += stack[sirOffset + 2] + } else { + routsum += stack[sirOffset] + goutsum += stack[sirOffset + 1] + boutsum += stack[sirOffset + 2] + } + } + stackPointer = radius + + for (x in 0 until width) { + red[yi] = dv[rsum] + green[yi] = dv[gsum] + blue[yi] = dv[bsum] + + rsum -= routsum + gsum -= goutsum + bsum -= boutsum + + stackStart = stackPointer - radius + div + sirOffset = (stackStart % div) * 3 + + routsum -= stack[sirOffset] + goutsum -= stack[sirOffset + 1] + boutsum -= stack[sirOffset + 2] + + if (y == 0) { + vMin[x] = min(x + radiusPlus1, wm) + } + val p = pixelBuffer[yw + vMin[x]] + + stack[sirOffset] = p shr 16 and 0xff + stack[sirOffset + 1] = p shr 8 and 0xff + stack[sirOffset + 2] = p and 0xff + + rinsum += stack[sirOffset] + ginsum += stack[sirOffset + 1] + binsum += stack[sirOffset + 2] + + rsum += rinsum + gsum += ginsum + bsum += binsum + + stackPointer = (stackPointer + 1) % div + sirOffset = (stackPointer) * 3 + + routsum += stack[sirOffset] + goutsum += stack[sirOffset + 1] + boutsum += stack[sirOffset + 2] + + rinsum -= stack[sirOffset] + ginsum -= stack[sirOffset + 1] + binsum -= stack[sirOffset + 2] + + yi++ + } + yw += width + } + + for (x in 0 until width) { + var rsum = 0 + var gsum = 0 + var bsum = 0 + var rinsum = 0 + var ginsum = 0 + var binsum = 0 + var routsum = 0 + var goutsum = 0 + var boutsum = 0 + + var yp = -radius * width + for (i in -radius..radius) { + val yiIndex = max(0, yp) + x + sirOffset = (i + radius) * 3 + stack[sirOffset] = red[yiIndex] + stack[sirOffset + 1] = green[yiIndex] + stack[sirOffset + 2] = blue[yiIndex] + rbs = radiusPlus1 - abs(i) + rsum += red[yiIndex] * rbs + gsum += green[yiIndex] * rbs + bsum += blue[yiIndex] * rbs + if (i > 0) { + rinsum += stack[sirOffset] + ginsum += stack[sirOffset + 1] + binsum += stack[sirOffset + 2] + } else { + routsum += stack[sirOffset] + goutsum += stack[sirOffset + 1] + boutsum += stack[sirOffset + 2] + } + if (i < hm) { + yp += width + } + } + var yiIndex = x + stackPointer = radius + + for (y in 0 until height) { + val baseColor = pixelBuffer[yiIndex] and -0x1000000 + pixelBuffer[yiIndex] = baseColor or (dv[rsum] shl 16) or (dv[gsum] shl 8) or dv[bsum] + + rsum -= routsum + gsum -= goutsum + bsum -= boutsum + + stackStart = stackPointer - radius + div + sirOffset = (stackStart % div) * 3 + + routsum -= stack[sirOffset] + goutsum -= stack[sirOffset + 1] + boutsum -= stack[sirOffset + 2] + + if (x == 0) { + vMin[y] = min(y + radiusPlus1, hm) * width + } + val p = x + vMin[y] + + stack[sirOffset] = red[p] + stack[sirOffset + 1] = green[p] + stack[sirOffset + 2] = blue[p] + + rinsum += stack[sirOffset] + ginsum += stack[sirOffset + 1] + binsum += stack[sirOffset + 2] + + rsum += rinsum + gsum += ginsum + bsum += binsum + + stackPointer = (stackPointer + 1) % div + sirOffset = stackPointer * 3 + + routsum += stack[sirOffset] + goutsum += stack[sirOffset + 1] + boutsum += stack[sirOffset + 2] + + rinsum -= stack[sirOffset] + ginsum -= stack[sirOffset + 1] + binsum -= stack[sirOffset + 2] + + yiIndex += width + } + } + + bitmap.setPixels(pixelBuffer, 0, width, 0, 0, width, height) + } + + private fun ensureCapacity(pixelCount: Int, minArraySize: Int) { + if (pixelBuffer.size < pixelCount) { + pixelBuffer = IntArray(pixelCount) + red = IntArray(pixelCount) + green = IntArray(pixelCount) + blue = IntArray(pixelCount) + } + if (vMin.size < minArraySize) { + vMin = IntArray(minArraySize) + } + } + } + + private companion object { + const val MIN_RADIUS: Int = 4 + const val DEFAULT_RADIUS: Int = 80 + const val MAX_RADIUS: Int = 96 + } +} From 7dcac47a7c0379d7dc8d6cce8fa0725f5e11c80c Mon Sep 17 00:00:00 2001 From: Aleksandar Apostolov Date: Wed, 12 Nov 2025 13:00:33 +0100 Subject: [PATCH 15/47] Spotless --- .../call/video/DefaultModerationVideoFilter.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt index 5b6bb4873b4..113cce52447 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt @@ -1,3 +1,19 @@ +/* + * 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. + */ + package io.getstream.video.android.core.call.video import android.graphics.Bitmap From a1a63d7f890a01bae7beba662eb9c2afea97fd47 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 17:47:42 +0530 Subject: [PATCH 16/47] update --- .../api/stream-video-android-core.api | 1 + .../android/core/moderations/ModerationConfig.kt | 3 ++- .../internal/service/CallServiceConfig.kt | 10 +--------- .../internal/service/CallServiceConfigBuilder.kt | 12 +++--------- 4 files changed, 7 insertions(+), 19 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 6c532af3242..7ab627f34cf 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -11787,6 +11787,7 @@ public final class io/getstream/video/android/core/moderations/CallModerationCon public final class io/getstream/video/android/core/moderations/ModerationBlurConfig { public fun (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)V + public synthetic fun (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z public final fun component2 ()J public final fun component3 ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt index 638d2be264e..2de0227f2df 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt @@ -17,11 +17,12 @@ package io.getstream.video.android.core.moderations import io.getstream.video.android.core.call.video.BitmapVideoFilter +import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter data class ModerationWarningConfig(val enable: Boolean, val displayTime: Long) data class ModerationBlurConfig( val enable: Boolean, val blurDuration: Long, - val bitmapVideoFilter: BitmapVideoFilter, + val bitmapVideoFilter: BitmapVideoFilter = DefaultModerationVideoFilter(), ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt index f9fe18d9b51..bd4daac5e2d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt @@ -16,9 +16,7 @@ package io.getstream.video.android.core.notifications.internal.service -import android.graphics.Bitmap import android.media.AudioAttributes -import io.getstream.video.android.core.call.video.BitmapVideoFilter import io.getstream.video.android.core.moderations.ModerationBlurConfig import io.getstream.video.android.core.moderations.ModerationWarningConfig import io.getstream.video.android.model.StreamCallId @@ -57,13 +55,7 @@ public data class CallServiceConfig( ), val moderationBlurConfig: ModerationBlurConfig = ModerationBlurConfig( true, - 10_000L, - object : - BitmapVideoFilter() { - override fun applyFilter(videoFrameBitmap: Bitmap) { - TODO("Not yet implemented") - } - }, + 15_000L, ), ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt index fe0785783cc..d2f5c18fbb5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt @@ -16,9 +16,8 @@ package io.getstream.video.android.core.notifications.internal.service -import android.graphics.Bitmap import android.media.AudioAttributes -import io.getstream.video.android.core.call.video.BitmapVideoFilter +import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter import io.getstream.video.android.core.moderations.ModerationBlurConfig import io.getstream.video.android.core.moderations.ModerationWarningConfig @@ -32,13 +31,8 @@ class CallServiceConfigBuilder { private var moderationBlurConfig: ModerationBlurConfig = ModerationBlurConfig( true, - 10_000L, - object : - BitmapVideoFilter() { - override fun applyFilter(videoFrameBitmap: Bitmap) { - TODO("Not yet implemented") - } - }, + 15_000L, + DefaultModerationVideoFilter(), ) fun setServiceClass(serviceClass: Class<*>): CallServiceConfigBuilder = apply { From f499d491587475be403474444c3eef059d8f76f3 Mon Sep 17 00:00:00 2001 From: Aleksandar Apostolov Date: Wed, 12 Nov 2025 13:25:10 +0100 Subject: [PATCH 17/47] Spotless --- .../video/DefaultModerationVideoFilter.kt | 261 ++++++++++++++++++ .../video/DefaultModerationVideoFilterTest.kt | 77 ++++++ 2 files changed, 338 insertions(+) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilterTest.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt new file mode 100644 index 00000000000..113cce52447 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilter.kt @@ -0,0 +1,261 @@ +/* + * 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. + */ + +package io.getstream.video.android.core.call.video + +import android.graphics.Bitmap +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * Uses a stack blur to aggressively obscure each frame without requiring RenderEffect/RenderScript. + */ +internal class DefaultModerationVideoFilter( + private val blurRadius: Int = DEFAULT_RADIUS, +) : BitmapVideoFilter() { + + private val stackBlurProcessor = StackBlurProcessor() + + override fun applyFilter(videoFrameBitmap: Bitmap) { + val radius = blurRadius.coerceIn(MIN_RADIUS, MAX_RADIUS) + if (radius <= 0 || videoFrameBitmap.width == 0 || videoFrameBitmap.height == 0) return + + stackBlurProcessor.blur(videoFrameBitmap, radius) + } + + private class StackBlurProcessor { + private var pixelBuffer = IntArray(0) + private var red = IntArray(0) + private var green = IntArray(0) + private var blue = IntArray(0) + private var vMin = IntArray(0) + + fun blur(bitmap: Bitmap, radius: Int) { + val width = bitmap.width + val height = bitmap.height + val wm = width - 1 + val hm = height - 1 + val size = width * height + + ensureCapacity(size, max(width, height)) + + bitmap.getPixels(pixelBuffer, 0, width, 0, 0, width, height) + + val div = radius + radius + 1 + val radiusPlus1 = radius + 1 + val divsum = (div + 1) shr 1 + val divsumSquared = divsum * divsum + val dv = IntArray(256 * divsumSquared) { it / divsumSquared } + + val stack = IntArray(div * 3) + var stackPointer: Int + var stackStart: Int + var sirOffset: Int + var rbs: Int + + var yi = 0 + var yw = 0 + + for (y in 0 until height) { + var rsum = 0 + var gsum = 0 + var bsum = 0 + var rinsum = 0 + var ginsum = 0 + var binsum = 0 + var routsum = 0 + var goutsum = 0 + var boutsum = 0 + + for (i in -radius..radius) { + val p = pixelBuffer[yi + min(wm, max(i, 0))] + sirOffset = (i + radius) * 3 + stack[sirOffset] = p shr 16 and 0xff + stack[sirOffset + 1] = p shr 8 and 0xff + stack[sirOffset + 2] = p and 0xff + rbs = radiusPlus1 - abs(i) + rsum += stack[sirOffset] * rbs + gsum += stack[sirOffset + 1] * rbs + bsum += stack[sirOffset + 2] * rbs + if (i > 0) { + rinsum += stack[sirOffset] + ginsum += stack[sirOffset + 1] + binsum += stack[sirOffset + 2] + } else { + routsum += stack[sirOffset] + goutsum += stack[sirOffset + 1] + boutsum += stack[sirOffset + 2] + } + } + stackPointer = radius + + for (x in 0 until width) { + red[yi] = dv[rsum] + green[yi] = dv[gsum] + blue[yi] = dv[bsum] + + rsum -= routsum + gsum -= goutsum + bsum -= boutsum + + stackStart = stackPointer - radius + div + sirOffset = (stackStart % div) * 3 + + routsum -= stack[sirOffset] + goutsum -= stack[sirOffset + 1] + boutsum -= stack[sirOffset + 2] + + if (y == 0) { + vMin[x] = min(x + radiusPlus1, wm) + } + val p = pixelBuffer[yw + vMin[x]] + + stack[sirOffset] = p shr 16 and 0xff + stack[sirOffset + 1] = p shr 8 and 0xff + stack[sirOffset + 2] = p and 0xff + + rinsum += stack[sirOffset] + ginsum += stack[sirOffset + 1] + binsum += stack[sirOffset + 2] + + rsum += rinsum + gsum += ginsum + bsum += binsum + + stackPointer = (stackPointer + 1) % div + sirOffset = (stackPointer) * 3 + + routsum += stack[sirOffset] + goutsum += stack[sirOffset + 1] + boutsum += stack[sirOffset + 2] + + rinsum -= stack[sirOffset] + ginsum -= stack[sirOffset + 1] + binsum -= stack[sirOffset + 2] + + yi++ + } + yw += width + } + + for (x in 0 until width) { + var rsum = 0 + var gsum = 0 + var bsum = 0 + var rinsum = 0 + var ginsum = 0 + var binsum = 0 + var routsum = 0 + var goutsum = 0 + var boutsum = 0 + + var yp = -radius * width + for (i in -radius..radius) { + val yiIndex = max(0, yp) + x + sirOffset = (i + radius) * 3 + stack[sirOffset] = red[yiIndex] + stack[sirOffset + 1] = green[yiIndex] + stack[sirOffset + 2] = blue[yiIndex] + rbs = radiusPlus1 - abs(i) + rsum += red[yiIndex] * rbs + gsum += green[yiIndex] * rbs + bsum += blue[yiIndex] * rbs + if (i > 0) { + rinsum += stack[sirOffset] + ginsum += stack[sirOffset + 1] + binsum += stack[sirOffset + 2] + } else { + routsum += stack[sirOffset] + goutsum += stack[sirOffset + 1] + boutsum += stack[sirOffset + 2] + } + if (i < hm) { + yp += width + } + } + var yiIndex = x + stackPointer = radius + + for (y in 0 until height) { + val baseColor = pixelBuffer[yiIndex] and -0x1000000 + pixelBuffer[yiIndex] = baseColor or (dv[rsum] shl 16) or (dv[gsum] shl 8) or dv[bsum] + + rsum -= routsum + gsum -= goutsum + bsum -= boutsum + + stackStart = stackPointer - radius + div + sirOffset = (stackStart % div) * 3 + + routsum -= stack[sirOffset] + goutsum -= stack[sirOffset + 1] + boutsum -= stack[sirOffset + 2] + + if (x == 0) { + vMin[y] = min(y + radiusPlus1, hm) * width + } + val p = x + vMin[y] + + stack[sirOffset] = red[p] + stack[sirOffset + 1] = green[p] + stack[sirOffset + 2] = blue[p] + + rinsum += stack[sirOffset] + ginsum += stack[sirOffset + 1] + binsum += stack[sirOffset + 2] + + rsum += rinsum + gsum += ginsum + bsum += binsum + + stackPointer = (stackPointer + 1) % div + sirOffset = stackPointer * 3 + + routsum += stack[sirOffset] + goutsum += stack[sirOffset + 1] + boutsum += stack[sirOffset + 2] + + rinsum -= stack[sirOffset] + ginsum -= stack[sirOffset + 1] + binsum -= stack[sirOffset + 2] + + yiIndex += width + } + } + + bitmap.setPixels(pixelBuffer, 0, width, 0, 0, width, height) + } + + private fun ensureCapacity(pixelCount: Int, minArraySize: Int) { + if (pixelBuffer.size < pixelCount) { + pixelBuffer = IntArray(pixelCount) + red = IntArray(pixelCount) + green = IntArray(pixelCount) + blue = IntArray(pixelCount) + } + if (vMin.size < minArraySize) { + vMin = IntArray(minArraySize) + } + } + } + + private companion object { + const val MIN_RADIUS: Int = 4 + const val DEFAULT_RADIUS: Int = 80 + const val MAX_RADIUS: Int = 96 + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilterTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilterTest.kt new file mode 100644 index 00000000000..963de2e1976 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/call/video/DefaultModerationVideoFilterTest.kt @@ -0,0 +1,77 @@ +/* + * 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. + */ + +package io.getstream.video.android.core.call.video + +import android.graphics.Bitmap +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class DefaultModerationVideoFilterTest { + + @Test + fun `blur filter significantly changes pixel data`() { + val bitmap = createPatternBitmap(width = 64, height = 64) + val before = bitmap.copy(bitmap.config!!, true) + + DefaultModerationVideoFilter(blurRadius = 48).applyFilter(bitmap) + + val changedPixels = countPixelDifferences(before, bitmap) + assertThat(changedPixels).isGreaterThan(64 * 64 / 2) + } + + @Test + fun `blur filter keeps single pixel intact`() { + val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + val color = 0xff336699.toInt() + bitmap.setPixel(0, 0, color) + + DefaultModerationVideoFilter(blurRadius = 48).applyFilter(bitmap) + + assertThat(bitmap.getPixel(0, 0)).isEqualTo(color) + } + + private fun createPatternBitmap(width: Int, height: Int): Bitmap { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + for (y in 0 until height) { + for (x in 0 until width) { + val r = (x * 4) and 0xff + val g = (y * 4) and 0xff + val b = ((x + y) * 2) and 0xff + val color = (0xff shl 24) or (r shl 16) or (g shl 8) or b + bitmap.setPixel(x, y, color) + } + } + return bitmap + } + + private fun countPixelDifferences(before: Bitmap, after: Bitmap): Int { + require(before.width == after.width) + require(before.height == after.height) + var changed = 0 + for (y in 0 until before.height) { + for (x in 0 until before.width) { + if (before.getPixel(x, y) != after.getPixel(x, y)) { + changed++ + } + } + } + return changed + } +} From 2ff7e77d5aa50e83bbc17309ce047eb0b9e98461 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 18:23:13 +0530 Subject: [PATCH 18/47] refactor all moderation related configuration into ModerationConfig --- .../android/util/StreamVideoInitHelper.kt | 6 ++--- .../core/moderations/ModerationConfig.kt | 13 +++++++++- .../core/moderations/ModerationManager.kt | 3 ++- .../internal/service/CallServiceConfig.kt | 12 ++-------- .../service/CallServiceConfigBuilder.kt | 24 ++++--------------- .../components/call/activecall/CallContent.kt | 6 ++--- 6 files changed, 26 insertions(+), 38 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index c39c2e69782..525cb1c146f 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -34,8 +34,7 @@ import io.getstream.video.android.core.StreamVideoBuilder import io.getstream.video.android.core.call.CallType import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi import io.getstream.video.android.core.logging.LoggingLevel -import io.getstream.video.android.core.moderations.ModerationBlurConfig -import io.getstream.video.android.core.moderations.ModerationWarningConfig +import io.getstream.video.android.core.moderations.ModerationConfig import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver import io.getstream.video.android.core.notifications.DefaultStreamIntentResolver import io.getstream.video.android.core.notifications.NotificationConfig @@ -214,8 +213,7 @@ object StreamVideoInitHelper { register(DefaultCallConfigurations.getLivestreamGuestCallServiceConfig()) register(CallType.AudioCall.name) { enableTelecom(true) } register(CallType.AnyMarker.name) { - setModerationBlurConfig(ModerationBlurConfig(true, 10_000L)) - setModerationWarningConfig(ModerationWarningConfig(true, 5_000L)) + setModerationConfig(ModerationConfig()) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt index 2de0227f2df..e16d509e076 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt @@ -21,8 +21,19 @@ import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter data class ModerationWarningConfig(val enable: Boolean, val displayTime: Long) -data class ModerationBlurConfig( +data class VideoModerationConfig( val enable: Boolean, val blurDuration: Long, val bitmapVideoFilter: BitmapVideoFilter = DefaultModerationVideoFilter(), ) + +data class ModerationConfig( + val moderationWarningConfig: ModerationWarningConfig = ModerationWarningConfig( + true, + 5_000L, + ), + val videoModerationConfig: VideoModerationConfig = VideoModerationConfig( + true, + 20_000L, + ), +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt index 683ea0f8f15..b63af76a364 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt @@ -22,6 +22,7 @@ import io.getstream.video.android.core.call.video.BitmapVideoFilter import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig class ModerationManager(private val call: Call) { + fun enableVideoModeration(bitmapVideoFilter: BitmapVideoFilter? = null) { if (bitmapVideoFilter != null) { call.videoFilter = bitmapVideoFilter @@ -29,7 +30,7 @@ class ModerationManager(private val call: Call) { val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() - call.videoFilter = callServiceConfig.moderationBlurConfig.bitmapVideoFilter + call.videoFilter = callServiceConfig.moderationConfig.videoModerationConfig.bitmapVideoFilter } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt index bd4daac5e2d..b36e85d6dee 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt @@ -17,8 +17,7 @@ package io.getstream.video.android.core.notifications.internal.service import android.media.AudioAttributes -import io.getstream.video.android.core.moderations.ModerationBlurConfig -import io.getstream.video.android.core.moderations.ModerationWarningConfig +import io.getstream.video.android.core.moderations.ModerationConfig import io.getstream.video.android.model.StreamCallId // Constants @@ -49,14 +48,7 @@ public data class CallServiceConfig( ), val serviceClass: Class<*> = CallService::class.java, val enableTelecom: Boolean = false, - val moderationWarningConfig: ModerationWarningConfig = ModerationWarningConfig( - true, - 5_000L, - ), - val moderationBlurConfig: ModerationBlurConfig = ModerationBlurConfig( - true, - 15_000L, - ), + val moderationConfig: ModerationConfig = ModerationConfig(), ) /** diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt index d2f5c18fbb5..03c1b71b5f6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt @@ -17,23 +17,14 @@ package io.getstream.video.android.core.notifications.internal.service import android.media.AudioAttributes -import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter -import io.getstream.video.android.core.moderations.ModerationBlurConfig -import io.getstream.video.android.core.moderations.ModerationWarningConfig +import io.getstream.video.android.core.moderations.ModerationConfig class CallServiceConfigBuilder { private var serviceClass: Class<*> = CallService::class.java private var runCallServiceInForeground: Boolean = true private var audioUsage: Int = AudioAttributes.USAGE_VOICE_COMMUNICATION private var enableTelecom: Boolean = false - private var moderationWarningConfig: ModerationWarningConfig = - ModerationWarningConfig(true, 5_000L) - private var moderationBlurConfig: ModerationBlurConfig = - ModerationBlurConfig( - true, - 15_000L, - DefaultModerationVideoFilter(), - ) + private var moderationConfig: ModerationConfig = ModerationConfig() fun setServiceClass(serviceClass: Class<*>): CallServiceConfigBuilder = apply { this.serviceClass = serviceClass @@ -51,12 +42,8 @@ class CallServiceConfigBuilder { this.enableTelecom = enableTelecom } - fun setModerationWarningConfig(moderationWarningConfig: ModerationWarningConfig): CallServiceConfigBuilder = apply { - this.moderationWarningConfig = moderationWarningConfig - } - - fun setModerationBlurConfig(moderationBlurConfig: ModerationBlurConfig): CallServiceConfigBuilder = apply { - this.moderationBlurConfig = moderationBlurConfig + fun setModerationConfig(moderationConfig: ModerationConfig): CallServiceConfigBuilder = apply { + this.moderationConfig = moderationConfig } fun build(): CallServiceConfig { @@ -65,8 +52,7 @@ class CallServiceConfigBuilder { runCallServiceInForeground = runCallServiceInForeground, audioUsage = audioUsage, enableTelecom = enableTelecom, - moderationWarningConfig = moderationWarningConfig, - moderationBlurConfig = moderationBlurConfig, + moderationConfig = moderationConfig, ) } } 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 0d975ddfbea..f38f35f6d59 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 @@ -165,7 +165,7 @@ public fun CallContent( moderationBlurUi: @Composable (Call) -> Unit = {}, moderationWarningUi: @Composable (Call, String?) -> Unit = { _, message -> val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() - val displayTime = callServiceConfig.moderationWarningConfig.displayTime + val displayTime = callServiceConfig.moderationConfig.moderationWarningConfig.displayTime DefaultModerationWarningUiContainer( call, message, @@ -269,7 +269,7 @@ private fun ModerationBlurUi(call: Call, moderationBlurUi: @Composable (Call) -> is CallModerationBlurEvent -> { blurEvent = event call.state.moderationManager.enableVideoModeration() - delay(callServiceConfig.moderationBlurConfig.blurDuration) + delay(callServiceConfig.moderationConfig.videoModerationConfig.blurDuration) blurEvent = null // auto-dismiss after config duration call.state.moderationManager.disableVideoModeration() } @@ -294,7 +294,7 @@ private fun ModerationWarningRootUi( when (event) { is CallModerationWarningEvent -> { warningEvent = event - delay(callServiceConfig.moderationWarningConfig.displayTime) + delay(callServiceConfig.moderationConfig.moderationWarningConfig.displayTime) warningEvent = null // auto-dismiss after config duration } } From 11df0acdcec3f5074fde7c212ffdd30460a57d14 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 18:25:18 +0530 Subject: [PATCH 19/47] refactor all moderation related configuration into ModerationConfig --- .../api/stream-video-android-core.api | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 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 7ab627f34cf..7e386b5f9ce 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -11785,18 +11785,17 @@ public final class io/getstream/video/android/core/moderations/CallModerationCon public static final field POLICY_VIOLATION Ljava/lang/String; } -public final class io/getstream/video/android/core/moderations/ModerationBlurConfig { - public fun (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)V - public synthetic fun (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun component2 ()J - public final fun component3 ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter; - public final fun copy (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)Lio/getstream/video/android/core/moderations/ModerationBlurConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/moderations/ModerationBlurConfig;ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;ILjava/lang/Object;)Lio/getstream/video/android/core/moderations/ModerationBlurConfig; +public final class io/getstream/video/android/core/moderations/ModerationConfig { + public fun ()V + public fun (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/VideoModerationConfig;)V + public synthetic fun (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/VideoModerationConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/core/moderations/ModerationWarningConfig; + public final fun component2 ()Lio/getstream/video/android/core/moderations/VideoModerationConfig; + public final fun copy (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/VideoModerationConfig;)Lio/getstream/video/android/core/moderations/ModerationConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/moderations/ModerationConfig;Lio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/VideoModerationConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/moderations/ModerationConfig; public fun equals (Ljava/lang/Object;)Z - public final fun getBitmapVideoFilter ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter; - public final fun getBlurDuration ()J - public final fun getEnable ()Z + public final fun getModerationWarningConfig ()Lio/getstream/video/android/core/moderations/ModerationWarningConfig; + public final fun getVideoModerationConfig ()Lio/getstream/video/android/core/moderations/VideoModerationConfig; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -11821,6 +11820,22 @@ public final class io/getstream/video/android/core/moderations/ModerationWarning public fun toString ()Ljava/lang/String; } +public final class io/getstream/video/android/core/moderations/VideoModerationConfig { + public fun (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)V + public synthetic fun (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component2 ()J + public final fun component3 ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter; + public final fun copy (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)Lio/getstream/video/android/core/moderations/VideoModerationConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/moderations/VideoModerationConfig;ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;ILjava/lang/Object;)Lio/getstream/video/android/core/moderations/VideoModerationConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getBitmapVideoFilter ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter; + public final fun getBlurDuration ()J + public final fun getEnable ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public class io/getstream/video/android/core/notifications/DefaultNotificationHandler : io/getstream/android/push/permissions/NotificationPermissionHandler, io/getstream/video/android/core/notifications/NotificationHandler { public static final field Companion Lio/getstream/video/android/core/notifications/DefaultNotificationHandler$Companion; public fun (Landroid/app/Application;Lio/getstream/android/push/permissions/NotificationPermissionHandler;ZI)V @@ -12256,23 +12271,21 @@ public final class io/getstream/video/android/core/notifications/internal/receiv public final class io/getstream/video/android/core/notifications/internal/service/CallServiceConfig { public fun ()V - public fun (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/ModerationBlurConfig;)V - public synthetic fun (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/ModerationBlurConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationConfig;)V + public synthetic fun (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z public final fun component2 ()I public final fun component3 ()Ljava/util/Map; public final fun component4 ()Ljava/lang/Class; public final fun component5 ()Z - public final fun component6 ()Lio/getstream/video/android/core/moderations/ModerationWarningConfig; - public final fun component7 ()Lio/getstream/video/android/core/moderations/ModerationBlurConfig; - public final fun copy (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/ModerationBlurConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/ModerationBlurConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun component6 ()Lio/getstream/video/android/core/moderations/ModerationConfig; + public final fun copy (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; public fun equals (Ljava/lang/Object;)Z public final fun getAudioUsage ()I public final fun getCallServicePerType ()Ljava/util/Map; public final fun getEnableTelecom ()Z - public final fun getModerationBlurConfig ()Lio/getstream/video/android/core/moderations/ModerationBlurConfig; - public final fun getModerationWarningConfig ()Lio/getstream/video/android/core/moderations/ModerationWarningConfig; + public final fun getModerationConfig ()Lio/getstream/video/android/core/moderations/ModerationConfig; public final fun getRunCallServiceInForeground ()Z public final fun getServiceClass ()Ljava/lang/Class; public fun hashCode ()I @@ -12284,8 +12297,7 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final fun build ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; public final fun enableTelecom (Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setAudioUsage (I)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; - public final fun setModerationBlurConfig (Lio/getstream/video/android/core/moderations/ModerationBlurConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; - public final fun setModerationWarningConfig (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; + public final fun setModerationConfig (Lio/getstream/video/android/core/moderations/ModerationConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setRunCallServiceInForeground (Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setServiceClass (Ljava/lang/Class;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; } From 5ce5293e1abf58fab38714800a67b3718cd6cab0 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 18:56:10 +0530 Subject: [PATCH 20/47] refactor all moderation related configuration into ModerationConfig --- .../getstream/video/android/util/StreamVideoInitHelper.kt | 7 ++++++- .../compose/ui/components/call/activecall/CallContent.kt | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 525cb1c146f..9331ad9d66f 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -35,6 +35,8 @@ import io.getstream.video.android.core.call.CallType import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.moderations.ModerationConfig +import io.getstream.video.android.core.moderations.ModerationWarningConfig +import io.getstream.video.android.core.moderations.VideoModerationConfig import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver import io.getstream.video.android.core.notifications.DefaultStreamIntentResolver import io.getstream.video.android.core.notifications.NotificationConfig @@ -213,7 +215,10 @@ object StreamVideoInitHelper { register(DefaultCallConfigurations.getLivestreamGuestCallServiceConfig()) register(CallType.AudioCall.name) { enableTelecom(true) } register(CallType.AnyMarker.name) { - setModerationConfig(ModerationConfig()) + setModerationConfig(ModerationConfig( + moderationWarningConfig = ModerationWarningConfig(enable = true, displayTime = 5_000L,), + videoModerationConfig = VideoModerationConfig(enable = true, blurDuration = 25_000L) + )) } } 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 f38f35f6d59..10f0f3f01a4 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 @@ -162,8 +162,8 @@ public fun CallContent( pictureInPictureContent: @Composable (Call) -> Unit = { DefaultPictureInPictureContent(it) }, enableDiagnostics: Boolean = false, closedCaptionUi: @Composable (Call) -> Unit = {}, - moderationBlurUi: @Composable (Call) -> Unit = {}, - moderationWarningUi: @Composable (Call, String?) -> Unit = { _, message -> + videoModerationBlurUi: @Composable (Call) -> Unit = {}, + videoModerationWarningUi: @Composable (Call, String?) -> Unit = { _, message -> val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() val displayTime = callServiceConfig.moderationConfig.moderationWarningConfig.displayTime DefaultModerationWarningUiContainer( @@ -252,8 +252,8 @@ public fun CallContent( } } closedCaptionUi(call) - ModerationBlurUi(call, moderationBlurUi) - ModerationWarningRootUi(call, moderationWarningUi) + ModerationBlurUi(call, videoModerationBlurUi) + ModerationWarningRootUi(call, videoModerationWarningUi) }, ) } From 129d851321a85be9208d55b87d54bc569ffc0498 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 19:02:58 +0530 Subject: [PATCH 21/47] refactor --- .../android/util/StreamVideoInitHelper.kt | 16 +++++++--- .../core/moderations/ModerationConfig.kt | 30 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 9331ad9d66f..f8cb6ea6b0c 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -215,10 +215,18 @@ object StreamVideoInitHelper { register(DefaultCallConfigurations.getLivestreamGuestCallServiceConfig()) register(CallType.AudioCall.name) { enableTelecom(true) } register(CallType.AnyMarker.name) { - setModerationConfig(ModerationConfig( - moderationWarningConfig = ModerationWarningConfig(enable = true, displayTime = 5_000L,), - videoModerationConfig = VideoModerationConfig(enable = true, blurDuration = 25_000L) - )) + setModerationConfig( + ModerationConfig( + moderationWarningConfig = ModerationWarningConfig( + enable = true, + displayTime = 5_000L, + ), + videoModerationConfig = VideoModerationConfig( + enable = true, + blurDuration = 25_000L, + ), + ), + ) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt index e16d509e076..c83cfc8a18d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationConfig.kt @@ -19,14 +19,44 @@ package io.getstream.video.android.core.moderations import io.getstream.video.android.core.call.video.BitmapVideoFilter import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter +/** + * Configuration for displaying moderation warnings during a call. + * + * @property enable Whether moderation warnings are enabled. + * @property displayTime The duration (in milliseconds) for which the moderation warning should be visible. + */ data class ModerationWarningConfig(val enable: Boolean, val displayTime: Long) +/** + * Configuration for video moderation behavior during policy violations. + * + * @property enable Whether video moderation (e.g., blurring) is enabled. + * @property blurDuration The duration (in milliseconds) for which the blur effect should remain active. + * @property bitmapVideoFilter The [BitmapVideoFilter] used to apply the moderation effect. + * By default, [DefaultModerationVideoFilter] is used. + */ data class VideoModerationConfig( val enable: Boolean, val blurDuration: Long, val bitmapVideoFilter: BitmapVideoFilter = DefaultModerationVideoFilter(), ) +/** + * Top-level configuration for all moderation features. + * + * This class allows customizing both warning and video moderation behavior. + * + * @property moderationWarningConfig Configuration for moderation warnings displayed to the user. + * @property videoModerationConfig Configuration for video moderation (e.g., applying blur effects). + * + * Example usage: + * ``` + * val moderationConfig = ModerationConfig( + * moderationWarningConfig = ModerationWarningConfig(enable = true, displayTime = 5000L), + * videoModerationConfig = VideoModerationConfig(enable = true, blurDuration = 20000L) + * ) + * ``` + */ data class ModerationConfig( val moderationWarningConfig: ModerationWarningConfig = ModerationWarningConfig( true, From c2ad72972f48ea934e286610211f9efff0eda581 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 19:47:31 +0530 Subject: [PATCH 22/47] add ut and comments --- .../core/moderations/ModerationManagerTest.kt | 102 ++++++++++++++++++ .../service/CallServiceConfigBuilderTest.kt | 71 ++++++++++++ .../components/call/activecall/CallContent.kt | 2 + 3 files changed, 175 insertions(+) create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilderTest.kt diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt new file mode 100644 index 00000000000..6472917f571 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt @@ -0,0 +1,102 @@ +package io.getstream.video.android.core.moderations + + + +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.ClientState +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.call.video.BitmapVideoFilter +import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry +import io.mockk.* +import org.junit.After +import org.junit.Before +import org.junit.Test + + +class ModerationManagerTest { + + private lateinit var call: Call + private lateinit var moderationManager: ModerationManager + + @Before + fun setup() { + mockkObject(StreamVideo) + + call = mockk(relaxed = true) + every { call.type } returns "default" + + moderationManager = ModerationManager(call) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `enableVideoModeration sets custom filter when provided`() { + // Given + val customFilter = mockk() + + // When + moderationManager.enableVideoModeration(customFilter) + + // Then + verify { call.videoFilter = customFilter } + } + + @Test + fun `enableVideoModeration sets default filter when no custom filter provided`() { + // Given + val defaultFilter = DefaultModerationVideoFilter() + val moderationConfig = ModerationConfig( + videoModerationConfig = VideoModerationConfig( + enable = true, + blurDuration = 20_000L, + bitmapVideoFilter = defaultFilter + ) + ) + + val callServiceConfig = CallServiceConfig(moderationConfig = moderationConfig) + + val mockStreamVideo = mockk(relaxed = true) + val mockStateStreamVideo = mockk(relaxed = true) + val mockCallConfigRegistry = mockk(relaxed = true) + + every { StreamVideo.instance() } returns mockStreamVideo + every { mockStreamVideo.state } returns mockStateStreamVideo + every { mockStateStreamVideo.callConfigRegistry } returns mockCallConfigRegistry + every { mockCallConfigRegistry.get(any()) } returns callServiceConfig + + // When + moderationManager.enableVideoModeration() + + // Then + verify { call.videoFilter = ofType(DefaultModerationVideoFilter::class) } + + } + + @Test + fun `enableVideoModeration falls back to empty CallServiceConfig when instance is null`() { + // Given + every { StreamVideo.instanceOrNull() } returns null + + // When + moderationManager.enableVideoModeration() + + // Then + verify { call.videoFilter = any() } + } + + @Test + fun `disableVideoModeration sets videoFilter to null`() { + // When + moderationManager.disableVideoModeration() + + // Then + verify { call.videoFilter = null } + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilderTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilderTest.kt new file mode 100644 index 00000000000..92f718908e0 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilderTest.kt @@ -0,0 +1,71 @@ +package io.getstream.video.android.core.notifications.internal.service + +import android.media.AudioAttributes +import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter +import io.getstream.video.android.core.moderations.ModerationConfig +import io.getstream.video.android.core.moderations.VideoModerationConfig +import org.junit.Assert.* +import org.junit.Test + +class CallServiceConfigBuilderTest { + + @Test + fun `build returns default CallServiceConfig when nothing is set`() { + // When + val config = CallServiceConfigBuilder().build() + + // Then + assertEquals(CallService::class.java, config.serviceClass) + assertTrue(config.runCallServiceInForeground) + assertEquals(AudioAttributes.USAGE_VOICE_COMMUNICATION, config.audioUsage) + assertFalse(config.enableTelecom) + assertNotNull(config.moderationConfig) + } + + @Test + fun `build returns CallServiceConfig with custom values`() { + // Given + val customServiceClass = CustomCallService::class.java + val customModerationConfig = ModerationConfig( + videoModerationConfig = VideoModerationConfig( + enable = true, + blurDuration = 5000L, + bitmapVideoFilter = DefaultModerationVideoFilter() + ) + ) + + // When + val config = CallServiceConfigBuilder() + .setServiceClass(customServiceClass) + .setRunCallServiceInForeground(false) + .setAudioUsage(AudioAttributes.USAGE_MEDIA) + .enableTelecom(true) + .setModerationConfig(customModerationConfig) + .build() + + // Then + assertEquals(customServiceClass, config.serviceClass) + assertFalse(config.runCallServiceInForeground) + assertEquals(AudioAttributes.USAGE_MEDIA, config.audioUsage) + assertTrue(config.enableTelecom) + assertEquals(customModerationConfig, config.moderationConfig) + } + + @Test + fun `builder should be reusable without affecting previous built instances`() { + // Given + val builder = CallServiceConfigBuilder() + .setRunCallServiceInForeground(false) + val firstConfig = builder.build() + + // When + builder.enableTelecom(true) + val secondConfig = builder.build() + + // Then + assertFalse(firstConfig.enableTelecom) // unchanged + assertTrue(secondConfig.enableTelecom) + } + + private class CustomCallService +} 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 10f0f3f01a4..8eed0dc484b 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 @@ -105,6 +105,8 @@ import kotlinx.coroutines.delay * @param pictureInPictureConfiguration User can provide Picture-In-Picture configuration. * @param pictureInPictureContent Content shown when the user enters Picture in Picture mode, if it's been enabled in the app. * @param closedCaptionUi You can pass your composable lambda here to render Closed Captions + * @param videoModerationBlurUi You can pass your composable lambda here to render your own UI on top of Blurry video + * @param videoModerationWarningUi You can pass your composable lambda here to render your own video moderation warning UI */ @Composable public fun CallContent( From da0a2af23b12aa5ad03c9c159445f0e8c20629cb Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 12 Nov 2025 19:50:34 +0530 Subject: [PATCH 23/47] add ut and comments --- .../core/moderations/ModerationManagerTest.kt | 30 ++++++++++++++----- .../service/CallServiceConfigBuilderTest.kt | 25 ++++++++++++++-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt index 6472917f571..485a6b369b9 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt @@ -1,6 +1,20 @@ -package io.getstream.video.android.core.moderations - +/* + * 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. + */ +package io.getstream.video.android.core.moderations import io.getstream.video.android.core.Call import io.getstream.video.android.core.ClientState @@ -10,12 +24,15 @@ import io.getstream.video.android.core.call.video.BitmapVideoFilter import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry -import io.mockk.* +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkAll +import io.mockk.verify import org.junit.After import org.junit.Before import org.junit.Test - class ModerationManagerTest { private lateinit var call: Call @@ -56,8 +73,8 @@ class ModerationManagerTest { videoModerationConfig = VideoModerationConfig( enable = true, blurDuration = 20_000L, - bitmapVideoFilter = defaultFilter - ) + bitmapVideoFilter = defaultFilter, + ), ) val callServiceConfig = CallServiceConfig(moderationConfig = moderationConfig) @@ -76,7 +93,6 @@ class ModerationManagerTest { // Then verify { call.videoFilter = ofType(DefaultModerationVideoFilter::class) } - } @Test diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilderTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilderTest.kt index 92f718908e0..d20bfef4697 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilderTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilderTest.kt @@ -1,10 +1,29 @@ +/* + * 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. + */ + package io.getstream.video.android.core.notifications.internal.service import android.media.AudioAttributes import io.getstream.video.android.core.call.video.DefaultModerationVideoFilter import io.getstream.video.android.core.moderations.ModerationConfig import io.getstream.video.android.core.moderations.VideoModerationConfig -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Test class CallServiceConfigBuilderTest { @@ -30,8 +49,8 @@ class CallServiceConfigBuilderTest { videoModerationConfig = VideoModerationConfig( enable = true, blurDuration = 5000L, - bitmapVideoFilter = DefaultModerationVideoFilter() - ) + bitmapVideoFilter = DefaultModerationVideoFilter(), + ), ) // When From fdd638102c7cd0580fa1330ab190dd5729948d42 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 13 Nov 2025 12:24:15 +0530 Subject: [PATCH 24/47] minor changes --- .../components/call/activecall/CallContent.kt | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) 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 8eed0dc484b..694d5478f91 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 @@ -263,23 +263,25 @@ public fun CallContent( @Composable private fun ModerationBlurUi(call: Call, moderationBlurUi: @Composable (Call) -> Unit) { - var blurEvent by remember { mutableStateOf(null) } val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() - LaunchedEffect(call) { - call.events.collect { event -> - when (event) { - is CallModerationBlurEvent -> { - blurEvent = event - call.state.moderationManager.enableVideoModeration() - delay(callServiceConfig.moderationConfig.videoModerationConfig.blurDuration) - blurEvent = null // auto-dismiss after config duration - call.state.moderationManager.disableVideoModeration() + if (callServiceConfig.moderationConfig.videoModerationConfig.enable) { + var blurEvent by remember { mutableStateOf(null) } + LaunchedEffect(call) { + call.events.collect { event -> + when (event) { + is CallModerationBlurEvent -> { + blurEvent = event + call.state.moderationManager.enableVideoModeration() + delay(callServiceConfig.moderationConfig.videoModerationConfig.blurDuration) + blurEvent = null // auto-dismiss after config duration + call.state.moderationManager.disableVideoModeration() + } } } } - } - blurEvent?.let { - moderationBlurUi(call) + blurEvent?.let { + moderationBlurUi(call) + } } } @@ -288,22 +290,25 @@ private fun ModerationWarningRootUi( call: Call, moderationWarningUi: @Composable (Call, String?) -> Unit, ) { - var warningEvent by remember { mutableStateOf(null) } val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() - - LaunchedEffect(call) { - call.events.collect { event -> - when (event) { - is CallModerationWarningEvent -> { - warningEvent = event - delay(callServiceConfig.moderationConfig.moderationWarningConfig.displayTime) - warningEvent = null // auto-dismiss after config duration + if (callServiceConfig.moderationConfig.moderationWarningConfig.enable) { + var warningEvent by remember { mutableStateOf(null) } + LaunchedEffect(call) { + call.events.collect { event -> + when (event) { + is CallModerationWarningEvent -> { + warningEvent = event + delay( + callServiceConfig.moderationConfig.moderationWarningConfig.displayTime, + ) + warningEvent = null // auto-dismiss after config duration + } } } } - } - warningEvent?.let { event -> - moderationWarningUi(call, event.message) + warningEvent?.let { event -> + moderationWarningUi(call, event.message) + } } } From 19ce5e28ebda69b5e13bafbbc97afe9e64793a51 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 13 Nov 2025 13:32:54 +0530 Subject: [PATCH 25/47] Make blurring a part of core-sdk --- .../io/getstream/video/android/core/CallState.kt | 13 +++++++++++++ .../ui/components/call/activecall/CallContent.kt | 2 -- 2 files changed, 13 insertions(+), 2 deletions(-) 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 e5c90d08f22..a62b4b4b3be 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 @@ -33,6 +33,7 @@ import io.getstream.android.video.generated.models.CallMemberAddedEvent import io.getstream.android.video.generated.models.CallMemberRemovedEvent import io.getstream.android.video.generated.models.CallMemberUpdatedEvent import io.getstream.android.video.generated.models.CallMemberUpdatedPermissionEvent +import io.getstream.android.video.generated.models.CallModerationBlurEvent import io.getstream.android.video.generated.models.CallParticipantResponse import io.getstream.android.video.generated.models.CallReactionEvent import io.getstream.android.video.generated.models.CallRecordingStartedEvent @@ -108,6 +109,7 @@ import io.getstream.video.android.core.model.ScreenSharingSession import io.getstream.video.android.core.model.VisibilityOnScreenState import io.getstream.video.android.core.moderations.ModerationManager import io.getstream.video.android.core.notifications.IncomingNotificationData +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository import io.getstream.video.android.core.permission.PermissionRequest import io.getstream.video.android.core.pinning.PinType @@ -1128,6 +1130,17 @@ public class CallState( is ClosedCaptionEvent, is CallClosedCaptionsStoppedEvent, -> closedCaptionManager.handleEvent(event) + + is CallModerationBlurEvent -> { + scope.launch { + val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() + if (callServiceConfig.moderationConfig.videoModerationConfig.enable) { + call.state.moderationManager.enableVideoModeration() + delay(callServiceConfig.moderationConfig.videoModerationConfig.blurDuration) + call.state.moderationManager.disableVideoModeration() + } + } + } } } 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 694d5478f91..2d3b5b57e53 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 @@ -271,10 +271,8 @@ private fun ModerationBlurUi(call: Call, moderationBlurUi: @Composable (Call) -> when (event) { is CallModerationBlurEvent -> { blurEvent = event - call.state.moderationManager.enableVideoModeration() delay(callServiceConfig.moderationConfig.videoModerationConfig.blurDuration) blurEvent = null // auto-dismiss after config duration - call.state.moderationManager.disableVideoModeration() } } } From 06001c5ad097aa5ed0d7d5215d6e00e81414e8a6 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 13 Nov 2025 17:23:27 +0530 Subject: [PATCH 26/47] rename methods --- .../api/stream-video-android-core.api | 6 +++--- .../kotlin/io/getstream/video/android/core/CallState.kt | 4 ++-- .../video/android/core/moderations/ModerationManager.kt | 4 ++-- .../android/core/moderations/ModerationManagerTest.kt | 8 ++++---- 4 files changed, 11 insertions(+), 11 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 7e386b5f9ce..143faa93138 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -11802,9 +11802,9 @@ public final class io/getstream/video/android/core/moderations/ModerationConfig public final class io/getstream/video/android/core/moderations/ModerationManager { public fun (Lio/getstream/video/android/core/Call;)V - public final fun disableVideoModeration ()V - public final fun enableVideoModeration (Lio/getstream/video/android/core/call/video/BitmapVideoFilter;)V - public static synthetic fun enableVideoModeration$default (Lio/getstream/video/android/core/moderations/ModerationManager;Lio/getstream/video/android/core/call/video/BitmapVideoFilter;ILjava/lang/Object;)V + public final fun applyVideoModeration (Lio/getstream/video/android/core/call/video/BitmapVideoFilter;)V + public static synthetic fun applyVideoModeration$default (Lio/getstream/video/android/core/moderations/ModerationManager;Lio/getstream/video/android/core/call/video/BitmapVideoFilter;ILjava/lang/Object;)V + public final fun clearVideoModeration ()V } public final class io/getstream/video/android/core/moderations/ModerationWarningConfig { 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 a62b4b4b3be..344627c6942 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 @@ -1135,9 +1135,9 @@ public class CallState( scope.launch { val callServiceConfig = StreamVideo.instanceOrNull()?.state?.callConfigRegistry?.get(call.type) ?: CallServiceConfig() if (callServiceConfig.moderationConfig.videoModerationConfig.enable) { - call.state.moderationManager.enableVideoModeration() + call.state.moderationManager.applyVideoModeration() delay(callServiceConfig.moderationConfig.videoModerationConfig.blurDuration) - call.state.moderationManager.disableVideoModeration() + call.state.moderationManager.clearVideoModeration() } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt index b63af76a364..45a95f083cb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/moderations/ModerationManager.kt @@ -23,7 +23,7 @@ import io.getstream.video.android.core.notifications.internal.service.CallServic class ModerationManager(private val call: Call) { - fun enableVideoModeration(bitmapVideoFilter: BitmapVideoFilter? = null) { + fun applyVideoModeration(bitmapVideoFilter: BitmapVideoFilter? = null) { if (bitmapVideoFilter != null) { call.videoFilter = bitmapVideoFilter } else { @@ -34,7 +34,7 @@ class ModerationManager(private val call: Call) { } } - fun disableVideoModeration() { + fun clearVideoModeration() { call.videoFilter = null } } diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt index 485a6b369b9..92e854b9915 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/moderations/ModerationManagerTest.kt @@ -59,7 +59,7 @@ class ModerationManagerTest { val customFilter = mockk() // When - moderationManager.enableVideoModeration(customFilter) + moderationManager.applyVideoModeration(customFilter) // Then verify { call.videoFilter = customFilter } @@ -89,7 +89,7 @@ class ModerationManagerTest { every { mockCallConfigRegistry.get(any()) } returns callServiceConfig // When - moderationManager.enableVideoModeration() + moderationManager.applyVideoModeration() // Then verify { call.videoFilter = ofType(DefaultModerationVideoFilter::class) } @@ -101,7 +101,7 @@ class ModerationManagerTest { every { StreamVideo.instanceOrNull() } returns null // When - moderationManager.enableVideoModeration() + moderationManager.applyVideoModeration() // Then verify { call.videoFilter = any() } @@ -110,7 +110,7 @@ class ModerationManagerTest { @Test fun `disableVideoModeration sets videoFilter to null`() { // When - moderationManager.disableVideoModeration() + moderationManager.clearVideoModeration() // Then verify { call.videoFilter = null } From 6d3d9a244238156f336d2c35751e7855ed407e4f Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 27 Nov 2025 13:54:39 +0530 Subject: [PATCH 27/47] add token repository --- .../video/android/core/StreamVideoBuilder.kt | 7 +++-- .../video/android/core/StreamVideoClient.kt | 9 ++++-- .../video/android/core/call/RtcSession.kt | 2 ++ .../module/CoordinatorAuthInterceptor.kt | 5 ++-- .../module/CoordinatorConnectionModule.kt | 7 +++-- .../internal/module/SfuConnectionModule.kt | 8 +++-- .../common/token/CacheableTokenProvider.kt | 11 ++++--- .../common/token/ConstantTokenProvider.kt | 4 +-- .../socket/common/token/TokenManagerImpl.kt | 17 +++++------ .../socket/common/token/TokenRepository.kt | 12 ++++++++ .../socket/coordinator/CoordinatorSocket.kt | 30 +++++++++++++++++-- .../CoordinatorSocketConnection.kt | 14 ++++++--- .../core/socket/sfu/SfuSocketConnection.kt | 6 ++-- 13 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index cc9dad27aed..fb0f3444654 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -39,6 +39,7 @@ import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.sounds.RingingCallVibrationConfig import io.getstream.video.android.core.sounds.Sounds import io.getstream.video.android.core.sounds.defaultResourcesRingingConfig @@ -106,7 +107,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( object : TokenProvider { override suspend fun loadToken(): String = legacy.invoke(null) } - } ?: ConstantTokenProvider(token), + } ?: ConstantTokenProvider(TokenRepository(token)), private val loggingLevel: LoggingLevel = LoggingLevel(), private val notificationConfig: NotificationConfig = NotificationConfig(), private val ringNotification: ((call: Call) -> Notification?)? = null, @@ -214,7 +215,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( // Android JSR-310 backport backport AndroidThreeTen.init(context) - + val tokenRepository = TokenRepository(token) // This connection module class exposes the connections to the various retrofit APIs. val coordinatorConnectionModule = CoordinatorConnectionModule( context = context, @@ -228,6 +229,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( userToken = token, tokenProvider = tokenProvider, lifecycle = lifecycle, + tokenRepository = tokenRepository ) val deviceTokenStorage = DeviceTokenStorage(context) @@ -272,6 +274,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( vibrationConfig = vibrationConfig, enableStereoForSubscriber = enableStereoForSubscriber, telecomConfig = telecomConfig, + tokenRepository = tokenRepository ) if (user.type == UserType.Guest) { 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 8413d696f91..c056f48ef6f 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 @@ -107,6 +107,7 @@ import io.getstream.video.android.core.socket.ErrorResponse import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState import io.getstream.video.android.core.sounds.CallSoundAndVibrationPlayer import io.getstream.video.android.core.sounds.RingingCallVibrationConfig @@ -159,7 +160,8 @@ internal class StreamVideoClient internal constructor( internal var token: String, private val lifecycle: Lifecycle, internal val coordinatorConnectionModule: CoordinatorConnectionModule, - internal val tokenProvider: TokenProvider = ConstantTokenProvider(token), + internal val tokenRepository: TokenRepository, + internal val tokenProvider: TokenProvider = ConstantTokenProvider(tokenRepository), internal val streamNotificationManager: StreamNotificationManager, internal val enableCallNotificationUpdates: Boolean, internal val callServiceConfigRegistry: CallServiceConfigRegistry = CallServiceConfigRegistry(), @@ -271,6 +273,7 @@ internal class StreamVideoClient internal constructor( // Retry once with a new token if the token is expired if (e.isAuthError()) { val newToken = tokenProvider.loadToken() + tokenRepository.updateToken(newToken) token = newToken coordinatorConnectionModule.updateToken(newToken) apiCall() @@ -440,7 +443,7 @@ internal class StreamVideoClient internal constructor( return Failure(Error.GenericError("failed to select location")) } - + //Noob override suspend fun connectAsync(): Deferred> { return scope.async { // wait for the guest user setup if we're using guest users @@ -460,7 +463,7 @@ internal class StreamVideoClient internal constructor( } } } - + //Noob private suspend fun refreshToken(error: Throwable) { tokenProvider?.let { val newToken = tokenProvider.loadToken() 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 249d14e78ef..23677310389 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 @@ -78,6 +78,7 @@ import io.getstream.video.android.core.model.VideoTrack import io.getstream.video.android.core.model.toPeerType import io.getstream.video.android.core.socket.common.VideoParser import io.getstream.video.android.core.socket.common.parser2.MoshiVideoParser +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.sfu.state.SfuSocketState import io.getstream.video.android.core.toJson import io.getstream.video.android.core.trace.PeerConnectionTraceKey @@ -241,6 +242,7 @@ public class RtcSession internal constructor( call.debug.fastReconnect() }, tracer = sfuTracer, + tokenRepository = TokenRepository(sfuToken) ) }, ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorAuthInterceptor.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorAuthInterceptor.kt index 414638653eb..2a33033fe77 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorAuthInterceptor.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorAuthInterceptor.kt @@ -16,6 +16,7 @@ package io.getstream.video.android.core.internal.module +import io.getstream.video.android.core.socket.common.token.TokenRepository import okhttp3.Interceptor import okhttp3.Response import java.io.IOException @@ -25,7 +26,7 @@ import java.io.IOException */ internal class CoordinatorAuthInterceptor( var apiKey: String, - var token: String, + val tokenRepository: TokenRepository, var authType: String = "jwt", ) : Interceptor { @@ -43,7 +44,7 @@ internal class CoordinatorAuthInterceptor( val updated = original.newBuilder() .url(updatedUrl) - .addHeader(HEADER_AUTHORIZATION, token) + .addHeader(HEADER_AUTHORIZATION, tokenRepository.getToken()) .header(STREAM_AUTH_TYPE, authType) .build() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt index e708d362ee4..b9fb8f7231d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt @@ -26,6 +26,7 @@ import io.getstream.video.android.core.header.HeadersUtil import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.coordinator.CoordinatorSocketConnection import io.getstream.video.android.core.trace.Tracer import io.getstream.video.android.model.ApiKey @@ -47,6 +48,7 @@ internal class CoordinatorConnectionModule( context: Context, tokenProvider: TokenProvider, user: User, + tokenRepository: TokenRepository, override val scope: CoroutineScope, // Common API override val apiUrl: String, @@ -59,7 +61,7 @@ internal class CoordinatorConnectionModule( override val tracer: Tracer = Tracer("coordinator"), ) : ConnectionModuleDeclaration { // Internals - private val authInterceptor = CoordinatorAuthInterceptor(apiKey, userToken) + private val authInterceptor = CoordinatorAuthInterceptor(apiKey, tokenRepository) private val retrofit: Retrofit by lazy { Retrofit.Builder().baseUrl(apiUrl) .addConverterFactory(ScalarsConverterFactory.create()) @@ -102,11 +104,12 @@ internal class CoordinatorConnectionModule( scope = scope, lifecycle = lifecycle, tokenProvider = tokenProvider, + tokenRepository = tokenRepository ) override fun updateToken(token: UserToken) { socketConnection.updateToken(token) - authInterceptor.token = token +// authInterceptor.token = token } override fun updateAuthType(authType: String) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt index e0a95525ee2..80696254ba8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt @@ -23,6 +23,7 @@ import io.getstream.video.android.core.api.SignalServerService import io.getstream.video.android.core.call.utils.SignalLostSignalingServiceDecorator import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.sfu.SfuSocketConnection import io.getstream.video.android.core.trace.Tracer import io.getstream.video.android.core.trace.tracedWith @@ -37,6 +38,7 @@ import java.util.concurrent.TimeUnit internal class SfuConnectionModule( context: Context, + val tokenRepository: TokenRepository, override val apiKey: ApiKey, override val apiUrl: String, override val wssUrl: String, @@ -57,7 +59,7 @@ internal class SfuConnectionModule( private fun buildSfuOkHttpClient(): OkHttpClient { val connectionTimeoutInMs = 10000L // create a new OkHTTP client and set timeouts - val authInterceptor = CoordinatorAuthInterceptor(apiKey, userToken) + val authInterceptor = CoordinatorAuthInterceptor(apiKey, tokenRepository) return OkHttpClient.Builder().addInterceptor(authInterceptor).addInterceptor( HttpLoggingInterceptor().apply { level = loggingLevel.httpLoggingLevel.level @@ -93,13 +95,15 @@ internal class SfuConnectionModule( apiKey = apiKey, scope = scope, httpClient = http, - tokenProvider = ConstantTokenProvider(userToken), + tokenProvider = ConstantTokenProvider(tokenRepository), lifecycle = lifecycle, networkStateProvider = networkStateProvider, + tokenRepository = tokenRepository ) override val socketConnection: SfuSocketConnection = _internalSocketConnection override fun updateToken(token: SfuToken) { + tokenRepository.updateToken(token) _internalSocketConnection.updateToken(token) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt index 39d127234ac..bca8f881e65 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt @@ -22,14 +22,17 @@ package io.getstream.video.android.core.socket.common.token * * @property tokenProvider The [TokenProvider] used to obtain new tokens. */ -internal class CacheableTokenProvider(private val tokenProvider: TokenProvider) : TokenProvider { - private var cachedToken = "" - override suspend fun loadToken(): String = tokenProvider.loadToken().also { cachedToken = it } +internal class CacheableTokenProvider( + private val tokenProvider: TokenProvider, + private val tokenRepository: TokenRepository, +) : TokenProvider { + + override suspend fun loadToken(): String = tokenProvider.loadToken().also { tokenRepository.updateToken(it) } /** * Obtain the cached token. * * @return The cached token. */ - fun getCachedToken(): String = cachedToken + fun getCachedToken(): String = tokenRepository.getToken() } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt index 03f32439eac..4cead1bcec4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt @@ -16,6 +16,6 @@ package io.getstream.video.android.core.socket.common.token -internal class ConstantTokenProvider(private val token: String) : TokenProvider { - override suspend fun loadToken(): String = token +internal class ConstantTokenProvider(private val tokenRepository: TokenRepository) : TokenProvider { + override suspend fun loadToken(): String = tokenRepository.getToken() } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt index e4c1bca4e62..02560c6b6e1 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt @@ -16,13 +16,12 @@ package io.getstream.video.android.core.socket.common.token -internal class TokenManagerImpl : TokenManager { - @Volatile - private var token: String = EMPTY_TOKEN +internal class TokenManagerImpl(private val tokenRepository: TokenRepository) : TokenManager { + private lateinit var provider: TokenProvider override fun updateToken(token: String) { - this.token = token + this.tokenRepository.updateToken(token) } override suspend fun ensureTokenLoaded() { @@ -33,27 +32,27 @@ internal class TokenManagerImpl : TokenManager { override suspend fun loadSync(): String { return provider.loadToken().also { - this.token = it + this.tokenRepository.updateToken(it) } } override fun setTokenProvider(provider: CacheableTokenProvider) { this.provider = provider - this.token = provider.getCachedToken() + this.tokenRepository.updateToken(provider.getCachedToken()) } override fun hasTokenProvider(): Boolean { return this::provider.isInitialized } - override fun getToken(): String = token + override fun getToken(): String = tokenRepository.getToken() override fun hasToken(): Boolean { - return token != EMPTY_TOKEN + return tokenRepository.getToken() != EMPTY_TOKEN } override fun expireToken() { - token = EMPTY_TOKEN + tokenRepository.updateToken(EMPTY_TOKEN) } companion object { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt new file mode 100644 index 00000000000..34188b6d2d8 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt @@ -0,0 +1,12 @@ +package io.getstream.video.android.core.socket.common.token + +import io.getstream.video.android.core.socket.common.token.TokenManagerImpl.Companion.EMPTY_TOKEN + +class TokenRepository(@Volatile private var token: String = EMPTY_TOKEN) { + + fun updateToken(token: String) { + this.token = token + } + + fun getToken(): String = token +} \ No newline at end of file diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt index 7e939e92951..84f751a9319 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt @@ -23,6 +23,7 @@ import io.getstream.android.video.generated.models.VideoEvent import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.dispatchers.DispatcherProvider import io.getstream.video.android.core.errors.DisconnectCause import io.getstream.video.android.core.errors.VideoErrorCode @@ -277,12 +278,35 @@ internal open class CoordinatorSocket( if (VideoErrorCode.isAuthenticationError(error.serverErrorCode)) { tokenManager.expireToken() } + //Noob + when(error.serverErrorCode){ + VideoErrorCode.VALIDATION_ERROR.code, + VideoErrorCode.TOKEN_EXPIRED.code -> { + StreamVideo.instanceOrNull()?.let { + val result = (it as StreamVideoClient).getEdges() + result.onSuccess { + logger.d { "edges success result: $result" } + }.onError { + logger.d { "edges fail result: $result" } + } + + } + tokenManager.expireToken() + logger.d { "load sync START" } + val token = tokenManager.loadSync() + tokenManager.updateToken(token) + if (token.isNotEmpty()) { + logger.d { "load sync END: $token" } + return + } + } + else->{} + } when (error.serverErrorCode) { VideoErrorCode.UNDEFINED_TOKEN.code, VideoErrorCode.INVALID_TOKEN.code, VideoErrorCode.API_KEY_NOT_FOUND.code, - VideoErrorCode.VALIDATION_ERROR.code, -> { logger.d { "One unrecoverable error happened. Error: $error. Error code: ${error.serverErrorCode}" @@ -290,7 +314,9 @@ internal open class CoordinatorSocket( coordinatorSocketStateService.onUnrecoverableError(error) } - else -> coordinatorSocketStateService.onNetworkError(error) + else -> { + coordinatorSocketStateService.onNetworkError(error) + } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt index b075c461f5f..58e775d8d5f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -38,6 +38,7 @@ import io.getstream.video.android.core.socket.common.scope.UserScope import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider import io.getstream.video.android.core.socket.common.token.TokenManagerImpl import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState import io.getstream.video.android.core.utils.isWhitespaceOnly import io.getstream.video.android.core.utils.mapState @@ -81,12 +82,13 @@ public open class CoordinatorSocketConnection( private val lifecycle: Lifecycle, /** Token provider */ private val tokenProvider: TokenProvider, + private val tokenRepository: TokenRepository ) : SocketListener(), SocketActions { // Private state private val parser: VideoParser = MoshiVideoParser() - private val tokenManager = TokenManagerImpl() + private val tokenManager = TokenManagerImpl(tokenRepository) // Internal state private val logger by taggedLogger("Video:Socket") @@ -120,13 +122,13 @@ public open class CoordinatorSocketConnection( // Init init { - tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider)) + tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider, tokenRepository)) } // Extension opportunity for subclasses override fun onCreated() { super.onCreated() - logger.d { "[onCreated] Socket is created" } + logger.d { "[onCreated] Socket is created, initial token: $token, tokenManager.getToken() = ${tokenManager.getToken()}" } scope.launch { logger.d { "[onConnected] Video socket created, user: $user" } if (token.isEmpty()) { @@ -134,7 +136,7 @@ public open class CoordinatorSocketConnection( disconnect() } else { val authRequest = WSAuthMessageRequest( - token = token, + token = tokenManager.getToken().ifEmpty { token }, userDetails = ConnectUserDetailsRequest( id = user.id, name = user.name.takeUnless { it.isWhitespaceOnly() }, @@ -197,6 +199,7 @@ public open class CoordinatorSocketConnection( connectionTimeout: Long, connected: suspend (connectionId: String) -> Unit, ) { + logger.d {"[whenConnected]"} scope.launch { internalSocket.awaitConnection(connectionTimeout) internalSocket.connectionIdOrError().also { @@ -215,16 +218,19 @@ public open class CoordinatorSocketConnection( override suspend fun sendEvent(event: VideoEvent): Boolean = internalSocket.sendEvent(event) override suspend fun connect(connectData: User) { + logger.d {"[connect]"} internalSocket.connectUser(connectData, connectData.isAnonymous()) } override suspend fun reconnect(data: User, force: Boolean) { + logger.d {"[reconnect]"} internalSocket.reconnectUser(data, data.isAnonymous(), force) } override suspend fun disconnect() = internalSocket.disconnect() override fun updateToken(token: UserToken) { + logger.d {"[updateToken]"} tokenManager.updateToken(token) } } 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 07693ed8019..74887451908 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 @@ -34,6 +34,7 @@ import io.getstream.video.android.core.socket.common.scope.UserScope import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider import io.getstream.video.android.core.socket.common.token.TokenManagerImpl import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.sfu.state.SfuSocketState import io.getstream.video.android.core.utils.mapState import io.getstream.video.android.model.ApiKey @@ -62,6 +63,7 @@ class SfuSocketConnection( private val lifecycle: Lifecycle, /** Token provider */ private val tokenProvider: TokenProvider, + private val tokenRepository: TokenRepository, ) : SocketListener(), SocketActions { @@ -70,7 +72,7 @@ class SfuSocketConnection( } private val logger by taggedLogger("Video:SfuSocket") - private val tokenManager = TokenManagerImpl() + private val tokenManager = TokenManagerImpl(tokenRepository) private val internalSocket: SfuSocket = SfuSocket( wssUrl = url, apiKey = apiKey, @@ -100,7 +102,7 @@ class SfuSocketConnection( // Initialization init { - tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider)) + tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider, tokenRepository)) } override fun onCreated() { From 9428b6ef3c3e3a9aa0a8a2b8a7687a9b0c99ae9d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 27 Nov 2025 14:20:28 +0530 Subject: [PATCH 28/47] add expiry-time in demo-app --- demo-app/build.gradle.kts | 3 ++ .../data/services/stream/StreamService.kt | 18 ++++++++-- .../video/android/ui/login/LoginViewModel.kt | 2 ++ .../android/util/StreamVideoInitHelper.kt | 33 +++++++++++++++++-- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/demo-app/build.gradle.kts b/demo-app/build.gradle.kts index 21a9e23cef4..b13cb6f4dca 100644 --- a/demo-app/build.gradle.kts +++ b/demo-app/build.gradle.kts @@ -294,6 +294,9 @@ dependencies { implementation(libs.audioswitch) + //Logging + implementation(libs.okhttp.logging) + // Also Leak Canary added in the previous block // Instrumentation tests diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt index eb2262c0032..518c96e3586 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt @@ -17,11 +17,14 @@ package io.getstream.video.android.data.services.stream import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import io.getstream.log.streamLog import io.getstream.video.android.model.User import io.getstream.video.android.models.UserCredentials import io.getstream.video.android.models.builtInCredentials import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.create import retrofit2.http.GET @@ -32,23 +35,32 @@ fun interface StreamService { suspend fun getAuthData( @Query("environment") environment: String, @Query("user_id") userId: String?, + @Query("exp") exp: Int, ): GetAuthDataResponse companion object { private const val BASE_URL = "https://pronto.getstream.io/" + public const val TOKEN_EXPIRY_TIME = 20 private val json = Json { ignoreUnknownKeys = true } - + private val okHttpClient = OkHttpClient.Builder() + .addInterceptor(HttpLoggingInterceptor { + streamLog(tag = "Video:Http") { it } + }.apply { + level = HttpLoggingInterceptor.Level.BODY + }) + .build() private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .client(okHttpClient) .build() private val serviceInstance = retrofit.create() - val instance = StreamService { environment, userId -> + val instance = StreamService { environment, userId, exp -> User.builtInCredentials[userId]?.toAuthDataResponse() - ?: serviceInstance.getAuthData(environment, userId) + ?: serviceInstance.getAuthData(environment, userId, exp) } } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt index c66b2a9649d..05e39754b9b 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt @@ -92,6 +92,7 @@ class LoginViewModel @Inject constructor( val authData = StreamService.instance.getAuthData( environment = it.env, userId = userId, + StreamService.TOKEN_EXPIRY_TIME ) val loggedInGoogleUser = if (autoLogIn) null else googleAccountRepository.getCurrentUser() @@ -128,6 +129,7 @@ class LoginViewModel @Inject constructor( val authData = StreamService.instance.getAuthData( environment = it.env, userId = user.id, + StreamService.TOKEN_EXPIRY_TIME ) // Store the data in the demo app dataStore.updateUser(user) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index f8cb6ea6b0c..99a8e422861 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -61,6 +61,7 @@ import io.getstream.video.android.util.config.AppConfig import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.runBlocking public enum class InitializedState { NOT_STARTED, RUNNING, FINISHED, FAILED @@ -124,6 +125,7 @@ object StreamVideoInitHelper { authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = userId, + StreamService.TOKEN_EXPIRY_TIME ) loggedInUser = User(id = authData.userId, role = "admin") @@ -140,6 +142,7 @@ object StreamVideoInitHelper { authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = loggedInUser.id, + StreamService.TOKEN_EXPIRY_TIME ) } @@ -195,9 +198,28 @@ object StreamVideoInitHelper { image = user.image.orEmpty(), ) +// chatClient.connectUser( +// user = chatUser, +// token = token, +// ).enqueue() + chatClient.connectUser( user = chatUser, - token = token, + tokenProvider = object : io.getstream.chat.android.client.token.TokenProvider { + override fun loadToken(): String { + return runBlocking { + Log.d("Noob","Chat token provider") + val email = user.custom?.get("email") + val authData = StreamService.instance.getAuthData( + environment = AppConfig.currentEnvironment.value!!.env, + userId = email, + StreamService.TOKEN_EXPIRY_TIME + ) + authData.token + } + } + + }, ).enqueue() } @@ -303,11 +325,16 @@ object StreamVideoInitHelper { ), tokenProvider = object : TokenProvider { override suspend fun loadToken(): String { - val email = user.custom?.get("email") + val userEmail = user.custom?.get("email") + val userId = user.id + val userIdForTokenRenewal = if (userEmail.isNullOrEmpty()) userId else userEmail + Log.d("Noob","Video token provider START") val authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, - userId = email, + userId = userIdForTokenRenewal, + StreamService.TOKEN_EXPIRY_TIME ) + Log.d("Noob","Video token provider END: ${authData.token}") return authData.token } }, From 8d9b961beb0c0460f6b4497bd43f27bf84dc5c49 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 27 Nov 2025 14:22:28 +0530 Subject: [PATCH 29/47] spotless and apidump --- .../data/services/stream/StreamService.kt | 12 +++++++----- .../video/android/ui/login/LoginViewModel.kt | 4 ++-- .../android/util/StreamVideoInitHelper.kt | 15 +++++++-------- .../api/stream-video-android-core.api | 16 ++++++++++++---- .../video/android/core/StreamVideoBuilder.kt | 4 ++-- .../video/android/core/StreamVideoClient.kt | 6 ++++-- .../video/android/core/call/RtcSession.kt | 2 +- .../module/CoordinatorConnectionModule.kt | 2 +- .../internal/module/SfuConnectionModule.kt | 2 +- .../common/token/CacheableTokenProvider.kt | 4 +++- .../socket/common/token/TokenRepository.kt | 18 +++++++++++++++++- .../socket/coordinator/CoordinatorSocket.kt | 10 +++++----- .../coordinator/CoordinatorSocketConnection.kt | 14 ++++++++------ 13 files changed, 70 insertions(+), 39 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt index 518c96e3586..d046149ed59 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt @@ -44,11 +44,13 @@ fun interface StreamService { private val json = Json { ignoreUnknownKeys = true } private val okHttpClient = OkHttpClient.Builder() - .addInterceptor(HttpLoggingInterceptor { - streamLog(tag = "Video:Http") { it } - }.apply { - level = HttpLoggingInterceptor.Level.BODY - }) + .addInterceptor( + HttpLoggingInterceptor { + streamLog(tag = "Video:Http") { it } + }.apply { + level = HttpLoggingInterceptor.Level.BODY + }, + ) .build() private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt index 05e39754b9b..a200efe5b29 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt @@ -92,7 +92,7 @@ class LoginViewModel @Inject constructor( val authData = StreamService.instance.getAuthData( environment = it.env, userId = userId, - StreamService.TOKEN_EXPIRY_TIME + StreamService.TOKEN_EXPIRY_TIME, ) val loggedInGoogleUser = if (autoLogIn) null else googleAccountRepository.getCurrentUser() @@ -129,7 +129,7 @@ class LoginViewModel @Inject constructor( val authData = StreamService.instance.getAuthData( environment = it.env, userId = user.id, - StreamService.TOKEN_EXPIRY_TIME + StreamService.TOKEN_EXPIRY_TIME, ) // Store the data in the demo app dataStore.updateUser(user) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 99a8e422861..1e32a6e477b 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -125,7 +125,7 @@ object StreamVideoInitHelper { authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = userId, - StreamService.TOKEN_EXPIRY_TIME + StreamService.TOKEN_EXPIRY_TIME, ) loggedInUser = User(id = authData.userId, role = "admin") @@ -142,7 +142,7 @@ object StreamVideoInitHelper { authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = loggedInUser.id, - StreamService.TOKEN_EXPIRY_TIME + StreamService.TOKEN_EXPIRY_TIME, ) } @@ -208,17 +208,16 @@ object StreamVideoInitHelper { tokenProvider = object : io.getstream.chat.android.client.token.TokenProvider { override fun loadToken(): String { return runBlocking { - Log.d("Noob","Chat token provider") + Log.d("Noob", "Chat token provider") val email = user.custom?.get("email") val authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = email, - StreamService.TOKEN_EXPIRY_TIME + StreamService.TOKEN_EXPIRY_TIME, ) authData.token } } - }, ).enqueue() } @@ -328,13 +327,13 @@ object StreamVideoInitHelper { val userEmail = user.custom?.get("email") val userId = user.id val userIdForTokenRenewal = if (userEmail.isNullOrEmpty()) userId else userEmail - Log.d("Noob","Video token provider START") + Log.d("Noob", "Video token provider START") val authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = userIdForTokenRenewal, - StreamService.TOKEN_EXPIRY_TIME + StreamService.TOKEN_EXPIRY_TIME, ) - Log.d("Noob","Video token provider END: ${authData.token}") + Log.d("Noob", "Video token provider END: ${authData.token}") return authData.token } }, 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 143faa93138..3ef127d2ffb 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -12829,9 +12829,17 @@ public abstract interface class io/getstream/video/android/core/socket/common/to public abstract fun loadToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class io/getstream/video/android/core/socket/common/token/TokenRepository { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getToken ()Ljava/lang/String; + public final fun updateToken (Ljava/lang/String;)V +} + public class io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection : io/getstream/video/android/core/socket/common/SocketListener, io/getstream/video/android/core/socket/common/SocketActions { - public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun connect (Lio/getstream/video/android/model/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public synthetic fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; @@ -13038,8 +13046,8 @@ public final class io/getstream/video/android/core/socket/coordinator/state/Vide public final class io/getstream/video/android/core/socket/sfu/SfuSocketConnection : io/getstream/video/android/core/socket/common/SocketListener, io/getstream/video/android/core/socket/common/SocketActions { public static final field Companion Lio/getstream/video/android/core/socket/sfu/SfuSocketConnection$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connect (Lstream/video/sfu/event/JoinRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index fb0f3444654..84bfcbf862c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -229,7 +229,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( userToken = token, tokenProvider = tokenProvider, lifecycle = lifecycle, - tokenRepository = tokenRepository + tokenRepository = tokenRepository, ) val deviceTokenStorage = DeviceTokenStorage(context) @@ -274,7 +274,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( vibrationConfig = vibrationConfig, enableStereoForSubscriber = enableStereoForSubscriber, telecomConfig = telecomConfig, - tokenRepository = tokenRepository + tokenRepository = tokenRepository, ) if (user.type == UserType.Guest) { 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 c056f48ef6f..332c1a19356 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 @@ -443,7 +443,8 @@ internal class StreamVideoClient internal constructor( return Failure(Error.GenericError("failed to select location")) } - //Noob + + // Noob override suspend fun connectAsync(): Deferred> { return scope.async { // wait for the guest user setup if we're using guest users @@ -463,7 +464,8 @@ internal class StreamVideoClient internal constructor( } } } - //Noob + + // Noob private suspend fun refreshToken(error: Throwable) { tokenProvider?.let { val newToken = tokenProvider.loadToken() 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 23677310389..3b5e3c5cc02 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 @@ -242,7 +242,7 @@ public class RtcSession internal constructor( call.debug.fastReconnect() }, tracer = sfuTracer, - tokenRepository = TokenRepository(sfuToken) + tokenRepository = TokenRepository(sfuToken), ) }, ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt index b9fb8f7231d..fad0c5815e9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt @@ -104,7 +104,7 @@ internal class CoordinatorConnectionModule( scope = scope, lifecycle = lifecycle, tokenProvider = tokenProvider, - tokenRepository = tokenRepository + tokenRepository = tokenRepository, ) override fun updateToken(token: UserToken) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt index 80696254ba8..3d7a4016321 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt @@ -98,7 +98,7 @@ internal class SfuConnectionModule( tokenProvider = ConstantTokenProvider(tokenRepository), lifecycle = lifecycle, networkStateProvider = networkStateProvider, - tokenRepository = tokenRepository + tokenRepository = tokenRepository, ) override val socketConnection: SfuSocketConnection = _internalSocketConnection diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt index bca8f881e65..c259fc039c6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt @@ -27,7 +27,9 @@ internal class CacheableTokenProvider( private val tokenRepository: TokenRepository, ) : TokenProvider { - override suspend fun loadToken(): String = tokenProvider.loadToken().also { tokenRepository.updateToken(it) } + override suspend fun loadToken(): String = tokenProvider.loadToken().also { + tokenRepository.updateToken(it) + } /** * Obtain the cached token. diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt index 34188b6d2d8..74986977d6e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt @@ -1,3 +1,19 @@ +/* + * 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. + */ + package io.getstream.video.android.core.socket.common.token import io.getstream.video.android.core.socket.common.token.TokenManagerImpl.Companion.EMPTY_TOKEN @@ -9,4 +25,4 @@ class TokenRepository(@Volatile private var token: String = EMPTY_TOKEN) { } fun getToken(): String = token -} \ No newline at end of file +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt index 84f751a9319..1c9e8886964 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt @@ -278,10 +278,11 @@ internal open class CoordinatorSocket( if (VideoErrorCode.isAuthenticationError(error.serverErrorCode)) { tokenManager.expireToken() } - //Noob - when(error.serverErrorCode){ + // Noob + when (error.serverErrorCode) { VideoErrorCode.VALIDATION_ERROR.code, - VideoErrorCode.TOKEN_EXPIRED.code -> { + VideoErrorCode.TOKEN_EXPIRED.code, + -> { StreamVideo.instanceOrNull()?.let { val result = (it as StreamVideoClient).getEdges() result.onSuccess { @@ -289,7 +290,6 @@ internal open class CoordinatorSocket( }.onError { logger.d { "edges fail result: $result" } } - } tokenManager.expireToken() logger.d { "load sync START" } @@ -300,7 +300,7 @@ internal open class CoordinatorSocket( return } } - else->{} + else -> {} } when (error.serverErrorCode) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt index 58e775d8d5f..15034084e16 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -82,7 +82,7 @@ public open class CoordinatorSocketConnection( private val lifecycle: Lifecycle, /** Token provider */ private val tokenProvider: TokenProvider, - private val tokenRepository: TokenRepository + private val tokenRepository: TokenRepository, ) : SocketListener(), SocketActions { @@ -128,7 +128,9 @@ public open class CoordinatorSocketConnection( // Extension opportunity for subclasses override fun onCreated() { super.onCreated() - logger.d { "[onCreated] Socket is created, initial token: $token, tokenManager.getToken() = ${tokenManager.getToken()}" } + logger.d { + "[onCreated] Socket is created, initial token: $token, tokenManager.getToken() = ${tokenManager.getToken()}" + } scope.launch { logger.d { "[onConnected] Video socket created, user: $user" } if (token.isEmpty()) { @@ -199,7 +201,7 @@ public open class CoordinatorSocketConnection( connectionTimeout: Long, connected: suspend (connectionId: String) -> Unit, ) { - logger.d {"[whenConnected]"} + logger.d { "[whenConnected]" } scope.launch { internalSocket.awaitConnection(connectionTimeout) internalSocket.connectionIdOrError().also { @@ -218,19 +220,19 @@ public open class CoordinatorSocketConnection( override suspend fun sendEvent(event: VideoEvent): Boolean = internalSocket.sendEvent(event) override suspend fun connect(connectData: User) { - logger.d {"[connect]"} + logger.d { "[connect]" } internalSocket.connectUser(connectData, connectData.isAnonymous()) } override suspend fun reconnect(data: User, force: Boolean) { - logger.d {"[reconnect]"} + logger.d { "[reconnect]" } internalSocket.reconnectUser(data, data.isAnonymous(), force) } override suspend fun disconnect() = internalSocket.disconnect() override fun updateToken(token: UserToken) { - logger.d {"[updateToken]"} + logger.d { "[updateToken]" } tokenManager.updateToken(token) } } From 2f532f741aed7724f33dcdc1a296a97d506506fb Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Dec 2025 16:02:42 +0530 Subject: [PATCH 30/47] single thread coordinator socket --- .../core/socket/common/scope/UserScope.kt | 20 +++++++++++++++---- .../CoordinatorSocketConnection.kt | 3 ++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/UserScope.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/UserScope.kt index 2b27e8529e9..c75d93f5cdc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/UserScope.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/UserScope.kt @@ -23,6 +23,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.job import kotlinx.coroutines.plus +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** * A user aware implementation of [CoroutineScope]. @@ -38,22 +40,29 @@ internal interface UserScope : CoroutineScope { * Cancels all children of the [UserJob] in this scope's context connected to the specified [userId]. */ fun cancelChildren(userId: UserId? = null) + + operator fun plus(context: CoroutineContext): UserScope } /** * Creates a user aware [CoroutineScope]. */ -internal fun UserScope(clientScope: ClientScope): UserScope = UserScopeImpl(clientScope) +internal fun UserScope( + clientScope: ClientScope = ClientScope(), + context: CoroutineContext = EmptyCoroutineContext, +): UserScope = UserScopeImpl(clientScope, context) /** * Inherits [ClientScope] and adds elements such as [UserIdentifier] and [UserJob]. */ private class UserScopeImpl( - clientScope: ClientScope, - userIdentifier: UserIdentifier = UserIdentifier(), + val clientScope: ClientScope, + additionalContext: CoroutineContext, + val userIdentifier: UserIdentifier = UserIdentifier(), ) : UserScope, CoroutineScope by ( - clientScope + userIdentifier + UserJob(clientScope.coroutineContext.job) { userIdentifier.value } + clientScope + userIdentifier + UserJob(clientScope.coroutineContext.job) { userIdentifier.value } + + additionalContext ) { /** @@ -68,4 +77,7 @@ private class UserScopeImpl( override fun cancelChildren(userId: UserId?) { (coroutineContext[Job] as UserJob).cancelChildren(userId) } + + override fun plus(context: CoroutineContext): UserScope = + UserScopeImpl(clientScope, this.coroutineContext + context, userIdentifier) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt index 15034084e16..b4dfd5894c2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -47,6 +47,7 @@ import io.getstream.video.android.model.User import io.getstream.video.android.model.User.Companion.isAnonymous import io.getstream.video.android.model.UserToken import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -111,7 +112,7 @@ public open class CoordinatorSocketConnection( parser, httpClient, ), - scope as? UserScope ?: UserScope(ClientScope()), + scope as? UserScope ?: UserScope(ClientScope(), Dispatchers.IO.limitedParallelism(1)), StreamLifecycleObserver(scope, lifecycle), networkStateProvider, ).also { From 90f15cbd2f41760d3ddd01f050c37957a4f5503c Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Dec 2025 16:08:09 +0530 Subject: [PATCH 31/47] single thread coordinator socket --- .../core/internal/module/CoordinatorConnectionModule.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt index fad0c5815e9..c87a3d799b6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt @@ -25,6 +25,7 @@ import io.getstream.log.streamLog import io.getstream.video.android.core.header.HeadersUtil import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.logging.LoggingLevel +import io.getstream.video.android.core.socket.common.scope.UserScope import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.coordinator.CoordinatorSocketConnection @@ -33,6 +34,7 @@ import io.getstream.video.android.model.ApiKey import io.getstream.video.android.model.User import io.getstream.video.android.model.UserToken import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit @@ -101,7 +103,7 @@ internal class CoordinatorConnectionModule( token = userToken, httpClient = http, networkStateProvider = networkStateProvider, - scope = scope, + scope = UserScope(context = scope.coroutineContext + Dispatchers.IO.limitedParallelism(1)), lifecycle = lifecycle, tokenProvider = tokenProvider, tokenRepository = tokenRepository, From b2c5f5cfd38518b7055a6e5099ae04bccdc6f6a9 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Dec 2025 16:23:16 +0530 Subject: [PATCH 32/47] single thread coordinator socket --- .../video/android/data/services/stream/StreamService.kt | 2 +- .../android/core/socket/coordinator/CoordinatorSocket.kt | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt index d046149ed59..c1b1125c8c1 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt @@ -40,7 +40,7 @@ fun interface StreamService { companion object { private const val BASE_URL = "https://pronto.getstream.io/" - public const val TOKEN_EXPIRY_TIME = 20 + public const val TOKEN_EXPIRY_TIME = 30 private val json = Json { ignoreUnknownKeys = true } private val okHttpClient = OkHttpClient.Builder() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt index 1c9e8886964..e0e429ddea4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt @@ -280,17 +280,8 @@ internal open class CoordinatorSocket( } // Noob when (error.serverErrorCode) { - VideoErrorCode.VALIDATION_ERROR.code, VideoErrorCode.TOKEN_EXPIRED.code, -> { - StreamVideo.instanceOrNull()?.let { - val result = (it as StreamVideoClient).getEdges() - result.onSuccess { - logger.d { "edges success result: $result" } - }.onError { - logger.d { "edges fail result: $result" } - } - } tokenManager.expireToken() logger.d { "load sync START" } val token = tokenManager.loadSync() From 3a5df3de78fc966911365e5d73c39866bee10579 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 1 Dec 2025 16:24:03 +0530 Subject: [PATCH 33/47] single thread coordinator socket --- .../video/android/core/socket/coordinator/CoordinatorSocket.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt index e0e429ddea4..b342bbf50a6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt @@ -23,7 +23,6 @@ import io.getstream.android.video.generated.models.VideoEvent import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.StreamVideo -import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.dispatchers.DispatcherProvider import io.getstream.video.android.core.errors.DisconnectCause import io.getstream.video.android.core.errors.VideoErrorCode From 150e3a4a51422c996df84f8483d50635c4d11669 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 13:50:15 +0530 Subject: [PATCH 34/47] refactor api files --- .../api/stream-video-android-core.api | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 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 33372d00a4c..f4a89cf5d4f 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -13118,9 +13118,17 @@ public abstract interface class io/getstream/video/android/core/socket/common/to public abstract fun loadToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class io/getstream/video/android/core/socket/common/token/TokenRepository { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getToken ()Ljava/lang/String; + public final fun updateToken (Ljava/lang/String;)V +} + public class io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection : io/getstream/video/android/core/socket/common/SocketListener, io/getstream/video/android/core/socket/common/SocketActions { - public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun connect (Lio/getstream/video/android/model/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public synthetic fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; @@ -13327,8 +13335,8 @@ public final class io/getstream/video/android/core/socket/coordinator/state/Vide public final class io/getstream/video/android/core/socket/sfu/SfuSocketConnection : io/getstream/video/android/core/socket/common/SocketListener, io/getstream/video/android/core/socket/common/SocketActions { public static final field Companion Lio/getstream/video/android/core/socket/sfu/SfuSocketConnection$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connect (Lstream/video/sfu/event/JoinRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; From 8b4dfc8c7a732262a2f1b8e40e4a4990a1e63221 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 13:51:07 +0530 Subject: [PATCH 35/47] refactor --- demo-app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo-app/build.gradle.kts b/demo-app/build.gradle.kts index 7f2b1709867..63edad05124 100644 --- a/demo-app/build.gradle.kts +++ b/demo-app/build.gradle.kts @@ -292,7 +292,7 @@ dependencies { implementation(libs.audioswitch) - //Logging + // Logging implementation(libs.okhttp.logging) // Also Leak Canary added in the previous block From 22640f4a7e0a51300ad97af34feb160917e47b90 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 15:35:02 +0530 Subject: [PATCH 36/47] 1. increase token expiry time 2. Refactor usage token --- .../video/android/data/services/stream/StreamService.kt | 2 +- .../core/internal/module/ConnectionModuleDeclaration.kt | 5 ----- .../core/internal/module/CoordinatorConnectionModule.kt | 8 +++----- .../android/core/internal/module/SfuConnectionModule.kt | 1 - .../socket/coordinator/CoordinatorSocketConnection.kt | 5 ++++- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt index c1b1125c8c1..d9c2072b956 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt @@ -40,7 +40,7 @@ fun interface StreamService { companion object { private const val BASE_URL = "https://pronto.getstream.io/" - public const val TOKEN_EXPIRY_TIME = 30 + const val TOKEN_EXPIRY_TIME = 7 * 24 * 60 * 60 //7d * 24 hrs * 60 mins * 60 sec private val json = Json { ignoreUnknownKeys = true } private val okHttpClient = OkHttpClient.Builder() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt index 98ac4d93838..999a73deb19 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt @@ -76,11 +76,6 @@ internal interface ConnectionModuleDeclaration { @@ -100,7 +99,7 @@ internal class CoordinatorConnectionModule( apiKey = apiKey, url = wssUrl, user = user, - token = userToken, + token = tokenRepository.getToken(), httpClient = http, networkStateProvider = networkStateProvider, scope = UserScope(context = scope.coroutineContext + Dispatchers.IO.limitedParallelism(1)), @@ -110,8 +109,7 @@ internal class CoordinatorConnectionModule( ) override fun updateToken(token: UserToken) { - socketConnection.updateToken(token) -// authInterceptor.token = token + tokenRepository.updateToken(token) } override fun updateAuthType(authType: String) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt index 3d7a4016321..cb21607fb54 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt @@ -43,7 +43,6 @@ internal class SfuConnectionModule( override val apiUrl: String, override val wssUrl: String, override val connectionTimeoutInMs: Long, - override val userToken: SfuToken, override val lifecycle: Lifecycle, override val tracer: Tracer, val onSignalingLost: (Error) -> Unit, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt index b4dfd5894c2..9ecfc69ecb2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -64,6 +64,8 @@ import okhttp3.OkHttpClient * - Raises the error if there is a permanent failure * - Flow to avoid concurrency related bugs * - Ability to wait till the socket is connected (important to prevent race conditions) + * + * This should be internal */ public open class CoordinatorSocketConnection( private val apiKey: ApiKey, @@ -72,6 +74,7 @@ public open class CoordinatorSocketConnection( /** The user to connect. */ private val user: User, /** The initial token. */ + @Deprecated("token is not used", ReplaceWith("Use tokenManager or tokenRepository.getToken() instead")) private val token: String, /** Inject your http client */ private val httpClient: OkHttpClient, @@ -134,7 +137,7 @@ public open class CoordinatorSocketConnection( } scope.launch { logger.d { "[onConnected] Video socket created, user: $user" } - if (token.isEmpty()) { + if (tokenManager.getToken().isEmpty()) { logger.e { "[onConnected] Token is empty. Disconnecting." } disconnect() } else { From ff72ed3b91e8a6c1a635c0eceeb0679324d1f42a Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 16:44:27 +0530 Subject: [PATCH 37/47] 1. increase token expiry time 2. Refactor usage token --- .../video/android/data/services/stream/StreamService.kt | 2 +- .../io/getstream/video/android/core/StreamVideoBuilder.kt | 1 - .../io/getstream/video/android/core/call/RtcSession.kt | 1 - .../core/socket/coordinator/CoordinatorSocketConnection.kt | 7 +++++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt index d9c2072b956..afe36995457 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt @@ -40,7 +40,7 @@ fun interface StreamService { companion object { private const val BASE_URL = "https://pronto.getstream.io/" - const val TOKEN_EXPIRY_TIME = 7 * 24 * 60 * 60 //7d * 24 hrs * 60 mins * 60 sec + const val TOKEN_EXPIRY_TIME = 7 * 24 * 60 * 60 // 7d * 24 hrs * 60 mins * 60 sec private val json = Json { ignoreUnknownKeys = true } private val okHttpClient = OkHttpClient.Builder() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index 84bfcbf862c..7646dd0bc1a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -226,7 +226,6 @@ public class StreamVideoBuilder @JvmOverloads constructor( loggingLevel = loggingLevel, user = user, apiKey = apiKey, - userToken = token, tokenProvider = tokenProvider, lifecycle = lifecycle, tokenRepository = tokenRepository, 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 d87c3094538..daf2a60d7bc 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 @@ -237,7 +237,6 @@ public class RtcSession internal constructor( apiUrl = sfuUrl, wssUrl = sfuWsUrl, connectionTimeoutInMs = 2000L, - userToken = sfuToken, lifecycle = lifecycle, onSignalingLost = { error -> call.debug.fastReconnect() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt index 9ecfc69ecb2..ece01db5da9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -74,7 +74,10 @@ public open class CoordinatorSocketConnection( /** The user to connect. */ private val user: User, /** The initial token. */ - @Deprecated("token is not used", ReplaceWith("Use tokenManager or tokenRepository.getToken() instead")) + @Deprecated( + "token is not used", + ReplaceWith("Use tokenManager or tokenRepository.getToken() instead"), + ) private val token: String, /** Inject your http client */ private val httpClient: OkHttpClient, @@ -142,7 +145,7 @@ public open class CoordinatorSocketConnection( disconnect() } else { val authRequest = WSAuthMessageRequest( - token = tokenManager.getToken().ifEmpty { token }, + token = tokenManager.getToken().ifEmpty { tokenRepository.getToken() }, userDetails = ConnectUserDetailsRequest( id = user.id, name = user.name.takeUnless { it.isWhitespaceOnly() }, From a8bc660e9c541fda3e814f1faa6e7827581f4bc9 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 20:19:54 +0530 Subject: [PATCH 38/47] 1. Update token provider --- .../video/android/core/StreamVideoBuilder.kt | 12 ++++++--- .../video/android/core/StreamVideoClient.kt | 4 +-- .../internal/module/SfuConnectionModule.kt | 2 +- .../common/token/CacheableTokenProvider.kt | 11 +++----- .../common/token/ConstantTokenProvider.kt | 4 +-- .../common/token/PersistingTokenProvider.kt | 27 +++++++++++++++++++ .../common/token/RepositoryTokenProvider.kt | 23 ++++++++++++++++ .../CoordinatorSocketConnection.kt | 4 +-- .../core/socket/sfu/SfuSocketConnection.kt | 4 +-- 9 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/RepositoryTokenProvider.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index 7646dd0bc1a..be76766b99a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -37,7 +37,7 @@ import io.getstream.video.android.core.permission.android.DefaultStreamPermissio import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope -import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.common.token.RepositoryTokenProvider import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.sounds.RingingCallVibrationConfig @@ -96,6 +96,7 @@ import java.net.ConnectException * @see ClientState.connection * */ + public class StreamVideoBuilder @JvmOverloads constructor( context: Context, private val apiKey: ApiKey, @@ -107,7 +108,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( object : TokenProvider { override suspend fun loadToken(): String = legacy.invoke(null) } - } ?: ConstantTokenProvider(TokenRepository(token)), + } ?: RepositoryTokenProvider(tokenRepository), private val loggingLevel: LoggingLevel = LoggingLevel(), private val notificationConfig: NotificationConfig = NotificationConfig(), private val ringNotification: ((call: Call) -> Notification?)? = null, @@ -215,7 +216,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( // Android JSR-310 backport backport AndroidThreeTen.init(context) - val tokenRepository = TokenRepository(token) + tokenRepository.updateToken(token) // This connection module class exposes the connections to the various retrofit APIs. val coordinatorConnectionModule = CoordinatorConnectionModule( context = context, @@ -350,6 +351,11 @@ public class StreamVideoBuilder @JvmOverloads constructor( } } +/** + * Refactor Later + */ +internal val tokenRepository = TokenRepository("") + sealed class GEO { /** Run calls over our global edge network, this is the default and right for most applications */ object GlobalEdgeNetwork : GEO() 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 3137d3e6b89..e7e59d83877 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 @@ -107,7 +107,7 @@ import io.getstream.video.android.core.permission.android.DefaultStreamPermissio import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.ErrorResponse import io.getstream.video.android.core.socket.common.scope.ClientScope -import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.common.token.RepositoryTokenProvider import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState @@ -163,7 +163,7 @@ internal class StreamVideoClient internal constructor( private val lifecycle: Lifecycle, internal val coordinatorConnectionModule: CoordinatorConnectionModule, internal val tokenRepository: TokenRepository, - internal val tokenProvider: TokenProvider = ConstantTokenProvider(tokenRepository), + internal val tokenProvider: TokenProvider = RepositoryTokenProvider(tokenRepository), internal val streamNotificationManager: StreamNotificationManager, internal val enableCallNotificationUpdates: Boolean, internal val callServiceConfigRegistry: CallServiceConfigRegistry = CallServiceConfigRegistry(), diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt index cb21607fb54..708707f9594 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt @@ -94,7 +94,7 @@ internal class SfuConnectionModule( apiKey = apiKey, scope = scope, httpClient = http, - tokenProvider = ConstantTokenProvider(tokenRepository), + tokenProvider = ConstantTokenProvider(tokenRepository.getToken()), lifecycle = lifecycle, networkStateProvider = networkStateProvider, tokenRepository = tokenRepository, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt index c259fc039c6..ef6445e3fc0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt @@ -22,19 +22,16 @@ package io.getstream.video.android.core.socket.common.token * * @property tokenProvider The [TokenProvider] used to obtain new tokens. */ -internal class CacheableTokenProvider( +internal open class CacheableTokenProvider( private val tokenProvider: TokenProvider, - private val tokenRepository: TokenRepository, ) : TokenProvider { - - override suspend fun loadToken(): String = tokenProvider.loadToken().also { - tokenRepository.updateToken(it) - } + internal var cachedToken = "" + override suspend fun loadToken(): String = tokenProvider.loadToken().also { cachedToken = it } /** * Obtain the cached token. * * @return The cached token. */ - fun getCachedToken(): String = tokenRepository.getToken() + fun getCachedToken(): String = cachedToken } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt index 4cead1bcec4..03f32439eac 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt @@ -16,6 +16,6 @@ package io.getstream.video.android.core.socket.common.token -internal class ConstantTokenProvider(private val tokenRepository: TokenRepository) : TokenProvider { - override suspend fun loadToken(): String = tokenRepository.getToken() +internal class ConstantTokenProvider(private val token: String) : TokenProvider { + override suspend fun loadToken(): String = token } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt new file mode 100644 index 00000000000..c889e69d72a --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014-2025 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.socket.common.token + +internal class PersistingTokenProvider( + private val tokenProvider: TokenProvider, + private val tokenRepository: TokenRepository, +) : CacheableTokenProvider(tokenProvider) { + override suspend fun loadToken(): String = tokenProvider.loadToken().also { + cachedToken = it + tokenRepository.updateToken(it) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/RepositoryTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/RepositoryTokenProvider.kt new file mode 100644 index 00000000000..54e6a1c1c8e --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/RepositoryTokenProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014-2025 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.socket.common.token + +internal class RepositoryTokenProvider(val tokenRepository: TokenRepository) : TokenProvider { + override suspend fun loadToken(): String { + return tokenRepository.getToken() + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt index ece01db5da9..00a42a22da6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -35,7 +35,7 @@ import io.getstream.video.android.core.socket.common.VideoParser import io.getstream.video.android.core.socket.common.parser2.MoshiVideoParser import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope -import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.PersistingTokenProvider import io.getstream.video.android.core.socket.common.token.TokenManagerImpl import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.core.socket.common.token.TokenRepository @@ -129,7 +129,7 @@ public open class CoordinatorSocketConnection( // Init init { - tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider, tokenRepository)) + tokenManager.setTokenProvider(PersistingTokenProvider(tokenProvider, tokenRepository)) } // Extension opportunity for subclasses 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 74887451908..73fef0b76d5 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 @@ -31,7 +31,7 @@ import io.getstream.video.android.core.socket.common.SocketListener import io.getstream.video.android.core.socket.common.StreamWebSocketEvent import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope -import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.PersistingTokenProvider import io.getstream.video.android.core.socket.common.token.TokenManagerImpl import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.core.socket.common.token.TokenRepository @@ -102,7 +102,7 @@ class SfuSocketConnection( // Initialization init { - tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider, tokenRepository)) + tokenManager.setTokenProvider(PersistingTokenProvider(tokenProvider, tokenRepository)) } override fun onCreated() { From 8935374ace5dbe445a346e252ea9db45e9bba919 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 20:47:50 +0530 Subject: [PATCH 39/47] chore: remove comments --- .../kotlin/io/getstream/video/android/core/StreamVideoClient.kt | 2 -- .../video/android/core/socket/coordinator/CoordinatorSocket.kt | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) 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 e7e59d83877..35665871dc5 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 @@ -446,7 +446,6 @@ internal class StreamVideoClient internal constructor( return Failure(Error.GenericError("failed to select location")) } - // Noob override suspend fun connectAsync(): Deferred> { return scope.async { // wait for the guest user setup if we're using guest users @@ -467,7 +466,6 @@ internal class StreamVideoClient internal constructor( } } - // Noob private suspend fun refreshToken(error: Throwable) { tokenProvider?.let { val newToken = tokenProvider.loadToken() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt index b342bbf50a6..9412111a91e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt @@ -277,7 +277,7 @@ internal open class CoordinatorSocket( if (VideoErrorCode.isAuthenticationError(error.serverErrorCode)) { tokenManager.expireToken() } - // Noob + when (error.serverErrorCode) { VideoErrorCode.TOKEN_EXPIRED.code, -> { From 2f081109e3792cfe697d1678c3d506bfce97902e Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 21:34:37 +0530 Subject: [PATCH 40/47] chore: improve token provider --- .../core/socket/common/token/CacheableTokenProvider.kt | 2 +- .../core/socket/common/token/PersistingTokenProvider.kt | 3 +-- .../video/android/core/socket/common/token/TokenManager.kt | 2 ++ .../android/core/socket/common/token/TokenManagerImpl.kt | 5 ++++- .../android/core/socket/common/token/TokenRepository.kt | 4 +++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt index ef6445e3fc0..5f7d092a53f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt @@ -22,7 +22,7 @@ package io.getstream.video.android.core.socket.common.token * * @property tokenProvider The [TokenProvider] used to obtain new tokens. */ -internal open class CacheableTokenProvider( +internal class CacheableTokenProvider( private val tokenProvider: TokenProvider, ) : TokenProvider { internal var cachedToken = "" diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt index c889e69d72a..38e8199a4fd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt @@ -19,9 +19,8 @@ package io.getstream.video.android.core.socket.common.token internal class PersistingTokenProvider( private val tokenProvider: TokenProvider, private val tokenRepository: TokenRepository, -) : CacheableTokenProvider(tokenProvider) { +) : TokenProvider { override suspend fun loadToken(): String = tokenProvider.loadToken().also { - cachedToken = it tokenRepository.updateToken(it) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt index abd71237c98..e7b11fdcc19 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt @@ -51,6 +51,8 @@ internal interface TokenManager { */ fun setTokenProvider(provider: CacheableTokenProvider) + fun setTokenProvider(provider: PersistingTokenProvider) + /** * Obtain last token loaded. * diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt index 02560c6b6e1..c5fef2d54ec 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt @@ -38,7 +38,10 @@ internal class TokenManagerImpl(private val tokenRepository: TokenRepository) : override fun setTokenProvider(provider: CacheableTokenProvider) { this.provider = provider - this.tokenRepository.updateToken(provider.getCachedToken()) + } + + override fun setTokenProvider(provider: PersistingTokenProvider) { + this.provider = provider } override fun hasTokenProvider(): Boolean { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt index 74986977d6e..f9036448822 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt @@ -24,5 +24,7 @@ class TokenRepository(@Volatile private var token: String = EMPTY_TOKEN) { this.token = token } - fun getToken(): String = token + fun getToken(): String { + return token + } } From 248c1e8df1bb8c507c1c90de2d193c7e2237829e Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 21:59:49 +0530 Subject: [PATCH 41/47] chore: update FakeTokenManager with PersistingTokenProvider --- .../getstream/video/android/core/token/FakeTokenManager.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt index 3b8b62cdf0c..b5ddc139e82 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt @@ -17,6 +17,7 @@ package io.getstream.video.android.core.token import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.PersistingTokenProvider import io.getstream.video.android.core.socket.common.token.TokenManager internal class FakeTokenManager( @@ -34,6 +35,10 @@ internal class FakeTokenManager( // empty } + override fun setTokenProvider(provider: PersistingTokenProvider) { + // empty + } + override fun hasTokenProvider(): Boolean { return true } From a0887495784524b6662cff0320b16f71c6a3ab1d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 8 Dec 2025 22:24:56 +0530 Subject: [PATCH 42/47] chore: Improve socket disconnection logging and add TODO The log message for socket disconnection now includes the specific error for better debugging. Additionally, a TODO comment was added to the `sendData` method in `SocketActions.kt` to refactor it to return a boolean in the future. --- .../video/android/core/socket/common/SocketActions.kt | 1 + .../core/socket/coordinator/CoordinatorSocketConnection.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt index 76b476cd88e..2570bd9be50 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt @@ -41,6 +41,7 @@ interface SocketActions { /** * Send raw data to the socket. If you already have a parsed event that can be sent. + * Refactor later to return bool */ fun sendData(data: String) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt index 00a42a22da6..6453ceaa4d9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -197,7 +197,9 @@ public open class CoordinatorSocketConnection( override fun onDisconnected(cause: DisconnectCause) { super.onDisconnected(cause) - logger.d { "[onDisconnected] Socket disconnected. Cause: $cause" } + logger.d { + "[onDisconnected] Socket disconnected. Cause: ${(cause as? DisconnectCause.Error)?.error}" + } } // API From d591a859b02ed4cf8beba110f6b58046395c95c9 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 10 Dec 2025 15:47:51 +0530 Subject: [PATCH 43/47] remove comments --- .../android/core/socket/coordinator/CoordinatorSocket.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt index 9412111a91e..890e82b359a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt @@ -282,13 +282,8 @@ internal open class CoordinatorSocket( VideoErrorCode.TOKEN_EXPIRED.code, -> { tokenManager.expireToken() - logger.d { "load sync START" } val token = tokenManager.loadSync() tokenManager.updateToken(token) - if (token.isNotEmpty()) { - logger.d { "load sync END: $token" } - return - } } else -> {} } From 92f6c38868fda5ead1e9519b12e002069c3badce Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 10 Dec 2025 15:54:29 +0530 Subject: [PATCH 44/47] chore: remove logs and commented out code --- .../getstream/video/android/util/StreamVideoInitHelper.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 1e32a6e477b..0bd321a37fd 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -198,17 +198,11 @@ object StreamVideoInitHelper { image = user.image.orEmpty(), ) -// chatClient.connectUser( -// user = chatUser, -// token = token, -// ).enqueue() - chatClient.connectUser( user = chatUser, tokenProvider = object : io.getstream.chat.android.client.token.TokenProvider { override fun loadToken(): String { return runBlocking { - Log.d("Noob", "Chat token provider") val email = user.custom?.get("email") val authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, @@ -327,13 +321,11 @@ object StreamVideoInitHelper { val userEmail = user.custom?.get("email") val userId = user.id val userIdForTokenRenewal = if (userEmail.isNullOrEmpty()) userId else userEmail - Log.d("Noob", "Video token provider START") val authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = userIdForTokenRenewal, StreamService.TOKEN_EXPIRY_TIME, ) - Log.d("Noob", "Video token provider END: ${authData.token}") return authData.token } }, From aa840c6743693280d54cfb208922691f06ef8221 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 10 Dec 2025 16:27:17 +0530 Subject: [PATCH 45/47] chore: remove token expiry parameter from auth The `TOKEN_EXPIRY_TIME` constant and its usage as a parameter in `getAuthData` calls have been removed. The token expiration is now handled by the backend. --- .../video/android/data/services/stream/StreamService.kt | 6 ++---- .../io/getstream/video/android/ui/login/LoginViewModel.kt | 2 -- .../getstream/video/android/util/StreamVideoInitHelper.kt | 4 ---- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt index afe36995457..38964adfdb2 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt @@ -35,12 +35,10 @@ fun interface StreamService { suspend fun getAuthData( @Query("environment") environment: String, @Query("user_id") userId: String?, - @Query("exp") exp: Int, ): GetAuthDataResponse companion object { private const val BASE_URL = "https://pronto.getstream.io/" - const val TOKEN_EXPIRY_TIME = 7 * 24 * 60 * 60 // 7d * 24 hrs * 60 mins * 60 sec private val json = Json { ignoreUnknownKeys = true } private val okHttpClient = OkHttpClient.Builder() @@ -60,9 +58,9 @@ fun interface StreamService { private val serviceInstance = retrofit.create() - val instance = StreamService { environment, userId, exp -> + val instance = StreamService { environment, userId -> User.builtInCredentials[userId]?.toAuthDataResponse() - ?: serviceInstance.getAuthData(environment, userId, exp) + ?: serviceInstance.getAuthData(environment, userId) } } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt index a200efe5b29..c66b2a9649d 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginViewModel.kt @@ -92,7 +92,6 @@ class LoginViewModel @Inject constructor( val authData = StreamService.instance.getAuthData( environment = it.env, userId = userId, - StreamService.TOKEN_EXPIRY_TIME, ) val loggedInGoogleUser = if (autoLogIn) null else googleAccountRepository.getCurrentUser() @@ -129,7 +128,6 @@ class LoginViewModel @Inject constructor( val authData = StreamService.instance.getAuthData( environment = it.env, userId = user.id, - StreamService.TOKEN_EXPIRY_TIME, ) // Store the data in the demo app dataStore.updateUser(user) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 0bd321a37fd..42f2d090878 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -125,7 +125,6 @@ object StreamVideoInitHelper { authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = userId, - StreamService.TOKEN_EXPIRY_TIME, ) loggedInUser = User(id = authData.userId, role = "admin") @@ -142,7 +141,6 @@ object StreamVideoInitHelper { authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = loggedInUser.id, - StreamService.TOKEN_EXPIRY_TIME, ) } @@ -207,7 +205,6 @@ object StreamVideoInitHelper { val authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = email, - StreamService.TOKEN_EXPIRY_TIME, ) authData.token } @@ -324,7 +321,6 @@ object StreamVideoInitHelper { val authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, userId = userIdForTokenRenewal, - StreamService.TOKEN_EXPIRY_TIME, ) return authData.token } From 2ca823d4422606578aa30b78acb5d0710fa9e2cb Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 10 Dec 2025 17:51:58 +0530 Subject: [PATCH 46/47] chore: log network requests to system.out for easier debugging --- .../core/internal/module/CoordinatorConnectionModule.kt | 4 ++-- .../getstream/video/android/core/base/IntegrationTestBase.kt | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt index f69c8088412..5bcc1156521 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt @@ -76,8 +76,8 @@ internal class CoordinatorConnectionModule( HeadersInterceptor(HeadersUtil()), ) .addInterceptor(authInterceptor).addInterceptor( - HttpLoggingInterceptor { - streamLog(tag = "Video:Http") { it } + HttpLoggingInterceptor { message-> + println(message) }.apply { level = loggingLevel.httpLoggingLevel.level }, diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt index 4b98f23ae42..9d422c6baa2 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt @@ -67,7 +67,9 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine object IntegrationTestState { + @Volatile var client: StreamVideo? = null + @Volatile var call: Call? = null } @@ -99,7 +101,7 @@ open class IntegrationTestBase(val connectCoordinatorWS: Boolean = true) : TestB geo = GEO.GlobalEdgeNetwork, user = testData.users["thierry"]!!, token = authData?.token!!, - loggingLevel = LoggingLevel(Priority.DEBUG, HttpLoggingLevel.BASIC), + loggingLevel = LoggingLevel(Priority.DEBUG, HttpLoggingLevel.BODY), ) // if (BuildConfig.CORE_TEST_LOCAL == "1") { // builder.videoDomain = "localhost" From 4f88ddca75004bfe4b868973ee0e8367885c99bc Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 10 Dec 2025 17:52:31 +0530 Subject: [PATCH 47/47] chore: log network requests to system.out for easier debugging --- .../core/internal/module/CoordinatorConnectionModule.kt | 3 +-- .../getstream/video/android/core/base/IntegrationTestBase.kt | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt index 5bcc1156521..d04ceac3514 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt @@ -21,7 +21,6 @@ import android.net.ConnectivityManager import androidx.lifecycle.Lifecycle import io.getstream.android.video.generated.apis.ProductvideoApi import io.getstream.android.video.generated.infrastructure.Serializer -import io.getstream.log.streamLog import io.getstream.video.android.core.header.HeadersUtil import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.logging.LoggingLevel @@ -76,7 +75,7 @@ internal class CoordinatorConnectionModule( HeadersInterceptor(HeadersUtil()), ) .addInterceptor(authInterceptor).addInterceptor( - HttpLoggingInterceptor { message-> + HttpLoggingInterceptor { message -> println(message) }.apply { level = loggingLevel.httpLoggingLevel.level diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt index 9d422c6baa2..5258888ff94 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt @@ -69,6 +69,7 @@ import kotlin.coroutines.suspendCoroutine object IntegrationTestState { @Volatile var client: StreamVideo? = null + @Volatile var call: Call? = null }