Navigating ExoPlayer Surface Types

Photo by dlxmedia.hu on Unsplash

Navigating ExoPlayer Surface Types

Tips for Smooth Video Playback in Android Apps

·

3 min read

ExoPlayer has become the go-to video player for Android development. During my recent integrations, I faced some challenges with its surface types. In this article, I'll dive into these surface types and share valuable insights to help you navigate them effectively.

If you use ExoPlayer with a dialog or bottom sheet and a scrollable view, you might encounter this issue.

The video will scroll over the top of the dialog or bottom sheet because the default surface type is SurfaceView.

Surface Types in ExoPlayer

There are different types of surfaces that can be used, each with its own characteristics.

  • SurfaceView

  • TextureView

  • Null

How They Render Video Differently

  • SurfaceView: Renders video content outside of the app's window, directly to a separate layer in the Android view hierarchy. This can lead to better performance and lower power usage.

  • TextureView: Renders video content within the app's window, which allows it to be transformed and animated like any other view. This flexibility comes at the cost of higher power consumption and potential performance drawbacks.

  • Null: No surface should be created for rendering video.

SurfaceView vs TextureView vs Null Characteristic

SurfaceViewTextureViewNull
Significantly Lower Power ConsumptionSmooth Animations and ScrollingAvoids the overhead of creating and managing a surface for audio only playback
More Accurate Frame TimingSupport more synchronized view animations for Android os <= 7.0Optimize performance for metadata Display Without Video
Support HDR
Support DRM-protected content
Full Resolution Rendering on Android TV

In summary, SurfaceView is recommended over the TextureView in most of the cases.

When should we consider change surface type?

Ask yourself...Surface Type
Is video audio only?null
Is Android os <= 7.0 required?TextureView
Is this video player view required to place on a dialog or bottom sheet or any overlay layout with scrolling feature?TextureView
If all of the above is NOSurfaceView

How to switch surface type?

PlayerView (ExoPlayer UI View) does not support changing the surface type at runtime. The simplest way to switch the surface type is by setting it in the PlayerView attribute in the XML layout.

<?xml version="1.0" encoding="utf-8"?>
<androidx.media3.ui.PlayerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/player_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:surface_type="texture_view">

</androidx.media3.ui.PlayerView>

What if the project is using Compose?

ExoPlayer currently does not provide a Composable PlayerView. Whether you switch the surface type or not, you need to use AndroidView to implement ExoPlayer.

So, what you need to do is similar to the XML layout: create an XML layout file with PlayerView.

Then, create a Composable to hold it.


import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import com.test.composableplayground.databinding.LayoutTextureExoplayerBinding

@Composable
fun ExoplayerTextureView(modifier: Modifier = Modifier, mediaItem: MediaItem) {
    val context = LocalContext.current

    val exoPlayer by remember {
        mutableStateOf(
            ExoPlayer.Builder(context).build()
        )
    }

    Box(modifier = modifier) {
        LifecycleEventEffect(event = Lifecycle.Event.ON_PAUSE) {
            exoPlayer.pause()
        }

        DisposableEffect(key1 = Unit) {
            onDispose {
                exoPlayer.release()
            }
        }

        DisposableEffect(key1 = mediaItem) {
            onDispose {
                exoPlayer.pause()
                exoPlayer.stop()
            }
        }

        LaunchedEffect(key1 = mediaItem) {
            exoPlayer.setMediaItem(mediaItem)
            exoPlayer.prepare()
        }

        AndroidViewBinding(
            factory = LayoutTextureExoplayerBinding::inflate,
            modifier = Modifier.fillMaxSize()
        ){
            playerView.player = exoPlayer
        }
    }
}

Result

To fix the issue where the PlayerView goes over the top of the bottom sheet, switch to using TextureView.

Reference

https://developer.android.com/media/media3/ui/playerview#surfacetype