diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index b6de11540f..88e4050d0f 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -127,6 +127,7 @@ public final class io/getstream/video/android/core/CallState { public final fun getErrors ()Lkotlinx/coroutines/flow/StateFlow; public final fun getIngress ()Lkotlinx/coroutines/flow/StateFlow; public final fun getLive ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getLiveDuration ()Lkotlinx/coroutines/flow/StateFlow; public final fun getLiveDurationInMs ()Lkotlinx/coroutines/flow/StateFlow; public final fun getLivestream ()Lkotlinx/coroutines/flow/StateFlow; public final fun getLocalParticipant ()Lkotlinx/coroutines/flow/StateFlow; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index 551e11de58..f911ef77a6 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -63,8 +63,8 @@ import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transform import kotlinx.coroutines.isActive @@ -125,6 +125,7 @@ import java.util.Date import java.util.Locale import java.util.SortedMap import java.util.UUID +import kotlin.time.Duration import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -372,7 +373,7 @@ public class CallState( } /** how long the call has been running, rounded to seconds, null if the call didn't start yet */ - public val duration: StateFlow = + public val duration: StateFlow = _durationInMs.transform { emit(((it ?: 0L) / 1000L).toDuration(DurationUnit.SECONDS)) } .stateIn(scope, SharingStarted.WhileSubscribed(10000L), null) @@ -422,17 +423,35 @@ public class CallState( /** the opposite of backstage, if we are live or not */ val live: StateFlow = _backstage.mapState { !it } - /** how many milliseconds the call has been running, null if the call didn't start yet */ - public val liveDurationInMs: StateFlow = - _durationInMs - .map { - if (live.value) { - it - } else { - null - } + /** + * How long the call has been live for, in milliseconds, or null if the call hasn't been live yet. + * Keeps its value when live ends and resets when live starts again. + * + * @see [liveDuration] + */ + public val liveDurationInMs = flow { + while (currentCoroutineContext().isActive) { + delay(1000) + + val liveStartedAt = _session.value?.liveStartedAt + val liveEndedAt = _session.value?.liveEndedAt ?: OffsetDateTime.now() + + liveStartedAt?.let { + val duration = liveEndedAt.toInstant().toEpochMilli() - liveStartedAt.toInstant().toEpochMilli() + emit(duration) } - .stateIn(scope, SharingStarted.WhileSubscribed(10000L), null) + } + }.distinctUntilChanged().stateIn(scope, SharingStarted.WhileSubscribed(10000L), null) + + /** + * How long the call has been live for, represented as [Duration], or null if the call hasn't been live yet. + * Keeps its value when live ends and resets when live starts again. + * + * @see [liveDurationInMs] + */ + public val liveDuration = liveDurationInMs.mapState { durationInMs -> + durationInMs?.takeIf { it >= 1000 }?.let { (it / 1000).toDuration(DurationUnit.SECONDS) } + } private val _egress: MutableStateFlow = MutableStateFlow(null) val egress: StateFlow = _egress