Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve which participant is shown in PiP, allow for mirroring the me video stream for better experience. #1185

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1857,6 +1857,7 @@ public final class io/getstream/video/android/compose/ui/components/video/Compos
}

public final class io/getstream/video/android/compose/ui/components/video/VideoRendererKt {
public static final fun VideoRenderer (Landroidx/compose/ui/Modifier;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/ParticipantState$Media;Lio/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
public static final fun VideoRenderer (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/ParticipantState$Media;Landroidx/compose/ui/Modifier;Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
}

Expand All @@ -1878,3 +1879,57 @@ public final class io/getstream/video/android/compose/ui/components/video/VideoS
public static fun values ()[Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;
}

public final class io/getstream/video/android/compose/ui/components/video/config/ComposableSingletons$VideoRendererConfigKt {
public static final field INSTANCE Lio/getstream/video/android/compose/ui/components/video/config/ComposableSingletons$VideoRendererConfigKt;
public static field lambda-1 Lkotlin/jvm/functions/Function3;
public static field lambda-2 Lkotlin/jvm/functions/Function3;
public fun <init> ()V
public final fun getLambda-1$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-2$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function3;
}

public final class io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfig {
public static final field $stable I
public fun <init> ()V
public fun <init> (ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)V
public synthetic fun <init> (ZLio/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 fun equals (Ljava/lang/Object;)Z
public final fun getFallbackContent ()Lkotlin/jvm/functions/Function3;
public final fun getMirrorStream ()Z
public final fun getScalingType ()Lio/getstream/video/android/compose/ui/components/video/VideoScalingType;
public fun hashCode ()I
public final fun setMirrorStream (Z)V
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/compose/ui/components/video/config/VideoRendererConfigCreationScope {
public static final field $stable I
public fun <init> ()V
public fun <init> (ZLio/getstream/video/android/compose/ui/components/video/VideoScalingType;Lkotlin/jvm/functions/Function3;)V
public synthetic fun <init> (ZLio/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 fun equals (Ljava/lang/Object;)Z
public final fun getFallbackContent ()Lkotlin/jvm/functions/Function3;
public final fun getMirrorStream ()Z
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 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 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;
}

Original file line number Diff line number Diff line change
Expand Up @@ -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.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand Down Expand Up @@ -254,19 +255,36 @@ internal fun DefaultPictureInPictureContent(call: Call) {
video = video?.value,
)
} else {
val activeSpeakers by call.state.activeSpeakers.collectAsStateWithLifecycle()
val me by call.state.me.collectAsStateWithLifecycle()
val participants by call.state.participants.collectAsStateWithLifecycle()
val notMeOfTwo by remember {
// Special case where there are only two participants to take always the other participant,
// regardless of video track.
derivedStateOf {
participants.takeIf {
it.size == 2
}?.firstOrNull { it.sessionId != me?.sessionId }
}
}
val activeSpeakers by call.state.activeSpeakers.collectAsStateWithLifecycle()
val dominantSpeaker by call.state.dominantSpeaker.collectAsStateWithLifecycle()
val notMeActiveOrDominant by remember {
derivedStateOf {
val activeNotMe = activeSpeakers.firstOrNull {
it.sessionId != me?.sessionId
}
val dominantNotMe = dominantSpeaker?.takeUnless {
it.sessionId == me?.sessionId
}

if (activeSpeakers.isNotEmpty()) {
ParticipantVideo(
call = call,
participant = activeSpeakers.first(),
style = RegularVideoRendererStyle(labelPosition = Alignment.BottomStart),
)
} else if (me != null) {
activeNotMe ?: dominantNotMe
}
}
val participantToShow = notMeOfTwo ?: notMeActiveOrDominant ?: me
if (participantToShow != null) {
ParticipantVideo(
call = call,
participant = me!!,
participant = participantToShow,
style = RegularVideoRendererStyle(labelPosition = Alignment.BottomStart),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ 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.config.videoRenderConfig
import io.getstream.video.android.core.Call
import io.getstream.video.android.core.CameraDirection
import io.getstream.video.android.core.ParticipantState
import io.getstream.video.android.core.model.NetworkQuality
import io.getstream.video.android.core.model.Reaction
Expand Down Expand Up @@ -246,11 +248,20 @@ public fun ParticipantVideoRenderer(
}

val video by participant.video.collectAsStateWithLifecycle()

val cameraDirection by call.camera.direction.collectAsStateWithLifecycle()
val me by call.state.me.collectAsStateWithLifecycle()
val mirror by remember {
derivedStateOf {
participant.sessionId == me?.sessionId && cameraDirection == CameraDirection.Front
}
}
VideoRenderer(
call = call,
video = video,
videoFallbackContent = videoFallbackContent,
videoRendererConfig = videoRenderConfig {
mirrorStream = mirror
this.fallbackContent = videoFallbackContent
},
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import io.getstream.log.StreamLog
import io.getstream.video.android.compose.theme.VideoTheme
import io.getstream.video.android.compose.ui.components.video.VideoScalingType.Companion.toCommonScalingType
import io.getstream.video.android.compose.ui.components.video.config.VideoRendererConfig
import io.getstream.video.android.compose.ui.components.video.config.videoRenderConfig
import io.getstream.video.android.core.Call
import io.getstream.video.android.core.ParticipantState
import io.getstream.video.android.core.model.MediaTrack
Expand All @@ -55,28 +57,12 @@ import io.getstream.video.android.mock.previewCall
import io.getstream.video.android.ui.common.renderer.StreamVideoTextureViewRenderer
import io.getstream.webrtc.android.ui.VideoTextureViewRenderer

/**
* Renders a single video track based on the call state.
*
* @param call The call state that contains all the tracks and participants.
* @param video A media contains a video track or an audio track to be rendered.
* @param modifier Modifier for styling.
* @param videoScalingType Setup the video scale type of this renderer.
* @param videoFallbackContent Content is shown the video track is failed to load or not available.
* @param onRendered An interface that will be invoked when the video is rendered.
*/
@Composable
public fun VideoRenderer(
modifier: Modifier = Modifier,
call: Call,
video: ParticipantState.Media?,
modifier: Modifier = Modifier,
videoScalingType: VideoScalingType = VideoScalingType.SCALE_ASPECT_FILL,
videoFallbackContent: @Composable (Call) -> Unit = {
DefaultMediaTrackFallbackContent(
modifier,
call,
)
},
videoRendererConfig: VideoRendererConfig = videoRenderConfig(),
onRendered: (VideoTextureViewRenderer) -> Unit = {},
) {
if (LocalInspectionMode.current) {
Expand All @@ -94,7 +80,7 @@ public fun VideoRenderer(
}

// Show avatar always behind the video.
videoFallbackContent.invoke(call)
videoRendererConfig.fallbackContent.invoke(call)

if (video?.enabled == true) {
val mediaTrack = video.track
Expand Down Expand Up @@ -125,7 +111,10 @@ public fun VideoRenderer(
trackType = trackType,
onRendered = onRendered,
)
setScalingType(scalingType = videoScalingType.toCommonScalingType())
setMirror(videoRendererConfig.mirrorStream)
setScalingType(
scalingType = videoRendererConfig.scalingType.toCommonScalingType(),
)
setupVideo(mediaTrack, this)

view = this
Expand All @@ -139,6 +128,43 @@ public fun VideoRenderer(
}
}

/**
* Renders a single video track based on the call state.
*
* @param call The call state that contains all the tracks and participants.
* @param video A media contains a video track or an audio track to be rendered.
* @param modifier Modifier for styling.
* @param videoScalingType Setup the video scale type of this renderer.
* @param videoFallbackContent Content is shown the video track is failed to load or not available.
* @param onRendered An interface that will be invoked when the video is rendered.
*/
@Deprecated("Use VideoRenderer which accepts `videoConfig` instead.")
@Composable
public fun VideoRenderer(
call: Call,
video: ParticipantState.Media?,
modifier: Modifier = Modifier,
videoScalingType: VideoScalingType = VideoScalingType.SCALE_ASPECT_FILL,
videoFallbackContent: @Composable (Call) -> Unit = {
DefaultMediaTrackFallbackContent(
modifier,
call,
)
},
onRendered: (VideoTextureViewRenderer) -> Unit = {},
) {
VideoRenderer(
call = call,
video = video,
modifier = modifier,
videoRendererConfig = videoRenderConfig {
this.videoScalingType = videoScalingType
this.fallbackContent = videoFallbackContent
},
onRendered = onRendered,
)
}

private fun cleanTrack(
view: VideoTextureViewRenderer?,
mediaTrack: MediaTrack?,
Expand All @@ -154,7 +180,6 @@ private fun cleanTrack(
}
}
}

private fun setupVideo(
mediaTrack: MediaTrack?,
renderer: VideoTextureViewRenderer,
Expand All @@ -171,7 +196,7 @@ private fun setupVideo(
}

@Composable
private fun DefaultMediaTrackFallbackContent(
internal fun DefaultMediaTrackFallbackContent(
modifier: Modifier,
call: Call,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.video.config

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
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

@Immutable
public data class VideoRendererConfig(
var mirrorStream: Boolean = false,
val scalingType: VideoScalingType = VideoScalingType.SCALE_ASPECT_FILL,
val fallbackContent: @Composable (Call) -> Unit = {},
)

@Immutable
public data class VideoRendererConfigCreationScope(
public var mirrorStream: Boolean = false,
public var videoScalingType: VideoScalingType = VideoScalingType.SCALE_ASPECT_FILL,
public var fallbackContent: @Composable (Call) -> Unit = {
DefaultMediaTrackFallbackContent(
modifier = Modifier,
call = it,
)
},
)

/**
* A builder method for a video renderer config.
*/
public inline fun videoRenderConfig(
block: VideoRendererConfigCreationScope.() -> Unit = {},
): VideoRendererConfig {
val scope = VideoRendererConfigCreationScope()
scope.block()
return VideoRendererConfig(
mirrorStream = scope.mirrorStream,
scalingType = scope.videoScalingType,
fallbackContent = scope.fallbackContent,
)
}
Loading