列表支持前后视频预加载

This commit is contained in:
Lindong 2026-01-27 15:36:24 +08:00
parent 45b63e870e
commit da258f69f6
4 changed files with 92 additions and 59 deletions

View File

@ -241,9 +241,10 @@ class HomeFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnSwit
handleEventOneVideoSwiped()
}
// Preload next N videos
// Preload next N videos and keep previous N videos
val preloadCount = 3
for (i in 1..preloadCount) {
// Next N
val nextPos = position + i
if (nextPos < mViewPagerAdapter.itemCount) {
val nextFragment = mViewPagerAdapter.getFragmentByIndex(nextPos)
@ -251,22 +252,35 @@ class HomeFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnSwit
nextFragment.preloadVideo()
}
}
}
// Release players for previous fragments to save memory
// We keep 'preloadCount' fragments after current, and maybe 0 before?
// But offscreenPageLimit keeps them in memory. We should release their heavy resources.
val offscreenLimit = 3
for (i in 1..offscreenLimit) {
// Prev N (Keep them ready for playback)
val prevPos = position - i
if (prevPos >= 0) {
val prevFragment = mViewPagerAdapter.getFragmentByIndex(prevPos)
if (prevFragment != null && prevFragment is HomeItemFragment) {
prevFragment.releasePlayer()
prevFragment.preloadVideo()
}
}
}
// Release players outside of the window (position - preloadCount - 1)
val releasePosBefore = position - preloadCount - 1
if (releasePosBefore >= 0) {
val prevFragment = mViewPagerAdapter.getFragmentByIndex(releasePosBefore)
if (prevFragment != null && prevFragment is HomeItemFragment) {
prevFragment.releasePlayer()
}
}
// Release players outside of the window (position + preloadCount + 1)
val releasePosAfter = position + preloadCount + 1
if (releasePosAfter < mViewPagerAdapter.itemCount) {
val nextFragment = mViewPagerAdapter.getFragmentByIndex(releasePosAfter)
if (nextFragment != null && nextFragment is HomeItemFragment) {
nextFragment.releasePlayer()
}
}
// load more
if (mViewPagerAdapter.itemCount > 0 && position >= mViewPagerAdapter.itemCount - 3) {
lifecycleScope.launch {

View File

@ -93,17 +93,23 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putFloat("curPlayedSecond", mCurPlayedSecond)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
mCurPlayedSecond = savedInstanceState.getFloat("curPlayedSecond", 0f)
}
}
override fun onDestroy() {
super.onDestroy()
mMaskBitmap?.recycle()
}
fun playVideo() {
mIsPreloading = false
binding?.playerContainer?.isVisible = true
@ -174,19 +180,21 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
mPlayerView?.enableAutomaticInitialization = true
}
// val playerUiController = MyPlayerControlView(mPlayerView!!)
// mPlayerView!!.setCustomPlayerUi(playerUiController.rootView)
mPlayerView!!.removeViews(1, mPlayerView!!.childCount - 1)
mPlayerView!!.addYouTubePlayerListener(object : AbstractYouTubePlayerListener() {
override fun onReady(@NonNull youTubePlayer: YouTubePlayer) {
override fun onReady(youTubePlayer: YouTubePlayer) {
mPlayer = youTubePlayer
val playerUiController = MyPlayerControlView(mPlayerView!!, youTubePlayer)
mPlayerView!!.setCustomPlayerUi(playerUiController.rootView)
if (mPendingPlay) {
mPlayer?.loadVideo(mVideoData!!.id, 0f)
mPlayer?.loadVideo(mVideoData!!.id, mCurPlayedSecond)
} else if (mIsPreloading) {
mPlayer?.mute()
mPlayer?.loadVideo(mVideoData!!.id, 0f)
mPlayer?.loadVideo(mVideoData!!.id, mCurPlayedSecond)
} else {
mPlayer?.cueVideo(mVideoData!!.id, 0f)
mPlayer?.cueVideo(mVideoData!!.id, mCurPlayedSecond)
}
}
@ -232,6 +240,7 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
PlayerConstants.PlayerState.ENDED -> {
togglePlayingState(false)
if (!mIsPreloading) {
mCurPlayedSecond = 0f
mPlayer?.loadVideo(mVideoData!!.id, 0f)
}
}
@ -272,9 +281,9 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
private fun togglePlayingState1(isPlaying: Boolean) {
if (mIsPlaying != isPlaying) {
mIsPlaying = isPlaying
if (mIsPlaying) {
binding?.circlePb?.isVisible = false
}
// if (mIsPlaying) {
// binding?.circlePb?.isVisible = false
// }
binding?.ivMask?.isVisible = !mIsPlaying
// Ensure playerContainer is visible when playing OR preloading (masked)
@ -295,7 +304,7 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
private fun switchState2Play() {
mThumbHandler.removeCallbacksAndMessages(null)
binding?.circlePb?.isVisible = false
// binding?.circlePb?.isVisible = false
binding?.ivMask?.isVisible = false
binding?.playerContainer?.isVisible = true
hidePlayIconAnim()
@ -305,7 +314,7 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
private fun switchState2Pause() {
binding?.ivMask?.isVisible = true
binding?.playerContainer?.isVisible = false
showPlayIconAnim()
// showPlayIconAnim()
mTickerTimer.pause()
}
@ -369,6 +378,7 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
private fun hidePlayIconAnim() {
return
if (!binding?.playIcon!!.isVisible) {
return
}
@ -404,6 +414,7 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
}
private fun showPlayIconAnim() {
return
with (binding?.playIcon!!) {
visibility = View.VISIBLE

View File

@ -21,13 +21,14 @@ import com.pierfrancescosoffritti.androidyoutubeplayer.core.customui.menu.YouTub
import com.pierfrancescosoffritti.androidyoutubeplayer.core.customui.utils.FadeViewHelper
import com.pierfrancescosoffritti.androidyoutubeplayer.core.customui.views.YouTubePlayerSeekBar
import com.pierfrancescosoffritti.androidyoutubeplayer.core.customui.views.YouTubePlayerSeekBarListener
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.YouTubePlayerCallback
import kotlin.jvm.javaClass
class MyPlayerControlView(
private val youTubePlayerView: YouTubePlayerView,
private val youTubePlayer: YouTubePlayer
) : PlayerUiController {
var youTubePlayer: YouTubePlayer? = null
val rootView: View = View.inflate(youTubePlayerView.context, R.layout.layout_player_controller, null)
@ -133,16 +134,25 @@ class MyPlayerControlView(
onMenuButtonClickListener = View.OnClickListener { youTubePlayerMenu.show(menuButton) }
initClickListeners()
youTubePlayerView.getYouTubePlayerWhenReady(object : YouTubePlayerCallback{
override fun onYouTubePlayer(youTubePlayer: YouTubePlayer) {
this@MyPlayerControlView.youTubePlayer = youTubePlayer
initClickListeners()
}
})
}
private fun initClickListeners() {
youTubePlayer.addListener(youtubePlayerSeekBar)
youTubePlayer.addListener(fadeControlsContainer)
youTubePlayer.addListener(youTubePlayerStateListener)
youtubePlayerSeekBar.youtubePlayerSeekBarListener = object : YouTubePlayerSeekBarListener {
override fun seekTo(time: Float) = youTubePlayer.seekTo(time)
private fun initClickListeners() {
youTubePlayer?.addListener(youtubePlayerSeekBar)
youTubePlayer?.addListener(fadeControlsContainer)
youTubePlayer?.addListener(youTubePlayerStateListener)
youTubePlayer?.let {
youtubePlayerSeekBar.youtubePlayerSeekBarListener = object : YouTubePlayerSeekBarListener {
override fun seekTo(time: Float) = it.seekTo(time)
}
}
panel.setOnClickListener { fadeControlsContainer.toggleVisibility() }
playPauseButton.setOnClickListener { onPlayButtonPressed() }
@ -270,9 +280,9 @@ class MyPlayerControlView(
private fun onPlayButtonPressed() {
if (isPlaying)
youTubePlayer.pause()
youTubePlayer?.pause()
else
youTubePlayer.play()
youTubePlayer?.play()
}
private fun updateState(state: PlayerConstants.PlayerState) {

View File

@ -8,44 +8,43 @@
android:id="@+id/player_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
android:layout_gravity="center" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_mask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="#00000000"
android:clickable="false"
android:layout_gravity="center"/>
android:clickable="false" />
<View
android:id="@+id/click_mask_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:layout_marginBottom="30dp"
/>
android:clickable="true" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/play_icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:padding="20dp"
android:src="@mipmap/icon_play"
android:visibility="gone"
android:layout_gravity="center"/>
android:visibility="gone" />
<LinearLayout
android:id="@+id/bottom_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom"
android:layout_marginVertical="25dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="80dp"
android:layout_gravity="bottom">
android:orientation="vertical">
<TextView
android:id="@+id/tv_video_from"
@ -53,34 +52,34 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:text="From"
android:textSize="15sp"
android:textColor="@color/white" />
android:textColor="@color/white"
android:textSize="15sp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_video_intro"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="introduce"
android:layout_marginEnd="45dp"
android:clickable="false"
android:maxLines="2"
android:layout_marginEnd="45dp"
android:textSize="15sp"
android:textColor="@color/white_al80" />
android:text="introduce"
android:textColor="@color/white_al80"
android:textSize="15sp" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_intro_expand"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="right|bottom"
android:paddingHorizontal="10dp"
android:paddingVertical="5dp"
android:layout_gravity="right|bottom"
android:src="@mipmap/arrow_up"
android:visibility="gone"
/>
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
@ -89,18 +88,17 @@
android:id="@+id/progress_bar_player"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_marginBottom="16dp"
android:layout_marginHorizontal="15dp"
android:layout_gravity="bottom"
/>
android:layout_marginHorizontal="15dp"
android:layout_marginBottom="16dp" />
<ProgressBar
android:id="@+id/circle_pb"
style="?android:attr/progressBarStyleLarge"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_gravity="center"
android:indeterminateTint="@android:color/white"
style="?android:attr/progressBarStyleLarge"
/>
android:visibility="gone" />
</FrameLayout>