diff --git a/StatisticReporter/src/main/java/com/gamedog/statisticreporter/shushu/ShushuManager.kt b/StatisticReporter/src/main/java/com/gamedog/statisticreporter/shushu/ShushuManager.kt index 1e1a4e1..d1da8a7 100644 --- a/StatisticReporter/src/main/java/com/gamedog/statisticreporter/shushu/ShushuManager.kt +++ b/StatisticReporter/src/main/java/com/gamedog/statisticreporter/shushu/ShushuManager.kt @@ -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.NORMAL) mShushuSdk = ThinkingAnalyticsSDK.sharedInstance(config) + + StatisticLogger.d("数数DeviceID: ${TDAnalytics.getDeviceId()}") configAutoTrack() sdkReady = true flushPendingEventsAsync() diff --git a/app/src/main/java/com/gamedog/vididin/main/fragments/HomeFragment.kt b/app/src/main/java/com/gamedog/vididin/main/fragments/HomeFragment.kt index fa4fe55..80df2f0 100644 --- a/app/src/main/java/com/gamedog/vididin/main/fragments/HomeFragment.kt +++ b/app/src/main/java/com/gamedog/vididin/main/fragments/HomeFragment.kt @@ -82,7 +82,7 @@ class HomeFragment : AppViewsFragment(), OnSwit override fun ViewBinding.initViews() { viewPager2.setPageTransformer { _, _ -> } - viewPager2.offscreenPageLimit = 1 + viewPager2.offscreenPageLimit = 3 viewPager2.adapter = mViewPagerAdapter @@ -236,13 +236,39 @@ class HomeFragment : AppViewsFragment(), OnSwit if (fragment != null) { setHomeTabStyle(fragment) val curFragment: HomeItemFragment = fragment as HomeItemFragment - curFragment.loadVideo() + curFragment.playVideo() 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 - if (mViewPagerAdapter.itemCount > 0 && position == mViewPagerAdapter.itemCount - 2) { + if (mViewPagerAdapter.itemCount > 0 && position >= mViewPagerAdapter.itemCount - 3) { lifecycleScope.launch { mViewModel.loadVideoList() } @@ -262,7 +288,6 @@ class HomeFragment : AppViewsFragment(), OnSwit }, false) - viewPager2.offscreenPageLimit = 3 } override fun ViewBinding.initObservers() { diff --git a/app/src/main/java/com/gamedog/vididin/main/fragments/home/fragment/HomeItemFragment.kt b/app/src/main/java/com/gamedog/vididin/main/fragments/home/fragment/HomeItemFragment.kt index 4ec5025..0091033 100644 --- a/app/src/main/java/com/gamedog/vididin/main/fragments/home/fragment/HomeItemFragment.kt +++ b/app/src/main/java/com/gamedog/vididin/main/fragments/home/fragment/HomeItemFragment.kt @@ -19,6 +19,7 @@ import androidx.core.view.isVisible import com.ama.core.architecture.appBase.AppViewsEmptyViewModelFragment import com.ama.core.architecture.util.AndroidUtil import com.ama.core.architecture.util.setOnClickBatch +import com.blankj.utilcode.util.LogUtils import com.gamedog.statisticreporter.StatisticUtil import com.viddin.videos.free.R 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.YouTubePlayer 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.views.YouTubePlayerView import com.viddin.videos.free.databinding.VididinappFeatureHomeItemLayoutBinding as ViewBinding @@ -43,6 +45,8 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment() { private var mCurPlayedSecond: Float = 0F private var mTotalDuration: Float = 0F private val mTickerTimer = TickerTimer() + private var mPendingPlay: Boolean = false + private var mIsPreloading: Boolean = false private val mThumbHandler = Handler(Looper.getMainLooper()) @@ -100,27 +104,67 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment() { - private fun playVideo() { - if (mPlayer != null && mVideoData != null && !mVideoData?.id.isNullOrEmpty()) { - mPlayer?.loadOrCueVideo( - lifecycle, - mVideoData!!.id, - 0f - ) - - /*mPlayerView?.isVisible?.let { - if (!it) { - mPlayer?.pause() - } - }*/ + fun playVideo() { + mIsPreloading = false + binding?.playerContainer?.isVisible = true + if (mPlayerView == null) { + initPlayerView(true) + } else { + if (mPlayer != null) { + mPlayer?.unMute() + mPlayer?.play() + } else { + mPendingPlay = true + } } } + fun preloadVideo() { + if (mPlayerView == null) { + 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? { return mVideoData?.id } - fun loadVideo() { + private fun initPlayerView(autoPlay: Boolean) { + mPendingPlay = autoPlay + LogUtils.d("[视频Item] initPlayerView ID: ${getVideoId()} autoPlay: $autoPlay") if (null == mPlayerView) { mPlayerView = YouTubePlayerView(requireContext()) val layoutParam = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) @@ -130,16 +174,20 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment() { mPlayerView?.enableAutomaticInitialization = true } - - val playerView = mPlayerView - playerView!!.addYouTubePlayerListener(object : AbstractYouTubePlayerListener() { + mPlayerView!!.addYouTubePlayerListener(object : AbstractYouTubePlayerListener() { override fun onReady(@NonNull youTubePlayer: YouTubePlayer) { mPlayer = youTubePlayer + val playerUiController = MyPlayerControlView(mPlayerView!!, youTubePlayer) + mPlayerView!!.setCustomPlayerUi(playerUiController.rootView) - val playerUiController = MyPlayerControlView(playerView, youTubePlayer) - playerView.setCustomPlayerUi(playerUiController.rootView) - - playVideo() + if (mPendingPlay) { + mPlayer?.loadVideo(mVideoData!!.id, 0f) + } else if (mIsPreloading) { + mPlayer?.mute() + mPlayer?.loadVideo(mVideoData!!.id, 0f) + } else { + mPlayer?.cueVideo(mVideoData!!.id, 0f) + } } override fun onCurrentSecond(youTubePlayer: YouTubePlayer, second: Float) { @@ -160,10 +208,20 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment() { ) { when (state) { PlayerConstants.PlayerState.PLAYING -> { - togglePlayingState(true) + if (mIsPreloading) { + mPlayer?.pause() + } else { + togglePlayingState(true) + } } PlayerConstants.PlayerState.PAUSED -> { - togglePlayingState(false) + if (mIsPreloading) { + // Do nothing or ensure mask is visible? + // togglePlayingState(false) sets mask visible. + togglePlayingState(false) + } else { + togglePlayingState(false) + } } PlayerConstants.PlayerState.UNKNOWN -> { togglePlayingState(false) @@ -173,7 +231,9 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment() { } PlayerConstants.PlayerState.ENDED -> { togglePlayingState(false) - playVideo() + if (!mIsPreloading) { + mPlayer?.loadVideo(mVideoData!!.id, 0f) + } } PlayerConstants.PlayerState.BUFFERING -> { //binding?.circlePb?.isVisible = true @@ -217,7 +277,8 @@ class HomeItemFragment : AppViewsEmptyViewModelFragment() { } binding?.ivMask?.isVisible = !mIsPlaying - binding?.playerContainer?.isVisible = mIsPlaying + // Ensure playerContainer is visible when playing OR preloading (masked) + binding?.playerContainer?.isVisible = mIsPlaying || mIsPreloading if (mIsPlaying) { hidePlayIconAnim()