Optimize short form content demo app

PiperOrigin-RevId: 592225900
This commit is contained in:
bachinger 2023-12-19 07:05:56 -08:00 committed by Copybara-Service
parent 7ca26f898d
commit 8940900c69
5 changed files with 69 additions and 33 deletions

View File

@ -82,7 +82,7 @@ class MediaSourceManager(
}
}
operator fun get(mediaItem: MediaItem): MediaSource {
operator fun get(mediaItem: MediaItem): PreloadMediaSource {
if (!sourceMap.containsKey(mediaItem)) {
add(mediaItem)
}

View File

@ -19,6 +19,7 @@ import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.annotation.OptIn
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.LoadControl
@ -80,6 +81,25 @@ class PlayerPool(
}
}
/** Calls [Player.play()] for the given player and pauses all other players. */
fun play(player: Player) {
pauseAllPlayers(player)
player.play()
}
/**
* Pauses all players.
*
* @param keepOngoingPlayer The optional player that should keep playing if not paused.
*/
fun pauseAllPlayers(keepOngoingPlayer: Player? = null) {
playerMap.values.forEach {
if (it != keepOngoingPlayer) {
it.pause()
}
}
}
fun releasePlayer(token: Int, player: ExoPlayer?) {
synchronized(playerMap) {
// Remove token from set of views requesting players & remove potential callbacks

View File

@ -43,20 +43,11 @@ class ViewPagerActivity : AppCompatActivity() {
Log.d("viewpager", "Backward cache is of size: ${mediaItemDatabase.lCacheSize}")
Log.d("viewpager", "Forward cache is of size: ${mediaItemDatabase.rCacheSize}")
viewPagerView = findViewById(R.id.viewPager)
viewPagerView.offscreenPageLimit = 1
viewPagerView.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {}
override fun onPageScrollStateChanged(state: Int) {
Log.d("viewpager", "onPageScrollStateChanged: state=$state")
}
override fun onPageSelected(position: Int) {
Log.d("viewpager", "onPageSelected: position=$position")
adapter.play(position)
}
}
)

View File

@ -30,7 +30,7 @@ import androidx.media3.demo.shortform.R
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.ProgressiveMediaSource
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
import androidx.media3.exoplayer.util.EventLogger
@ -47,6 +47,7 @@ class ViewPagerMediaAdapter(
private val mediaSourceManager: MediaSourceManager
private var viewCounter = 0
private var playerPool: PlayerPool
private val holderMap: MutableMap<Int, ViewPagerMediaHolder>
init {
playbackThread.start()
@ -61,9 +62,10 @@ class ViewPagerMediaAdapter(
renderersFactory,
DefaultBandwidthMeter.getSingletonInstance(context)
)
holderMap = mutableMapOf()
mediaSourceManager =
MediaSourceManager(
ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context)),
DefaultMediaSourceFactory(DefaultDataSource.Factory(context)),
playbackThread.looper,
loadControl.allocator,
renderersFactory,
@ -99,6 +101,14 @@ class ViewPagerMediaAdapter(
mediaSourceManager.addAll(reachableMediaItems)
}
override fun onViewAttachedToWindow(holder: ViewPagerMediaHolder) {
holderMap[holder.currentToken] = holder
}
override fun onViewDetachedFromWindow(holder: ViewPagerMediaHolder) {
holderMap.remove(holder.currentToken)
}
override fun getItemCount(): Int {
// Effectively infinite scroll
return Int.MAX_VALUE
@ -106,7 +116,6 @@ class ViewPagerMediaAdapter(
override fun onViewRecycled(holder: ViewPagerMediaHolder) {
super.onViewRecycled(holder)
Log.d("viewpager", "Recycling the view")
}
fun onDestroy() {
@ -115,6 +124,10 @@ class ViewPagerMediaAdapter(
mediaSourceManager.release()
}
fun play(position: Int) {
holderMap[position]?.let { holder -> holder.player?.let { playerPool.play(it) } }
}
inner class Factory : PlayerPool.PlayerFactory {
private var playerCounter = 0
@ -122,10 +135,10 @@ class ViewPagerMediaAdapter(
val loadControl =
DefaultLoadControl.Builder()
.setBufferDurationsMs(
DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
DefaultLoadControl.DEFAULT_MAX_BUFFER_MS,
/* bufferForPlaybackMs= */ 0,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
/* minBufferMs= */ 15_000,
/* maxBufferMs= */ 15_000,
/* bufferForPlaybackMs= */ 500,
/* bufferForPlaybackAfterRebufferMs= */ 1_000
)
.build()
val player = ExoPlayer.Builder(context).setLoadControl(loadControl).build()

View File

@ -18,25 +18,27 @@ package androidx.media3.demo.shortform.viewpager
import android.util.Log
import android.view.View
import androidx.annotation.OptIn
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.demo.shortform.PlayerPool
import androidx.media3.demo.shortform.R
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.exoplayer.source.preload.PreloadMediaSource
import androidx.media3.ui.PlayerView
import androidx.recyclerview.widget.RecyclerView
@OptIn(UnstableApi::class) // Using PreloadMediaSource.
class ViewPagerMediaHolder(
itemView: View,
private val viewCounter: Int,
private val playerPool: PlayerPool
) : RecyclerView.ViewHolder(itemView), View.OnAttachStateChangeListener {
private val playerView: PlayerView = itemView.findViewById(R.id.player_view)
private var player: ExoPlayer? = null
private var exoPlayer: ExoPlayer? = null
private var isInView: Boolean = false
private var token: Int = -1
private lateinit var mediaSource: MediaSource
private lateinit var mediaSource: PreloadMediaSource
init {
// Define click listener for the ViewHolder's View
@ -47,7 +49,16 @@ class ViewPagerMediaHolder(
}
}
@OptIn(UnstableApi::class)
val currentToken: Int
get() {
return token
}
val player: Player?
get() {
return exoPlayer
}
override fun onViewAttachedToWindow(view: View) {
Log.d("viewpager", "onViewAttachedToWindow: $viewCounter")
isInView = true
@ -59,36 +70,37 @@ class ViewPagerMediaHolder(
override fun onViewDetachedFromWindow(view: View) {
Log.d("viewpager", "onViewDetachedFromWindow: $viewCounter")
isInView = false
releasePlayer(player)
releasePlayer(exoPlayer)
// This is a hacky way of keep preloading sources that are removed from players. This does only
// work because the demo app cycles endlessly through the same 5 URIs. Preloading is still
// uncoordinated meaning it just preloading as soon as this method is called.
mediaSource.preload(0)
}
fun bindData(token: Int, mediaSource: MediaSource) {
fun bindData(token: Int, mediaSource: PreloadMediaSource) {
this.mediaSource = mediaSource
this.token = token
}
@OptIn(UnstableApi::class)
fun releasePlayer(player: ExoPlayer?) {
playerPool.releasePlayer(token, player ?: this.player)
this.player = null
playerPool.releasePlayer(token, player ?: exoPlayer)
this.exoPlayer = null
playerView.player = null
}
@OptIn(UnstableApi::class)
fun setupPlayer(player: ExoPlayer) {
if (!isInView) {
releasePlayer(player)
} else {
if (player != this.player) {
releasePlayer(this.player)
if (player != exoPlayer) {
releasePlayer(exoPlayer)
}
player.run {
repeatMode = ExoPlayer.REPEAT_MODE_ONE
setMediaSource(mediaSource)
seekTo(currentPosition)
playWhenReady = true
this@ViewPagerMediaHolder.player = player
this@ViewPagerMediaHolder.exoPlayer = player
player.prepare()
playerView.player = player
}