Skip to content

Commit 7738b37

Browse files
Integration Video calls moderation in Video SDK (#1565)
* feat: add video moderation * feat: add video moderation * feat: add video moderation * feat: add video moderation * Make ModerationWarningUiContainer as public api * remove dependency of stream-video-android-filters-video from android-ui-compose * remove all public-apis * revert BlurIntensity enum * revert BlurIntensity enum * revert changes in strings.xml * sdk will render moderation warning ui and will blur the video * sdk will render moderation warning ui and will blur the video * sdk will render moderation warning ui and will blur the video * Add default moderation filter * Spotless * update * Spotless * refactor all moderation related configuration into ModerationConfig * refactor all moderation related configuration into ModerationConfig * refactor all moderation related configuration into ModerationConfig * refactor * add ut and comments * add ut and comments * minor changes * Make blurring a part of core-sdk * rename methods * update api --------- Co-authored-by: Aleksandar Apostolov <[email protected]>
1 parent 7ccfc00 commit 7738b37

File tree

27 files changed

+1388
-11
lines changed

27 files changed

+1388
-11
lines changed

demo-app/src/main/kotlin/io/getstream/video/android/App.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,21 @@ package io.getstream.video.android
1919
import android.app.Application
2020
import android.content.Context
2121
import dagger.hilt.android.HiltAndroidApp
22+
import io.getstream.android.video.generated.models.CallEndedEvent
23+
import io.getstream.video.android.core.StreamVideo
24+
import io.getstream.video.android.core.moderations.CallModerationConstants
25+
import io.getstream.video.android.data.model.PolicyViolationUiData
2226
import io.getstream.video.android.datastore.delegate.StreamUserDataStore
2327
import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil
2428
import io.getstream.video.android.util.StreamVideoInitHelper
29+
import kotlinx.coroutines.CoroutineScope
30+
import kotlinx.coroutines.Dispatchers
31+
import kotlinx.coroutines.flow.MutableStateFlow
32+
import kotlinx.coroutines.flow.collectLatest
33+
import kotlinx.coroutines.flow.filterNotNull
34+
import kotlinx.coroutines.flow.flatMapLatest
35+
import kotlinx.coroutines.flow.flowOf
36+
import kotlinx.coroutines.launch
2537
import kotlinx.coroutines.runBlocking
2638

2739
@HiltAndroidApp
@@ -52,6 +64,33 @@ class App : Application() {
5264
useRandomUserAsFallback = false,
5365
)
5466
}
67+
68+
observePolicyViolation()
69+
}
70+
71+
internal var policyViolationUiData: MutableStateFlow<PolicyViolationUiData?> =
72+
MutableStateFlow(null)
73+
74+
private fun observePolicyViolation() {
75+
CoroutineScope(Dispatchers.Default).launch {
76+
StreamVideo.instanceState
77+
.flatMapLatest { instance ->
78+
instance?.state?.activeCall ?: flowOf(null)
79+
}.filterNotNull()
80+
.collectLatest { call ->
81+
call.events.collectLatest { event ->
82+
if (event is CallEndedEvent) {
83+
if (event.reason == CallModerationConstants.POLICY_VIOLATION) {
84+
policyViolationUiData.value = PolicyViolationUiData(
85+
getString(R.string.policy_violation_title),
86+
getString(R.string.policy_violation_message),
87+
getString(R.string.policy_violation_action_button),
88+
)
89+
}
90+
}
91+
}
92+
}
93+
}
5594
}
5695
}
5796

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.video.android.data.model
18+
19+
internal data class PolicyViolationUiData(val title: String, val message: String, val actionButtonText: String)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.video.android.ui
18+
19+
import androidx.compose.foundation.background
20+
import androidx.compose.foundation.layout.Box
21+
import androidx.compose.foundation.layout.Column
22+
import androidx.compose.foundation.layout.Spacer
23+
import androidx.compose.foundation.layout.fillMaxWidth
24+
import androidx.compose.foundation.layout.height
25+
import androidx.compose.foundation.layout.padding
26+
import androidx.compose.foundation.shape.RoundedCornerShape
27+
import androidx.compose.material.Text
28+
import androidx.compose.runtime.Composable
29+
import androidx.compose.ui.Alignment
30+
import androidx.compose.ui.Modifier
31+
import androidx.compose.ui.graphics.Color
32+
import androidx.compose.ui.text.font.FontWeight
33+
import androidx.compose.ui.text.style.TextAlign
34+
import androidx.compose.ui.tooling.preview.Preview
35+
import androidx.compose.ui.unit.dp
36+
import androidx.compose.ui.unit.sp
37+
import androidx.compose.ui.window.Dialog
38+
import io.getstream.video.android.compose.theme.VideoTheme
39+
import io.getstream.video.android.compose.ui.components.base.StreamButton
40+
41+
@Composable
42+
fun SingleButtonDialog(
43+
title: String,
44+
message: String,
45+
buttonText: String,
46+
onButtonClick: () -> Unit,
47+
) {
48+
Dialog(
49+
onDismissRequest = { /* No-op → prevents outside dismiss */ },
50+
) {
51+
Box(
52+
modifier = Modifier
53+
.fillMaxWidth()
54+
.background(Color.White, shape = RoundedCornerShape(12.dp))
55+
.padding(24.dp),
56+
) {
57+
Column(
58+
horizontalAlignment = Alignment.CenterHorizontally,
59+
) {
60+
Text(
61+
color = VideoTheme.colors.baseSheetPrimary,
62+
fontWeight = FontWeight.Bold,
63+
text = title,
64+
fontSize = 18.sp,
65+
)
66+
Spacer(modifier = Modifier.height(8.dp))
67+
Text(
68+
text = message,
69+
fontSize = 14.sp,
70+
color = Color.Gray,
71+
textAlign = TextAlign.Center,
72+
)
73+
Spacer(modifier = Modifier.height(24.dp))
74+
StreamButton(
75+
style = VideoTheme.styles.buttonStyles.secondaryButtonStyle(),
76+
text = buttonText,
77+
onClick = onButtonClick,
78+
)
79+
}
80+
}
81+
}
82+
}
83+
84+
@Preview
85+
@Composable
86+
fun SingleButtonDialogDemo() {
87+
VideoTheme {
88+
SingleButtonDialog(
89+
"Policy Violation Detected",
90+
"We have to end your call as we have detected policy violation",
91+
"OK",
92+
) { }
93+
}
94+
}

demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import androidx.compose.ui.window.PopupPositionProvider
9090
import androidx.hilt.navigation.compose.hiltViewModel
9191
import androidx.lifecycle.compose.collectAsStateWithLifecycle
9292
import io.getstream.video.android.R
93+
import io.getstream.video.android.app
9394
import io.getstream.video.android.compose.theme.VideoTheme
9495
import io.getstream.video.android.compose.ui.components.avatar.UserAvatar
9596
import io.getstream.video.android.compose.ui.components.base.StreamButton
@@ -101,6 +102,7 @@ import io.getstream.video.android.mock.StreamPreviewDataUtils
101102
import io.getstream.video.android.mock.previewUsers
102103
import io.getstream.video.android.model.User
103104
import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil
105+
import io.getstream.video.android.ui.SingleButtonDialog
104106
import io.getstream.video.android.util.config.AppConfig
105107
import io.getstream.video.android.util.config.types.StreamEnvironment
106108

@@ -173,6 +175,13 @@ fun CallJoinScreen(
173175
navigateUpToLogin.invoke(callJoinViewModel.autoLogInAfterLogOut)
174176
}
175177
}
178+
val appContext = LocalContext.current.applicationContext
179+
val policyViolation by appContext.app.policyViolationUiData.collectAsStateWithLifecycle()
180+
policyViolation?.let {
181+
SingleButtonDialog(it.title, it.message, it.actionButtonText) {
182+
appContext.app.policyViolationUiData.value = null
183+
}
184+
}
176185
}
177186

178187
@Composable

demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import io.getstream.video.android.core.StreamVideoBuilder
3434
import io.getstream.video.android.core.call.CallType
3535
import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi
3636
import io.getstream.video.android.core.logging.LoggingLevel
37+
import io.getstream.video.android.core.moderations.ModerationConfig
38+
import io.getstream.video.android.core.moderations.ModerationWarningConfig
39+
import io.getstream.video.android.core.moderations.VideoModerationConfig
3740
import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver
3841
import io.getstream.video.android.core.notifications.DefaultStreamIntentResolver
3942
import io.getstream.video.android.core.notifications.NotificationConfig
@@ -211,6 +214,20 @@ object StreamVideoInitHelper {
211214
callServiceConfigRegistry.apply {
212215
register(DefaultCallConfigurations.getLivestreamGuestCallServiceConfig())
213216
register(CallType.AudioCall.name) { enableTelecom(true) }
217+
register(CallType.AnyMarker.name) {
218+
setModerationConfig(
219+
ModerationConfig(
220+
moderationWarningConfig = ModerationWarningConfig(
221+
enable = true,
222+
displayTime = 5_000L,
223+
),
224+
videoModerationConfig = VideoModerationConfig(
225+
enable = true,
226+
blurDuration = 25_000L,
227+
),
228+
),
229+
)
230+
}
214231
}
215232

216233
return StreamVideoBuilder(

demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ object AppConfig {
7474
displayName = "Pronto Staging",
7575
sharelink = "https://pronto-staging.getstream.io/join/",
7676
),
77+
StreamEnvironment(
78+
env = "video-moderation",
79+
aliases = emptyList(),
80+
displayName = "Video Moderation",
81+
sharelink = "https://pronto.getstream.io/join/",
82+
),
7783
)
7884
val currentEnvironment = MutableStateFlow(availableEnvironments.default(BuildConfig.FLAVOR))
7985

demo-app/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@
5656
<string name="in_app_update_canceled">Please consider installing the update.\nIt contains important features or bug fixes.</string>
5757
<string name="in_app_update_failed">App update failed. Try again later</string>
5858
<string name="you_are_offline">You are offline. Check your internet connection.</string>
59+
<string name="policy_violation_title">Policy Violated</string>
60+
<string name="policy_violation_message">We have to end your call as we have detected policy violation</string>
61+
<string name="policy_violation_action_button">OK</string>
62+
<string name="moderation_warning_title">Warning</string>
63+
5964
<plurals name="chat_typing">
6065
<item quantity="one">%s is typing</item>
6166
<item quantity="other">%s and %d more are typing</item>

stream-video-android-core/api/stream-video-android-core.api

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7728,6 +7728,7 @@ public final class io/getstream/video/android/core/CallState {
77287728
public final fun getMe ()Lkotlinx/coroutines/flow/StateFlow;
77297729
public final fun getMember (Ljava/lang/String;)Lio/getstream/video/android/core/MemberState;
77307730
public final fun getMembers ()Lkotlinx/coroutines/flow/StateFlow;
7731+
public final fun getModerationManager ()Lio/getstream/video/android/core/moderations/ModerationManager;
77317732
public final fun getOrCreateParticipant (Ljava/lang/String;Ljava/lang/String;ZLstream/video/sfu/models/ParticipantSource;)Lio/getstream/video/android/core/ParticipantState;
77327733
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;
77337734
public final fun getOwnCapabilities ()Lkotlinx/coroutines/flow/StateFlow;
@@ -12066,6 +12067,62 @@ public final class io/getstream/video/android/core/model/VisibilityOnScreenState
1206612067
public static fun values ()[Lio/getstream/video/android/core/model/VisibilityOnScreenState;
1206712068
}
1206812069

12070+
public final class io/getstream/video/android/core/moderations/CallModerationConstants {
12071+
public static final field INSTANCE Lio/getstream/video/android/core/moderations/CallModerationConstants;
12072+
public static final field POLICY_VIOLATION Ljava/lang/String;
12073+
}
12074+
12075+
public final class io/getstream/video/android/core/moderations/ModerationConfig {
12076+
public fun <init> ()V
12077+
public fun <init> (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/VideoModerationConfig;)V
12078+
public synthetic fun <init> (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;Lio/getstream/video/android/core/moderations/VideoModerationConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
12079+
public final fun component1 ()Lio/getstream/video/android/core/moderations/ModerationWarningConfig;
12080+
public final fun component2 ()Lio/getstream/video/android/core/moderations/VideoModerationConfig;
12081+
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;
12082+
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;
12083+
public fun equals (Ljava/lang/Object;)Z
12084+
public final fun getModerationWarningConfig ()Lio/getstream/video/android/core/moderations/ModerationWarningConfig;
12085+
public final fun getVideoModerationConfig ()Lio/getstream/video/android/core/moderations/VideoModerationConfig;
12086+
public fun hashCode ()I
12087+
public fun toString ()Ljava/lang/String;
12088+
}
12089+
12090+
public final class io/getstream/video/android/core/moderations/ModerationManager {
12091+
public fun <init> (Lio/getstream/video/android/core/Call;)V
12092+
public final fun applyVideoModeration (Lio/getstream/video/android/core/call/video/BitmapVideoFilter;)V
12093+
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
12094+
public final fun clearVideoModeration ()V
12095+
}
12096+
12097+
public final class io/getstream/video/android/core/moderations/ModerationWarningConfig {
12098+
public fun <init> (ZJ)V
12099+
public final fun component1 ()Z
12100+
public final fun component2 ()J
12101+
public final fun copy (ZJ)Lio/getstream/video/android/core/moderations/ModerationWarningConfig;
12102+
public static synthetic fun copy$default (Lio/getstream/video/android/core/moderations/ModerationWarningConfig;ZJILjava/lang/Object;)Lio/getstream/video/android/core/moderations/ModerationWarningConfig;
12103+
public fun equals (Ljava/lang/Object;)Z
12104+
public final fun getDisplayTime ()J
12105+
public final fun getEnable ()Z
12106+
public fun hashCode ()I
12107+
public fun toString ()Ljava/lang/String;
12108+
}
12109+
12110+
public final class io/getstream/video/android/core/moderations/VideoModerationConfig {
12111+
public fun <init> (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)V
12112+
public synthetic fun <init> (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
12113+
public final fun component1 ()Z
12114+
public final fun component2 ()J
12115+
public final fun component3 ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter;
12116+
public final fun copy (ZJLio/getstream/video/android/core/call/video/BitmapVideoFilter;)Lio/getstream/video/android/core/moderations/VideoModerationConfig;
12117+
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;
12118+
public fun equals (Ljava/lang/Object;)Z
12119+
public final fun getBitmapVideoFilter ()Lio/getstream/video/android/core/call/video/BitmapVideoFilter;
12120+
public final fun getBlurDuration ()J
12121+
public final fun getEnable ()Z
12122+
public fun hashCode ()I
12123+
public fun toString ()Ljava/lang/String;
12124+
}
12125+
1206912126
public class io/getstream/video/android/core/notifications/DefaultNotificationHandler : io/getstream/android/push/permissions/NotificationPermissionHandler, io/getstream/video/android/core/notifications/NotificationHandler {
1207012127
public static final field Companion Lio/getstream/video/android/core/notifications/DefaultNotificationHandler$Companion;
1207112128
public fun <init> (Landroid/app/Application;Lio/getstream/android/push/permissions/NotificationPermissionHandler;ZI)V
@@ -12501,19 +12558,21 @@ public final class io/getstream/video/android/core/notifications/internal/receiv
1250112558

1250212559
public final class io/getstream/video/android/core/notifications/internal/service/CallServiceConfig {
1250312560
public fun <init> ()V
12504-
public fun <init> (ZILjava/util/Map;Ljava/lang/Class;Z)V
12505-
public synthetic fun <init> (ZILjava/util/Map;Ljava/lang/Class;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
12561+
public fun <init> (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationConfig;)V
12562+
public synthetic fun <init> (ZILjava/util/Map;Ljava/lang/Class;ZLio/getstream/video/android/core/moderations/ModerationConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
1250612563
public final fun component1 ()Z
1250712564
public final fun component2 ()I
1250812565
public final fun component3 ()Ljava/util/Map;
1250912566
public final fun component4 ()Ljava/lang/Class;
1251012567
public final fun component5 ()Z
12511-
public final fun copy (ZILjava/util/Map;Ljava/lang/Class;Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;
12512-
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;
12568+
public final fun component6 ()Lio/getstream/video/android/core/moderations/ModerationConfig;
12569+
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;
12570+
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;
1251312571
public fun equals (Ljava/lang/Object;)Z
1251412572
public final fun getAudioUsage ()I
1251512573
public final fun getCallServicePerType ()Ljava/util/Map;
1251612574
public final fun getEnableTelecom ()Z
12575+
public final fun getModerationConfig ()Lio/getstream/video/android/core/moderations/ModerationConfig;
1251712576
public final fun getRunCallServiceInForeground ()Z
1251812577
public final fun getServiceClass ()Ljava/lang/Class;
1251912578
public fun hashCode ()I
@@ -12525,6 +12584,7 @@ public final class io/getstream/video/android/core/notifications/internal/servic
1252512584
public final fun build ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;
1252612585
public final fun enableTelecom (Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder;
1252712586
public final fun setAudioUsage (I)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder;
12587+
public final fun setModerationConfig (Lio/getstream/video/android/core/moderations/ModerationConfig;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder;
1252812588
public final fun setRunCallServiceInForeground (Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder;
1252912589
public final fun setServiceClass (Ljava/lang/Class;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder;
1253012590
}

0 commit comments

Comments
 (0)