davidliu
Committed by GitHub

fix state not being recomputed when track is attached (#61)

... ... @@ -7,7 +7,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.mockito.junit.MockitoJUnit
@ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
abstract class BaseTest {
// Uncomment to enable logging in tests.
//@get:Rule
... ... @@ -19,6 +19,6 @@ abstract class BaseTest {
@get:Rule
var coroutineRule = TestCoroutineRule()
@ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
fun runTest(testBody: suspend TestScope.() -> Unit) = coroutineRule.scope.runTest(testBody = testBody)
}
... ...
package io.livekit.android.room.participant
import io.livekit.android.coroutines.TestCoroutineRule
import io.livekit.android.BaseTest
import io.livekit.android.room.SignalClient
import io.livekit.android.room.track.TrackPublication
import io.livekit.android.util.flow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import livekit.LivekitModels
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito
class RemoteParticipantTest {
@OptIn(ExperimentalCoroutinesApi::class)
class RemoteParticipantTest : BaseTest() {
@get:Rule
var coroutineRule = TestCoroutineRule()
lateinit var signalClient: SignalClient
lateinit var participant: RemoteParticipant
... ... @@ -50,7 +52,6 @@ class RemoteParticipantTest {
val newTrackInfo = LivekitModels.ParticipantInfo.newBuilder(INFO)
.addTracks(TRACK_INFO)
.build()
participant.updateFromInfo(newTrackInfo)
assertEquals(1, participant.tracks.values.size)
... ... @@ -58,6 +59,54 @@ class RemoteParticipantTest {
}
@Test
fun tracksFlow() = runTest {
val newTrackInfo = LivekitModels.ParticipantInfo.newBuilder(INFO)
.addTracks(TRACK_INFO)
.build()
val emissions = mutableListOf<Map<String, TrackPublication>>()
val job = launch {
participant::tracks.flow.collect {
emissions.add(it)
}
}
assertEquals(1, emissions.size)
assertEquals(emptyMap<String, TrackPublication>(), emissions.first())
participant.updateFromInfo(newTrackInfo)
job.cancel()
assertEquals(2, emissions.size)
assertEquals(1, emissions[1].size)
}
@Test
fun audioTracksFlow() = runTest {
val newTrackInfo = LivekitModels.ParticipantInfo.newBuilder(INFO)
.addTracks(TRACK_INFO)
.build()
val emissions = mutableListOf<Map<String, TrackPublication>>()
val job = launch {
participant::audioTracks.flow.collect {
emissions.add(it)
}
}
assertEquals(1, emissions.size)
assertEquals(emptyMap<String, TrackPublication>(), emissions.first())
participant.updateFromInfo(newTrackInfo)
job.cancel()
assertEquals(2, emissions.size)
assertEquals(1, emissions[1].size)
}
@Test
fun updateFromInfoRemovesTrack() {
val newTrackInfo = LivekitModels.ParticipantInfo.newBuilder(INFO)
.addTracks(TRACK_INFO)
... ...
... ... @@ -2,7 +2,6 @@ package io.livekit.android.composesample
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
... ... @@ -21,8 +20,6 @@ import io.livekit.android.composesample.ui.theme.NoVideoBackground
import io.livekit.android.room.Room
import io.livekit.android.room.participant.ConnectionQuality
import io.livekit.android.room.participant.Participant
import io.livekit.android.room.track.Track
import io.livekit.android.room.track.VideoTrack
import io.livekit.android.util.flow
/**
... ... @@ -50,33 +47,20 @@ fun ParticipantItem(
}
}
) {
val (videoCamOff, identityBar, identityText, muteIndicator, connectionIndicator) = createRefs()
val videoTrack = participant.getTrackPublication(Track.Source.SCREEN_SHARE)?.track as? VideoTrack
?: participant.getTrackPublication(Track.Source.CAMERA)?.track as? VideoTrack
?: videoTracks.values.firstOrNull()?.track as? VideoTrack
val (videoItem, identityBar, identityText, muteIndicator, connectionIndicator) = createRefs()
if (videoTrack != null && videoTrack.enabled) {
VideoItemTrackSelector(
room = room,
participant = participant,
modifier = Modifier.fillMaxSize()
)
} else {
Icon(
painter = painterResource(id = R.drawable.outline_videocam_off_24),
contentDescription = null,
tint = Color.White,
modifier = Modifier.constrainAs(videoCamOff) {
modifier = Modifier.constrainAs(videoItem) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.wrapContent
height = Dimension.wrapContent
width = Dimension.fillToConstraints
height = Dimension.fillToConstraints
}
)
}
Surface(
color = Color(0x80000000),
... ...
package io.livekit.android.composesample
import androidx.compose.foundation.layout.Box
import androidx.compose.material.Icon
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.viewinterop.AndroidView
import com.github.ajalt.timberkt.Timber
import io.livekit.android.renderer.TextureViewRenderer
import io.livekit.android.room.Room
import io.livekit.android.room.participant.Participant
... ... @@ -12,7 +18,7 @@ import io.livekit.android.room.track.Track
import io.livekit.android.room.track.VideoTrack
import io.livekit.android.room.track.video.ComposeVisibility
import io.livekit.android.util.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.*
/**
* Widget for displaying a VideoTrack. Handles the Compose <-> AndroidView interop needed to use
... ... @@ -24,6 +30,7 @@ fun VideoItem(
videoTrack: VideoTrack,
modifier: Modifier = Modifier
) {
val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() }
var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) }
var view: TextureViewRenderer? by remember { mutableStateOf(null) }
... ... @@ -91,9 +98,21 @@ fun VideoItemTrackSelector(
val subscribedVideoTracksFlow by remember(participant) {
mutableStateOf(
participant::videoTracks.flow
.map { tracks -> tracks.values.filter { pub -> pub.subscribed } }
.flatMapLatest { videoTracks ->
combine(
videoTracks.values
.map { trackPublication ->
// Re-emit when track changes
trackPublication::track.flow
.map { trackPublication }
}
) { trackPubs ->
trackPubs.toList().filter { trackPublication -> trackPublication.track != null }
}
}
)
}
val videoTracks by subscribedVideoTracksFlow.collectAsState(initial = emptyList())
val videoTrack = videoTracks.firstOrNull { pub -> pub.source == Track.Source.SCREEN_SHARE }?.track as? VideoTrack
?: videoTracks.firstOrNull { pub -> pub.source == Track.Source.CAMERA }?.track as? VideoTrack
... ... @@ -105,5 +124,14 @@ fun VideoItemTrackSelector(
videoTrack = videoTrack,
modifier = modifier
)
} else {
Box(modifier = modifier) {
Icon(
painter = painterResource(id = R.drawable.outline_videocam_off_24),
contentDescription = null,
tint = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
\ No newline at end of file
... ...