Android TV: Getting Started

Learn how to create your first Android TV app! In this tutorial, you’ll create an Android TV app for viewing RayWenderlich Youtube channel videos! By Ivan Kušt.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Presenting Video Details

Leanback provides DetailsSupportFragment, which makes setting up video details screen easy.

Creating Details View for Selected Playlist

Open VideoDetailsFragment.kt and notice it already extends DetailsSupportFragment. Its view contains two data rows, which you’ll implement now.

First, add a function that creates an adapter for actions available for the video:

private fun getActionAdapter() = ArrayObjectAdapter().apply {
  add(
      Action(
          ACTION_WATCH,
          resources.getString(R.string.watch_action_title),
          resources.getString(R.string.watch_action_subtitle)
      )
  )
}

The function creates an instance of ArrayObjectAdapter with a single Action item. The item represents action to watch the video that also passes the respective title and subtitle.

Now, create a function that creates a details row:

private fun createDetailsOverviewRow(
  selectedVideo: Video, 
  detailsAdapter: ArrayObjectAdapter
): DetailsOverviewRow {

  val context = requireContext()

  // 1
  val row = DetailsOverviewRow(selectedVideo).apply {
    imageDrawable = ContextCompat.getDrawable(context, R.drawable.default_background)
    actionsAdapter = getActionAdapter()
  }

  // 2
  val width = resources.getDimensionPixelSize(R.dimen.details_thumbnail_width)
  val height = resources.getDimensionPixelSize(R.dimen.details_thumbnail_height)

  // 3
  loadDrawable(requireActivity(), selectedVideo.cardImageUrl, R.drawable.default_background, width, height)
  { resource ->
    row.imageDrawable = resource
    // 4
    detailsAdapter.notifyArrayItemRangeChanged(0, detailsAdapter.size())
  }

  return row
}

This function creates the first data row. It contains details for the video: thumbnail, description and action for playing the video.

Here, you:

  1. Create a new instance of DetailsOverviewRow and set default video thumbnail and play action.
  2. Get the width and height for the thumbnail drawable.
  3. Load a drawable using loadDrawable() from Util.kt. If loading is successful, you show the loaded image.
  4. Notify the details adapter that held data has changed.

Then, add a function that handles actions:

private fun onActionClicked(action: Action, videoItem: VideoItem) {
  if (action.id == ACTION_WATCH) {
    val intent = VideoPlaybackActivity.newIntent(requireContext(), videoItem)
    startActivity(intent)
  }
}

It checks whether the action has ID ACTION_WATCH and, if it does, starts VideoPlaybackActivity.

Now, create a function that creates a presenter for the details row:

private fun createDetailsOverviewRowPresenter(
  videoItem: VideoItem,
  actionHandler: (Action, VideoItem) -> Unit
): FullWidthDetailsOverviewRowPresenter =
  // 1
   FullWidthDetailsOverviewRowPresenter(DetailsDescriptionPresenter()).apply {
    // 2
    backgroundColor =
        ContextCompat.getColor(requireContext(), R.color.selected_background)

    // 3
    val sharedElementHelper = FullWidthDetailsOverviewSharedElementHelper()
    sharedElementHelper.setSharedElementEnterTransition(
        activity, 
        VideoDetailsActivity.SHARED_ELEMENT_NAME
    )
    setListener(sharedElementHelper)
    isParticipatingEntranceTransition = true

    // 4
    onActionClickedListener = OnActionClickedListener { 
      actionHandler(it, videoItem) 
    }
  }

Through these steps, you:

  1. Create an instance of FullWidthDetailsOverviewRowPresenter and configure it in apply below
  2. Set the background color
  3. Set up shared transition with Catalog screen
  4. Set a click listener

Finally, create a function:

