From e539cfc024f9d43a2df4e89d2c496e596ba349ad Mon Sep 17 00:00:00 2001 From: KhooLy <73142442+KhooLy@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:13:32 +0300 Subject: [PATCH] fix: reset Android torrent telemetry sessions --- .../catalog/PlayerPlaybackRuntimeEffects.kt | 43 ++++++++++++++++--- .../fluxa/app/player/TorrentStreamManager.kt | 18 +++++++- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/PlayerPlaybackRuntimeEffects.kt b/app/src/main/java/com/fluxa/app/ui/catalog/PlayerPlaybackRuntimeEffects.kt index 1f23d71..3ef5543 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/PlayerPlaybackRuntimeEffects.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/PlayerPlaybackRuntimeEffects.kt @@ -7,6 +7,7 @@ import android.app.Activity import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.getValue import androidx.media3.common.Player @@ -31,6 +32,12 @@ import com.fluxa.app.shared.feature.player.TorrentStreamStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import java.util.UUID + +private data class PlaybackTelemetrySession( + val id: String?, + val startedAtMs: Long +) @Composable internal fun ExoPlayerListenerEffect( @@ -58,11 +65,22 @@ internal fun ExoPlayerListenerEffect( val latestAutoFallbackOnStreamError by rememberUpdatedState(autoFallbackOnStreamError) val latestCurrentStreamIndex by rememberUpdatedState(currentStreamIndex) val latestCurrentStreamsSize by rememberUpdatedState(currentStreamsSize) + val telemetrySession = remember(currentUrl) { + PlaybackTelemetrySession( + id = currentUrl.takeIf { it.isTorrentPlaybackUrl() }?.let { UUID.randomUUID().toString() }, + startedAtMs = android.os.SystemClock.elapsedRealtime() + ) + } + val latestTelemetrySession by rememberUpdatedState(telemetrySession) + + LaunchedEffect(telemetrySession) { + telemetrySession.id?.let(torrentManager::beginPlaybackTelemetry) + } DisposableEffect(exoPlayer, useMpvBackend) { - val playbackStartedAt = android.os.SystemClock.elapsedRealtime() var firstFrameReported = false var stallStartedAt: Long? = null + var listenerSessionId: String? = null val listener = object : Player.Listener { override fun onMetadata(metadata: androidx.media3.common.Metadata) { if (useMpvBackend) return @@ -87,14 +105,20 @@ internal fun ExoPlayerListenerEffect( override fun onPlaybackStateChanged(playbackState: Int) { if (useMpvBackend) return - if (currentUrl.isTorrentPlaybackUrl() && exoPlayer.currentPosition > 0L) { + val telemetry = latestTelemetrySession + if (listenerSessionId != telemetry.id) { + listenerSessionId = telemetry.id + firstFrameReported = false + stallStartedAt = null + } + if (telemetry.id != null && exoPlayer.currentPosition > 0L) { val now = android.os.SystemClock.elapsedRealtime() if (playbackState == Player.STATE_BUFFERING && stallStartedAt == null) { stallStartedAt = now - torrentManager.recordPlaybackTelemetry("stallStarted") + torrentManager.recordPlaybackTelemetry("stallStarted", sessionId = telemetry.id) } else if (playbackState != Player.STATE_BUFFERING) { stallStartedAt?.let { startedAt -> - torrentManager.recordPlaybackTelemetry("stallEnded", now - startedAt) + torrentManager.recordPlaybackTelemetry("stallEnded", now - startedAt, telemetry.id) stallStartedAt = null } } @@ -132,11 +156,18 @@ internal fun ExoPlayerListenerEffect( override fun onRenderedFirstFrame() { if (useMpvBackend) return - if (!firstFrameReported && currentUrl.isTorrentPlaybackUrl()) { + val telemetry = latestTelemetrySession + if (listenerSessionId != telemetry.id) { + listenerSessionId = telemetry.id + firstFrameReported = false + stallStartedAt = null + } + if (!firstFrameReported && telemetry.id != null) { firstFrameReported = true torrentManager.recordPlaybackTelemetry( "firstFrame", - android.os.SystemClock.elapsedRealtime() - playbackStartedAt + android.os.SystemClock.elapsedRealtime() - telemetry.startedAtMs, + telemetry.id ) } latestUpdateEngine.value { copy(render = render.copy(isVideoRendered = true), playerError = null) } diff --git a/player/src/androidMain/kotlin/com/fluxa/app/player/TorrentStreamManager.kt b/player/src/androidMain/kotlin/com/fluxa/app/player/TorrentStreamManager.kt index 8421412..1feafc1 100644 --- a/player/src/androidMain/kotlin/com/fluxa/app/player/TorrentStreamManager.kt +++ b/player/src/androidMain/kotlin/com/fluxa/app/player/TorrentStreamManager.kt @@ -53,6 +53,7 @@ class TorrentStreamManager private constructor() { @Volatile private var appContext: Context? = null @Volatile private var engine: TorrentServerEngine? = null @Volatile private var activeTorrentLink: String? = null + @Volatile private var activeTelemetrySessionId: String? = null private val _status = MutableStateFlow(TorrentStreamStatus()) val status: StateFlow = _status.asStateFlow() @@ -177,18 +178,31 @@ class TorrentStreamManager private constructor() { } } activeTorrentLink = null + activeTelemetrySessionId = null } /** * Reports player-side milestones to the local streaming engine. This is * best-effort telemetry: a failed report must never affect playback. */ - fun recordPlaybackTelemetry(event: String, elapsedMs: Long? = null) { + fun beginPlaybackTelemetry(sessionId: String) { + if (sessionId.isBlank() || activeTorrentLink == null) return + activeTelemetrySessionId = sessionId + recordPlaybackTelemetry("sessionStarted", sessionId = sessionId) + } + + fun recordPlaybackTelemetry(event: String, elapsedMs: Long? = null, sessionId: String) { val link = activeTorrentLink ?: return + if (activeTelemetrySessionId != sessionId) return scope.launch { runCatching { val payload = gson.toJson( - mapOf("link" to link, "event" to event, "elapsedMs" to elapsedMs) + mapOf( + "link" to link, + "sessionId" to sessionId, + "event" to event, + "elapsedMs" to elapsedMs + ) ) val request = Request.Builder() .url("${Constants.LocalServer.TORRENT_SERVER_BASE_URL}/telemetry")