Skip to content

Commit

Permalink
Fix a bug where the video would be missing on Android in a livestream (
Browse files Browse the repository at this point in the history
…#1211)

* allow edit call id

* add leave btn

* fix spotless

* add LiveVideoContent

* Handle ICERestart event correctly.

(cherry picked from commit 45ef8fd)

* fix emitLivestreamVideo

* spotless, api_dump

* modify LiveVideoContent

* modify LiveGuest background

* fix VideoRenderer component

* fix import

* Update the tutorial UI

* Spotless & ApiDump

---------

Co-authored-by: kanat <[email protected]>
  • Loading branch information
aleksandar-apostolov and kanat authored Oct 23, 2024
1 parent 275cbfb commit 4c007e9
Show file tree
Hide file tree
Showing 15 changed files with 350 additions and 129 deletions.
12 changes: 12 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 @@ -549,6 +549,7 @@ public final class io/getstream/video/android/core/ParticipantState {
public final fun muteVideo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun pin (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun setSessionId (Ljava/lang/String;)V
public final fun setVideoTrack (Lio/getstream/video/android/core/model/VideoTrack;)V
public fun toString ()Ljava/lang/String;
public final fun unpin (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun updateAudioLevel (F)V
Expand Down Expand Up @@ -3039,6 +3040,17 @@ public final class io/getstream/video/android/core/events/GoAwayEvent : io/getst
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/events/ICERestartEvent : io/getstream/video/android/core/events/SfuDataEvent {
public fun <init> (Lstream/video/sfu/models/PeerType;)V
public final fun component1 ()Lstream/video/sfu/models/PeerType;
public final fun copy (Lstream/video/sfu/models/PeerType;)Lio/getstream/video/android/core/events/ICERestartEvent;
public static synthetic fun copy$default (Lio/getstream/video/android/core/events/ICERestartEvent;Lstream/video/sfu/models/PeerType;ILjava/lang/Object;)Lio/getstream/video/android/core/events/ICERestartEvent;
public fun equals (Ljava/lang/Object;)Z
public final fun getPeerType ()Lstream/video/sfu/models/PeerType;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/video/android/core/events/ICETrickleEvent : io/getstream/video/android/core/events/SfuDataEvent {
public fun <init> (Ljava/lang/String;Lstream/video/sfu/models/PeerType;)V
public final fun component1 ()Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,9 @@ public class Call(
ring: Boolean = false,
notify: Boolean = false,
): Result<RtcSession> {
logger.d { "[join] #ringing; create: $create, ring: $ring, notify: $notify" }
logger.d {
"[join] #ringing; #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions"
}
val permissionPass =
clientImpl.permissionCheck.checkAndroidPermissions(clientImpl.context, this)
// Check android permissions and log a warning to make sure developers requested adequate permissions prior to using the call.
Expand Down Expand Up @@ -378,6 +380,9 @@ public class Call(
"Call $cid has already been joined. Please use call.leave before joining it again",
)
}
logger.d {
"[joinInternal] #track; create: $create, ring: $ring, notify: $notify, createOptions: $createOptions"
}

// step 1. call the join endpoint to get a list of SFUs

Expand Down Expand Up @@ -493,14 +498,19 @@ public class Call(
// first check if sfuSocketReconnectionTime isn't already set - if yes
// then we are already doing a full reconnect
if (state._connection.value == RealtimeConnection.Migrating) {
logger.d { "Skipping disconnected channel event - we are migrating" }
logger.d {
"[handleSignalChannelDisconnect] #track; Skipping disconnected channel event - we are migrating"
}
return
}

if (!isRetry && sfuSocketReconnectionTime != null) {
logger.d { "[handleSignalChannelDisconnect] Already doing a full reconnect cycle - ignoring call" }
logger.d {
"[handleSignalChannelDisconnect] #track; Already doing a full reconnect cycle - ignoring call"
}
return
}
logger.d { "[handleSignalChannelDisconnect] #track; isRetry: $isRetry" }

if (!isRetry) {
state._connection.value = RealtimeConnection.Reconnecting
Expand Down Expand Up @@ -675,11 +685,14 @@ public class Call(
}

fun setVisibility(sessionId: String, trackType: TrackType, visible: Boolean) {
logger.i {
"[setVisibility] #track; #sfu; sessionId: $sessionId, trackType: $trackType, visible: $visible"
}
session?.updateTrackDimensions(sessionId, trackType, visible)
}

fun handleEvent(event: VideoEvent) {
logger.i { "[call handleEvent] #sfu; event: $event" }
logger.v { "[call handleEvent] #sfu; event.type: ${event.getEventType()}" }

when (event) {
is GoAwayEvent ->
Expand Down Expand Up @@ -708,40 +721,52 @@ public class Call(
trackType: TrackType,
onRendered: (VideoTextureViewRenderer) -> Unit = {},
) {
logger.d { "[initRenderer] #sfu; sessionId: $sessionId" }
logger.d { "[initRenderer] #sfu; #track; sessionId: $sessionId" }

// Note this comes from peerConnectionFactory.eglBase
videoRenderer.init(
clientImpl.peerConnectionFactory.eglBase.eglBaseContext,
object : RendererCommon.RendererEvents {
override fun onFirstFrameRendered() {
logger.d { "[initRenderer.onFirstFrameRendered] #sfu; sessionId: $sessionId" }
val width = videoRenderer.measuredWidth
val height = videoRenderer.measuredHeight
logger.i {
"[initRenderer.onFirstFrameRendered] #sfu; #track; " +
"trackType: $trackType, dimension: ($width - $height), " +
"sessionId: $sessionId"
}
if (trackType != TrackType.TRACK_TYPE_SCREEN_SHARE) {
session?.updateTrackDimensions(
sessionId,
trackType,
true,
VideoDimension(
videoRenderer.measuredWidth,
videoRenderer.measuredHeight,
),
VideoDimension(width, height),
)
}
onRendered(videoRenderer)
}

override fun onFrameResolutionChanged(p0: Int, p1: Int, p2: Int) {
logger.d { "[initRenderer.onFrameResolutionChanged] #sfu; sessionId: $sessionId" }
override fun onFrameResolutionChanged(
videoWidth: Int,
videoHeight: Int,
rotation: Int,
) {
val width = videoRenderer.measuredWidth
val height = videoRenderer.measuredHeight
logger.v {
"[initRenderer.onFrameResolutionChanged] #sfu; #track; " +
"trackType: $trackType, " +
"dimension1: ($width - $height), " +
"dimension2: ($videoWidth - $videoHeight), " +
"sessionId: $sessionId"
}

if (trackType != TrackType.TRACK_TYPE_SCREEN_SHARE) {
session?.updateTrackDimensions(
sessionId,
trackType,
true,
VideoDimension(
videoRenderer.measuredWidth,
videoRenderer.measuredHeight,
),
VideoDimension(videoWidth, videoHeight),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,9 @@ public class CallState(
private val livestreamFlow: Flow<ParticipantState.Video?> = channelFlow {
fun emitLivestreamVideo() {
val participants = participants.value
val filteredVideo =
participants.mapNotNull { it.video.value }.firstOrNull { it.track != null }
val filteredVideo = participants.firstOrNull {
it.video.value?.enabled == true
}?.video?.value
scope.launch {
if (_backstage.value) {
send(null)
Expand All @@ -277,12 +278,17 @@ public class CallState(

scope.launch {
_participants.collect {
logger.v {
"[livestreamFlow] #track; participants: ${it.size} =>" +
"${it.map { "${it.value.userId.value} - ${it.value.video.value?.enabled}" }}"
}
emitLivestreamVideo()
}
}

// TODO: could optimize performance by subscribing only to relevant events
call.subscribe {
logger.v { "[livestreamFlow] #track; event.type: ${it.getEventType()}" }
if (it is TrackPublishedEvent) {
val participant = getOrCreateParticipant(it.sessionId, it.userId)

Expand All @@ -307,6 +313,7 @@ public class CallState(
}

// emit livestream Video
logger.d { "[livestreamFlow] #track; no args" }
emitLivestreamVideo()

awaitClose { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.getstream.video.android.core

import androidx.compose.runtime.Stable
import io.getstream.log.taggedLogger
import io.getstream.result.Result
import io.getstream.video.android.core.internal.InternalStreamVideoApi
import io.getstream.video.android.core.model.AudioTrack
Expand Down Expand Up @@ -55,6 +56,8 @@ public data class ParticipantState(
var trackLookupPrefix: String = "",
) {

private val logger by taggedLogger("ParticipantState")

val isLocal by lazy {
sessionId == call.session?.sessionId
}
Expand Down Expand Up @@ -199,6 +202,11 @@ public data class ParticipantState(
internal val _roles = MutableStateFlow<List<String>>(emptyList())
val roles: StateFlow<List<String>> = _roles

fun setVideoTrack(track: VideoTrack?) {
logger.i { "[setVideoTrack] #sfu; #track; userId: ${userId.value} track: $track" }
_videoTrack.value = track
}

fun updateFromParticipantInfo(participant: Participant) {
sessionId = participant.session_id

Expand Down
Loading

0 comments on commit 4c007e9

Please sign in to comment.