fix: PiP aspect ratio matches remote video when available
- Track remote video aspect ratio in CallController via VideoFrame rotatedWidth/rotatedHeight - PiP uses video's aspect ratio when remote video is active, falls back to 9:16 portrait for audio-only calls - Observe isRemoteVideoActive to update PiP params when video starts/stops during a call https://claude.ai/code/session_01Ak5tTkujpjNG1r5ASuPipZ
This commit is contained in:
@@ -82,11 +82,18 @@ class CallController(
|
|||||||
// Remote video frame monitoring — detects when peer stops sending video
|
// Remote video frame monitoring — detects when peer stops sending video
|
||||||
private val _isRemoteVideoActive = MutableStateFlow(false)
|
private val _isRemoteVideoActive = MutableStateFlow(false)
|
||||||
val isRemoteVideoActive: StateFlow<Boolean> = _isRemoteVideoActive.asStateFlow()
|
val isRemoteVideoActive: StateFlow<Boolean> = _isRemoteVideoActive.asStateFlow()
|
||||||
|
private val _remoteVideoAspectRatio = MutableStateFlow<Float?>(null)
|
||||||
|
val remoteVideoAspectRatio: StateFlow<Float?> = _remoteVideoAspectRatio.asStateFlow()
|
||||||
private val lastRemoteFrameTimeMs = AtomicLong(0L)
|
private val lastRemoteFrameTimeMs = AtomicLong(0L)
|
||||||
private var remoteVideoMonitorJob: kotlinx.coroutines.Job? = null
|
private var remoteVideoMonitorJob: kotlinx.coroutines.Job? = null
|
||||||
private val remoteFrameSink =
|
private val remoteFrameSink =
|
||||||
VideoSink { _: VideoFrame ->
|
VideoSink { frame: VideoFrame ->
|
||||||
lastRemoteFrameTimeMs.set(System.currentTimeMillis())
|
lastRemoteFrameTimeMs.set(System.currentTimeMillis())
|
||||||
|
val w = frame.rotatedWidth
|
||||||
|
val h = frame.rotatedHeight
|
||||||
|
if (w > 0 && h > 0) {
|
||||||
|
_remoteVideoAspectRatio.value = w.toFloat() / h.toFloat()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio/video toggle state (UI concerns, not domain state)
|
// Audio/video toggle state (UI concerns, not domain state)
|
||||||
@@ -358,6 +365,7 @@ class CallController(
|
|||||||
_isAudioMuted.value = false
|
_isAudioMuted.value = false
|
||||||
_isVideoEnabled.value = false
|
_isVideoEnabled.value = false
|
||||||
_isRemoteVideoActive.value = false
|
_isRemoteVideoActive.value = false
|
||||||
|
_remoteVideoAspectRatio.value = null
|
||||||
videoPausedByProximity = false
|
videoPausedByProximity = false
|
||||||
webRtcSession?.dispose()
|
webRtcSession?.dispose()
|
||||||
webRtcSession = null
|
webRtcSession = null
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import androidx.activity.compose.setContent
|
|||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.call.CallState
|
import com.vitorpamplona.amethyst.commons.call.CallState
|
||||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||||
@@ -79,6 +80,7 @@ class CallActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerPipReceiver()
|
registerPipReceiver()
|
||||||
|
observeVideoStateForPip(callController)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
AmethystTheme {
|
AmethystTheme {
|
||||||
@@ -140,6 +142,15 @@ class CallActivity : AppCompatActivity() {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeVideoStateForPip(controller: com.vitorpamplona.amethyst.service.call.CallController?) {
|
||||||
|
controller ?: return
|
||||||
|
lifecycleScope.launch {
|
||||||
|
controller.isRemoteVideoActive.collect {
|
||||||
|
updatePipParams()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun enterPipIfActive() {
|
private fun enterPipIfActive() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||||
val callManager = ActiveCallHolder.callManager ?: return
|
val callManager = ActiveCallHolder.callManager ?: return
|
||||||
@@ -168,10 +179,11 @@ class CallActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildPipParams(): PictureInPictureParams {
|
private fun buildPipParams(): PictureInPictureParams {
|
||||||
|
val aspectRatio = computePipAspectRatio()
|
||||||
val builder =
|
val builder =
|
||||||
PictureInPictureParams
|
PictureInPictureParams
|
||||||
.Builder()
|
.Builder()
|
||||||
.setAspectRatio(Rational(9, 16))
|
.setAspectRatio(aspectRatio)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
builder.setActions(buildPipActions())
|
builder.setActions(buildPipActions())
|
||||||
@@ -180,6 +192,22 @@ class CallActivity : AppCompatActivity() {
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun computePipAspectRatio(): Rational {
|
||||||
|
val controller = ActiveCallHolder.callController ?: return Rational(9, 16)
|
||||||
|
val isVideoActive = controller.isRemoteVideoActive.value
|
||||||
|
val videoRatio = controller.remoteVideoAspectRatio.value
|
||||||
|
|
||||||
|
if (isVideoActive && videoRatio != null && videoRatio > 0f) {
|
||||||
|
// Clamp to Android's allowed PiP range (roughly 1:2.39 to 2.39:1)
|
||||||
|
val clamped = videoRatio.coerceIn(0.42f, 2.39f)
|
||||||
|
val num = (clamped * 1000).toInt()
|
||||||
|
return Rational(num, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No active video — use portrait ratio
|
||||||
|
return Rational(9, 16)
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildPipActions(): List<RemoteAction> {
|
private fun buildPipActions(): List<RemoteAction> {
|
||||||
val actions = mutableListOf<RemoteAction>()
|
val actions = mutableListOf<RemoteAction>()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user