This write-up is divided into 5 cohesive parts for better delivery. This post is the second in this series. It is about playing a video from a remote source by using Jetpack Compose. The link for other posts can be found at the bottom.

Deciding on a Player

Before we start with how to play a video in a composable view, we need to decide on a player class. It would be rather a huge undertaking to develop a player from scratch. In this project, we are only interested in UI side of things. That relieves us from individual frame rendering, remote data fetching, and state management of a classic media player.

Android’s own MediaPlayer SDK is alright for a simple Video Player project. I’ve even used it in one of my older open source projects called BetterVideoPlayer. However, it has quite a few problems when it comes to supporting variety of codecs, especially from remote media sources. Rather than dealing with basic MediaPlayer problems, I decided to opt-in for ExoPlayer2 which is used by Youtube Android App. It has its own UI module to make things even easier for us but we’re going to limit our usage to only a surface(PlayerView) that will render the video.

Add this to build.gradle dependencies on app module.

implementation 'com.google.android.exoplayer:exoplayer:2.11.7'

Since we do not want to concern ourselves with managing a player at this time, we can get-away with a basic configuration to barely play a video.

@Composable
	fun VideoPlayer() {
	    // This is the official way to access current context from Composable functions
	    val context = ContextAmbient.current

	    // Do not recreate the player everytime this Composable commits
	    val exoPlayer = remember {
	        SimpleExoPlayer.Builder(context).build().apply {
	            val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(context,
	                Util.getUserAgent(context, context.packageName))

	            val source = ProgressiveMediaSource.Factory(dataSourceFactory)
	                .createMediaSource(Uri.parse(
	                    // Big Buck Bunny from Blender Project
	                    "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
	                ))

	            this.prepare(source)
	        }
	    }
	}

Before we go further, let’s add INTERNET permission to AndroidManifest since we will be using remote sources.

<uses-permission android:name="android.permission.INTERNET"/>

Inflating a Traditional View

ExoPlayer needs a surface to render the video that it holds. However, ExoPlayer currently does not support composable functions as legitimate Android Views. It works with TextureView, SurfaceView, and ExoPlayer’s own PlayerView. The first two choices require fine graining details about position, size, and aspect ratio. Instead of trying to deal with these problems, we can go with PlayerView which works quite alright with ExoPlayer.

Then the big question comes. How are we going to render a traditional Android View in a Composable. The answer is [AndroidView](https://joebirch.co/android/exploring-jetpack-compose-android-view/). It has a very simple interface that works well for most use cases including ours. A layout resource id to inflate and a callback to execute after inflation is completed.

AndroidView(R.layout.some_layout) { someLayoutView ->
    ...
}

This will be our gateway to creating a PlayerView and only place where we will ever use good old XML files. Create a new resource layout file called surface.xml with the following content

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:fitsSystemWindows="false">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/player_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:use_controller="false"/>

</FrameLayout>

Now, we can add AndroidView into our composition to finally glue together a VideoPlayer.

@Composable
	fun VideoPlayer() {
	    // This is the official way to access current context from Composable functions
	    val context = ContextAmbient.current

	    // Do not recreate the player everytime this Composable commits
	    val exoPlayer = remember {
	        SimpleExoPlayer.Builder(context).build().apply {
	            val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(context,
	                Util.getUserAgent(context, context.packageName))

	            val source = ProgressiveMediaSource.Factory(dataSourceFactory)
	                .createMediaSource(Uri.parse(
	                    // Big Buck Bunny from Blender Project
	                    "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
	                ))

	            this.prepare(source)
	        }
	    }

	    // Gateway to legacy Android Views through XML inflation. 
	    AndroidView(resId = R.layout.surface) {
	        val exoPlayerView = it.findViewById<PlayerView>(R.id.player_view)

	        exoPlayerView.player = exoPlayer
	        exoPlayer.playWhenReady = true
	    }
	}

#jetpack-compose #videos #players #android

Playing a Video with Jetpack Compose
38.80 GEEK