Hey folks! I’m using ExoPlayer in Compose with `An...
# compose
c
Hey folks! I’m using ExoPlayer in Compose with
AndroidView
and I need to set a layout attribute as described at https://exoplayer.dev/ui-components.html#choosing-a-surface-type. I’m having trouble finding how I should do this. Could someone point me in the right direction? I’m going to add a small code snippet in a reply below.
Copy code
AndroidView(
    modifier = Modifier.padding(0.dp, 0.dp, 32.dp, 0.dp),
    factory = { viewBlockContext ->
        PlayerView(viewBlockContext).apply {
            hideController()
            useController = false
            resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
            player = exoPlayer
            layoutParams = LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            setKeepContentOnPlayerReset(true)
            setShutterBackgroundColor(android.graphics.Color.WHITE)
            exoPlayer.repeatMode = Player.REPEAT_MODE_ALL
        }
    }
)
That’s what I’ve got right now. It seems like I should maybe do something with an AttributeSet when
PlayerView
is initialized?
a
generally the
LayoutParams
on the view returned by the
AndroidView
factory don't matter, compose uses the modifier applied to the
AndroidView
composable to determine how the view should be measured
it looks like the equivalent would be to add
.fillMaxSize()
after your padding
or do you mean a specific attribute on the player view?
ah, for the exoplayer surface type specifically, it looks like exoplayer diverges from best practices here and you may need to inflate a layout xml resource to get it.
AttributeSet
is not subclassable by apps so you won't be able to fake it that way.
(The divergence is that views should always provide 1-1 equivalence between xml properties and available setters in code)
you can do the
LayoutInflater.from(context)
dance in the factory block or use
AndroidViewBinding
+ viewbinding instead; the latter is nicer for usability but if this is the only place you're using it, setting it all up is probably overkill
c
Ah, this is great info
This is just a one-off. I’ve got this one composable and I need to dynamically scale X to mirror the video, which can only happy when I set the surface type to texture_view 😅
It sounds like the
LayoutInflater.from(context)
is probably the way to go
👍 1
Could you maybe show me an example of that?
a
yeah, stick it in a layout file of just the one view and cast out of
layoutInflater.inflate(<http://R.layout.my|R.layout.my>_layout_file, null)
with
null
being the
ViewGroup
parent parameter
c
I’m a React guy who was dropped into this project so please bear with me as I ask some possibly dumb followups…
So I’ll create a layout resource file for this. Where exactly is
layoutInflater
called when using
AndroidView
in compose? You mentioned the factory block, so would it be before I call
PlayerView
?
c
i guess it depends on your use case but i instantiate the view direclty (very simplified snippet):
Copy code
AndroidView(
        factory = { PlayerView(context).apply {
            keepScreenOn = true
            useController = false
        }
 })
c
Yeah, that works fine for most things but they don’t let you change the surface type with code
And you can’t control the scale with the default surface
I think I’m on the right track with the layout inflater
c
ah yes that's possible you might have to inflate then
c
I’m missing something here. Could you show me an example of what it could look like to inflate in that factory?
hmm i did get it rendering but it feels wrong
Copy code
AndroidView(
            modifier = Modifier.padding(0.dp, 0.dp, 32.dp, 0.dp),
            factory = { viewBlockContext ->
                var output: PlayerView? = null
                LayoutInflater.from(viewBlockContext).apply {
                    (inflate(R.layout.exercise_video_view, null) as PlayerView).apply {
                        hideController()
                        player = exoPlayer
                        output = this
                    }
                }
                output!!
            }
        )
that
output
var seems gross
c
just remove the outer
apply
?
c
😂
thanks, i was staring at this for too long
😁 1
fantastic, i’m in business
I can now dynamically control video mirroring
For posterity, I now have this layout
Copy code
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.exoplayer2.ui.PlayerView xmlns:android="<http://schemas.android.com/apk/res/android>"
    xmlns:app="<http://schemas.android.com/apk/res-auto>"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:keep_content_on_player_reset="true"
    app:repeat_toggle_modes="all"
    app:shutter_background_color="@color/white"
    app:surface_type="texture_view"
    app:use_controller="false">

</com.google.android.exoplayer2.ui.PlayerView>
And it’s used with Compose
Copy code
AndroidView(
            modifier = Modifier.padding(0.dp, 0.dp, 32.dp, 0.dp),
            factory = { viewBlockContext ->
                val inflater = LayoutInflater.from(viewBlockContext)
                (inflater.inflate(R.layout.exercise_video_view, null) as PlayerView).apply {
                    scaleX = if (mirrored) -1f else 1f
                    hideController()
                    player = exoPlayer
                }
            }
        )
👍 2
Thank you both for your help! I never would have figured this out on my own
🙏 1
👍 1
446 Views