Constrain live preview while keeping stream controls visible

This commit is contained in:
evgeny committed 2026-10-12 11:21:30 +02:00
1 parent ef76ca5de3
commit 18661f0ef0
2 files changed
+18 -7

No files matched your search

@@ -76,7 +76,7 @@ object StreamController {
fun ended(id: Long, owner: StreamService) {
if (service === owner) service = null
val st = mutable.value ?: return
if (st.id != id) return
if (st.id != id || !st.ongoing) return
capturing = false
mutable.value = st.copy(phase = if (st.error != null) StreamPhase.FAILED else StreamPhase.ENDED)
LogManager.addLog("INFO", "stream", "publisher released id=$id")
@@ -10,11 +10,14 @@ import android.view.TextureView
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -111,11 +114,15 @@ fun StreamScreen(channelId: String, channelName: String, onBack: () -> Unit) {
}
local?.error?.let { Text(stringResource(it), color = MaterialTheme.colorScheme.error) }
if (sources.isEmpty() && local?.ongoing != true) Text(stringResource(R.string.stream_none))
sources.forEach { source ->
TextButton(onClick = { selected = source }) { Text(stringResource(R.string.stream_watch, source.name.ifEmpty { source.id.toULong().toString() })) }
Column(Modifier.fillMaxWidth().heightIn(max = 160.dp).verticalScroll(rememberScrollState())) {
sources.forEach { source ->
TextButton(onClick = { selected = source }) { Text(stringResource(R.string.stream_watch, source.name.ifEmpty { source.id.toULong().toString() })) }
}
}
selected?.let { source ->
key(group, source.id, source.epoch) { LiveStreamView(group, source.id, source.self) }
Box(Modifier.weight(1f).fillMaxWidth()) {
key(group, source.id, source.epoch) { LiveStreamView(group, source.id, source.self) }
}
TextButton(onClick = { selected = null }) { Text(stringResource(R.string.stream_close_view)) }
}
}
@@ -129,8 +136,10 @@ private fun LiveStreamView(group: Long, source: Long, localPreview: Boolean) {
val status by engine.status.collectAsState()
var surface by remember { mutableStateOf<Surface?>(null) }
DisposableEffect(engine) { onDispose { engine.stop() } }
Text(stringResource(status))
AndroidView(factory = { context ->
Column(Modifier.fillMaxSize()) {
Text(stringResource(status))
Box(Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
AndroidView(factory = { context ->
TextureView(context).apply {
surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {
@@ -143,7 +152,9 @@ private fun LiveStreamView(group: Long, source: Long, localPreview: Boolean) {
override fun onSurfaceTextureUpdated(texture: SurfaceTexture) {}
}
}
}, modifier = Modifier.fillMaxWidth().aspectRatio(aspect), onRelease = { view ->
}, modifier = Modifier.aspectRatio(aspect, matchHeightConstraintsFirst = true), onRelease = { view ->
engine.stop(); view.surfaceTextureListener = null; surface?.release(); surface = null
})
}
}
}