private fun createPresenterSelector(videoItem: VideoItem) = 
  ClassPresenterSelector().apply {
    // 1
    addClassPresenter(
        DetailsOverviewRow::class.java,
        createDetailsOverviewRowPresenter(videoItem, ::onActionClicked)
    )

    // 2
    addClassPresenter(
        ListRow::class.java,
        ListRowPresenter()
    )
  }

This function returns ClassPresenterSelector with a proper presenter instance depending on the class of the rendering item:

  1. DetailsOverviewRowPresenter created by createDetailsOverviewRowPresenter() for DetailsOverivewRow items.
  2. ListRowPresenter for ListRow items.

Now, override onActivityCreated() and add the following code:

override fun onActivityCreated(savedInstanceState: Bundle?) {
  super.onActivityCreated(savedInstanceState)

  val videoItem = arguments?.getSerializable(ARGUMENT_VIDEO) as VideoItem
}

Here, you get VideoItem passed from arguments.

Then, add the code at the end of onActivityCreated():

adapter = ArrayObjectAdapter(createPresenterSelector(videoItem)).apply {
  add(createDetailsOverviewRow(videoItem.video, this))
}

With these lines, you create and assign an adapter for VideoDetailsFragment.

Build and run. Select a video, and you’ll see the new details screen.

App details screen showing video's title, source and brief description

Previewing Recommendations

Add a new function for creating recommendations to VideoDetailsFragment.kt:

private fun createRelatedVideosRow(videoItem: VideoItem): ListRow {
  // 1
  val selectedVideo = videoItem.video
  val playlistVideos = videoItem.playlist

  // 2
  val recommendations = ArrayList(playlistVideos.filterNot { it == selectedVideo })

  // 3
  val listRowAdapter = ArrayObjectAdapter(CatalogCardPresenter())
  val header = HeaderItem(0, getString(R.string.related_videos))

  if (recommendations.isNotEmpty()) {
    for (i in 0 until NUM_RECOMMENDATIONS) {
      listRowAdapter.add(
          VideoItem(recommendations.random(), playlistVideos)
      )
    }
  }

  // 4
  return ListRow(header, listRowAdapter)
}

Here is the explanation. You:

  1. Get the selected video and list of all videos in the corresponding playlist from VideoItem
  2. Construct recommendations by filtering out selected video from the playlist videos
  3. Create an adapter that holds all the recommendations and fill it with items that represent recommendations
  4. Wrap the new adapter in ListRow and return it

Add the following line at the end of the apply block in onActivityCreated():

add(createRelatedVideosRow(videoItem))

Build and run. Open details for a video and navigate down to see recommended videos.

App details screen showing video recommendations

Previewing Recommendation Video Details

Set an item click listener by adding the following code to the end of onActivityCreated():

onItemViewClickedListener = OnItemViewClickedListener { itemViewHolder, item, _, _ ->
  if(item is VideoItem) {
    showVideoDetails(requireActivity(), itemViewHolder, item)
  }
}

In this code, you present video details just as you did for catalog videos.

Build and run. Select a video recommendation to see the details of that video.

App details screen showing video's title, source and brief description

Setting Details View Background

To add a final touch to the details screen, call initializeBackground() at the end of onActivityCreated():

initializeBackground(videoItem.video)

Build and run. You’ll see the background on the details screen now.

App details screen with background set up

Select any video from the catalog and open its details. Then, select Watch Now For Free.

You’ll see an empty screen for now.

Empty video playback screen

Playing a Video

And now for the most important part — playing the video.

Leanback provides two useful Fragments for implementing a playback screen:

  • PlaybackSupportFragment: A fragment for displaying playback controls and video
  • VideoSupportFragment: A subclass of PlaybackSupportFragment that provides SurfaceView for rendering the video

The only supported way of playing YouTube videos currently is using its IFrame player from WebView.

Note: If you’re interested in YouTube’s IFrame player, you can find more details here.

You’ll use a library that wraps the IFrame player into View.

Introducing the Glue Mechanisms

Leanback has a specific way of separating UI video playback controls from the video player. The following diagram shows the classes:

