Optimize short form content demo app
PiperOrigin-RevId: 592225900
This commit is contained in:
parent
7ca26f898d
commit
8940900c69
@ -82,7 +82,7 @@ class MediaSourceManager(
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(mediaItem: MediaItem): MediaSource {
|
||||
operator fun get(mediaItem: MediaItem): PreloadMediaSource {
|
||||
if (!sourceMap.containsKey(mediaItem)) {
|
||||
add(mediaItem)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user