package com.bitmovin.bitcodin.Overlay; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; import com.bitmovin.bitcodin.R; import com.bitmovin.bitcodin.Settings; import com.bitmovin.bitcodin.Utils; import com.google.android.exoplayer.VideoSurfaceView; import java.lang.ref.WeakReference; @SuppressWarnings("FieldCanBeLocal") public class ControlOverlay { private Context context; private RelativeLayout overlayWrapper; /* controls */ private RelativeLayout controlWrapper; private ImageView playPauseButton; private ProgressBar playerProgressBar; private SeekBar playerSeekBar; private TextView playbackTime; private ImageView changeScalingButton; /* tooltip */ private LinearLayout tooltipWrapper; private TextView tooltipText; /* title */ private RelativeLayout titleWrapper; private TextView title; /* animations */ private AlphaAnimation fadeInTooltipAnimation; private AlphaAnimation fadeOutTooltipAnimation; private AlphaAnimation fadeInOverlayAnimation; private AlphaAnimation fadeOutOverlayAnimation; /* other */ private MediaPlayerControl mPlayerControl; private VideoSurfaceView mVideoSurfaceView; private int currentScalingMode; private boolean overlayVisible; private boolean tooltipVisible; private boolean currentlyDragging; private int[] playerSeekBarPosition; private ProgressHandler mProgressHandler; private enum FadingViews { OVERLAY, TOOLTIP } public ControlOverlay(final Context context, RelativeLayout overlayWrapper, LinearLayout tooltipWrapper, VideoSurfaceView mVideoSurfaceView) { this.context = context; this.overlayWrapper = overlayWrapper; this.tooltipWrapper = tooltipWrapper; this.mVideoSurfaceView = mVideoSurfaceView; if (this.overlayWrapper != null) { /* controls */ this.controlWrapper = (RelativeLayout) this.overlayWrapper.findViewById(R.id.player_rlt_control_wrapper); this.playPauseButton = (ImageView) this.controlWrapper.findViewById(R.id.player_iv_control_play_pause); this.playerProgressBar = (ProgressBar) this.controlWrapper.findViewById(R.id.player_pb_control_progress); this.playerSeekBar = (SeekBar) this.controlWrapper.findViewById(R.id.player_sb_control_seek); this.playbackTime = (TextView) this.controlWrapper.findViewById(R.id.player_tv_control_playback_time); this.changeScalingButton = (ImageView) this.controlWrapper.findViewById(R.id.player_iv_control_change_scaling); /* tooltip */ this.tooltipText = (TextView) this.tooltipWrapper.findViewById(R.id.player_tv_tooltip_text); /* title */ this.titleWrapper = (RelativeLayout) this.overlayWrapper.findViewById(R.id.player_rlt_title_wrapper); this.title = (TextView) this.titleWrapper.findViewById(R.id.player_tv_title); this.playerProgressBar.setMax(1000); this.playerSeekBar.setMax(1000); this.controlWrapper.setOnClickListener(this.controlWrapperClickListener); this.playPauseButton.setOnClickListener(this.playPauseClickListener); this.playerSeekBar.setOnSeekBarChangeListener(this.playerSeekBarChangeListener); this.changeScalingButton.setOnClickListener(this.changeScalingClickListener); this.setupAnimations(); this.currentScalingMode = VideoSurfaceView.ScalingMode.FIT; this.overlayVisible = false; this.tooltipVisible = false; this.currentlyDragging = false; this.playerSeekBarPosition = new int[2]; this.mProgressHandler = new ProgressHandler(this); } } public void setMediaPlayer(MediaPlayerControl mPlayerControl) { this.mPlayerControl = mPlayerControl; show(); } // region animations and visibility private void setupAnimations() { this.fadeInOverlayAnimation = new AlphaAnimation(0, 1); this.fadeInOverlayAnimation.setDuration(Settings.FADE_IN_DURATION); this.fadeInOverlayAnimation.setAnimationListener(this.mAnimationListener); this.fadeOutOverlayAnimation = new AlphaAnimation(1, 0); this.fadeOutOverlayAnimation.setDuration(Settings.FADE_OUT_DURATION); this.fadeOutOverlayAnimation.setAnimationListener(this.mAnimationListener); this.fadeInTooltipAnimation = new AlphaAnimation(0, 1); this.fadeInTooltipAnimation.setDuration(Settings.FADE_IN_DURATION); this.fadeInTooltipAnimation.setAnimationListener(this.mAnimationListener); this.fadeOutTooltipAnimation = new AlphaAnimation(1, 0); this.fadeOutTooltipAnimation.setDuration(Settings.FADE_OUT_DURATION); this.fadeOutTooltipAnimation.setAnimationListener(this.mAnimationListener); } private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { if (animation == fadeInOverlayAnimation) { overlayWrapper.setVisibility(View.VISIBLE); overlayVisible = true; } else if (animation == fadeInTooltipAnimation) { tooltipWrapper.setVisibility(View.VISIBLE); updateTooltipPosition(); tooltipVisible = true; } else if (animation == fadeOutOverlayAnimation) { overlayVisible = false; } else if (animation == fadeOutTooltipAnimation) { tooltipVisible = false; } } @Override public void onAnimationEnd(Animation animation) { if (animation == fadeOutOverlayAnimation) { overlayWrapper.setVisibility(View.GONE); overlayVisible = false; } else if (animation == fadeOutTooltipAnimation) { tooltipWrapper.setVisibility(View.GONE); tooltipVisible = false; } } @Override public void onAnimationRepeat(Animation animation) { } }; public void show() { setProgress(); updatePlayPause(); fadeView(true, FadingViews.OVERLAY); this.mProgressHandler.sendEmptyMessage(ProgressHandler.Actions.SHOW_PROGRESS); this.mProgressHandler.removeMessages(ProgressHandler.Actions.FADE_OUT); this.mProgressHandler.sendMessageDelayed( this.mProgressHandler.obtainMessage(ProgressHandler.Actions.FADE_OUT), Settings.FADE_OUT_TIMEOUT ); } public void hide() { try { if (this.currentlyDragging || this.mPlayerControl.hasEnded()) { this.mProgressHandler.removeMessages(ProgressHandler.Actions.FADE_OUT); this.mProgressHandler.sendMessageDelayed( this.mProgressHandler.obtainMessage(ProgressHandler.Actions.FADE_OUT), Settings.FADE_OUT_TIMEOUT ); } else { fadeView(false, FadingViews.OVERLAY); } } catch (IllegalArgumentException ex) { Log.w("MediaController", "already removed"); } } private void fadeView(boolean fadeIn, FadingViews view) { Animation mAnimation; switch (view) { case OVERLAY: if (!(this.overlayVisible ^ fadeIn)) break; mAnimation = fadeIn ? this.fadeInOverlayAnimation : this.fadeOutOverlayAnimation; this.overlayWrapper.clearAnimation(); mAnimation.reset(); this.overlayWrapper.setAnimation(mAnimation); mAnimation.start(); break; case TOOLTIP: if (!(this.tooltipVisible ^ fadeIn)) break; mAnimation = fadeIn ? this.fadeInTooltipAnimation : this.fadeOutTooltipAnimation; this.tooltipWrapper.clearAnimation(); mAnimation.reset(); this.tooltipWrapper.setAnimation(mAnimation); mAnimation.start(); break; } } // endregion animations and visibility // region click listeners private View.OnClickListener changeScalingClickListener = new View.OnClickListener() { @Override public void onClick(View v) { show(); switch (currentScalingMode) { case VideoSurfaceView.ScalingMode.FIT: currentScalingMode = VideoSurfaceView.ScalingMode.CROP; changeScalingButton.setImageResource(R.drawable.scaling_crop); break; case VideoSurfaceView.ScalingMode.CROP: currentScalingMode = VideoSurfaceView.ScalingMode.STRETCH; changeScalingButton.setImageResource(R.drawable.scaling_stretch); break; case VideoSurfaceView.ScalingMode.STRETCH: currentScalingMode = VideoSurfaceView.ScalingMode.FIT; changeScalingButton.setImageResource(R.drawable.scaling_fit); break; } if (mVideoSurfaceView != null) { mVideoSurfaceView.setScalingMode(currentScalingMode); } } }; private View.OnClickListener playPauseClickListener = new View.OnClickListener() { @Override public void onClick(View v) { doPauseResume(); show(); } }; private View.OnClickListener controlWrapperClickListener = new View.OnClickListener() { @Override public void onClick(View v) { show(); } }; private SeekBar.OnSeekBarChangeListener playerSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { long duration = mPlayerControl.getDuration(); long newPosition = (duration * progress) / 1000L; mPlayerControl.seekTo((int) newPosition); playerProgressBar.setProgress(progress); tooltipText.setText(timeToString((int) newPosition)); updateTooltipPosition(); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { show(); fadeView(true, FadingViews.TOOLTIP); currentlyDragging = true; mProgressHandler.removeMessages(ProgressHandler.Actions.SHOW_PROGRESS); } @Override public void onStopTrackingTouch(SeekBar seekBar) { currentlyDragging = false; setProgress(); updatePlayPause(); show(); fadeView(false, FadingViews.TOOLTIP); mProgressHandler.sendEmptyMessage(ProgressHandler.Actions.SHOW_PROGRESS); } }; // endregion click listeners private void updatePlayPause() { final int resId = (this.mPlayerControl.isPlaying() && !this.mPlayerControl.hasEnded() && !this.mPlayerControl.isIdling()) ? R.drawable.pause : R.drawable.play; this.playPauseButton.post(new Runnable() { @Override public void run() { playPauseButton.setImageResource(resId); } }); } private void doPauseResume() { if (this.mPlayerControl.isPlaying()) { this.mPlayerControl.pause(); } else { if (this.mPlayerControl.hasEnded()) { this.mPlayerControl.seekTo(0); } this.mPlayerControl.start(); } updatePlayPause(); } private int setProgress() { if (this.mPlayerControl == null || this.currentlyDragging) { return 0; } final int position = this.mPlayerControl.getCurrentPosition(); int duration = this.mPlayerControl.getDuration(); if (this.playerProgressBar != null) { if (duration > 0) { this.playerProgressBar.setProgress((int) (1000L * position / duration)); this.playerSeekBar.setProgress((int) (1000L * position / duration)); } this.playerProgressBar.setSecondaryProgress(this.mPlayerControl.getBufferPercentage() * 10); } this.playbackTime.setText(timeToString(position) + " / " + timeToString(duration)); updatePlayPause(); return position; } private String timeToString(int timeMs) { int totalSeconds = timeMs / 1000; int seconds = totalSeconds % 60; int minutes = (totalSeconds / 60) % 60; int hours = totalSeconds / 3600; if (hours > 0) { return String.format("%d:%02d:%02d", hours, minutes, seconds); } else { return String.format("%02d:%02d", minutes, seconds); } } private void updateTooltipPosition() { this.playerSeekBar.getLocationOnScreen(this.playerSeekBarPosition); this.tooltipWrapper.setY( this.playerSeekBarPosition[1] - this.tooltipWrapper.getHeight() + Utils.toPx(this.context, 17) ); this.tooltipWrapper.setX( this.playerSeekBarPosition[0] + Utils.toPx(this.context, 5) + (int) ( ((float) this.playerSeekBar.getProgress() / (float) this.playerSeekBar.getMax()) * ((float) this.playerSeekBar.getWidth() - Utils.toPx(this.context, 10)) ) - (this.tooltipWrapper.getWidth() / 2) ); } public void setTitle(final String titleStr) { this.title.post(new Runnable() { @Override public void run() { title.setText(titleStr); } }); } public void pause() { if (this.mPlayerControl != null && this.mPlayerControl.isPlaying()) { this.mPlayerControl.pause(); } } public void play() { if (this.mPlayerControl != null && !this.mPlayerControl.hasEnded()) { this.mPlayerControl.start(); } } @SuppressWarnings("unused") public interface MediaPlayerControl { void start(); void pause(); int getDuration(); int getCurrentPosition(); void seekTo(int pos); boolean isPlaying(); boolean hasEnded(); boolean isIdling(); int getBufferPercentage(); boolean canPause(); boolean canSeekBackward(); boolean canSeekForward(); /** * Get the audio session id for the player used by this VideoView. This can be used to * apply audio effects to the audio track of a video. * * @return The audio session, or 0 if there was an error. */ int getAudioSessionId(); } private static class ProgressHandler extends Handler { private WeakReference<ControlOverlay> mControlOverlayRef; public ProgressHandler(ControlOverlay mControlOverlay) { this.mControlOverlayRef = new WeakReference<>(mControlOverlay); } @Override public void handleMessage(Message msg) { ControlOverlay mControlOverlay = this.mControlOverlayRef.get(); switch (msg.what) { case Actions.FADE_OUT: mControlOverlay.hide(); break; case Actions.SHOW_PROGRESS: mControlOverlay.setProgress(); if (!mControlOverlay.currentlyDragging && mControlOverlay.mPlayerControl.isPlaying()) { sendMessageDelayed(obtainMessage(Actions.SHOW_PROGRESS), Settings.CONTROL_UPDATE_RATE); } break; } } public final class Actions { public static final int FADE_OUT = 0; public static final int SHOW_PROGRESS = 1; } } }