package com.distantfuture.videos.youtube;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.BounceInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.SeekBar;
import android.widget.TextView;
import com.distantfuture.videos.R;
import com.distantfuture.videos.imageutils.ToolbarIcons;
import com.distantfuture.videos.misc.AppUtils;
import com.distantfuture.videos.misc.Utils;
public class VideoPlayer {
private final int mAnimationDuration = 300;
private final int mIconSize = 32;
private View mVideoBox;
private Context mContext;
private VideoPlayerFragment mVideoFragment;
private VideoPlayerStateListener mListener;
private ImageView mMuteButton;
private TextView mSeekFlashTextView;
private TextView mTimeRemainingTextView;
private View mTopBar;
public VideoPlayer(Activity activity, int fragmentContainerResID, VideoPlayerStateListener l) {
super();
mListener = l;
mContext = activity.getApplicationContext();
// install video fragment
// will already exist if restoring Activity
mVideoFragment = (VideoPlayerFragment) activity.getFragmentManager()
.findFragmentById(fragmentContainerResID);
// hide top bar when going fullscreen
mTopBar = activity.findViewById(R.id.top_bar);
mVideoFragment.setVideoFragmentListener(new VideoPlayerFragment.VideoFragmentListener() {
@Override
public void onFullScreen(boolean fullscreen) {
mTopBar.setVisibility(fullscreen ? View.GONE : View.VISIBLE);
}
@Override
public void playerInitialized() {
if (AppUtils.instance(mContext).alwaysPlayFullscreen())
mVideoFragment.setFullscreen(true);
// we avoid showing the view until after fullscreen is set after the player is setup
mVideoBox.setVisibility(View.VISIBLE);
}
});
mVideoBox = activity.findViewById(R.id.video_player_box);
setupToolbar();
}
public void open(final PlayerParams params) {
if (visible())
playerShown(params);
else {
Utils.vibrate(mContext);
if (AppUtils.instance(mContext).alwaysPlayFullscreen()) {
playerShown(params);
} else {
// update mute button since it could still be in mute mode
updateMuteButton();
boolean animate = Utils.isPortrait(mContext);
if (animate) {
// Initially translate off the screen so that it can be animated in from below.
mVideoBox.setTranslationY(-mVideoBox.getHeight());
mVideoBox.setAlpha(0f);
mVideoBox.setVisibility(View.VISIBLE);
mVideoBox.animate()
.translationY(0)
.alpha(1f)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setDuration(mAnimationDuration)
.withEndAction(new Runnable() {
@Override
public void run() {
playerShown(params);
}
});
} else {
mVideoBox.setVisibility(View.VISIBLE);
playerShown(params);
}
}
}
}
public PlayerParams playerParams() {
return mVideoFragment.playerParams();
}
public void close() {
if (visible()) {
Utils.vibrate(mContext);
mVideoFragment.closingPlayer();
boolean animate = Utils.isPortrait(mContext);
if (animate) {
mVideoBox.animate()
.translationYBy(-mVideoBox.getHeight())
.alpha(.3f)
.setInterpolator(new AccelerateInterpolator())
.setDuration(animate ? mAnimationDuration : 0)
.withEndAction(new Runnable() {
@Override
public void run() {
playerClosed();
}
});
} else
playerClosed();
}
}
public String title() {
return mVideoFragment.getTitle();
}
public String videoId() {
return mVideoFragment.getVideoId();
}
public boolean visible() {
return (mVideoBox.getVisibility() == View.VISIBLE);
}
public void toggleMute() {
mVideoFragment.mute(!mVideoFragment.isMute());
updateMuteButton();
}
public void toggleFullscreen() {
mVideoFragment.setFullscreen(true);
}
public void skip(int seconds) {
mVideoFragment.seekRelativeSeconds(seconds);
}
private void updateMuteButton() {
if (mVideoFragment.isMute())
mMuteButton.setImageDrawable(ToolbarIcons.icon(mContext, ToolbarIcons.IconID.SOUND, Color.RED, mIconSize));
else
mMuteButton.setImageDrawable(ToolbarIcons.icon(mContext, ToolbarIcons.IconID.SOUND, Color.WHITE, mIconSize));
}
private void playerShown(PlayerParams playerParams) {
// action bar menu needs to update
Activity host = (Activity) mVideoBox.getContext();
if (host != null)
host.invalidateOptionsMenu();
mVideoFragment.setVideo(playerParams);
// actionbar subtitle needs a refresh when new video starts playing, so it's not just open/close events
// that need state changed messages
if (mListener != null)
mListener.stateChanged();
}
// ------------------------------------------------------------------------------------------------
// private
private void playerClosed() {
// action bar menu needs to update
Activity host = (Activity) mVideoBox.getContext();
if (host != null)
host.invalidateOptionsMenu();
mVideoBox.setVisibility(View.INVISIBLE);
// reset this so it's 00:00 next time it's shown
mTimeRemainingTextView.setText("00:00");
if (mListener != null) {
mListener.stateChanged();
}
}
private void showSeekPopupWindow(View anchorView) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View popupContentsView = inflater.inflate(R.layout.video_seek_popup, null);
final PopupWindow pw = new PopupWindow(popupContentsView, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT, true); // if false, clicks to dismiss window also get passed to views below (should be true)
// hack_alert: must set some kind of background so that clicking outside the view will dismiss the popup (known bug in Android)
pw.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
pw.setOutsideTouchable(true);
pw.setAnimationStyle(-1);
pw.showAsDropDown(anchorView);
float time = mVideoFragment.getCurrentTimeMillis();
final float duration = mVideoFragment.getDurationMillis();
float currentPercent = time / duration;
int startValue = (int) (currentPercent * 100);
SeekBar sb = (SeekBar) popupContentsView.findViewById(R.id.video_seek_bar);
sb.setMax(100); // using max like a percent
sb.setProgress(startValue);
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
float progressPercent = progress / 100.0f;
int seekTo = (int) (progressPercent * duration);
mTimeRemainingTextView.setText(Utils.millisecondsToDuration(seekTo));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// seek when released
float progressPercent = seekBar.getProgress() / 100.0f;
int seekTo = (int) (progressPercent * duration);
mVideoFragment.seekToMillis(seekTo);
pw.dismiss();
}
});
}
private void setupToolbar() {
ImageView b;
// close button
b = (ImageView) mVideoBox.findViewById(R.id.close_button);
b.setImageDrawable(ToolbarIcons.icon(mContext, ToolbarIcons.IconID.CLOSE, Color.WHITE, mIconSize));
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
close();
}
});
// Mute button
mMuteButton = (ImageView) mVideoBox.findViewById(R.id.mute_button);
mMuteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleMute();
}
});
updateMuteButton();
// Skip back button
b = (ImageView) mVideoBox.findViewById(R.id.skip_back_button);
b.setImageDrawable(ToolbarIcons.icon(mContext, ToolbarIcons.IconID.STEP_BACK, Color.WHITE, mIconSize));
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
skip(-10);
}
});
// Skip ahead button
b = (ImageView) mVideoBox.findViewById(R.id.skip_ahead_button);
b.setImageDrawable(ToolbarIcons.icon(mContext, ToolbarIcons.IconID.STEP_FORWARD, Color.WHITE, mIconSize));
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
skip(10);
}
});
mTimeRemainingTextView = (TextView) mVideoBox.findViewById(R.id.time_remaining);
mTimeRemainingTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// put it below toolbar
View tb = mVideoBox.findViewById(R.id.video_toolbar_view);
showSeekPopupWindow(tb);
}
});
mSeekFlashTextView = (TextView) mVideoBox.findViewById(R.id.seek_flash);
// we let the video fragment update us in it's own timer
mVideoFragment.setTimeRemainingListener(new VideoPlayerFragment.TimeRemainingListener() {
// call this on the main thread
@Override
public void setTimeRemainingText(final String timeRemaining) {
mTimeRemainingTextView.setText(timeRemaining);
}
@Override
public void setSeekFlashText(final String seekFlash) {
mSeekFlashTextView.setText(seekFlash);
int duration = 300;
// fade old one out
mTimeRemainingTextView.animate().setDuration(duration).alpha(0.0f);
// start off off the screen, make visible
mSeekFlashTextView.setTranslationY(-60.0f);
mSeekFlashTextView.setVisibility(View.VISIBLE);
// run animation, new time slides in from top, old time slides off
mSeekFlashTextView.animate()
.setDuration(duration)
.translationY(0)
.setInterpolator(new BounceInterpolator())
.withEndAction(new Runnable() {
@Override
public void run() {
mSeekFlashTextView.setVisibility(View.INVISIBLE);
mTimeRemainingTextView.setText(seekFlash);
mTimeRemainingTextView.setAlpha(1.0f);
}
});
}
});
}
abstract public interface VideoPlayerStateListener {
abstract public void stateChanged();
}
public static class PlayerParams implements Parcelable {
public static final Parcelable.Creator<PlayerParams> CREATOR = new Parcelable.Creator<PlayerParams>() {
public PlayerParams createFromParcel(Parcel in) {
return new PlayerParams(in);
}
public PlayerParams[] newArray(int size) {
return new PlayerParams[size];
}
};
public final String videoId;
public final String title;
public final int index; // used for the play_next feature if enabled
public PlayerParams(String videoId, String title, int index) {
this.videoId = videoId;
this.title = title;
this.index = index;
}
// ===================================================================
// Parcelable - we send this to the service inside an intent
private PlayerParams(Parcel in) {
videoId = in.readString();
title = in.readString();
index = in.readInt();
}
public boolean equals(PlayerParams inParams) {
if (inParams == null)
return false;
if (index != inParams.index)
return false;
if (!videoId.equals(inParams.videoId))
return false;
if (!title.equals(inParams.title))
return false;
return true;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(videoId);
dest.writeString(title);
dest.writeInt(index);
}
}
}