davidliu
Committed by GitHub

Add mirror parameter to VideoRenderer (#218)

@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 </inspection_tool> 7 </inspection_tool>
8 <inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true"> 8 <inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
9 <option name="composableFile" value="true" /> 9 <option name="composableFile" value="true" />
  10 + <option name="previewFile" value="true" />
10 </inspection_tool> 11 </inspection_tool>
11 <inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true"> 12 <inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
12 <option name="composableFile" value="true" /> 13 <option name="composableFile" value="true" />
@@ -14,6 +15,7 @@ @@ -14,6 +15,7 @@
14 </inspection_tool> 15 </inspection_tool>
15 <inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true"> 16 <inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
16 <option name="composableFile" value="true" /> 17 <option name="composableFile" value="true" />
  18 + <option name="previewFile" value="true" />
17 </inspection_tool> 19 </inspection_tool>
18 <inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true"> 20 <inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
19 <option name="composableFile" value="true" /> 21 <option name="composableFile" value="true" />
@@ -29,6 +31,7 @@ @@ -29,6 +31,7 @@
29 </inspection_tool> 31 </inspection_tool>
30 <inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true"> 32 <inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
31 <option name="composableFile" value="true" /> 33 <option name="composableFile" value="true" />
  34 + <option name="previewFile" value="true" />
32 </inspection_tool> 35 </inspection_tool>
33 <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true"> 36 <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
34 <option name="composableFile" value="true" /> 37 <option name="composableFile" value="true" />
1 package io.livekit.android.compose 1 package io.livekit.android.compose
2 2
  3 +import android.graphics.Matrix
3 import androidx.compose.runtime.* 4 import androidx.compose.runtime.*
4 import androidx.compose.ui.Modifier 5 import androidx.compose.ui.Modifier
5 import androidx.compose.ui.layout.onGloballyPositioned 6 import androidx.compose.ui.layout.onGloballyPositioned
@@ -18,12 +19,20 @@ import io.livekit.android.room.track.video.ComposeVisibility @@ -18,12 +19,20 @@ import io.livekit.android.room.track.video.ComposeVisibility
18 fun VideoRenderer( 19 fun VideoRenderer(
19 room: Room, 20 room: Room,
20 videoTrack: VideoTrack, 21 videoTrack: VideoTrack,
21 - modifier: Modifier = Modifier 22 + modifier: Modifier = Modifier,
  23 + mirror: Boolean = false,
22 ) { 24 ) {
23 25
24 val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() } 26 val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() }
25 var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) } 27 var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) }
26 var view: TextureViewRenderer? by remember { mutableStateOf(null) } 28 var view: TextureViewRenderer? by remember { mutableStateOf(null) }
  29 + var videoScale by remember { mutableStateOf(1f) }
  30 +
  31 + videoScale = if (mirror) {
  32 + -1f
  33 + } else {
  34 + 1f
  35 + }
27 36
28 fun cleanupVideoTrack() { 37 fun cleanupVideoTrack() {
29 view?.let { boundVideoTrack?.removeRenderer(it) } 38 view?.let { boundVideoTrack?.removeRenderer(it) }
@@ -45,6 +54,11 @@ fun VideoRenderer( @@ -45,6 +54,11 @@ fun VideoRenderer(
45 } 54 }
46 } 55 }
47 56
  57 + DisposableEffect(view, videoScale) {
  58 + view?.scaleX = videoScale
  59 + onDispose { }
  60 + }
  61 +
48 DisposableEffect(room, videoTrack) { 62 DisposableEffect(room, videoTrack) {
49 onDispose { 63 onDispose {
50 videoSinkVisibility.onDispose() 64 videoSinkVisibility.onDispose()
@@ -13,28 +13,42 @@ import io.livekit.android.room.participant.Participant @@ -13,28 +13,42 @@ import io.livekit.android.room.participant.Participant
13 import io.livekit.android.room.track.Track 13 import io.livekit.android.room.track.Track
14 import io.livekit.android.room.track.VideoTrack 14 import io.livekit.android.room.track.VideoTrack
15 import io.livekit.android.util.flow 15 import io.livekit.android.util.flow
16 -import kotlinx.coroutines.flow.*  
17 16
18 /** 17 /**
19 - * This widget primarily serves as a way to observe changes in [videoTracks]. 18 + * This widget primarily serves as a way to observe changes in [Participant.videoTracks].
20 */ 19 */
21 @Composable 20 @Composable
22 fun VideoItemTrackSelector( 21 fun VideoItemTrackSelector(
23 room: Room, 22 room: Room,
24 participant: Participant, 23 participant: Participant,
25 - modifier: Modifier = Modifier 24 + modifier: Modifier = Modifier,
  25 + mirror: Boolean = false,
26 ) { 26 ) {
27 val videoTrackMap by participant::videoTracks.flow.collectAsState(initial = emptyList()) 27 val videoTrackMap by participant::videoTracks.flow.collectAsState(initial = emptyList())
28 val videoPubs = videoTrackMap.filter { (pub) -> pub.subscribed } 28 val videoPubs = videoTrackMap.filter { (pub) -> pub.subscribed }
29 .map { (pub) -> pub } 29 .map { (pub) -> pub }
30 - val videoTrack = videoPubs.firstOrNull { pub -> pub.source == Track.Source.SCREEN_SHARE }?.track as? VideoTrack  
31 - ?: videoPubs.firstOrNull { pub -> pub.source == Track.Source.CAMERA }?.track as? VideoTrack  
32 - ?: videoPubs.firstOrNull()?.track as? VideoTrack  
33 30
34 - if (videoTrack != null) { 31 + // Find the most appropriate video stream to show
  32 + // Prioritize screen share, then camera, then any video stream.
  33 + val videoPub = videoPubs.firstOrNull { pub -> pub.source == Track.Source.SCREEN_SHARE }
  34 + ?: videoPubs.firstOrNull { pub -> pub.source == Track.Source.CAMERA }
  35 + ?: videoPubs.firstOrNull()
  36 +
  37 + val videoTrack = videoPub?.track as? VideoTrack
  38 + val videoMuted by
  39 + if (videoPub != null) {
  40 + videoPub::muted.flow.collectAsState()
  41 + } else {
  42 + remember(videoPub) {
  43 + derivedStateOf { false }
  44 + }
  45 + }
  46 +
  47 + if (videoTrack != null && !videoMuted) {
35 VideoRenderer( 48 VideoRenderer(
36 room = room, 49 room = room,
37 videoTrack = videoTrack, 50 videoTrack = videoTrack,
  51 + mirror = mirror,
38 modifier = modifier 52 modifier = modifier
39 ) 53 )
40 } else { 54 } else {