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 9a3a3853c02..de97cbf15c1 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 @@ -91,6 +91,7 @@ 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.RegularVideoRendererStyle import io.getstream.video.android.compose.ui.components.call.renderer.copy +import io.getstream.video.android.compose.ui.components.video.VideoScalingType import io.getstream.video.android.core.Call import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.call.state.ChooseLayout @@ -149,6 +150,7 @@ fun CallScreen( val scope = rememberCoroutineScope() val messageScope = rememberCoroutineScope() var showingLandscapeControls by remember { mutableStateOf(false) } + var preferredScaleType by remember { mutableStateOf(VideoScalingType.SCALE_ASPECT_FILL) } val connection by call.state.connection.collectAsStateWithLifecycle() val me by call.state.me.collectAsState() @@ -320,6 +322,7 @@ fun CallScreen( call = call, participant = participant, style = style, + scalingType = preferredScaleType, reactionContent = { CustomReactionContent( participant = participant, @@ -501,6 +504,10 @@ fun CallScreen( onNoiseCancellation = { isNoiseCancellationEnabled = call.toggleAudioProcessing() }, + onSelectScaleType = { + preferredScaleType = it + isShowingSettingMenu = false + }, onShowCallStats = { isShowingStats = true isShowingSettingMenu = false diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/MenuDefinitions.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/MenuDefinitions.kt index 6364a1ff9b5..df032d9791b 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/MenuDefinitions.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/MenuDefinitions.kt @@ -21,9 +21,13 @@ import android.os.Build import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.MobileScreenShare import androidx.compose.material.icons.automirrored.filled.ReadMore +import androidx.compose.material.icons.filled.AspectRatio import androidx.compose.material.icons.filled.Audiotrack import androidx.compose.material.icons.filled.AutoGraph +import androidx.compose.material.icons.filled.Balance import androidx.compose.material.icons.filled.BluetoothAudio +import androidx.compose.material.icons.filled.Crop +import androidx.compose.material.icons.filled.CropFree import androidx.compose.material.icons.filled.Feedback import androidx.compose.material.icons.filled.Headphones import androidx.compose.material.icons.filled.HeadsetMic @@ -37,6 +41,7 @@ import androidx.compose.material.icons.filled.SwitchLeft import androidx.compose.material.icons.filled.VideoFile import androidx.compose.material.icons.filled.VideoLibrary import androidx.compose.material.icons.filled.VideoSettings +import io.getstream.video.android.compose.ui.components.video.VideoScalingType import io.getstream.video.android.core.audio.StreamAudioDevice import io.getstream.video.android.ui.menu.base.ActionMenuItem import io.getstream.video.android.ui.menu.base.DynamicSubMenuItem @@ -64,6 +69,7 @@ fun defaultStreamMenu( onDeviceSelected: (StreamAudioDevice) -> Unit, onSfuRejoinClick: () -> Unit, onSfuFastReconnectClick: () -> Unit, + onSelectScaleType: (VideoScalingType) -> Unit, availableDevices: List, loadRecordings: suspend () -> List, ) = buildList { @@ -138,6 +144,7 @@ fun defaultStreamMenu( onSwitchSfuClick, onSfuRejoinClick, onSfuFastReconnectClick, + onSelectScaleType, ), ), ) @@ -196,6 +203,24 @@ fun reconnectMenu( ), ) +fun scaleTypeMenu(onSelectScaleType: (VideoScalingType) -> Unit): List = listOf( + ActionMenuItem( + title = "Scale FIT", + icon = Icons.Default.CropFree, + action = { onSelectScaleType(VideoScalingType.SCALE_ASPECT_FIT) }, + ), + ActionMenuItem( + title = "Scale FILL", + icon = Icons.Default.Crop, + action = { onSelectScaleType(VideoScalingType.SCALE_ASPECT_FILL) }, + ), + ActionMenuItem( + title = "Scale BALANCED", + icon = Icons.Default.Balance, + action = { onSelectScaleType(VideoScalingType.SCALE_ASPECT_BALANCED) }, + ), +) + /** * Optionally defines the debug sub-menu of the demo app. */ @@ -208,6 +233,7 @@ fun debugSubmenu( onSwitchSfuClick: () -> Unit, onSfuRejoinClick: () -> Unit, onSfuFastReconnectClick: () -> Unit, + onSelectScaleType: (VideoScalingType) -> Unit, ) = listOf( SubMenuItem( title = "Available video codecs", @@ -219,6 +245,13 @@ fun debugSubmenu( icon = Icons.Default.Audiotrack, action = onToggleAudioFilterClick, ), + SubMenuItem( + title = "Scale type", + icon = Icons.Default.AspectRatio, + items = scaleTypeMenu( + onSelectScaleType, + ), + ), SubMenuItem( title = "Reconnect V2", icon = Icons.Default.Replay, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt index 552bbde4249..74299275354 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt @@ -50,6 +50,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus import com.google.accompanist.permissions.rememberPermissionState import io.getstream.video.android.compose.theme.VideoTheme +import io.getstream.video.android.compose.ui.components.video.VideoScalingType import io.getstream.video.android.core.Call import io.getstream.video.android.core.call.audio.InputAudioFilter import io.getstream.video.android.core.mapper.ReactionMapper @@ -75,6 +76,7 @@ internal fun SettingsMenu( onShowFeedback: () -> Unit, onNoiseCancellation: () -> Unit, onShowCallStats: () -> Unit, + onSelectScaleType: (VideoScalingType) -> Unit, ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -237,6 +239,7 @@ internal fun SettingsMenu( onSfuRejoinClick = onSfuRejoinClick, onSfuFastReconnectClick = onSfuFastReconnectClick, isScreenShareEnabled = isScreenSharing, + onSelectScaleType = onSelectScaleType, loadRecordings = onLoadRecordings, ), ) @@ -298,6 +301,7 @@ private fun SettingsMenuPreview() { availableDevices = emptyList(), onDeviceSelected = {}, onShowFeedback = {}, + onSelectScaleType = {}, onNoiseCancellation = {}, loadRecordings = { emptyList() }, ), diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/base/DynamicMenu.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/base/DynamicMenu.kt index 404562a8162..463ca2625a9 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/base/DynamicMenu.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/base/DynamicMenu.kt @@ -225,6 +225,7 @@ private fun DynamicMenuPreview() { onDeviceSelected = {}, onShowFeedback = {}, onNoiseCancellation = {}, + onSelectScaleType = {}, loadRecordings = { emptyList() }, ), ) @@ -252,6 +253,7 @@ private fun DynamicMenuDebugOptionPreview() { availableDevices = emptyList(), onDeviceSelected = {}, onShowFeedback = {}, + onSelectScaleType = { }, onNoiseCancellation = {}, loadRecordings = { emptyList() }, ), @@ -272,6 +274,7 @@ private fun DynamicMenuDebugPreview() { onRestartPublisherIceClick = { }, onRestartSubscriberIceClick = { }, onToggleAudioFilterClick = { }, + onSelectScaleType = { }, onSwitchSfuClick = { }, ), ) 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 d96f8152d08..017fbb75b1a 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 @@ -1349,8 +1349,8 @@ public final class io/getstream/video/android/compose/ui/components/call/rendere public final class io/getstream/video/android/compose/ui/components/call/renderer/ParticipantVideoKt { public static final fun ParticipantLabel (Landroidx/compose/foundation/layout/BoxScope;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/ParticipantState;Landroidx/compose/ui/Alignment;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V public static final fun ParticipantLabel (Landroidx/compose/foundation/layout/BoxScope;Ljava/lang/String;ZLandroidx/compose/ui/Alignment;ZZFLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V - public static final fun ParticipantVideo (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/ParticipantState;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;II)V - public static final fun ParticipantVideoRenderer (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/ParticipantState;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V + public static final fun ParticipantVideo (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/ParticipantState;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/call/renderer/VideoRendererStyle;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function6;Landroidx/compose/runtime/Composer;II)V + public static final fun ParticipantVideoRenderer (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/ParticipantState;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V } public final class io/getstream/video/android/compose/ui/components/call/renderer/ParticipantsLayoutKt { @@ -1891,16 +1891,18 @@ public final class io/getstream/video/android/compose/ui/components/video/config public final class io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig { public static final field $stable I public fun ()V - public fun (ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)V - public synthetic fun (ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZLio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)V + public synthetic fun (ZLio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z - public final fun component2 ()Lio/getstream/video/android/compose/ui/components/video/VideoScalingType; - public final fun component3 ()Lkotlin/jvm/functions/Function3; - public final fun copy (ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig;ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig; + public final fun component2 ()Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig; + public final fun component3 ()Lio/getstream/video/android/compose/ui/components/video/VideoScalingType; + public final fun component4 ()Lkotlin/jvm/functions/Function3; + public final fun copy (ZLio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig;ZLio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig; public fun equals (Ljava/lang/Object;)Z public final fun getFallbackContent ()Lkotlin/jvm/functions/Function3; public final fun getMirrorStream ()Z + public final fun getModifiers ()Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig; public final fun getScalingType ()Lio/getstream/video/android/compose/ui/components/video/VideoScalingType; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -1909,26 +1911,64 @@ public final class io/getstream/video/android/compose/ui/components/video/config public final class io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigCreationScope { public static final field $stable I public fun ()V - public fun (ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)V - public synthetic fun (ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZLio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)V + public synthetic fun (ZLio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z - public final fun component2 ()Lio/getstream/video/android/compose/ui/components/video/VideoScalingType; - public final fun component3 ()Lkotlin/jvm/functions/Function3; - public final fun copy (ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigCreationScope; - public static synthetic fun copy$default (Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigCreationScope;ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigCreationScope; + public final fun component2 ()Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig; + public final fun component3 ()Lio/getstream/video/android/compose/ui/components/video/VideoScalingType; + public final fun component4 ()Lkotlin/jvm/functions/Function3; + public final fun copy (ZLio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigCreationScope; + public static synthetic fun copy$default (Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigCreationScope;ZLio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigCreationScope; public fun equals (Ljava/lang/Object;)Z public final fun getFallbackContent ()Lkotlin/jvm/functions/Function3; public final fun getMirrorStream ()Z + public final fun getModifiers ()Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig; public final fun getVideoScalingType ()Lio/getstream/video/android/compose/ui/components/video/VideoScalingType; public fun hashCode ()I public final fun setFallbackContent (Lkotlin/jvm/functions/Function3;)V public final fun setMirrorStream (Z)V + public final fun setModifiers (Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;)V public final fun setVideoScalingType (Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;)V public fun toString ()Ljava/lang/String; } public final class io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigKt { + public static final fun videoComponentModifiers (Lkotlin/jvm/functions/Function1;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig; public static final fun videoRenderConfig (Lkotlin/jvm/functions/Function1;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig; public static synthetic fun videoRenderConfig$default (Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig; } +public final class io/getstream/video/android/compose/ui/components/video/config/VideoRendererModifierScope { + public static final field $stable I + public fun ()V + public fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlin/jvm/functions/Function1; + public final fun component2 ()Lkotlin/jvm/functions/Function1; + public final fun copy (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifierScope; + public static synthetic fun copy$default (Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifierScope;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifierScope; + public fun equals (Ljava/lang/Object;)Z + public final fun getComponentModifier ()Lkotlin/jvm/functions/Function1; + public final fun getContainerModifier ()Lkotlin/jvm/functions/Function1; + public fun hashCode ()I + public final fun setComponentModifier (Lkotlin/jvm/functions/Function1;)V + public final fun setContainerModifier (Lkotlin/jvm/functions/Function1;)V + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig { + public static final field $stable I + public fun ()V + public fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlin/jvm/functions/Function1; + public final fun component2 ()Lkotlin/jvm/functions/Function1; + public final fun copy (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererModifiersConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getComponentModifier ()Lkotlin/jvm/functions/Function1; + public final fun getContainerModifier ()Lkotlin/jvm/functions/Function1; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantVideo.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantVideo.kt index 4414cb5f1cd..771e124b9e6 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantVideo.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantVideo.kt @@ -82,6 +82,7 @@ import io.getstream.video.android.compose.ui.components.indicator.GenericIndicat import io.getstream.video.android.compose.ui.components.indicator.NetworkQualityIndicator import io.getstream.video.android.compose.ui.components.indicator.SoundIndicator import io.getstream.video.android.compose.ui.components.video.VideoRenderer +import io.getstream.video.android.compose.ui.components.video.VideoScalingType import io.getstream.video.android.compose.ui.components.video.config.videoRenderConfig import io.getstream.video.android.core.Call import io.getstream.video.android.core.CameraDirection @@ -94,6 +95,7 @@ import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall import io.getstream.video.android.mock.previewParticipantsList import io.getstream.video.android.ui.common.R +import io.getstream.video.android.ui.common.util.StreamVideoUiDelicateApi import kotlinx.coroutines.delay /** @@ -127,6 +129,7 @@ public fun ParticipantVideo( .height(VideoTheme.dimens.componentHeightM), ) }, + scalingType: VideoScalingType = VideoScalingType.SCALE_ASPECT_FILL, videoFallbackContent: @Composable (Call) -> Unit = { val userName by participant.userNameOrId.collectAsStateWithLifecycle() val userImage by participant.image.collectAsStateWithLifecycle() @@ -196,6 +199,7 @@ public fun ParticipantVideo( ParticipantVideoRenderer( call = call, participant = participant, + scalingType = scalingType, videoFallbackContent = videoFallbackContent, ) @@ -221,12 +225,15 @@ public fun ParticipantVideo( * * @param call The call that contains all the participants state and tracks. * @param participant Participant to render. + * @param scalingType The scaling type for the video renderer. * @param videoFallbackContent Content is shown the video track is failed to load or not available. */ +@OptIn(StreamVideoUiDelicateApi::class) @Composable public fun ParticipantVideoRenderer( call: Call, participant: ParticipantState, + scalingType: VideoScalingType = VideoScalingType.SCALE_ASPECT_FILL, videoFallbackContent: @Composable (Call) -> Unit = { val userName by participant.userNameOrId.collectAsStateWithLifecycle() val userImage by participant.image.collectAsStateWithLifecycle() @@ -255,9 +262,10 @@ public fun ParticipantVideoRenderer( cameraDirection == CameraDirection.Front && me?.sessionId == participant.sessionId, ) } - val videoRendererConfig = remember(mirror, videoFallbackContent) { + val videoRendererConfig = remember(mirror, scalingType, videoFallbackContent) { videoRenderConfig { mirrorStream = mirror + this.videoScalingType = scalingType this.fallbackContent = videoFallbackContent } } diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/VideoRenderer.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/VideoRenderer.kt index 072a6bc3d33..62fa843d8ba 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/VideoRenderer.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/VideoRenderer.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(StreamVideoUiDelicateApi::class) + package io.getstream.video.android.compose.ui.components.video import androidx.compose.foundation.Image @@ -55,6 +57,7 @@ import io.getstream.video.android.core.model.VideoTrack import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall import io.getstream.video.android.ui.common.renderer.StreamVideoTextureViewRenderer +import io.getstream.video.android.ui.common.util.StreamVideoUiDelicateApi import io.getstream.webrtc.android.ui.VideoTextureViewRenderer @Composable @@ -67,7 +70,6 @@ public fun VideoRenderer( ) { Box( modifier = modifier - .fillMaxSize() .testTag("video_renderer_container"), ) { if (LocalInspectionMode.current) { @@ -106,7 +108,10 @@ public fun VideoRenderer( } if (mediaTrack != null) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Box( + modifier = videoRendererConfig.modifiers.containerModifier.invoke(this), + contentAlignment = Alignment.Center, + ) { AndroidView( factory = { context -> StreamVideoTextureViewRenderer(context).apply { @@ -132,8 +137,11 @@ public fun VideoRenderer( ) setupVideo(mediaTrack, v) }, - modifier = Modifier - .fillMaxSize() + modifier = videoRendererConfig + .modifiers + .componentModifier( + this, + ) .testTag("video_renderer"), ) } diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig.kt index 5997a627ac7..d5f7d6580c1 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig.kt @@ -16,24 +16,89 @@ package io.getstream.video.android.compose.ui.components.video.config +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import io.getstream.video.android.compose.ui.components.video.DefaultMediaTrackFallbackContent import io.getstream.video.android.compose.ui.components.video.VideoScalingType import io.getstream.video.android.core.Call +import io.getstream.video.android.ui.common.util.StreamVideoExperimentalApi +import io.getstream.video.android.ui.common.util.StreamVideoUiDelicateApi +private val defaultScalingType = VideoScalingType.SCALE_ASPECT_FILL +private val defaultVideoContainerModifier: BoxScope.() -> Modifier = { + Modifier + .fillMaxSize() + .align(Alignment.Center) +} +private val defaultVideoComponentModifier: BoxScope.() -> Modifier = + { + Modifier + .wrapContentSize() + .align(Alignment.Center) + } + +/** + * The [VideoRenderer] consists of two components one [Box] that acts as a container and another [AndroidView] that holds the actual [TextureView] for rendering the video. + * Only modify these modifiers if you want to change the default behavior of the [VideoRenderer] and understand exactly the effect they have on the layout. + */ +@StreamVideoUiDelicateApi +@Immutable +public data class VideoRendererModifiersConfig( + val containerModifier: BoxScope.() -> Modifier = defaultVideoContainerModifier, + val componentModifier: BoxScope.() -> Modifier = defaultVideoComponentModifier, +) + +/** + * A scope to create a modifier config. + */ +@StreamVideoUiDelicateApi +@StreamVideoExperimentalApi( + "Experimental exposure of internal modifiers for the video renderer. Maybe removed in the future without notice.", +) +@Immutable +public data class VideoRendererModifierScope( + var containerModifier: BoxScope.() -> Modifier = defaultVideoContainerModifier, + var componentModifier: BoxScope.() -> Modifier = defaultVideoComponentModifier, +) + +/** + * Builders scope for the builder function for the internal component modifiers. + */ +@StreamVideoExperimentalApi( + "Experimental exposure of internal modifiers for the video renderer. Maybe removed in the future without notice.", +) +@StreamVideoUiDelicateApi +public inline fun videoComponentModifiers( + block: VideoRendererModifierScope.() -> Unit, +): VideoRendererModifiersConfig { + val scope = VideoRendererModifierScope() + scope.block() + return VideoRendererModifiersConfig( + containerModifier = scope.containerModifier, + componentModifier = scope.componentModifier, + ) +} + +@OptIn(StreamVideoUiDelicateApi::class) @Immutable public data class VideoRendererConfig( val mirrorStream: Boolean = false, - val scalingType: VideoScalingType = VideoScalingType.SCALE_ASPECT_FILL, + val modifiers: VideoRendererModifiersConfig = VideoRendererModifiersConfig(), + val scalingType: VideoScalingType = defaultScalingType, val fallbackContent: @Composable (Call) -> Unit = {}, ) +@OptIn(StreamVideoUiDelicateApi::class) @Immutable public data class VideoRendererConfigCreationScope( public var mirrorStream: Boolean = false, - public var videoScalingType: VideoScalingType = VideoScalingType.SCALE_ASPECT_FILL, + public var modifiers: VideoRendererModifiersConfig = VideoRendererModifiersConfig(), + public var videoScalingType: VideoScalingType = defaultScalingType, public var fallbackContent: @Composable (Call) -> Unit = { DefaultMediaTrackFallbackContent( modifier = Modifier, @@ -45,6 +110,7 @@ public data class VideoRendererConfigCreationScope( /** * A builder method for a video renderer config. */ +@OptIn(StreamVideoUiDelicateApi::class) public inline fun videoRenderConfig( block: VideoRendererConfigCreationScope.() -> Unit = {}, ): VideoRendererConfig { @@ -52,6 +118,7 @@ public inline fun videoRenderConfig( scope.block() return VideoRendererConfig( mirrorStream = scope.mirrorStream, + modifiers = scope.modifiers, scalingType = scope.videoScalingType, fallbackContent = scope.fallbackContent, ) diff --git a/stream-video-android-ui-core/api/stream-video-android-ui-core.api b/stream-video-android-ui-core/api/stream-video-android-ui-core.api index ffa3b075764..985648e4dac 100644 --- a/stream-video-android-ui-core/api/stream-video-android-ui-core.api +++ b/stream-video-android-ui-core/api/stream-video-android-ui-core.api @@ -165,6 +165,13 @@ public final class io/getstream/video/android/ui/common/util/ResourcesKt { public abstract interface annotation class io/getstream/video/android/ui/common/util/StreamCallActivityDelicateApi : java/lang/annotation/Annotation { } +public abstract interface annotation class io/getstream/video/android/ui/common/util/StreamVideoExperimentalApi : java/lang/annotation/Annotation { + public abstract fun message ()Ljava/lang/String; +} + +public abstract interface annotation class io/getstream/video/android/ui/common/util/StreamVideoUiDelicateApi : java/lang/annotation/Annotation { +} + public final class io/getstream/video/android/ui/common/view/ParticipantContentView : android/widget/LinearLayout { public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/util/StreamCallActivityDelicateApi.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/util/StreamCallActivityDelicateApi.kt index d3f5e3310db..140c79e85de 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/util/StreamCallActivityDelicateApi.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/util/StreamCallActivityDelicateApi.kt @@ -30,3 +30,35 @@ package io.getstream.video.android.ui.common.util message = "This is a delicate Stream Video SDK Api, overriding this API may interfere on how the activity handles the call state.", ) public annotation class StreamCallActivityDelicateApi() + +@MustBeDocumented +@Retention(AnnotationRetention.BINARY) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.FIELD, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.PROPERTY, +) +@RequiresOptIn( + level = RequiresOptIn.Level.WARNING, + message = "This is a delicate Stream Video SDK Api, overriding this API may interfere on how the video is rendered and may introduce unwanted behavior.", +) +public annotation class StreamVideoUiDelicateApi() + +@MustBeDocumented +@Retention(AnnotationRetention.BINARY) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.FIELD, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.PROPERTY, +) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, + message = "This is an experimental Stream Video SDK Api, it may have breaking changes without notice.", +) +public annotation class StreamVideoExperimentalApi(val message: String)