content: package de.danoeh.antennapod.ui.screen.playback.video;
  
  import android.app.PictureInPictureParams;
  import android.app.PictureInPictureUiState;
  import android.content.Intent;
  import android.graphics.PixelFormat;
  import android.graphics.drawable.ColorDrawable;
  import android.media.AudioManager;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Looper;
  import android.util.Log;
  import android.util.Pair;
  import android.view.Gravity;
  import android.view.KeyEvent;
  import android.view.LayoutInflater;
  import android.view.Menu;
  import android.view.MenuInflater;
  import android.view.MenuItem;
  import android.view.MotionEvent;
  import android.view.SurfaceHolder;
  import android.view.View;
  import android.view.Window;
  import android.view.WindowManager;
  import android.view.animation.AlphaAnimation;
  import android.view.animation.Animation;
  import android.view.animation.AnimationSet;
  import android.view.animation.AnimationUtils;
  import android.view.animation.ScaleAnimation;
  import android.widget.EditText;
  import android.widget.FrameLayout;
  import android.widget.SeekBar;
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
  import com.bumptech.glide.Glide;
  import com.google.android.material.dialog.MaterialAlertDialogBuilder;
  import de.danoeh.antennapod.R;
  import de.danoeh.antennapod.activity.MainActivity;
  import de.danoeh.antennapod.event.MessageEvent;
  import de.danoeh.antennapod.event.playback.BufferUpdateEvent;
  import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
  import de.danoeh.antennapod.event.PlayerErrorEvent;
  import de.danoeh.antennapod.event.playback.PlaybackServiceEvent;
  import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent;
  import de.danoeh.antennapod.ui.episodeslist.FeedItemMenuHandler;
  import de.danoeh.antennapod.ui.screen.chapter.ChaptersFragment;
  import de.danoeh.antennapod.playback.service.PlaybackController;
  import de.danoeh.antennapod.playback.service.PlaybackService;
  import de.danoeh.antennapod.storage.preferences.UserPreferences;
  import de.danoeh.antennapod.storage.database.DBReader;
  import de.danoeh.antennapod.storage.database.DBWriter;
  import de.danoeh.antennapod.ui.common.Converter;
  import de.danoeh.antennapod.ui.common.IntentUtils;
  import de.danoeh.antennapod.ui.screen.playback.TranscriptDialogFragment;
  import de.danoeh.antennapod.databinding.VideoplayerActivityBinding;
  import de.danoeh.antennapod.ui.share.ShareDialog;
  import de.danoeh.antennapod.ui.screen.feed.preferences.SkipPreferenceDialog;
  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.playback.base.PlayerStatus;
  import de.danoeh.antennapod.playback.cast.CastEnabledActivity;
  import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
  import de.danoeh.antennapod.ui.episodes.TimeSpeedConverter;
  import de.danoeh.antennapod.ui.screen.playback.MediaPlayerErrorDialog;
  import de.danoeh.antennapod.ui.screen.playback.PlaybackControlsDialog;
  import de.danoeh.antennapod.ui.screen.playback.SleepTimerDialog;
  import de.danoeh.antennapod.ui.screen.playback.VariableSpeedDialog;
  import io.reactivex.rxjava3.core.Observable;
  import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
  import io.reactivex.rxjava3.disposables.Disposable;
  import io.reactivex.rxjava3.schedulers.Schedulers;
  import org.apache.commons.lang3.StringUtils;
  import org.greenrobot.eventbus.EventBus;
  import org.greenrobot.eventbus.Subscribe;
  import org.greenrobot.eventbus.ThreadMode;
  
  import java.util.Collections;
  
  /**
   * Activity for playing video files.
   */
  public class VideoplayerActivity extends CastEnabledActivity implements SeekBar.OnSeekBarChangeListener {
      private static final String TAG = "VideoplayerActivity";
  
      /**
       * True if video controls are currently visible.
       */
      private boolean videoControlsShowing = true;
      private boolean videoSurfaceCreated = false;
      private boolean destroyingDueToReload = false;
      private long lastScreenTap = 0;
      private final Handler videoControlsHider = new Handler(Looper.getMainLooper());
      private VideoplayerActivityBinding viewBinding;
      private PlaybackController controller;
      private boolean showTimeLeft = false;
      private boolean isFavorite = false;
      private boolean switchToAudioOnly = false;
      private Disposable disposable;
      private float prog;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
          getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
          // has to be called before setting layout content
          supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
          setTheme(R.style.Theme_AntennaPod_VideoPlayer);
          super.onCreate(savedInstanceState);
  
          Log.d(TAG, "onCreate()");
  
          getWindow().setFormat(PixelFormat.TRANSPARENT);
          viewBinding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this));
          setContentView(viewBinding.getRoot());
          setupView();
          getSupportActionBar().setBackgroundDrawable(new ColorDrawable(0x80000000));
          getSupportActionBar().setDisplayHomeAsUpEnabled(true);
          setupPip();
      }
  
      void setupPip() {
          if (Build.VERSION.SDK_INT < 26) {
              return;
          }
          PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
          if (Build.VERSION.SDK_INT >= 31) {
              builder.setAutoEnterEnabled(true);
              builder.setSourceRectHint(viewBinding.getRoot().getClipBounds());
          }
          setPictureInPictureParams(builder.build());
      }
  
      @Override
      public void onPictureInPictureUiStateChanged(@NonNull PictureInPictureUiState pipState) {
          super.onPictureInPictureUiStateChanged(pipState);
          if (Build.VERSION.SDK_INT < 35) {
              return;
          }
          if (pipState.isTransitioningToPip()) {
              hideVideoControls(false);
          }
      }
  
      @Override
      protected void onResume() {
          super.onResume();
          switchToAudioOnly = false;
          if (PlaybackService.isCasting()) {
              Intent intent = PlaybackService.getPlayerActivityIntent(this);
              if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) {
                  destroyingDueToReload = true;
                  finish();
                  startActivity(intent);
              }
          }
      }
  
      @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.removeCallbacks(hideVideoControls);
          }
          // Controller released; we will not receive buffering updates
          viewBinding.progressBar.setVisibility(View.GONE);
      }
  
      @Override
      public void onUserLeaveHint() {
          if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
              compatEnterPictureInPicture();
          }
      }
  
      @Override
      protected void onStart() {
          super.onStart();
          controller = newPlaybackController();
          controller.init();
          loadMediaInfo();
          onPositionObserverUpdate();
          EventBus.getDefault().register(this);
      }
  
      @Override
      protected void onPause() {
          if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
              if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) {
                  controller.pause();
              }
          }
          super.onPause();
      }
  
      @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
              protected void updatePlayButtonShowsPlay(boolean showPlay) {
                  viewBinding.playButton.setIsShowPlay(showPlay);
                  if (showPlay) {
                      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                  } else {
                      getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                      setupVideoAspectRatio();
                      if (videoSurfaceCreated && controller != null) {
                          Log.d(TAG, "Videosurface already created, setting videosurface now");
                          controller.setVideoSurface(viewBinding.videoView.getHolder());
                      }
                  }
              }
  
              @Override
              public void loadMediaInfo() {
                  VideoplayerActivity.this.loadMediaInfo();
              }
  
              @Override
              public void onPlaybackEnd() {
                  finish();
              }
          };
      }
  
      @Subscribe(threadMode = ThreadMode.MAIN)
      @SuppressWarnings("unused")
      public void bufferUpdate(BufferUpdateEvent event) {
          if (event.hasStarted()) {
              viewBinding.progressBar.setVisibility(View.VISIBLE);
          } else if (event.hasEnded()) {
              viewBinding.progressBar.setVisibility(View.INVISIBLE);
          } else {
              viewBinding.sbPosition.setSecondaryProgress((int) (event.getProgress() * viewBinding.sbPosition.getMax()));
          }
      }
  
      @Subscribe(threadMode = ThreadMode.MAIN)
      @SuppressWarnings("unused")
      public void sleepTimerUpdate(SleepTimerUpdatedEvent event) {
          if (event.isCancelled() || event.wasJustEnabled()) {
              supportInvalidateOptionsMenu();
          }
      }
  
      protected void loadMediaInfo() {
          Log.d(TAG, "loadMediaInfo()");
          if (controller == null || controller.getMedia() == null) {
              return;
          }
          if (controller.getStatus() == PlayerStatus.PLAYING && !controller.isPlayingVideoLocally()) {
              Log.d(TAG, "Closing, no longer video");
              destroyingDueToReload = true;
              finish();
              new MainActivityStarter(this).withOpenPlayer().start();
              return;
          }
          showTimeLeft = UserPreferences.shouldShowRemainingTime();
          onPositionObserverUpdate();
          checkFavorite();
          Playable media = controller.getMedia();
          if (media != null) {
              getSupportActionBar().setSubtitle(media.getEpisodeTitle());
              getSupportActionBar().setTitle(media.getFeedTitle());
          }
      }
  
      protected void setupView() {
          showTimeLeft = UserPreferences.shouldShowRemainingTime();
          Log.d("timeleft", showTimeLeft ? "true" : "false");
          viewBinding.durationLabel.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);
              }
              viewBinding.durationLabel.setText(length);
  
              UserPreferences.setShowRemainTimeSetting(showTimeLeft);
              Log.d("timeleft on click", showTimeLeft ? "true" : "false");
          });
  
          viewBinding.sbPosition.setOnSeekBarChangeListener(this);
          viewBinding.rewindButton.setOnClickListener(v -> onRewind());
          viewBinding.rewindButton.setOnLongClickListener(v -> {
              SkipPreferenceDialog.showSkipPreference(VideoplayerActivity.this,
                      SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null);
              return true;
          });
          viewBinding.playButton.setIsVideoScreen(true);
          viewBinding.playButton.setOnClickListener(v -> onPlayPause());
          viewBinding.fastForwardButton.setOnClickListener(v -> onFastForward());
          viewBinding.fastForwardButton.setOnLongClickListener(v -> {
              SkipPreferenceDialog.showSkipPreference(VideoplayerActivity.this,
                      SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null);
              return false;
          });
          // To suppress touches directly below the slider
          viewBinding.bottomControlsContainer.setOnTouchListener((view, motionEvent) -> true);
          viewBinding.bottomControlsContainer.setFitsSystemWindows(true);
          viewBinding.videoView.getHolder().addCallback(surfaceHolderCallback);
          viewBinding.videoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
  
          setupVideoControlsToggler();
          getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                  WindowManager.LayoutParams.FLAG_FULLSCREEN);
  
          viewBinding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched);
          viewBinding.videoPlayerContainer.getViewTreeObserver().addOnGlobalLayoutListener(() ->
                  viewBinding.videoView.setAvailableSize(
                          viewBinding.videoPlayerContainer.getWidth(), viewBinding.videoPlayerContainer.getHeight()));
      }
  
      private final Runnable hideVideoControls = () -> {
          if (videoControlsShowing) {
              Log.d(TAG, "Hiding video controls");
              getSupportActionBar().hide();
              hideVideoControls(true);
              videoControlsShowing = false;
          }
      };
  
      private final View.OnTouchListener onVideoviewTouched = (v, event) -> {
          if (event.getAction() != MotionEvent.ACTION_DOWN) {
              return false;
          }
          if (PictureInPictureUtil.isInPictureInPictureMode(this)) {
              return true;
          }
          videoControlsHider.removeCallbacks(hideVideoControls);
  
          if (System.currentTimeMillis() - lastScreenTap < 300) {
              if (event.getX() > v.getMeasuredWidth() / 2.0f) {
                  onFastForward();
                  showSkipAnimation(true);
              } else {
                  onRewind();
                  showSkipAnimation(false);
              }
              if (videoControlsShowing) {
                  getSupportActionBar().hide();
                  hideVideoControls(false);
                  videoControlsShowing = false;
              }
              return true;
          }
  
          toggleVideoControlsVisibility();
          if (videoControlsShowing) {
              setupVideoControlsToggler();
          }
  
          lastScreenTap = System.currentTimeMillis();
          return true;
      };
  
      private void showSkipAnimation(boolean isForward) {
          AnimationSet skipAnimation = new AnimationSet(true);
          skipAnimation.addAnimation(new ScaleAnimation(1f, 2f, 1f, 2f,
                  Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f));
          skipAnimation.addAnimation(new AlphaAnimation(1f, 0f));
          skipAnimation.setFillAfter(false);
          skipAnimation.setDuration(800);
  
          FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) viewBinding.skipAnimationImage.getLayoutParams();
          if (isForward) {
              viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white);
              params.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
          } else {
              viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white);
              params.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
          }
  
          viewBinding.skipAnimationImage.setVisibility(View.VISIBLE);
          viewBinding.skipAnimationImage.setLayoutParams(params);
          viewBinding.skipAnimationImage.startAnimation(skipAnimation);
          skipAnimation.setAnimationListener(new Animation.AnimationListener() {
              @Override
              public void onAnimationStart(Animation animation) {
              }
  
              @Override
              public void onAnimationEnd(Animation animation) {
                  viewBinding.skipAnimationImage.setVisibility(View.GONE);
              }
  
              @Override
              public void onAnimationRepeat(Animation animation) {
  
              }
          });
      }
  
      private void setupVideoControlsToggler() {
          videoControlsHider.removeCallbacks(hideVideoControls);
          videoControlsHider.postDelayed(hideVideoControls, 2500);
      }
  
      private void setupVideoAspectRatio() {
          if (videoSurfaceCreated && controller != null) {
              Pair<Integer, Integer> videoSize = controller.getVideoSize();
              if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
                  Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second);
                  viewBinding.videoView.setVideoSize(videoSize.first, videoSize.second);
              } else {
                  Log.e(TAG, "Could not determine video size");
              }
          }
      }
  
      private void toggleVideoControlsVisibility() {
          if (videoControlsShowing) {
              getSupportActionBar().hide();
              hideVideoControls(true);
          } else {
              getSupportActionBar().show();
              showVideoControls();
          }
          videoControlsShowing = !videoControlsShowing;
      }
  
      void onRewind() {
          if (controller == null) {
              return;
          }
          int curr = controller.getPosition();
          controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000);
          setupVideoControlsToggler();
      }
  
      void onPlayPause() {
          if (controller == null) {
              return;
          }
          controller.playPause();
          setupVideoControlsToggler();
      }
  
      void onFastForward() {
          if (controller == null) {
              return;
          }
          int curr = controller.getPosition();
          controller.seekTo(curr + UserPreferences.getFastForwardSecs() * 1000);
          setupVideoControlsToggler();
      }
  
      private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() {
          @Override
          public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
              holder.setFixedSize(width, height);
          }
  
          @Override
          public void surfaceCreated(SurfaceHolder holder) {
              Log.d(TAG, "Videoview holder created");
              videoSurfaceCreated = true;
              if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) {
                  controller.setVideoSurface(holder);
              }
              setupVideoAspectRatio();
          }
  
          @Override
          public void surfaceDestroyed(SurfaceHolder holder) {
              Log.d(TAG, "Videosurface was destroyed");
              videoSurfaceCreated = false;
              if (controller != null && !destroyingDueToReload && !switchToAudioOnly) {
                  controller.notifyVideoSurfaceAbandoned();
              }
          }
      };
  
      private void showVideoControls() {
          viewBinding.bottomControlsContainer.setVisibility(View.VISIBLE);
          viewBinding.controlsContainer.setVisibility(View.VISIBLE);
          final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_in);
          if (animation != null) {
              viewBinding.bottomControlsContainer.startAnimation(animation);
              viewBinding.controlsContainer.startAnimation(animation);
          }
          viewBinding.videoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
      }
  
      private void hideVideoControls(boolean showAnimation) {
          if (showAnimation) {
              final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_out);
              if (animation != null) {
                  viewBinding.bottomControlsContainer.startAnimation(animation);
                  viewBinding.controlsContainer.startAnimation(animation);
              }
          }
          getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
                  | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                  | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
          viewBinding.bottomControlsContainer.setFitsSystemWindows(true);
  
          viewBinding.bottomControlsContainer.setVisibility(View.GONE);
          viewBinding.controlsContainer.setVisibility(View.GONE);
      }
  
      @Subscribe(threadMode = ThreadMode.MAIN)
      public void onEventMainThread(PlaybackPositionEvent event) {
          onPositionObserverUpdate();
      }
  
      @Subscribe(threadMode = ThreadMode.MAIN)
      public void onPlaybackServiceChanged(PlaybackServiceEvent event) {
          if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) {
              finish();
          }
      }
  
      @Subscribe(threadMode = ThreadMode.MAIN)
      public void onMediaPlayerError(PlayerErrorEvent event) {
          MediaPlayerErrorDialog.show(this, event);
      }
  
      @Subscribe(threadMode = ThreadMode.MAIN)
      public void onEventMainThread(MessageEvent event) {
          Log.d(TAG, "onEvent(" + event + ")");
          final MaterialAlertDialogBuilder errorDialog = new MaterialAlertDialogBuilder(this);
          errorDialog.setMessage(event.message);
          if (event.action != null) {
              errorDialog.setPositiveButton(event.actionText, (dialog, which) -> event.action.accept(this));
          }
          errorDialog.show();
      }
  
      @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
          if (isFeedMedia) {
              FeedItemMenuHandler.onPrepareMenu(menu, Collections.singletonList(((FeedMedia) media).getItem()));
          }
  
          menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller.sleepTimerActive());
          menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive());
          menu.findItem(R.id.player_switch_to_audio_only).setVisible(true);
          menu.findItem(R.id.audio_controls).setVisible(controller.getAudioTracks().size() >= 2);
          menu.findItem(R.id.playback_speed).setVisible(true);
          menu.findItem(R.id.player_show_chapters).setVisible(true);
          return true;
      }
  
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          if (item.getItemId() == R.id.player_switch_to_audio_only) {
              switchToAudioOnly = true;
              finish();
              return true;
          } else 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);
              startActivity(intent);
              finish();
              return true;
          } else if (item.getItemId() == R.id.player_show_chapters) {
              new ChaptersFragment().show(getSupportFragmentManager(), ChaptersFragment.TAG);
              return true;
          } else if (item.getItemId() == R.id.transcript_item) {
              new TranscriptDialogFragment().show(getSupportFragmentManager(), TranscriptDialogFragment.TAG);
              return true;
          }
  
          if (controller == null) {
              return false;
          }
  
          Playable media = controller.getMedia();
          if (media == null) {
              return false;
          }
          final @Nullable FeedItem feedItem = getFeedItem(media); // some options option requires FeedItem
          if (item.getItemId() == R.id.add_to_favorites_item && feedItem != null) {
              DBWriter.addFavoriteItem(feedItem);
              isFavorite = true;
              invalidateOptionsMenu();
          } else if (item.getItemId() == R.id.remove_from_favorites_item && feedItem != null) {
              DBWriter.removeFavoriteItem(feedItem);
              isFavorite = false;
              invalidateOptionsMenu();
          } else if (item.getItemId() == R.id.disable_sleeptimer_item
                  || item.getItemId() == R.id.set_sleeptimer_item) {
              new SleepTimerDialog().show(getSupportFragmentManager(), "SleepTimerDialog");
          } else if (item.getItemId() == R.id.audio_controls) {
              PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance();
              dialog.show(getSupportFragmentManager(), "playback_controls");
          } else if (item.getItemId() == R.id.open_feed_item && feedItem != null) {
              new MainActivityStarter(this).withOpenFeed(feedItem.getFeedId()).withClearTop().start();
          } else if (item.getItemId() == R.id.visit_website_item) {
              IntentUtils.openInBrowser(VideoplayerActivity.this, getWebsiteLinkWithFallback(media));
          } else if (item.getItemId() == R.id.share_item && feedItem != null) {
              ShareDialog shareDialog = ShareDialog.newInstance(feedItem);
              shareDialog.show(getSupportFragmentManager(), "ShareEpisodeDialog");
          } else if (item.getItemId() == R.id.playback_speed) {
              new VariableSpeedDialog().show(getSupportFragmentManager(), null);
          } else {
              return false;
          }
          return true;
      }
  
      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 (((FeedMedia) media).getItem()).getLinkWithFallback();
          }
          return null;
      }
  
      void onPositionObserverUpdate() {
          if (controller == 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 == Playable.INVALID_TIME
                  || duration == Playable.INVALID_TIME) {
              Log.w(TAG, "Could not react to position observer update because of invalid time");
              return;
          }
          viewBinding.positionLabel.setText(Converter.getDurationStringLong(currentPosition));
          if (showTimeLeft) {
              viewBinding.durationLabel.setText("-" + Converter.getDurationStringLong(remainingTime));
          } else {
              viewBinding.durationLabel.setText(Converter.getDurationStringLong(duration));
          }
          updateProgressbarPosition(currentPosition, duration);
      }
  
      private void updateProgressbarPosition(int position, int duration) {
          Log.d(TAG, "updateProgressbarPosition(" + position + ", " + duration + ")");
          float progress = ((float) position) / duration;
          viewBinding.sbPosition.setProgress((int) (progress * viewBinding.sbPosition.getMax()));
      }
  
      @Override
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
          if (controller == null) {
              return;
          }
          if (fromUser) {
              prog = progress / ((float) seekBar.getMax());
              TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
              int position = converter.convert((int) (prog * controller.getDuration()));
              viewBinding.seekPositionLabel.setText(Converter.getDurationStringLong(position));
          }
      }
  
      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {
          viewBinding.seekCardView.setScaleX(.8f);
          viewBinding.seekCardView.setScaleY(.8f);
          viewBinding.seekCardView.animate()
                  .setInterpolator(new FastOutSlowInInterpolator())
                  .alpha(1f).scaleX(1f).scaleY(1f)
                  .setDuration(200)
                  .start();
          videoControlsHider.removeCallbacks(hideVideoControls);
      }
  
      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
          if (controller != null) {
              controller.seekTo((int) (prog * controller.getDuration()));
          }
          viewBinding.seekCardView.setScaleX(1f);
          viewBinding.seekCardView.setScaleY(1f);
          viewBinding.seekCardView.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() {
          if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
              getSupportActionBar().hide();
              hideVideoControls(false);
              enterPictureInPictureMode();
          }
      }
  
      //Hardware keyboard support
      @Override
      public boolean onKeyUp(int keyCode, KeyEvent event) {
          View currentFocus = getCurrentFocus();
          if (currentFocus instanceof EditText) {
              return super.onKeyUp(keyCode, event);
          }
  
          AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
  
          switch (keyCode) {
              case KeyEvent.KEYCODE_P: //Fallthrough
              case KeyEvent.KEYCODE_SPACE:
                  onPlayPause();
                  toggleVideoControlsVisibility();
                  return true;
              case KeyEvent.KEYCODE_J: //Fallthrough
              case KeyEvent.KEYCODE_A:
              case KeyEvent.KEYCODE_COMMA:
                  onRewind();
                  showSkipAnimation(false);
                  return true;
              case KeyEvent.KEYCODE_K: //Fallthrough
              case KeyEvent.KEYCODE_D:
              case KeyEvent.KEYCODE_PERIOD:
                  onFastForward();
                  showSkipAnimation(true);
                  return true;
              case KeyEvent.KEYCODE_F: //Fallthrough
              case KeyEvent.KEYCODE_ESCAPE:
                  //Exit fullscreen mode
                  onBackPressed();
                  return true;
              case KeyEvent.KEYCODE_I:
                  compatEnterPictureInPicture();
                  return true;
              case KeyEvent.KEYCODE_PLUS: //Fallthrough
              case KeyEvent.KEYCODE_W:
                  audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                          AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
                  return true;
              case KeyEvent.KEYCODE_MINUS: //Fallthrough
              case KeyEvent.KEYCODE_S:
                  audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                          AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI);
                  return true;
              case KeyEvent.KEYCODE_M:
                  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                      audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                              AudioManager.ADJUST_TOGGLE_MUTE, AudioManager.FLAG_SHOW_UI);
                      return true;
                  }
                  break;
              default:
                  break;
          }
  
          //Go to x% of video:
          if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
              controller.seekTo((int) (0.1f * (keyCode - KeyEvent.KEYCODE_0) * controller.getDuration()));
              return true;
          }
          return super.onKeyUp(keyCode, event);
      }
  }
  