视频优化

This commit is contained in:
Lindong 2026-01-27 14:03:32 +08:00
parent 13ee0de952
commit 6f34b5b990
3 changed files with 117 additions and 29 deletions

View File

@ -65,6 +65,8 @@ class ShushuManager private constructor() {
config.setMode(/*if (BuildConfig.DEBUG) TDConfig.ModeEnum.DEBUG else*/ TDConfig.ModeEnum.DEBUG) config.setMode(/*if (BuildConfig.DEBUG) TDConfig.ModeEnum.DEBUG else*/ TDConfig.ModeEnum.DEBUG)
// config.setMode(/*if (BuildConfig.DEBUG) TDConfig.ModeEnum.DEBUG else*/ TDConfig.ModeEnum.NORMAL) // config.setMode(/*if (BuildConfig.DEBUG) TDConfig.ModeEnum.DEBUG else*/ TDConfig.ModeEnum.NORMAL)
mShushuSdk = ThinkingAnalyticsSDK.sharedInstance(config) mShushuSdk = ThinkingAnalyticsSDK.sharedInstance(config)
StatisticLogger.d("数数DeviceID: ${TDAnalytics.getDeviceId()}")
configAutoTrack() configAutoTrack()
sdkReady = true sdkReady = true
flushPendingEventsAsync() flushPendingEventsAsync()

View File

@ -82,7 +82,7 @@ class HomeFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnSwit
override fun ViewBinding.initViews() { override fun ViewBinding.initViews() {
viewPager2.setPageTransformer { _, _ -> } viewPager2.setPageTransformer { _, _ -> }
viewPager2.offscreenPageLimit = 1 viewPager2.offscreenPageLimit = 3
viewPager2.adapter = mViewPagerAdapter viewPager2.adapter = mViewPagerAdapter
@ -236,13 +236,39 @@ class HomeFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnSwit
if (fragment != null) { if (fragment != null) {
setHomeTabStyle(fragment) setHomeTabStyle(fragment)
val curFragment: HomeItemFragment = fragment as HomeItemFragment val curFragment: HomeItemFragment = fragment as HomeItemFragment
curFragment.loadVideo() curFragment.playVideo()
handleEventOneVideoSwiped() handleEventOneVideoSwiped()
} }
// Preload next N videos
val preloadCount = 3
for (i in 1..preloadCount) {
val nextPos = position + i
if (nextPos < mViewPagerAdapter.itemCount) {
val nextFragment = mViewPagerAdapter.getFragmentByIndex(nextPos)
if (nextFragment != null && nextFragment is HomeItemFragment) {
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) {
val prevPos = position - i
if (prevPos >= 0) {
val prevFragment = mViewPagerAdapter.getFragmentByIndex(prevPos)
if (prevFragment != null && prevFragment is HomeItemFragment) {
prevFragment.releasePlayer()
}
}
}
// load more // load more
if (mViewPagerAdapter.itemCount > 0 && position == mViewPagerAdapter.itemCount - 2) { if (mViewPagerAdapter.itemCount > 0 && position >= mViewPagerAdapter.itemCount - 3) {
lifecycleScope.launch { lifecycleScope.launch {
mViewModel.loadVideoList() mViewModel.loadVideoList()
} }
@ -262,7 +288,6 @@ class HomeFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnSwit
}, false) }, false)
viewPager2.offscreenPageLimit = 3
} }
override fun ViewBinding.initObservers() { override fun ViewBinding.initObservers() {

View File

@ -19,6 +19,7 @@ import androidx.core.view.isVisible
import com.ama.core.architecture.appBase.AppViewsEmptyViewModelFragment import com.ama.core.architecture.appBase.AppViewsEmptyViewModelFragment
import com.ama.core.architecture.util.AndroidUtil import com.ama.core.architecture.util.AndroidUtil
import com.ama.core.architecture.util.setOnClickBatch import com.ama.core.architecture.util.setOnClickBatch
import com.blankj.utilcode.util.LogUtils
import com.gamedog.statisticreporter.StatisticUtil import com.gamedog.statisticreporter.StatisticUtil
import com.viddin.videos.free.R import com.viddin.videos.free.R
import com.gamedog.vididin.beans.YoutubeVideo import com.gamedog.vididin.beans.YoutubeVideo
@ -27,6 +28,7 @@ import com.gamedog.vididin.youtubestatistic.TickerTimer
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.PlayerConstants import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.PlayerConstants
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.YouTubePlayerCallback
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.utils.loadOrCueVideo import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.utils.loadOrCueVideo
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView
import com.viddin.videos.free.databinding.VididinappFeatureHomeItemLayoutBinding as ViewBinding import com.viddin.videos.free.databinding.VididinappFeatureHomeItemLayoutBinding as ViewBinding
@ -43,6 +45,8 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
private var mCurPlayedSecond: Float = 0F private var mCurPlayedSecond: Float = 0F
private var mTotalDuration: Float = 0F private var mTotalDuration: Float = 0F
private val mTickerTimer = TickerTimer() private val mTickerTimer = TickerTimer()
private var mPendingPlay: Boolean = false
private var mIsPreloading: Boolean = false
private val mThumbHandler = Handler(Looper.getMainLooper()) private val mThumbHandler = Handler(Looper.getMainLooper())
@ -100,27 +104,67 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
private fun playVideo() { fun playVideo() {
if (mPlayer != null && mVideoData != null && !mVideoData?.id.isNullOrEmpty()) { mIsPreloading = false
mPlayer?.loadOrCueVideo( binding?.playerContainer?.isVisible = true
lifecycle, if (mPlayerView == null) {
mVideoData!!.id, initPlayerView(true)
0f } else {
) if (mPlayer != null) {
mPlayer?.unMute()
mPlayer?.play()
} else {
mPendingPlay = true
}
}
}
/*mPlayerView?.isVisible?.let { fun preloadVideo() {
if (!it) { if (mPlayerView == null) {
mPlayer?.pause() mIsPreloading = true
binding?.playerContainer?.isVisible = true
initPlayerView(false)
} else {
if (mPlayer != null && mIsPreloading) {
// Already preloading or preloaded
} }
}*/
} }
} }
fun releasePlayer() {
LogUtils.d("[视频Item] releasePlayer ID: ${getVideoId()}")
mIsPreloading = false
mPendingPlay = false
if (mPlayerView != null) {
// Try to save last frame as mask if possible and not already saved
if (mMaskBitmap == null && binding?.playerContainer?.isVisible == true) {
// This is a best-effort sync capture or just skip to avoid async complexity during release
// For now, we just release to free memory immediately.
}
mPlayerView!!.release()
binding?.playerContainer?.removeView(mPlayerView)
mPlayerView = null
mPlayer = null
}
binding?.playerContainer?.isVisible = false
binding?.ivMask?.isVisible = true
if (mMaskBitmap != null) {
binding?.ivMask?.setImageBitmap(mMaskBitmap)
}
showPlayIconAnim()
mTickerTimer.pause()
}
fun getVideoId(): String? { fun getVideoId(): String? {
return mVideoData?.id return mVideoData?.id
} }
fun loadVideo() { private fun initPlayerView(autoPlay: Boolean) {
mPendingPlay = autoPlay
LogUtils.d("[视频Item] initPlayerView ID: ${getVideoId()} autoPlay: $autoPlay")
if (null == mPlayerView) { if (null == mPlayerView) {
mPlayerView = YouTubePlayerView(requireContext()) mPlayerView = YouTubePlayerView(requireContext())
val layoutParam = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) val layoutParam = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
@ -130,16 +174,20 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
mPlayerView?.enableAutomaticInitialization = true mPlayerView?.enableAutomaticInitialization = true
} }
mPlayerView!!.addYouTubePlayerListener(object : AbstractYouTubePlayerListener() {
val playerView = mPlayerView
playerView!!.addYouTubePlayerListener(object : AbstractYouTubePlayerListener() {
override fun onReady(@NonNull youTubePlayer: YouTubePlayer) { override fun onReady(@NonNull youTubePlayer: YouTubePlayer) {
mPlayer = youTubePlayer mPlayer = youTubePlayer
val playerUiController = MyPlayerControlView(mPlayerView!!, youTubePlayer)
mPlayerView!!.setCustomPlayerUi(playerUiController.rootView)
val playerUiController = MyPlayerControlView(playerView, youTubePlayer) if (mPendingPlay) {
playerView.setCustomPlayerUi(playerUiController.rootView) mPlayer?.loadVideo(mVideoData!!.id, 0f)
} else if (mIsPreloading) {
playVideo() mPlayer?.mute()
mPlayer?.loadVideo(mVideoData!!.id, 0f)
} else {
mPlayer?.cueVideo(mVideoData!!.id, 0f)
}
} }
override fun onCurrentSecond(youTubePlayer: YouTubePlayer, second: Float) { override fun onCurrentSecond(youTubePlayer: YouTubePlayer, second: Float) {
@ -160,10 +208,20 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
) { ) {
when (state) { when (state) {
PlayerConstants.PlayerState.PLAYING -> { PlayerConstants.PlayerState.PLAYING -> {
if (mIsPreloading) {
mPlayer?.pause()
} else {
togglePlayingState(true) togglePlayingState(true)
} }
}
PlayerConstants.PlayerState.PAUSED -> { PlayerConstants.PlayerState.PAUSED -> {
if (mIsPreloading) {
// Do nothing or ensure mask is visible?
// togglePlayingState(false) sets mask visible.
togglePlayingState(false) togglePlayingState(false)
} else {
togglePlayingState(false)
}
} }
PlayerConstants.PlayerState.UNKNOWN -> { PlayerConstants.PlayerState.UNKNOWN -> {
togglePlayingState(false) togglePlayingState(false)
@ -173,7 +231,9 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
} }
PlayerConstants.PlayerState.ENDED -> { PlayerConstants.PlayerState.ENDED -> {
togglePlayingState(false) togglePlayingState(false)
playVideo() if (!mIsPreloading) {
mPlayer?.loadVideo(mVideoData!!.id, 0f)
}
} }
PlayerConstants.PlayerState.BUFFERING -> { PlayerConstants.PlayerState.BUFFERING -> {
//binding?.circlePb?.isVisible = true //binding?.circlePb?.isVisible = true
@ -217,7 +277,8 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment<ViewBinding>() {
} }
binding?.ivMask?.isVisible = !mIsPlaying binding?.ivMask?.isVisible = !mIsPlaying
binding?.playerContainer?.isVisible = mIsPlaying // Ensure playerContainer is visible when playing OR preloading (masked)
binding?.playerContainer?.isVisible = mIsPlaying || mIsPreloading
if (mIsPlaying) { if (mIsPlaying) {
hidePlayIconAnim() hidePlayIconAnim()