From c3d7209f09f3b6c3974ada7836fe08f9d131e09b Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Fri, 14 May 2021 21:39:27 +0200 Subject: Moved all code from MediaPlayerActivity to VideoPlayerActivity --- .../antennapod/activity/VideoplayerActivity.java | 547 +++++++++++++++++++-- 1 file changed, 513 insertions(+), 34 deletions(-) (limited to 'app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java') diff --git a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java index b3bf0ebc8..dc4345c21 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -1,7 +1,9 @@ package de.danoeh.antennapod.activity; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.Intent; +import android.graphics.PixelFormat; import android.graphics.drawable.ColorDrawable; import android.media.AudioManager; import android.os.Build; @@ -9,12 +11,19 @@ import android.os.Bundle; import android.os.Handler; import android.view.Gravity; import android.view.KeyEvent; +import android.view.MenuInflater; import android.view.animation.AlphaAnimation; import android.view.animation.AnimationSet; import android.view.animation.ScaleAnimation; import android.widget.EditText; +import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; +import androidx.core.app.ActivityOptionsCompat; import androidx.core.view.WindowCompat; import androidx.appcompat.app.ActionBar; import android.util.Log; @@ -33,21 +42,51 @@ import android.widget.ProgressBar; import android.widget.SeekBar; import java.lang.ref.WeakReference; +import java.text.NumberFormat; import java.util.concurrent.atomic.AtomicBoolean; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; +import com.bumptech.glide.Glide; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.event.ServiceEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlayerStatus; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.FeedItemUtil; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.ShareUtils; +import de.danoeh.antennapod.core.util.StorageUtils; +import de.danoeh.antennapod.core.util.TimeSpeedConverter; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; +import de.danoeh.antennapod.core.util.playback.MediaPlayerError; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.dialog.PlaybackControlsDialog; +import de.danoeh.antennapod.dialog.ShareDialog; +import de.danoeh.antennapod.dialog.SkipPreferenceDialog; +import de.danoeh.antennapod.dialog.SleepTimerDialog; +import de.danoeh.antennapod.model.feed.FeedItem; +import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.playback.Playable; import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; import de.danoeh.antennapod.view.AspectRatioVideoView; +import de.danoeh.antennapod.view.PlayButton; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; /** * Activity for playing video files. */ -public class VideoplayerActivity extends MediaplayerActivity { +public class VideoplayerActivity extends CastEnabledActivity implements SeekBar.OnSeekBarChangeListener { private static final String TAG = "VideoplayerActivity"; /** @@ -68,6 +107,23 @@ public class VideoplayerActivity extends MediaplayerActivity { private ProgressBar progressIndicator; private FrameLayout videoframe; private ImageView skipAnimationView; + private TextView txtvPosition; + private TextView txtvLength; + private SeekBar sbPosition; + private ImageButton butRev; + private TextView txtvRev; + private PlayButton butPlay; + private ImageButton butFF; + private TextView txtvFF; + private ImageButton butSkip; + private CardView cardViewSeek; + private TextView txtvSeek; + + private PlaybackController controller; + private boolean showTimeLeft = false; + private boolean isFavorite = false; + private Disposable disposable; + private float prog; @SuppressLint("AppCompatMethod") @Override @@ -75,13 +131,21 @@ public class VideoplayerActivity extends MediaplayerActivity { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY); // has to be called before setting layout content + setTheme(R.style.Theme_AntennaPod_VideoPlayer); super.onCreate(savedInstanceState); + + Log.d(TAG, "onCreate()"); + StorageUtils.checkStorageAvailability(this); + + getWindow().setFormat(PixelFormat.TRANSPARENT); + setupGUI(); getSupportActionBar().setBackgroundDrawable(new ColorDrawable(0x80000000)); } @Override protected void onResume() { super.onResume(); + StorageUtils.checkStorageAvailability(this); if (PlaybackService.isCasting()) { Intent intent = PlaybackService.getPlayerActivityIntent(this); if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) { @@ -94,6 +158,14 @@ public class VideoplayerActivity extends MediaplayerActivity { @Override protected void onStop() { + if (controller != null) { + controller.release(); + controller = null; // prevent leak + } + if (disposable != null) { + disposable.dispose(); + } + EventBus.getDefault().unregister(this); super.onStop(); if (!PictureInPictureUtil.isInPictureInPictureMode(this)) { videoControlsHider.stop(); @@ -109,6 +181,16 @@ public class VideoplayerActivity extends MediaplayerActivity { } } + @Override + protected void onStart() { + super.onStart(); + controller = newPlaybackController(); + controller.init(); + loadMediaInfo(); + onPositionObserverUpdate(); + EventBus.getDefault().register(this); + } + @Override protected void onPause() { if (!PictureInPictureUtil.isInPictureInPictureMode(this)) { @@ -126,9 +208,94 @@ public class VideoplayerActivity extends MediaplayerActivity { super.onDestroy(); } + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + Glide.get(this).trimMemory(level); + } + @Override + public void onLowMemory() { + super.onLowMemory(); + Glide.get(this).clearMemory(); + } + + private PlaybackController newPlaybackController() { + return new PlaybackController(this) { + @Override + public void onPositionObserverUpdate() { + VideoplayerActivity.this.onPositionObserverUpdate(); + } + + @Override + public void onBufferStart() { + VideoplayerActivity.this.onBufferStart(); + } + + @Override + public void onBufferEnd() { + VideoplayerActivity.this.onBufferEnd(); + } + + @Override + public void onBufferUpdate(float progress) { + if (sbPosition != null) { + sbPosition.setSecondaryProgress((int) (progress * sbPosition.getMax())); + } + } + + @Override + public void handleError(int code) { + VideoplayerActivity.this.handleError(code); + } + + @Override + public void onReloadNotification(int code) { + VideoplayerActivity.this.onReloadNotification(code); + } + + @Override + public void onSleepTimerUpdate() { + supportInvalidateOptionsMenu(); + } + + @Override + protected void updatePlayButtonShowsPlay(boolean showPlay) { + butPlay.setIsShowPlay(showPlay); + } + + @Override + public void loadMediaInfo() { + VideoplayerActivity.this.loadMediaInfo(); + } + + @Override + public void onAwaitingVideoSurface() { + VideoplayerActivity.this.onAwaitingVideoSurface(); + } + + @Override + public void onPlaybackEnd() { + finish(); + } + + @Override + protected void setScreenOn(boolean enable) { + super.setScreenOn(enable); + VideoplayerActivity.this.setScreenOn(enable); + } + }; + } + protected void loadMediaInfo() { - super.loadMediaInfo(); + Log.d(TAG, "loadMediaInfo()"); + if (controller == null || controller.getMedia() == null) { + return; + } + showTimeLeft = UserPreferences.shouldShowRemainingTime(); + onPositionObserverUpdate(); + checkFavorite(); Playable media = controller.getMedia(); if (media != null) { getSupportActionBar().setSubtitle(media.getEpisodeTitle()); @@ -136,12 +303,90 @@ public class VideoplayerActivity extends MediaplayerActivity { } } - @Override protected void setupGUI() { if (isSetup.getAndSet(true)) { return; } - super.setupGUI(); + setContentView(R.layout.videoplayer_activity); + sbPosition = findViewById(R.id.sbPosition); + txtvPosition = findViewById(R.id.txtvPosition); + cardViewSeek = findViewById(R.id.cardViewSeek); + txtvSeek = findViewById(R.id.txtvSeek); + + showTimeLeft = UserPreferences.shouldShowRemainingTime(); + Log.d("timeleft", showTimeLeft ? "true" : "false"); + txtvLength = findViewById(R.id.txtvLength); + if (txtvLength != null) { + txtvLength.setOnClickListener(v -> { + showTimeLeft = !showTimeLeft; + Playable media = controller.getMedia(); + if (media == null) { + return; + } + + TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier()); + String length; + if (showTimeLeft) { + int remainingTime = converter.convert( + media.getDuration() - media.getPosition()); + + length = "-" + Converter.getDurationStringLong(remainingTime); + } else { + int duration = converter.convert(media.getDuration()); + length = Converter.getDurationStringLong(duration); + } + txtvLength.setText(length); + + UserPreferences.setShowRemainTimeSetting(showTimeLeft); + Log.d("timeleft on click", showTimeLeft ? "true" : "false"); + }); + } + + butRev = findViewById(R.id.butRev); + txtvRev = findViewById(R.id.txtvRev); + if (txtvRev != null) { + txtvRev.setText(NumberFormat.getInstance().format(UserPreferences.getRewindSecs())); + } + butPlay = findViewById(R.id.butPlay); + butPlay.setIsVideoScreen(true); + butFF = findViewById(R.id.butFF); + txtvFF = findViewById(R.id.txtvFF); + if (txtvFF != null) { + txtvFF.setText(NumberFormat.getInstance().format(UserPreferences.getFastForwardSecs())); + } + butSkip = findViewById(R.id.butSkip); + + // SEEKBAR SETUP + + sbPosition.setOnSeekBarChangeListener(this); + + // BUTTON SETUP + + if (butRev != null) { + butRev.setOnClickListener(v -> onRewind()); + butRev.setOnLongClickListener(v -> { + SkipPreferenceDialog.showSkipPreference(VideoplayerActivity.this, + SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev); + return true; + }); + } + + butPlay.setOnClickListener(v -> onPlayPause()); + + if (butFF != null) { + butFF.setOnClickListener(v -> onFastForward()); + butFF.setOnLongClickListener(v -> { + SkipPreferenceDialog.showSkipPreference(VideoplayerActivity.this, + SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF); + return false; + }); + } + + if (butSkip != null) { + butSkip.setOnClickListener(v -> + IntentUtils.sendLocalBroadcast(VideoplayerActivity.this, PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); + } + getSupportActionBar().setDisplayHomeAsUpEnabled(true); controls = findViewById(R.id.controls); videoOverlay = findViewById(R.id.overlay); @@ -163,7 +408,6 @@ public class VideoplayerActivity extends MediaplayerActivity { videoview.setAvailableSize(videoframe.getWidth(), videoframe.getHeight())); } - @Override protected void onAwaitingVideoSurface() { setupVideoAspectRatio(); if (videoSurfaceCreated && controller != null) { @@ -273,24 +517,45 @@ public class VideoplayerActivity extends MediaplayerActivity { videoControlsShowing = !videoControlsShowing; } - @Override - protected void onRewind() { - super.onRewind(); + void onRewind() { + if (controller == null) { + return; + } + int curr = controller.getPosition(); + controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000); setupVideoControlsToggler(); } - @Override - protected void onPlayPause() { - super.onPlayPause(); + void onPlayPause() { + if(controller == null) { + return; + } + controller.init(); + controller.playPause(); setupVideoControlsToggler(); } - @Override - protected void onFastForward() { - super.onFastForward(); + void onFastForward() { + if (controller == null) { + return; + } + int curr = controller.getPosition(); + controller.seekTo(curr + UserPreferences.getFastForwardSecs() * 1000); setupVideoControlsToggler(); } + private void handleError(int errorCode) { + final AlertDialog.Builder errorDialog = new AlertDialog.Builder(this); + errorDialog.setTitle(R.string.error_label); + errorDialog.setMessage(MediaPlayerError.getErrorString(this, errorCode)); + errorDialog.setNeutralButton("OK", + (dialog, which) -> { + dialog.dismiss(); + finish(); + } + ); + errorDialog.create().show(); + } private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() { @Override @@ -321,8 +586,6 @@ public class VideoplayerActivity extends MediaplayerActivity { } }; - - @Override protected void onReloadNotification(int notificationCode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && PictureInPictureUtil.isInPictureInPictureMode(this)) { if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO @@ -339,24 +602,10 @@ public class VideoplayerActivity extends MediaplayerActivity { } } - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - super.onStartTrackingTouch(seekBar); - videoControlsHider.stop(); - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - super.onStopTrackingTouch(seekBar); - setupVideoControlsToggler(); - } - - @Override protected void onBufferStart() { progressIndicator.setVisibility(View.VISIBLE); } - @Override protected void onBufferEnd() { progressIndicator.setVisibility(View.INVISIBLE); } @@ -395,9 +644,7 @@ public class VideoplayerActivity extends MediaplayerActivity { hideVideoControls(true); } - @Override - protected void setScreenOn(boolean enable) { - super.setScreenOn(enable); + private void setScreenOn(boolean enable) { if (enable) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { @@ -405,9 +652,58 @@ public class VideoplayerActivity extends MediaplayerActivity { } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + onPositionObserverUpdate(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlaybackServiceChanged(ServiceEvent event) { + if (event.action == ServiceEvent.Action.SERVICE_SHUT_DOWN) { + finish(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + requestCastButton(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.mediaplayer, menu); + return true; + } + @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); + if (controller == null) { + return false; + } + Playable media = controller.getMedia(); + boolean isFeedMedia = (media instanceof FeedMedia); + + menu.findItem(R.id.open_feed_item).setVisible(isFeedMedia); // FeedMedia implies it belongs to a Feed + + boolean hasWebsiteLink = ( getWebsiteLinkWithFallback(media) != null ); + menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink); + + boolean isItemAndHasLink = isFeedMedia && + ShareUtils.hasLinkToShare(((FeedMedia) media).getItem()); + + boolean isItemHasDownloadLink = isFeedMedia && ((FeedMedia) media).getDownload_url() != null; + + menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink); + + menu.findItem(R.id.add_to_favorites_item).setVisible(false); + menu.findItem(R.id.remove_from_favorites_item).setVisible(false); + if (isFeedMedia) { + menu.findItem(R.id.add_to_favorites_item).setVisible(!isFavorite); + menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite); + } + + menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller.sleepTimerActive()); + menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive()); + if (PictureInPictureUtil.supportsPictureInPicture(this)) { menu.findItem(R.id.player_go_to_picture_in_picture).setVisible(true); } @@ -421,7 +717,190 @@ public class VideoplayerActivity extends MediaplayerActivity { compatEnterPictureInPicture(); return true; } - return super.onOptionsItemSelected(item); + if (controller == null) { + return false; + } + Playable media = controller.getMedia(); + if (item.getItemId() == android.R.id.home) { + Intent intent = new Intent(VideoplayerActivity.this, + MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); + + View cover = findViewById(R.id.imgvCover); + if (cover != null) { + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation(VideoplayerActivity.this, cover, "coverTransition"); + startActivity(intent, options.toBundle()); + } else { + startActivity(intent); + } + finish(); + return true; + } else { + if (media != null) { + final @Nullable FeedItem feedItem = getFeedItem(media); // some options option requires FeedItem + switch (item.getItemId()) { + case R.id.add_to_favorites_item: + if (feedItem != null) { + DBWriter.addFavoriteItem(feedItem); + isFavorite = true; + invalidateOptionsMenu(); + } + break; + case R.id.remove_from_favorites_item: + if (feedItem != null) { + DBWriter.removeFavoriteItem(feedItem); + isFavorite = false; + invalidateOptionsMenu(); + } + break; + case R.id.disable_sleeptimer_item: // Fall-through + case R.id.set_sleeptimer_item: + new SleepTimerDialog().show(getSupportFragmentManager(), "SleepTimerDialog"); + break; + case R.id.audio_controls: + PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(); + dialog.show(getSupportFragmentManager(), "playback_controls"); + break; + case R.id.open_feed_item: + if (feedItem != null) { + Intent intent = MainActivity.getIntentToOpenFeed(this, feedItem.getFeedId()); + startActivity(intent); + } + break; + case R.id.visit_website_item: + IntentUtils.openInBrowser(VideoplayerActivity.this, getWebsiteLinkWithFallback(media)); + break; + case R.id.share_item: + if (feedItem != null) { + ShareDialog shareDialog = ShareDialog.newInstance(feedItem); + shareDialog.show(getSupportFragmentManager(), "ShareEpisodeDialog"); + } + break; + default: + return false; + } + return true; + } else { + return false; + } + } + } + + private static String getWebsiteLinkWithFallback(Playable media) { + if (media == null) { + return null; + } else if (StringUtils.isNotBlank(media.getWebsiteLink())) { + return media.getWebsiteLink(); + } else if (media instanceof FeedMedia) { + return FeedItemUtil.getLinkWithFallback(((FeedMedia)media).getItem()); + } + return null; + } + + void onPositionObserverUpdate() { + if (controller == null || txtvPosition == null || txtvLength == null) { + return; + } + + TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier()); + int currentPosition = converter.convert(controller.getPosition()); + int duration = converter.convert(controller.getDuration()); + int remainingTime = converter.convert( + controller.getDuration() - controller.getPosition()); + Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition)); + if (currentPosition == PlaybackService.INVALID_TIME || + duration == PlaybackService.INVALID_TIME) { + Log.w(TAG, "Could not react to position observer update because of invalid time"); + return; + } + txtvPosition.setText(Converter.getDurationStringLong(currentPosition)); + if (showTimeLeft) { + txtvLength.setText("-" + Converter.getDurationStringLong(remainingTime)); + } else { + txtvLength.setText(Converter.getDurationStringLong(duration)); + } + updateProgressbarPosition(currentPosition, duration); + } + + private void updateProgressbarPosition(int position, int duration) { + Log.d(TAG, "updateProgressbarPosition(" + position + ", " + duration + ")"); + if(sbPosition == null) { + return; + } + float progress = ((float) position) / duration; + sbPosition.setProgress((int) (progress * sbPosition.getMax())); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (controller == null || txtvLength == null) { + return; + } + if (fromUser) { + prog = progress / ((float) seekBar.getMax()); + TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier()); + int position = converter.convert((int) (prog * controller.getDuration())); + txtvSeek.setText(Converter.getDurationStringLong(position)); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + cardViewSeek.setScaleX(.8f); + cardViewSeek.setScaleY(.8f); + cardViewSeek.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(1f).scaleX(1f).scaleY(1f) + .setDuration(200) + .start(); + videoControlsHider.stop(); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (controller != null) { + controller.seekTo((int) (prog * controller.getDuration())); + } + cardViewSeek.setScaleX(1f); + cardViewSeek.setScaleY(1f); + cardViewSeek.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(0f).scaleX(.8f).scaleY(.8f) + .setDuration(200) + .start(); + setupVideoControlsToggler(); + } + + private void checkFavorite() { + FeedItem feedItem = getFeedItem(controller.getMedia()); + if (feedItem == null) { + return; + } + if (disposable != null) { + disposable.dispose(); + } + disposable = Observable.fromCallable(() -> DBReader.getFeedItem(feedItem.getId())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + item -> { + boolean isFav = item.isTagged(FeedItem.TAG_FAVORITE); + if (isFavorite != isFav) { + isFavorite = isFav; + invalidateOptionsMenu(); + } + }, error -> Log.e(TAG, Log.getStackTraceString(error))); + } + + @Nullable + private static FeedItem getFeedItem(@Nullable Playable playable) { + if (playable instanceof FeedMedia) { + return ((FeedMedia) playable).getItem(); + } else { + return null; + } } private void compatEnterPictureInPicture() { -- cgit v1.2.3