Skip to content

Commit ca9ad26

Browse files
Pause incoming video if network conditions are bad (#1479)
* SDP offers should be processed serially * Fix wildcard * Add new SFU events * Pause videos when the network conditions are bad * ApiDump * Improve livestream player when working with the video "pause" * Spotless * Fix compile * Update audio usage in demo-app for livestreams * Update audio usage in demo-app for livestreams * Update example in demo-app * Spotless * Spotless * Do not stop channel
1 parent 27cf52f commit ca9ad26

26 files changed

Lines changed: 2453 additions & 860 deletions

File tree

demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import androidx.compose.material.icons.Icons
4848
import androidx.compose.material.icons.filled.Close
4949
import androidx.compose.material.icons.filled.MoreVert
5050
import androidx.compose.material.icons.filled.People
51+
import androidx.compose.material.icons.filled.SignalWifiBad
5152
import androidx.compose.material.rememberModalBottomSheetState
5253
import androidx.compose.runtime.Composable
5354
import androidx.compose.runtime.LaunchedEffect
@@ -62,19 +63,23 @@ import androidx.compose.runtime.rememberUpdatedState
6263
import androidx.compose.runtime.saveable.rememberSaveable
6364
import androidx.compose.runtime.setValue
6465
import androidx.compose.ui.Alignment
66+
import androidx.compose.ui.Alignment.Companion.CenterVertically
6567
import androidx.compose.ui.Modifier
6668
import androidx.compose.ui.draw.clip
6769
import androidx.compose.ui.graphics.Color
6870
import androidx.compose.ui.graphics.vector.ImageVector
6971
import androidx.compose.ui.platform.LocalConfiguration
7072
import androidx.compose.ui.platform.LocalContext
7173
import androidx.compose.ui.platform.testTag
74+
import androidx.compose.ui.res.stringResource
7275
import androidx.compose.ui.res.vectorResource
7376
import androidx.compose.ui.state.ToggleableState
77+
import androidx.compose.ui.text.style.TextAlign
7478
import androidx.compose.ui.tooling.preview.Preview
7579
import androidx.compose.ui.unit.IntOffset
7680
import androidx.compose.ui.unit.IntSize
7781
import androidx.compose.ui.unit.dp
82+
import androidx.compose.ui.unit.sp
7883
import androidx.compose.ui.window.Popup
7984
import androidx.lifecycle.compose.collectAsStateWithLifecycle
8085
import io.getstream.android.video.generated.models.TranscriptionSettingsResponse
@@ -123,11 +128,15 @@ import io.getstream.video.android.ui.menu.SettingsMenu
123128
import io.getstream.video.android.ui.menu.VideoFilter
124129
import io.getstream.video.android.ui.menu.availableVideoFilters
125130
import io.getstream.video.android.util.config.AppConfig
131+
import kotlinx.coroutines.FlowPreview
126132
import kotlinx.coroutines.delay
127133
import kotlinx.coroutines.flow.collectLatest
134+
import kotlinx.coroutines.flow.distinctUntilChanged
128135
import kotlinx.coroutines.flow.map
136+
import kotlinx.coroutines.flow.sample
129137
import kotlinx.coroutines.launch
130138

139+
@OptIn(FlowPreview::class)
131140
@Composable
132141
fun CallScreen(
133142
call: Call,
@@ -174,6 +183,23 @@ fun CallScreen(
174183

175184
val connection by call.state.connection.collectAsStateWithLifecycle()
176185
val me by call.state.me.collectAsStateWithLifecycle()
186+
var anyPausedVideos by remember { mutableStateOf(false) }
187+
188+
LaunchedEffect(key1 = call) {
189+
while (true) {
190+
delay(2000)
191+
call.state.participants
192+
.sample(2000L)
193+
.map { participants ->
194+
// Compute only paused videos
195+
participants.filter { it.videoPaused.value }
196+
}
197+
.distinctUntilChanged() // Only emit if the filtered list actually changes
198+
.collect { pausedVideos ->
199+
anyPausedVideos = pausedVideos.isNotEmpty()
200+
}
201+
}
202+
}
177203

178204
LaunchedEffect(key1 = connection) {
179205
if (connection == RealtimeConnection.Disconnected) {
@@ -567,7 +593,8 @@ fun CallScreen(
567593
val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT
568594

569595
val popupYOffset = if (isPortrait) {
570-
-(VideoTheme.dimens.componentHeightL + VideoTheme.dimens.spacingS).toPx().toInt()
596+
-(VideoTheme.dimens.componentHeightL + VideoTheme.dimens.spacingS).toPx()
597+
.toInt()
571598
} else {
572599
0
573600
}
@@ -610,6 +637,10 @@ fun CallScreen(
610637
SpeakingWhileMuted()
611638
}
612639

640+
if (anyPausedVideos) {
641+
BadNetworkLabel()
642+
}
643+
613644
if (showingLandscapeControls && orientation == Configuration.ORIENTATION_LANDSCAPE) {
614645
LandscapeControls(call, onChat = {
615646
showingLandscapeControls = false
@@ -800,6 +831,44 @@ private fun SpeakingWhileMuted() {
800831
}
801832
}
802833

834+
@Composable
835+
private fun BadNetworkLabel(
836+
modifier: Modifier = Modifier,
837+
) {
838+
Box(modifier = modifier.fillMaxSize()) {
839+
Row(
840+
modifier = modifier
841+
.align(Alignment.BottomCenter)
842+
.padding(vertical = 90.dp, horizontal = 16.dp)
843+
.background(
844+
color = VideoTheme.colors.baseSheetQuarternary,
845+
shape = VideoTheme.shapes.sheet,
846+
)
847+
.testTag("video_renderer_fallback_bad_network"),
848+
horizontalArrangement = Arrangement.Center,
849+
verticalAlignment = Alignment.CenterVertically,
850+
) {
851+
Icon(
852+
modifier = Modifier
853+
.padding(12.dp)
854+
.align(CenterVertically),
855+
imageVector = Icons.Default.SignalWifiBad,
856+
contentDescription = null,
857+
tint = VideoTheme.colors.basePrimary,
858+
)
859+
Text(
860+
modifier = Modifier.padding(12.dp),
861+
text = stringResource(
862+
id = io.getstream.video.android.ui.common.R.string.stream_video_call_bad_network,
863+
),
864+
color = VideoTheme.colors.basePrimary,
865+
textAlign = TextAlign.Center,
866+
fontSize = 14.sp,
867+
)
868+
}
869+
}
870+
}
871+
803872
@Composable
804873
fun isTablet(): Boolean {
805874
val configuration = LocalConfiguration.current

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import io.getstream.video.android.core.StreamVideoBuilder
3232
import io.getstream.video.android.core.logging.LoggingLevel
3333
import io.getstream.video.android.core.notifications.NotificationConfig
3434
import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry
35+
import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations
3536
import io.getstream.video.android.core.socket.common.token.TokenProvider
3637
import io.getstream.video.android.data.services.stream.GetAuthDataResponse
3738
import io.getstream.video.android.data.services.stream.StreamService
@@ -191,13 +192,18 @@ object StreamVideoInitHelper {
191192
token: String,
192193
loggingLevel: LoggingLevel,
193194
): StreamVideo {
195+
val callServiceConfigRegistry = CallServiceConfigRegistry()
196+
callServiceConfigRegistry.register(
197+
DefaultCallConfigurations.getLivestreamGuestCallServiceConfig(),
198+
)
194199
return StreamVideoBuilder(
195200
context = context,
196201
apiKey = apiKey,
197202
user = user,
198203
token = token,
199204
loggingLevel = loggingLevel,
200205
ensureSingleInstance = false,
206+
callServiceConfigRegistry = callServiceConfigRegistry,
201207
notificationConfig = testNotificationConfig ?: NotificationConfig(
202208
pushDeviceGenerators = listOf(
203209
FirebasePushDeviceGenerator(
@@ -220,7 +226,6 @@ object StreamVideoInitHelper {
220226
callUpdatesAfterLeave = true,
221227
appName = "Stream Video Demo App",
222228
audioProcessing = NoiseCancellation(context),
223-
callServiceConfigRegistry = CallServiceConfigRegistry(),
224229
).build()
225230
}
226231
}

0 commit comments

Comments
 (0)