diff --git a/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/PlayerUIController.java b/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/PlayerUIController.java index ce4d9e1cf..4fdd44d72 100644 --- a/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/PlayerUIController.java +++ b/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/PlayerUIController.java @@ -114,6 +114,10 @@ public class PlayerUIController extends BasePlayerController { disableUiAutoHideTimeout(); disableSuggestionsResetTimeout(); + if (getPlayer() == null) { + return false; + } + boolean isHandled = handleBackKey(keyCode) || handleMenuKey(keyCode) || handleConfirmKey(keyCode) || handleStopKey(keyCode) || handleNumKeys(keyCode) || handlePlayPauseKey(keyCode) || handleLeftRightSkip(keyCode); @@ -600,10 +604,6 @@ public class PlayerUIController extends BasePlayerController { } private boolean handleMenuKey(int keyCode) { - if (getPlayer() == null) { - return false; - } - boolean controlsShown = getPlayer().isOverlayShown(); boolean suggestionsShown = getPlayer().isSuggestionsShown(); @@ -619,10 +619,6 @@ public class PlayerUIController extends BasePlayerController { } private boolean handleConfirmKey(int keyCode) { - if (getPlayer() == null) { - return false; - } - boolean controlsShown = getPlayer().isOverlayShown(); if (KeyHelpers.isConfirmKey(keyCode) && !controlsShown) { diff --git a/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/VideoStateController.java b/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/VideoStateController.java index 2aa813b4c..debb2c8a9 100644 --- a/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/VideoStateController.java +++ b/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/VideoStateController.java @@ -119,6 +119,10 @@ public class VideoStateController extends BasePlayerController { @Override public void onEngineReleased() { + if (getPlayer() == null) { + return; + } + // Save previous state if (getPlayer().containsMedia()) { setPlayEnabled(getPlayer().getPlayWhenReady()); diff --git a/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/exoplayer/controller/ExoPlayerController.java b/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/exoplayer/controller/ExoPlayerController.java index f3975115c..d405d8671 100644 --- a/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/exoplayer/controller/ExoPlayerController.java +++ b/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/exoplayer/controller/ExoPlayerController.java @@ -23,7 +23,6 @@ import com.liskovsoft.smartyoutubetv2.common.app.models.data.Video; import com.liskovsoft.smartyoutubetv2.common.app.models.playback.listener.PlayerEventListener; import com.liskovsoft.smartyoutubetv2.common.exoplayer.ExoMediaSourceFactory; import com.liskovsoft.smartyoutubetv2.common.exoplayer.errors.TrackErrorFixer; -import com.liskovsoft.smartyoutubetv2.common.exoplayer.other.ExoPlayerInitializer; import com.liskovsoft.smartyoutubetv2.common.exoplayer.other.VolumeBooster; import com.liskovsoft.smartyoutubetv2.common.exoplayer.selector.ExoFormatItem; import com.liskovsoft.smartyoutubetv2.common.exoplayer.selector.FormatItem; @@ -53,6 +52,7 @@ public class ExoPlayerController implements Player.EventListener, PlayerControll private PlayerView mPlayerView; private VolumeBooster mVolumeBooster; private boolean mIsEnded; + private Runnable mOnVideoLoaded; public ExoPlayerController(Context context, PlayerEventListener eventListener) { PlayerTweaksData playerTweaksData = PlayerTweaksData.instance(context); @@ -301,14 +301,15 @@ public class ExoPlayerController implements Player.EventListener, PlayerControll } private void notifyOnVideoLoad() { - if (mVideo == null) { - return; - } - if (mOnSourceChanged) { mOnSourceChanged = false; + mEventListener.onVideoLoaded(mVideo); + if (mOnVideoLoaded != null) { + mOnVideoLoaded.run(); + } + // Produce thread sync problems // Attempt to read from field 'java.util.TreeMap$Node java.util.TreeMap$Node.left' on a null object reference //mTrackSelectorManager.fixTracksSelection(); @@ -440,6 +441,11 @@ public class ExoPlayerController implements Player.EventListener, PlayerControll } } + @Override + public void setOnVideoLoaded(Runnable onVideoLoaded) { + mOnVideoLoaded = onVideoLoaded; + } + private void setQualityInfo(String qualityInfoStr) { if (mPlayerView != null && qualityInfoStr != null) { mPlayerView.setQualityInfo(qualityInfoStr); diff --git a/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/exoplayer/controller/PlayerController.java b/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/exoplayer/controller/PlayerController.java index 24a1477ab..24505f934 100644 --- a/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/exoplayer/controller/PlayerController.java +++ b/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/exoplayer/controller/PlayerController.java @@ -43,4 +43,5 @@ public interface PlayerController { void setVolume(float volume); float getVolume(); void resetPlayerState(); + void setOnVideoLoaded(Runnable onVideoLoaded); } diff --git a/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/presenter/VideoCardPresenter.java b/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/presenter/VideoCardPresenter.java index 730243ae3..8521ae7cb 100644 --- a/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/presenter/VideoCardPresenter.java +++ b/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/presenter/VideoCardPresenter.java @@ -133,7 +133,7 @@ public class VideoCardPresenter extends LongClickPresenter { if (mIsAnimatedPreviewsEnabled) { cardView.setPreviewUrl(video.previewUrl); - //cardView.setPreviewVideoId(video.videoId); + cardView.setPreviewVideoId(video.videoId); } cardView.setMainImageDimensions(mWidth, mHeight); @@ -170,6 +170,9 @@ public class VideoCardPresenter extends LongClickPresenter { // Remove references to images so that the garbage collector can free up memory. cardView.setBadgeImage(null); cardView.setMainImage(null); + + // Cleanup Glide resources. https://chatgpt.com/share/682120c5-e428-8010-b848-371b2dec0cd5 + Glide.with(cardView.getContext()).clear(cardView.getMainImageView()); } private void updateDimensions(Context context) { diff --git a/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/widgets/complexcardview/ComplexImageView.java b/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/widgets/complexcardview/ComplexImageView.java index df1e467c1..9177eb5b9 100644 --- a/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/widgets/complexcardview/ComplexImageView.java +++ b/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/widgets/complexcardview/ComplexImageView.java @@ -4,13 +4,15 @@ import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; + import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.liskovsoft.smartyoutubetv2.common.utils.Utils; import com.liskovsoft.smartyoutubetv2.tv.R; import com.liskovsoft.smartyoutubetv2.tv.ui.widgets.embedplayer.EmbedPlayerView; import com.liskovsoft.smartyoutubetv2.tv.util.ViewUtil; @@ -19,11 +21,15 @@ public class ComplexImageView extends RelativeLayout { private ImageView mMainImage; private ImageView mPreviewImage; private EmbedPlayerView mPreviewPlayer; + private FrameLayout mPreviewContainer; private ProgressBar mProgressBar; private TextView mBadgeText; private String mPreviewUrl; private String mPreviewVideoId; private ViewGroup mProgressContainer; + private int mPreviewWidth; + private int mPreviewHeight; + private Runnable mCreateAndStartPlayer; public ComplexImageView(Context context) { super(context); @@ -43,11 +49,10 @@ public class ComplexImageView extends RelativeLayout { private void init() { inflate(getContext(), R.layout.text_badge_image_view, this); mMainImage = findViewById(R.id.main_image); - mPreviewImage = findViewById(R.id.preview_image); - mPreviewPlayer = findViewById(R.id.preview_player); mBadgeText = findViewById(R.id.extra_text_badge); mProgressBar = findViewById(R.id.clip_progress); mProgressContainer = findViewById(R.id.clip_info); + mPreviewContainer = findViewById(R.id.preview_container); } /** @@ -108,49 +113,52 @@ public class ComplexImageView extends RelativeLayout { mPreviewVideoId = videoId; } - //public void startPlayback() { - // if (mPreviewUrl == null) { - // return; - // } - // - // mPreviewImage.setVisibility(View.VISIBLE); - // - // Glide.with(getContext().getApplicationContext()) // FIX: "You cannot start a load for a destroyed activity" - // .load(mPreviewUrl) - // .apply(ViewUtil.glideOptions()) - // .into(mPreviewImage); - //} - // - //public void stopPlayback() { - // if (mPreviewUrl == null) { - // return; - // } - // - // mPreviewImage.setVisibility(View.GONE); - // mPreviewImage.setImageDrawable(null); - //} - public void startPlayback() { if (mPreviewUrl != null) { - mPreviewImage.setVisibility(View.VISIBLE); + if (mPreviewImage == null) { + mPreviewImage = new ImageView(getContext()); + mPreviewImage.setScaleType(ScaleType.CENTER_CROP); + mPreviewImage.setAdjustViewBounds(true); + mPreviewContainer.addView(mPreviewImage, new FrameLayout.LayoutParams(mPreviewWidth, mPreviewHeight)); + } Glide.with(getContext().getApplicationContext()) // FIX: "You cannot start a load for a destroyed activity" .load(mPreviewUrl) .apply(ViewUtil.glideOptions()) .into(mPreviewImage); } else if (mPreviewVideoId != null) { - mPreviewPlayer.setVisibility(View.VISIBLE); - mPreviewPlayer.openVideo(mPreviewVideoId); + if (mCreateAndStartPlayer == null) { + mCreateAndStartPlayer = this::createAndStartPlayer; + } + + Utils.postDelayed(mCreateAndStartPlayer, 3_000); } } + private void createAndStartPlayer() { + if (mPreviewPlayer == null) { + mPreviewPlayer = new EmbedPlayerView(getContext()); + mPreviewPlayer.setUseController(false); + mPreviewPlayer.setOnLoad(() -> mPreviewContainer.addView(mPreviewPlayer, new FrameLayout.LayoutParams(mPreviewWidth, mPreviewHeight))); + } + + mPreviewPlayer.openVideo(mPreviewVideoId); + } + public void stopPlayback() { if (mPreviewUrl != null) { - mPreviewImage.setVisibility(View.GONE); + mPreviewContainer.removeView(mPreviewImage); mPreviewImage.setImageDrawable(null); + Glide.with(getContext()).clear(mPreviewImage); + mPreviewImage = null; } else if (mPreviewVideoId != null) { - mPreviewPlayer.setVisibility(View.GONE); - mPreviewPlayer.finish(); // TODO: not implemented + Utils.removeCallbacks(mCreateAndStartPlayer); + + if (mPreviewPlayer != null) { + mPreviewContainer.removeView(mPreviewPlayer); + mPreviewPlayer.finish(); + mPreviewPlayer = null; + } } } @@ -178,9 +186,11 @@ public class ComplexImageView extends RelativeLayout { } private void setPreviewDimensions(int width, int height) { - ViewGroup.LayoutParams lp = mPreviewImage.getLayoutParams(); - lp.width = width; - lp.height = height; - mPreviewImage.setLayoutParams(lp); + mPreviewWidth = width; + mPreviewHeight = height; + //ViewGroup.LayoutParams lp = mPreviewImage.getLayoutParams(); + //lp.width = width; + //lp.height = height; + //mPreviewImage.setLayoutParams(lp); } } diff --git a/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/widgets/embedplayer/EmbedPlayerView.java b/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/widgets/embedplayer/EmbedPlayerView.java index 8c7bec482..472d7f4cf 100644 --- a/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/widgets/embedplayer/EmbedPlayerView.java +++ b/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/widgets/embedplayer/EmbedPlayerView.java @@ -36,6 +36,8 @@ public class EmbedPlayerView extends PlayerView implements PlaybackView { private ExoPlayerInitializer mPlayerInitializer; private PlayerController mExoPlayerController; private PlaybackPresenter mPlaybackPresenter; + private Video mVideo; + private Runnable mOnLoad; public EmbedPlayerView(Context context) { super(context); @@ -216,12 +218,16 @@ public class EmbedPlayerView extends PlayerView implements PlaybackView { @Override public void setVideo(Video item) { + mVideo = item; + if (mExoPlayerController != null) { + mExoPlayerController.setVideo(mVideo); + } } @Override public Video getVideo() { - return null; + return mVideo; } @Override @@ -448,17 +454,28 @@ public class EmbedPlayerView extends PlayerView implements PlaybackView { } public void openVideo(Video video) { - initPlayer(); + if (mPlaybackPresenter == null) { + mPlaybackPresenter = PlaybackPresenter.instance(getContext()); + } + + // Fullscreen playback is running. Skipping + if (mPlaybackPresenter.getView() != null) { + return; + } + + mVideo = video; mPlaybackPresenter.onNewVideo(video); + initPlayer(); } - private void createPlayer() { + private void createPlayerObjects() { // Use default or pass your bandwidthMeter here: bandwidthMeter = new DefaultBandwidthMeter.Builder(getContext()).build() DefaultTrackSelector trackSelector = new RestoreTrackSelector(new AdaptiveTrackSelection.Factory()); mExoPlayerController.setTrackSelector(trackSelector); DefaultRenderersFactory renderersFactory = new CustomOverridesRenderersFactory(getContext()); mPlayer = mPlayerInitializer.createPlayer(getContext(), renderersFactory, trackSelector); + mPlayer.setPlayWhenReady(true); // Fix seeking on TextureView (some devices only) if (PlayerTweaksData.instance(getContext()).isTextureViewEnabled()) { @@ -467,6 +484,7 @@ public class EmbedPlayerView extends PlayerView implements PlaybackView { } mExoPlayerController.setPlayer(mPlayer); + mExoPlayerController.setVideo(mVideo); if (PlayerTweaksData.instance(getContext()).isAudioFocusEnabled()) { ExoPlayerInitializer.enableAudioFocus(mPlayer, true); @@ -476,8 +494,9 @@ public class EmbedPlayerView extends PlayerView implements PlaybackView { } private void releasePlayer() { - if (mPlayer != null) { + if (isEngineInitialized()) { Log.d(TAG, "releasePlayer: Start releasing player engine..."); + mOnLoad = null; mPlaybackPresenter.onEngineReleased(); destroyPlayerObjects(); } @@ -488,19 +507,31 @@ public class EmbedPlayerView extends PlayerView implements PlaybackView { mExoPlayerController.release(); mPlayer = null; setPlayer(null); - mPlaybackPresenter.setView(null); + //mPlaybackPresenter.setView(null); } private void initPlayer() { - if (mPlayer != null) { + if (isEngineInitialized()) { return; } mPlayerInitializer = new ExoPlayerInitializer(getContext()); - mPlaybackPresenter = PlaybackPresenter.instance(getContext()); mPlaybackPresenter.setView(this); mExoPlayerController = new ExoPlayerController(getContext(), mPlaybackPresenter); - mPlaybackPresenter.onViewInitialized(); - createPlayer(); + mExoPlayerController.setOnVideoLoaded(this::onVideoLoaded); + mPlaybackPresenter.onViewInitialized(); // init all controllers + createPlayerObjects(); + mPlaybackPresenter.onEngineInitialized(); // start playback + } + + private void onVideoLoaded() { + if (mOnLoad != null) { + mOnLoad.run(); + mOnLoad = null; + } + } + + public void setOnLoad(Runnable onLoad) { + mOnLoad = onLoad; } } diff --git a/smarttubetv/src/main/res/layout/text_badge_image_view.xml b/smarttubetv/src/main/res/layout/text_badge_image_view.xml index 5d97a3eef..77d6cf499 100644 --- a/smarttubetv/src/main/res/layout/text_badge_image_view.xml +++ b/smarttubetv/src/main/res/layout/text_badge_image_view.xml @@ -12,20 +12,10 @@ android:scaleType="centerCrop" android:adjustViewBounds="true" tools:ignore="ContentDescription"/> - - + android:layout_height="wrap_content" /> - + android:layout_height="wrap_content" />