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

[AND 24] Implement manual quality selection #1242

Merged
merged 12 commits into from
Dec 6, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ 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
import io.getstream.video.android.core.model.PreferredVideoResolution
import io.getstream.video.android.core.utils.isEnabled
import io.getstream.video.android.filters.video.BlurredBackgroundVideoFilter
import io.getstream.video.android.filters.video.VirtualBackgroundVideoFilter
Expand Down Expand Up @@ -149,6 +150,10 @@ fun CallScreen(
val messageScope = rememberCoroutineScope()
var showingLandscapeControls by remember { mutableStateOf(false) }
var preferredScaleType by remember { mutableStateOf(VideoScalingType.SCALE_ASPECT_FILL) }
var selectedIncomingVideoResolution by remember {
mutableStateOf<PreferredVideoResolution?>(null)
}
var isIncomingVideoEnabled by remember { mutableStateOf(true) }

val connection by call.state.connection.collectAsStateWithLifecycle()
val me by call.state.me.collectAsStateWithLifecycle()
Expand Down Expand Up @@ -501,6 +506,23 @@ fun CallScreen(
onNoiseCancellation = {
isNoiseCancellationEnabled = call.toggleAudioProcessing()
},
selectedIncomingVideoResolution = selectedIncomingVideoResolution,
onSelectIncomingVideoResolution = {
call.setIncomingVideoEnabled(true)
isIncomingVideoEnabled = true

call.setPreferredIncomingVideoResolution(it)
selectedIncomingVideoResolution = it

isShowingSettingMenu = false
},
isIncomingVideoEnabled = isIncomingVideoEnabled,
onToggleIncomingVideoVisibility = {
call.setIncomingVideoEnabled(it)
isIncomingVideoEnabled = it

isShowingSettingMenu = false
},
onSelectScaleType = {
preferredScaleType = it
isShowingSettingMenu = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ 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 androidx.compose.material.icons.filled.Videocam
import androidx.compose.material.icons.filled.VideocamOff
import io.getstream.video.android.compose.ui.components.video.VideoScalingType
import io.getstream.video.android.core.audio.StreamAudioDevice
import io.getstream.video.android.core.model.PreferredVideoResolution
import io.getstream.video.android.ui.menu.base.ActionMenuItem
import io.getstream.video.android.ui.menu.base.DynamicSubMenuItem
import io.getstream.video.android.ui.menu.base.MenuItem
Expand All @@ -66,6 +69,10 @@ fun defaultStreamMenu(
onSwitchSfuClick: () -> Unit,
onShowFeedback: () -> Unit,
onNoiseCancellation: () -> Unit,
selectedIncomingVideoResolution: PreferredVideoResolution?,
onSelectIncomingVideoResolution: (PreferredVideoResolution?) -> Unit,
isIncomingVideoEnabled: Boolean,
onToggleIncomingVideoEnabled: (Boolean) -> Unit,
onDeviceSelected: (StreamAudioDevice) -> Unit,
onSfuRejoinClick: () -> Unit,
onSfuFastReconnectClick: () -> Unit,
Expand Down Expand Up @@ -130,6 +137,65 @@ fun defaultStreamMenu(
),
)
}
add(
SubMenuItem(
title = "Incoming video settings",
icon = Icons.Default.VideoSettings,
items = listOf(
ActionMenuItem(
title = "Auto Quality",
icon = Icons.Default.AspectRatio,
highlight = selectedIncomingVideoResolution == null,
action = { onSelectIncomingVideoResolution(null) },
),
ActionMenuItem(
title = "4K 2160p",
icon = Icons.Default.AspectRatio,
highlight = selectedIncomingVideoResolution == PreferredVideoResolution(3840, 2160),
action = {
onSelectIncomingVideoResolution(PreferredVideoResolution(3840, 2160))
},
),
ActionMenuItem(
title = "Full HD 1080p",
icon = Icons.Default.AspectRatio,
highlight = selectedIncomingVideoResolution == PreferredVideoResolution(1920, 1080),
action = {
onSelectIncomingVideoResolution(PreferredVideoResolution(1920, 1080))
},
),
ActionMenuItem(
title = "HD 720p",
icon = Icons.Default.AspectRatio,
highlight = selectedIncomingVideoResolution == PreferredVideoResolution(1280, 720),
action = {
onSelectIncomingVideoResolution(PreferredVideoResolution(1280, 720))
},
),
ActionMenuItem(
title = "SD 480p",
icon = Icons.Default.AspectRatio,
highlight = selectedIncomingVideoResolution == PreferredVideoResolution(640, 480),
action = {
onSelectIncomingVideoResolution(PreferredVideoResolution(640, 480))
},
),
ActionMenuItem(
title = "Data Saver 144p",
icon = Icons.Default.AspectRatio,
highlight = selectedIncomingVideoResolution == PreferredVideoResolution(256, 144),
action = {
onSelectIncomingVideoResolution(PreferredVideoResolution(256, 144))
},
),
ActionMenuItem(
title = if (isIncomingVideoEnabled) "Disable incoming video" else "Enable incoming video",
icon = if (isIncomingVideoEnabled) Icons.Default.VideocamOff else Icons.Default.Videocam,
action = { onToggleIncomingVideoEnabled(!isIncomingVideoEnabled) },
),
),
),
)
if (showDebugOptions) {
add(
SubMenuItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ 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
import io.getstream.video.android.core.model.PreferredVideoResolution
import io.getstream.video.android.tooling.extensions.toPx
import io.getstream.video.android.ui.call.ReactionsMenu
import io.getstream.video.android.ui.menu.base.ActionMenuItem
Expand All @@ -75,6 +76,10 @@ internal fun SettingsMenu(
onSelectVideoFilter: (Int) -> Unit,
onShowFeedback: () -> Unit,
onNoiseCancellation: () -> Unit,
selectedIncomingVideoResolution: PreferredVideoResolution?,
onSelectIncomingVideoResolution: (PreferredVideoResolution?) -> Unit,
isIncomingVideoEnabled: Boolean,
onToggleIncomingVideoVisibility: (Boolean) -> Unit,
onShowCallStats: () -> Unit,
onSelectScaleType: (VideoScalingType) -> Unit,
) {
Expand Down Expand Up @@ -236,6 +241,10 @@ internal fun SettingsMenu(
onSwitchSfuClick = onSwitchSfuClick,
onShowCallStats = onShowCallStats,
onNoiseCancellation = onNoiseCancellation,
selectedIncomingVideoResolution = selectedIncomingVideoResolution,
onSelectIncomingVideoResolution = { onSelectIncomingVideoResolution(it) },
isIncomingVideoEnabled = isIncomingVideoEnabled,
onToggleIncomingVideoEnabled = { onToggleIncomingVideoVisibility(it) },
onSfuRejoinClick = onSfuRejoinClick,
onSfuFastReconnectClick = onSfuFastReconnectClick,
isScreenShareEnabled = isScreenSharing,
Expand Down Expand Up @@ -303,6 +312,10 @@ private fun SettingsMenuPreview() {
onShowFeedback = {},
onSelectScaleType = {},
onNoiseCancellation = {},
selectedIncomingVideoResolution = null,
onSelectIncomingVideoResolution = {},
isIncomingVideoEnabled = true,
onToggleIncomingVideoEnabled = {},
loadRecordings = { emptyList() },
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ private fun DynamicMenuPreview() {
onDeviceSelected = {},
onShowFeedback = {},
onNoiseCancellation = {},
selectedIncomingVideoResolution = null,
onSelectIncomingVideoResolution = {},
isIncomingVideoEnabled = true,
onToggleIncomingVideoEnabled = {},
onSelectScaleType = {},
loadRecordings = { emptyList() },
),
Expand Down Expand Up @@ -255,6 +259,10 @@ private fun DynamicMenuDebugOptionPreview() {
onShowFeedback = {},
onSelectScaleType = { },
onNoiseCancellation = {},
selectedIncomingVideoResolution = null,
onSelectIncomingVideoResolution = {},
isIncomingVideoEnabled = true,
onToggleIncomingVideoEnabled = {},
loadRecordings = { emptyList() },
),
)
Expand Down
22 changes: 22 additions & 0 deletions stream-video-android-core/api/stream-video-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public final class io/getstream/video/android/core/Call {
public static synthetic fun sendReaction$default (Lio/getstream/video/android/core/Call;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public final fun setAudioFilter (Lio/getstream/video/android/core/call/audio/InputAudioFilter;)V
public final fun setAudioProcessingEnabled (Z)V
public final fun setIncomingVideoEnabled (Ljava/lang/Boolean;Ljava/util/List;)V
public static synthetic fun setIncomingVideoEnabled$default (Lio/getstream/video/android/core/Call;Ljava/lang/Boolean;Ljava/util/List;ILjava/lang/Object;)V
public final fun setPreferredIncomingVideoResolution (Lio/getstream/video/android/core/model/PreferredVideoResolution;Ljava/util/List;)V
public static synthetic fun setPreferredIncomingVideoResolution$default (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/model/PreferredVideoResolution;Ljava/util/List;ILjava/lang/Object;)V
public final fun setSessionId (Ljava/lang/String;)V
public final fun setVideoFilter (Lio/getstream/video/android/core/call/video/VideoFilter;)V
public final fun setVisibility (Ljava/lang/String;Lstream/video/sfu/models/TrackType;Z)V
Expand Down Expand Up @@ -149,6 +153,7 @@ public final class io/getstream/video/android/core/CallState {
public final fun getOwnCapabilities ()Lkotlinx/coroutines/flow/StateFlow;
public final fun getParticipantBySessionId (Ljava/lang/String;)Lio/getstream/video/android/core/ParticipantState;
public final fun getParticipantCounts ()Lkotlinx/coroutines/flow/StateFlow;
public final fun getParticipantVideoEnabledOverrides ()Lkotlinx/coroutines/flow/StateFlow;
public final fun getParticipants ()Lkotlinx/coroutines/flow/StateFlow;
public final fun getPermissionRequests ()Lkotlinx/coroutines/flow/StateFlow;
public final fun getPinnedParticipants ()Lkotlinx/coroutines/flow/StateFlow;
Expand Down Expand Up @@ -2857,6 +2862,10 @@ public final class io/getstream/video/android/core/call/stats/model/discriminato
public final fun fromAlias (Ljava/lang/String;)Lio/getstream/video/android/core/call/stats/model/discriminator/RtcReportType;
}

public final class io/getstream/video/android/core/call/utils/TrackOverridesHandlerKt {
public static final field ALL_PARTICIPANTS Ljava/lang/String;
}

public abstract class io/getstream/video/android/core/call/video/BitmapVideoFilter : io/getstream/video/android/core/call/video/VideoFilter {
public fun <init> ()V
public abstract fun applyFilter (Landroid/graphics/Bitmap;)V
Expand Down Expand Up @@ -3959,6 +3968,19 @@ public final class io/getstream/video/android/core/model/NetworkQuality$UnSpecif
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/model/PreferredVideoResolution {
public fun <init> (II)V
public final fun component1 ()I
public final fun component2 ()I
public final fun copy (II)Lio/getstream/video/android/core/model/PreferredVideoResolution;
public static synthetic fun copy$default (Lio/getstream/video/android/core/model/PreferredVideoResolution;IIILjava/lang/Object;)Lio/getstream/video/android/core/model/PreferredVideoResolution;
public fun equals (Ljava/lang/Object;)Z
public final fun getHeight ()I
public final fun getWidth ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/model/QueriedCalls {
public fun <init> (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;)V
public final fun component1 ()Ljava/util/List;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import io.getstream.video.android.core.events.VideoEventListener
import io.getstream.video.android.core.internal.InternalStreamVideoApi
import io.getstream.video.android.core.internal.network.NetworkStateProvider
import io.getstream.video.android.core.model.MuteUsersData
import io.getstream.video.android.core.model.PreferredVideoResolution
import io.getstream.video.android.core.model.QueriedMembers
import io.getstream.video.android.core.model.RejectReason
import io.getstream.video.android.core.model.SortField
Expand Down Expand Up @@ -1289,6 +1290,34 @@ public class Call(
return clientImpl.listTranscription(type, id)
}

/**
* Sets the preferred incoming video resolution.
*
* @param resolution The preferred resolution. Set to `null` to switch back to auto.
* @param sessionIds The participant session IDs to apply the resolution to. If `null`, the resolution will be applied to all participants.
*/
fun setPreferredIncomingVideoResolution(
resolution: PreferredVideoResolution?,
sessionIds: List<String>? = null,
) {
session?.let { session ->
session.trackOverridesHandler.updateOverrides(
sessionIds = sessionIds,
dimensions = resolution?.let { VideoDimension(it.width, it.height) },
)
}
}

/**
* Enables/disables incoming video feed.
*
* @param enabled Whether the video feed should be enabled or disabled. Set to `null` to switch back to auto.
* @param sessionIds The participant session IDs to enable/disable the video feed for. If `null`, the setting will be applied to all participants.
*/
fun setIncomingVideoEnabled(enabled: Boolean?, sessionIds: List<String>? = null) {
liviu-timar marked this conversation as resolved.
Show resolved Hide resolved
session?.trackOverridesHandler?.updateOverrides(sessionIds, visible = enabled)
}

@InternalStreamVideoApi
public val debug = Debug(this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
Expand Down Expand Up @@ -559,10 +560,13 @@ public class CallState(
internal val _reactions = MutableStateFlow<List<ReactionResponse>>(emptyList())
val reactions: StateFlow<List<ReactionResponse>> = _reactions

private val _errors: MutableStateFlow<List<ErrorEvent>> =
MutableStateFlow(emptyList())
private val _errors: MutableStateFlow<List<ErrorEvent>> = MutableStateFlow(emptyList())
public val errors: StateFlow<List<ErrorEvent>> = _errors

internal val _participantVideoEnabledOverrides =
MutableStateFlow<Map<String, Boolean?>>(emptyMap())
val participantVideoEnabledOverrides = _participantVideoEnabledOverrides.asStateFlow()

private var speakingWhileMutedResetJob: Job? = null
private var autoJoiningCall: Job? = null
private var ringingTimerJob: Job? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ public class CallStats(val call: Call, val callScope: CoroutineScope) {
val received = it.members["framesReceived"] as? Long
val duration = it.members["totalFramesDuration"] as? Long
if (participantId != null) {
logger.i {
"receiving video for $participantId at $frameWidth: ${it.members["frameWidth"]} and rendering it at ${visibleAt?.dimensions?.width} visible: ${visibleAt?.visible}"
logger.v {
"[stats] #manual-quality-selection; receiving video for $participantId at $frameWidth: ${it.members["frameWidth"]} and rendering it at ${visibleAt?.dimensions?.width} visible: ${visibleAt?.visible}"
}
}
}
Expand Down
Loading
Loading