package io.github.xwz.base.views; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Point; import android.os.Handler; import android.support.v17.leanback.widget.BaseCardView; import android.support.v17.leanback.widget.ImageCardView; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceView; import android.view.View; import android.view.accessibility.CaptioningManager; import android.widget.MediaController; import android.widget.TextView; import com.google.android.exoplayer.AspectRatioFrameLayout; import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.text.CaptionStyleCompat; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.SubtitleLayout; import com.google.android.exoplayer.util.DebugTextViewHelper; import com.google.android.exoplayer.util.Util; import java.util.Arrays; import java.util.List; import io.github.xwz.base.R; import io.github.xwz.base.api.EpisodeBaseModel; import io.github.xwz.base.player.VideoPlayer; public class VideoPlayerView { private static final String TAG = "VideoPlayerView"; private final View shutterView; private final AspectRatioFrameLayout videoFrame; private final SurfaceView surfaceView; private final TextView debugTextView; private final TextView playerStateTextView; private final View debugView; private final TextView statusTextView; private final SubtitleLayout subtitleLayout; private final EpisodeCardView nextEpisode; private final View nextEpisodeCard; private final View episodeDetails; private final TextView episodeTitle; private final TextView seriesTitle; private final TextView duration; private DebugTextViewHelper debugViewHelper; private final Context mContext; private final PlaybackControls mediaController; private MediaController.MediaPlayerControl mPlayer; private static final List<Integer> PLAY_PAUSE_EVENTS = Arrays.asList( KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_STOP, KeyEvent.KEYCODE_DPAD_CENTER ); private static final List<Integer> PLAYER_EVENTS = Arrays.asList( KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_MUTE, KeyEvent.KEYCODE_CAMERA ); public VideoPlayerView(Context context, PlaybackControls controller, View root) { mContext = context; mediaController = controller; shutterView = root.findViewById(R.id.shutter); videoFrame = (AspectRatioFrameLayout) root.findViewById(R.id.video_frame); surfaceView = (SurfaceView) root.findViewById(R.id.surface_view); debugTextView = (TextView) root.findViewById(R.id.debug_text_view); debugView = root.findViewById(R.id.debug_view); statusTextView = (TextView) root.findViewById(R.id.status); playerStateTextView = (TextView) root.findViewById(R.id.player_state_view); subtitleLayout = (SubtitleLayout) root.findViewById(R.id.subtitles); nextEpisodeCard = root.findViewById(R.id.next_episode_card); episodeDetails = root.findViewById(R.id.episode_details); episodeTitle = (TextView) root.findViewById(R.id.episode_title); seriesTitle = (TextView) root.findViewById(R.id.series_title); duration = (TextView) root.findViewById(R.id.duration); ImageCardView card = (ImageCardView) root.findViewById(R.id.next_episode); card.setFocusable(true); card.setFocusableInTouchMode(true); card.setInfoVisibility(View.VISIBLE); card.setExtraVisibility(View.VISIBLE); card.setInfoAreaBackgroundColor(context.getResources().getColor(R.color.black_900)); Point size = new Point(context.getResources().getDimensionPixelSize(R.dimen.card_width), context.getResources().getDimensionPixelSize(R.dimen.card_height)); nextEpisode = new EpisodeCardView(context, card, size, false); nextEpisode.getImageCardView().setCardType(BaseCardView.CARD_TYPE_INFO_OVER); debugView.setVisibility(View.GONE); root.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { return handleTouchEvents(view, motionEvent); } }); root.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { return handleKeyEvents(v, keyCode, event); } }); } private boolean handleKeyEvents(View v, int keyCode, KeyEvent event) { final boolean uniqueDown = event.getRepeatCount() == 0 && event.getAction() == KeyEvent.ACTION_DOWN; if (uniqueDown) { Log.d(TAG, "keyCode:" + keyCode + ", event:" + event); } if (PLAY_PAUSE_EVENTS.contains(keyCode) && uniqueDown) { doPauseResume(); return true; } else if (PLAYER_EVENTS.contains(keyCode)) { return mediaController.dispatchKeyEvent(event, 0); } return false; } private boolean handleTouchEvents(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { toggleControlsVisibility(); } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) { view.performClick(); } return true; } private void doPauseResume() { Log.d(TAG, "doPauseResume:" + mPlayer); if (mPlayer != null) { if (mPlayer.isPlaying()) { mPlayer.pause(); showControls(); } else { mPlayer.start(); hideControlsDelayed(); } } } public void setEpisode(EpisodeBaseModel episode) { episodeTitle.setText(episode.getTitle()); seriesTitle.setText(episode.getSeriesTitle()); duration.setText(episode.getDurationText()); showShutter(true); showEpisodeDetails(); } public void setMediaPlayer(MediaController.MediaPlayerControl player) { mPlayer = player; } public void resetView() { nextEpisodeCard.setVisibility(View.GONE); } public void setVideoFrameAspectRatio(float ratio) { videoFrame.setAspectRatio(ratio); } public void showShutter(boolean show) { shutterView.setVisibility(show ? View.VISIBLE : View.GONE); } public void setCues(List<Cue> cues) { subtitleLayout.setCues(cues); } public void startDebugView(VideoPlayer player) { debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper.start(); } public void stopDebugView() { debugViewHelper.stop(); debugViewHelper = null; } private void toggleControlsVisibility() { if (mediaController.isShowing()) { hideControls(); } else { showControls(); } } public void showControls() { Log.d(TAG, "Show controls"); mediaController.show(0); showEpisodeDetails(); } private void showEpisodeDetails() { episodeDetails.setVisibility(View.VISIBLE); } private void hideControls() { Log.d(TAG, "Hide controls"); mediaController.hide(); episodeDetails.setVisibility(View.GONE); } public void configureSubtitleView() { CaptionStyleCompat captionStyle; float captionFontScale; if (Util.SDK_INT >= 19) { captionStyle = getUserCaptionStyleV19(); captionFontScale = getUserCaptionFontScaleV19(); } else { captionStyle = CaptionStyleCompat.DEFAULT; captionFontScale = 1.0f; } subtitleLayout.setStyle(captionStyle); subtitleLayout.setFontScale(captionFontScale); } public Surface getVideoSurface() { return surfaceView.getHolder().getSurface(); } public void onStateChanged(boolean playWhenReady, int playbackState) { if (playbackState == ExoPlayer.STATE_ENDED) { showControls(); } String text = "playWhenReady=" + playWhenReady + ", playbackState="; switch (playbackState) { case ExoPlayer.STATE_BUFFERING: text += "buffering"; showStatusText(R.string.buffering); break; case ExoPlayer.STATE_ENDED: text += "ended"; break; case ExoPlayer.STATE_IDLE: text += "idle"; break; case ExoPlayer.STATE_PREPARING: text += "preparing"; showStatusText(R.string.loading); break; case ExoPlayer.STATE_READY: text += "ready"; hideStatusText(); showShutter(false); if (mPlayer != null && mPlayer.isPlaying()) { hideControlsDelayed(); } break; default: text += "unknown"; break; } playerStateTextView.setText(text); } private void hideControlsDelayed() { final Handler handler = new Handler(); Log.d(TAG, "hideControlsDelayed"); handler.postDelayed(new Runnable() { @Override public void run() { hideControls(); } }, 3000); } public void suggestNextEpisode(EpisodeBaseModel episode) { nextEpisode.setEpisode(episode); nextEpisodeCard.setVisibility(View.VISIBLE); } private void showStatusText(int resId) { showStatusText(mContext.getResources().getString(resId)); } private void hideStatusText() { statusTextView.setVisibility(View.GONE); } public void showStatusText(String text) { showShutter(false); statusTextView.setVisibility(View.VISIBLE); statusTextView.setText(text); } @TargetApi(19) private float getUserCaptionFontScaleV19() { CaptioningManager captioningManager = (CaptioningManager) mContext.getSystemService(Context.CAPTIONING_SERVICE); return captioningManager.getFontScale(); } @TargetApi(19) private CaptionStyleCompat getUserCaptionStyleV19() { CaptioningManager captioningManager = (CaptioningManager) mContext.getSystemService(Context.CAPTIONING_SERVICE); return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); } }