Skip to content
This repository was archived by the owner on Feb 10, 2026. It is now read-only.

Commit 1026e14

Browse files
update pip layout
1 parent 5b96341 commit 1026e14

1 file changed

Lines changed: 90 additions & 70 deletions

File tree

packages/react-native-client/android/src/main/java/io/fishjam/reactnative/PipContainerView.kt

Lines changed: 90 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import fishjam.media_events.server.Server
1919
import io.fishjam.reactnative.helpers.PictureInPictureHelperFragment
2020
import io.fishjam.reactnative.managers.TrackUpdateListener
2121

22+
data class RemoteTrackInfo(
23+
val videoTrack: com.fishjamcloud.client.media.VideoTrack?,
24+
val displayName: String?,
25+
val hasVideoTrack: Boolean
26+
)
27+
2228
class PipContainerView(
2329
context: Context,
2430
appContext: AppContext
@@ -34,6 +40,8 @@ class PipContainerView(
3440
private var primaryPlaceholder: android.widget.TextView? = null
3541
private var secondaryPlaceholder: android.widget.TextView? = null
3642
private var splitScreenContainer: android.widget.LinearLayout? = null
43+
private var primaryContainer: FrameLayout? = null
44+
private var secondaryContainer: FrameLayout? = null
3745

3846
@RequiresApi(Build.VERSION_CODES.O)
3947
private var pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
@@ -85,78 +93,93 @@ class PipContainerView(
8593
val peers = RNFishjamClient.getAllPeers()
8694
val localEndpointId = RNFishjamClient.fishjamClient.getLocalEndpoint().id
8795
val localPeer = peers.firstOrNull { it.id == localEndpointId } ?: return null
88-
96+
8997
return localPeer.tracks.values.firstOrNull { track ->
90-
track is com.fishjamcloud.client.media.VideoTrack &&
98+
track is com.fishjamcloud.client.media.VideoTrack &&
9199
(track.metadata as? Map<*, *>)?.get("type") == "camera"
92100
} as? com.fishjamcloud.client.media.VideoTrack
93101
}
94102

95-
private fun findRemoteVadActiveTrack(): com.fishjamcloud.client.media.VideoTrack? {
103+
private fun findRemoteVadActiveTrack(): RemoteTrackInfo? {
96104
val peers = RNFishjamClient.getAllPeers()
97105
val localEndpointId = RNFishjamClient.fishjamClient.getLocalEndpoint().id
98106
val remotePeers = peers.filter { it.id != localEndpointId }
99-
107+
100108
// First pass: look for active VAD
101109
for (peer in remotePeers) {
102110
// Find if this peer has an active VAD audio track
103111
val hasActiveVad = peer.tracks.values.any { track ->
104-
track is com.fishjamcloud.client.media.RemoteAudioTrack &&
112+
track is com.fishjamcloud.client.media.RemoteAudioTrack &&
105113
track.vadStatus == Server.MediaEvent.VadNotification.Status.STATUS_SPEECH
106114
}
107-
115+
108116
if (hasActiveVad) {
109-
// Return the first video track from this peer
110-
return peer.tracks.values.firstOrNull { track ->
117+
val videoTrack = peer.tracks.values.firstOrNull { track ->
111118
track is com.fishjamcloud.client.media.VideoTrack
112119
} as? com.fishjamcloud.client.media.VideoTrack
120+
121+
val displayName = (peer.metadata?.get("displayName") ?: peer.metadata?.get("name") ?: peer.id) as? String ?: peer.id
122+
123+
return RemoteTrackInfo(
124+
videoTrack = videoTrack,
125+
displayName = displayName,
126+
hasVideoTrack = videoTrack != null
127+
)
113128
}
114129
}
115-
116-
// Fallback: return first available remote video track
130+
131+
// Fallback: return first available remote peer with video track
117132
for (peer in remotePeers) {
118133
val videoTrack = peer.tracks.values.firstOrNull { track ->
119134
track is com.fishjamcloud.client.media.VideoTrack
120135
} as? com.fishjamcloud.client.media.VideoTrack
121-
136+
122137
if (videoTrack != null) {
123-
return videoTrack
138+
val displayName = (peer.metadata?.get("displayName") ?: peer.metadata?.get("name") ?: peer.id) as? String ?: peer.id
139+
return RemoteTrackInfo(
140+
videoTrack = videoTrack,
141+
displayName = displayName,
142+
hasVideoTrack = true
143+
)
124144
}
125145
}
126-
146+
127147
return null
128148
}
129149

130150
private fun updatePipViews() {
131151
val localCameraTrack = findLocalCameraTrack()
132-
val remoteVadTrack = findRemoteVadActiveTrack()
133-
152+
val remoteTrackInfo = findRemoteVadActiveTrack()
153+
134154
// Update primary view (local camera)
135155
if (localCameraTrack != null) {
136-
if (primaryVideoView == null) {
137-
primaryVideoView = VideoRendererView(context, appContext)
138-
}
139-
primaryVideoView?.let { view ->
140-
view.init(localCameraTrack.id())
141-
}
156+
primaryVideoView?.init(localCameraTrack.id())
157+
primaryVideoView?.visibility = View.VISIBLE
142158
primaryPlaceholder?.visibility = View.GONE
143159
} else {
144-
primaryVideoView = null
160+
primaryVideoView?.visibility = View.GONE
145161
primaryPlaceholder?.visibility = View.VISIBLE
146162
}
147-
163+
148164
// Update secondary view (remote VAD)
149-
if (remoteVadTrack != null) {
150-
if (secondaryVideoView == null) {
151-
secondaryVideoView = VideoRendererView(context, appContext)
165+
if (remoteTrackInfo != null) {
166+
if (remoteTrackInfo.hasVideoTrack) {
167+
// Has video track - show video
168+
secondaryVideoView?.init(remoteTrackInfo.videoTrack!!.id())
169+
secondaryVideoView?.visibility = View.VISIBLE
170+
secondaryPlaceholder?.visibility = View.GONE
171+
} else {
172+
// Has audio only - show display name
173+
secondaryVideoView?.visibility = View.GONE
174+
secondaryPlaceholder?.text = remoteTrackInfo.displayName
175+
secondaryPlaceholder?.visibility = View.VISIBLE
152176
}
153-
secondaryVideoView?.let { view ->
154-
view.init(remoteVadTrack.id())
155-
}
156-
secondaryPlaceholder?.visibility = View.GONE
177+
secondaryContainer?.visibility = View.VISIBLE
157178
} else {
158-
secondaryVideoView = null
159-
secondaryPlaceholder?.visibility = View.VISIBLE
179+
// No remote track - hide secondary container entirely
180+
secondaryVideoView?.visibility = View.GONE
181+
secondaryPlaceholder?.visibility = View.GONE
182+
secondaryContainer?.visibility = View.GONE
160183
}
161184
}
162185

@@ -187,8 +210,7 @@ class PipContainerView(
187210
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
188211
updateAutoEnterEnabled()
189212
}
190-
191-
// Register for track updates
213+
192214
RNFishjamClient.trackUpdateListenersManager.add(this)
193215
}
194216

@@ -201,8 +223,7 @@ class PipContainerView(
201223
.remove(fragment)
202224
.commitAllowingStateLoss()
203225
}
204-
205-
// Unregister from track updates
226+
206227
RNFishjamClient.trackUpdateListenersManager.remove(this)
207228
}
208229

@@ -229,79 +250,74 @@ class PipContainerView(
229250
)
230251
}
231252

232-
// Create primary container (left)
233-
val primaryContainer = FrameLayout(context).apply {
253+
primaryContainer = FrameLayout(context).apply {
234254
layoutParams = LinearLayout.LayoutParams(
235255
0,
236256
ViewGroup.LayoutParams.MATCH_PARENT,
237257
1f
238258
)
239259
}
240260

241-
// Create secondary container (right)
242-
val secondaryContainer = FrameLayout(context).apply {
261+
secondaryContainer = FrameLayout(context).apply {
243262
layoutParams = LinearLayout.LayoutParams(
244263
0,
245264
ViewGroup.LayoutParams.MATCH_PARENT,
246265
1f
247266
)
248267
}
249268

250-
// Create placeholders
251269
primaryPlaceholder = createPlaceholderTextView(primaryPlaceholderText)
252270
secondaryPlaceholder = createPlaceholderTextView(secondaryPlaceholderText)
253271

254-
primaryContainer.addView(
272+
primaryVideoView = VideoRendererView(context, appContext).apply {
273+
setVideoLayout("FILL")
274+
visibility = View.GONE
275+
}
276+
277+
secondaryVideoView = VideoRendererView(context, appContext).apply {
278+
setVideoLayout("FILL")
279+
visibility = View.GONE
280+
}
281+
282+
primaryContainer?.addView(
255283
primaryPlaceholder,
256284
FrameLayout.LayoutParams(
257285
ViewGroup.LayoutParams.MATCH_PARENT,
258286
ViewGroup.LayoutParams.MATCH_PARENT
259287
)
260288
)
261-
262-
secondaryContainer.addView(
263-
secondaryPlaceholder,
289+
primaryContainer?.addView(
290+
primaryVideoView,
264291
FrameLayout.LayoutParams(
265292
ViewGroup.LayoutParams.MATCH_PARENT,
266293
ViewGroup.LayoutParams.MATCH_PARENT
267294
)
268295
)
269296

270-
// Add video views if available
271-
primaryVideoView?.let {
272-
primaryContainer.addView(
273-
it,
274-
FrameLayout.LayoutParams(
275-
ViewGroup.LayoutParams.MATCH_PARENT,
276-
ViewGroup.LayoutParams.MATCH_PARENT
277-
)
297+
secondaryContainer?.addView(
298+
secondaryPlaceholder,
299+
FrameLayout.LayoutParams(
300+
ViewGroup.LayoutParams.MATCH_PARENT,
301+
ViewGroup.LayoutParams.MATCH_PARENT
278302
)
279-
primaryPlaceholder?.visibility = View.GONE
280-
}
281-
282-
secondaryVideoView?.let {
283-
secondaryContainer.addView(
284-
it,
285-
FrameLayout.LayoutParams(
286-
ViewGroup.LayoutParams.MATCH_PARENT,
287-
ViewGroup.LayoutParams.MATCH_PARENT
288-
)
303+
)
304+
secondaryContainer?.addView(
305+
secondaryVideoView,
306+
FrameLayout.LayoutParams(
307+
ViewGroup.LayoutParams.MATCH_PARENT,
308+
ViewGroup.LayoutParams.MATCH_PARENT
289309
)
290-
secondaryPlaceholder?.visibility = View.GONE
291-
}
310+
)
292311

293-
container.addView(primaryContainer)
294-
container.addView(secondaryContainer)
312+
primaryContainer?.let { container.addView(it) }
313+
secondaryContainer?.let { container.addView(it) }
295314

296315
return container
297316
}
298317

299318
fun layoutForPiPEnter() {
300319
if (rootView == null) return
301320

302-
// Update tracks before entering PiP
303-
updatePipViews()
304-
305321
// Hide all root view children
306322
for (i in 0 until rootView.childCount) {
307323
val child = rootView.getChildAt(i)
@@ -312,6 +328,8 @@ class PipContainerView(
312328
// Create and add split screen container
313329
splitScreenContainer = createSplitScreenContainer()
314330
rootView.addView(splitScreenContainer)
331+
332+
updatePipViews()
315333
}
316334

317335
fun layoutForPiPExit() {
@@ -330,6 +348,8 @@ class PipContainerView(
330348
primaryPlaceholder = null
331349
secondaryPlaceholder = null
332350
splitScreenContainer = null
351+
primaryContainer = null
352+
secondaryContainer = null
333353

334354
// Restore visibility of other root view children
335355
var visibilityIndex = 0

0 commit comments

Comments
 (0)