Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/src/main/java/nl/rogro82/pipup/PopupProps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ data class PopupProps(
// hardware decoder), false = always hardware, null/absent = automatic (software on
// Android < 8 and on Amlogic SoCs, whose hardware decoder shares the video layer with
// the HDMI input and freezes it when released - reported on a Xiaomi projector).
// height: optional explicit box height. When set, the stream area (and its poster)
// uses it until the first frame reports the stream's display aspect; without it the
// box takes the poster's aspect, which is wrong for anamorphic NVR snapshots.
data class Video(val uri: String, val width: Int = DEFAULT_MEDIA_WIDTH, val muted: Boolean = false,
val poster: String? = null, val softwareDecoder: Boolean? = null): Media()
val poster: String? = null, val softwareDecoder: Boolean? = null,
val height: Int? = null): Media()
@JsonIgnoreProperties(ignoreUnknown = true)
data class Image(val uri: String, val width: Int = DEFAULT_MEDIA_WIDTH): Media()
@JsonIgnoreProperties(ignoreUnknown = true)
Expand Down
20 changes: 17 additions & 3 deletions app/src/main/java/nl/rogro82/pipup/PopupView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,13 @@ sealed class PopupView(context: Context, val popup: PopupProps) : LinearLayout(c
this@Video.visibility = View.VISIBLE
}
frame.addView(textureView)
val provisionalHeight = provisionalVideoHeight(media.width, media.height)
attachPoster(frame, media.poster,
FrameLayout.LayoutParams(media.width, media.width * 9 / 16).apply { gravity = Gravity.CENTER }
) { pw, ph -> sizeTo(media.width, (media.width.toLong() * ph / pw).toInt()) }
sizeTo(media.width, media.width * 9 / 16)
FrameLayout.LayoutParams(media.width, provisionalHeight).apply { gravity = Gravity.CENTER }
) { pw, ph ->
posterVideoHeight(media.width, media.height, pw, ph)?.let { sizeTo(media.width, it) }
}
sizeTo(media.width, provisionalHeight)

val isRtsp = uri.startsWith("rtsp://", ignoreCase = true) || uri.startsWith("rtsps://", ignoreCase = true)
val item = MediaItem.fromUri(uri)
Expand Down Expand Up @@ -967,3 +970,14 @@ internal fun heightForDisplayAspect(targetWidth: Int, width: Int, height: Int, p
val displayWidth = width * ratio
return Math.round(targetWidth * height / displayWidth)
}

/// Stream-area height before the first frame: an explicit [requestedHeight], else 16:9.
internal fun provisionalVideoHeight(width: Int, requestedHeight: Int?): Int =
requestedHeight?.takeIf { it > 0 } ?: (width * 9 / 16)

/// Height to resize the stream area to once the poster has decoded, or null to keep the box.
/// With an explicit [requestedHeight] the box stays put: a camera snapshot can be anamorphic
/// (an NVR's 704x480 still of a 4:3 picture), so its pixel size is not the stream's shape.
internal fun posterVideoHeight(width: Int, requestedHeight: Int?, posterWidth: Int, posterHeight: Int): Int? =
if (requestedHeight != null && requestedHeight > 0) null
else (width.toLong() * posterHeight / posterWidth).toInt()
32 changes: 32 additions & 0 deletions app/src/test/java/nl/rogro82/pipup/VideoPosterHeightTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package nl.rogro82.pipup

import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

class VideoPosterHeightTest {
@Test
fun withoutRequestedHeightTheBoxStartsSixteenNine() {
assertEquals(360, provisionalVideoHeight(640, null))
}

@Test
fun requestedHeightSetsTheProvisionalBox() {
assertEquals(480, provisionalVideoHeight(640, 480))
}

@Test
fun nonPositiveRequestedHeightIsIgnored() {
assertEquals(360, provisionalVideoHeight(640, 0))
}

@Test
fun withoutRequestedHeightThePosterSetsTheShape() {
assertEquals(436, posterVideoHeight(640, null, 704, 480))
}

@Test
fun requestedHeightKeepsTheBoxForAnAnamorphicPoster() {
assertNull(posterVideoHeight(640, 480, 704, 480))
}
}
Loading