Glue classes diagram

PlaybackSupportFragment provides the basic functionality of a video playback screen. It uses PlaybackTransportControlGlue and PlaybackSupportFragmentGlueHost to communicate with PlayerAdapter. PlayerAdapter is a wrapper for a class (or more classes) for playing a video. PlaybackTransportControlGlue handles displaying playback controls and communicating with PlayerAdapter.

This setup looks complicated at first, but it just separates video player logic from Fragment that displays video and controls.

To add support for your video player, you extend PlayerAdapter and PlaybackTransportControlGlue. For playing YouTube videos, you’ll use EmbeddedPlayerAdapter, which is provided in the project.

First, you have to finish implementing VideoPlaybackControlGlue, which communicates with EmbeddedPlayerAdapter.

Open VideoPlaybackControlGlue.kt and add fields that hold playback actions:

private lateinit var skipPreviousAction: PlaybackControlsRow.SkipPreviousAction
private lateinit var skipNextAction: PlaybackControlsRow.SkipNextAction
private lateinit var fastForwardAction : PlaybackControlsRow.FastForwardAction
private lateinit var rewindAction : PlaybackControlsRow.RewindAction

Then, initialize actions in onCreatePrimaryActions(). Add this function at the end of the class:

override fun onCreatePrimaryActions(primaryActionsAdapter: ArrayObjectAdapter?) {
  super.onCreatePrimaryActions(primaryActionsAdapter)

  // 1
  skipPreviousAction = PlaybackControlsRow.SkipPreviousAction(context)
  rewindAction = PlaybackControlsRow.RewindAction(context)
  fastForwardAction = PlaybackControlsRow.FastForwardAction(context)
  skipNextAction = PlaybackControlsRow.SkipNextAction(context)

  // 2
  primaryActionsAdapter?.apply {
    add(skipPreviousAction)
    add(rewindAction)
    add(fastForwardAction)
    add(skipNextAction)
  }
}

Leanback calls this function when it creates a row of primary video controls. Here’s what it’s doing:

  1. Creates new instances of Skip Previous, Rewind, Fast Forward and Skip Next actions.
  2. Adds created actions to an adapter for controls row.

Add the following line at the end of next():

playerAdapter.next()

This calls next() from EmbeddedPlayerAdapter once the user clicks the Skip Next action.

And add the following at the end of previous():

playerAdapter.previous()

This line calls previous() once the user clicks the Skip Previous action.

Add the following to onActionClicked():

when(action) {
  rewindAction -> playerAdapter.rewind()
  fastForwardAction -> playerAdapter.fastForward()
  else -> super.onActionClicked(action)
}

This handles the remaining actions. If the action is not recognized, let the super implementation handle it.

Now, open VideoPlaybackFragment.kt and add this code to the end of onCreate():

//1
playerGlue = VideoPlaybackControlGlue(
    requireActivity(),
    EmbeddedPlayerAdapter(lifecycleScope)
).apply {
  //2
  host = PlaybackSupportFragmentGlueHost(this@VideoPlaybackFragment)

  isControlsOverlayAutoHideEnabled = true

  //3
  title = videoItem.video.title
  subtitle = videoItem.video.description
}

With this code, you:

  1. Pass activity reference and instance of EmbeddedPlayerAdapter to VideoPlaybackControlGlue. This is the wrapper for the YouTube IFrame player that VideoPlaybackControlGlue uses to link video controls with IFrame player.
  2. Set PlaybackSupportFragmentGlueHost as host and controls to be auto-hidden.
  3. Set a video title and subtitle.
Note: Check onCreateView() to see initialization of the YouTubePlayerView. For more information on all the available properties, you can check android-youtube-player library.

Now, build and run and start a video!

App video playback screen showing a man speaking into a microphone and a description from Apple of what Combine does

That’s it! You’ve just created your first Android TV app. Congrats! :]