package com.etiennelawlor.loop.fragments;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.MediaRouteButton;
import android.support.v7.view.ContextThemeWrapper;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.etiennelawlor.loop.LoopApplication;
import com.etiennelawlor.loop.R;
import com.etiennelawlor.loop.models.AccessToken;
import com.etiennelawlor.loop.models.VideoSavedState;
import com.etiennelawlor.loop.network.ServiceGenerator;
import com.etiennelawlor.loop.network.VimeoPlayerService;
import com.etiennelawlor.loop.network.interceptors.AuthorizedNetworkInterceptor;
import com.etiennelawlor.loop.network.models.response.Video;
import com.etiennelawlor.loop.network.models.response.VideoConfig;
import com.etiennelawlor.loop.prefs.LoopPrefs;
import com.etiennelawlor.loop.utilities.FontCache;
import com.etiennelawlor.loop.utilities.NetworkLogUtility;
import com.etiennelawlor.loop.utilities.NetworkUtility;
import com.etiennelawlor.loop.utilities.TrestleUtility;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManagerListener;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import com.google.android.gms.common.images.WebImage;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
/**
* Created by etiennelawlor on 1/3/17.
*/
public class VideoDetailsFragment extends BaseFragment {
// ExoPlayer Guide
// https://google.github.io/ExoPlayer/guide.html
// https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/ui/SimpleExoPlayerView.html
// region Constants
private static final int VIDEO_SHARE_REQUEST_CODE = 1002;
public static final String KEY_VIDEO_ID = "KEY_VIDEO_ID";
public static final String KEY_VIDEO = "KEY_VIDEO";
// endregion
// region Views
@BindView(R.id.toolbar)
Toolbar toolbar;
@Nullable
@BindView(R.id.viewpager)
ViewPager viewPager;
@Nullable
@BindView(R.id.tabs)
TabLayout tabLayout;
@BindView(R.id.sepv)
SimpleExoPlayerView simpleExoPlayerView;
// @BindView(R.id.loading_iv)
// LoadingImageView loadingImageView;
// @BindView(R.id.error_ll)
// LinearLayout errorLinearLayout;
// @BindView(R.id.error_tv)
// TextView errorTextView;
@BindView(R.id.exo_thumbnail)
ImageView thumbnailImageView;
@BindView(R.id.cast_info_tv)
TextView castInfoTextView;
@BindView(R.id.exo_shutter)
View shutterView;
@BindView(R.id.exo_progress)
SeekBar exoSeekBar;
@BindView(R.id.exo_play)
ImageButton exoPlayButton;
@BindView(R.id.exo_pause)
ImageButton exoPauseButton;
@BindView(R.id.exo_replay)
ImageButton replayImageButton;
@BindView(R.id.exo_btns_fl)
FrameLayout exoButtonsFrameLayout;
@BindView(R.id.control_view_ll)
LinearLayout controlViewLinearLayout;
@BindView(R.id.mrb)
MediaRouteButton mediaRouteButton;
@BindView(R.id.exo_content_frame)
AspectRatioFrameLayout aspectRatioFrameLayout;
// endregion
// region Member Variables
private Video video;
private String videoUrl;
private VimeoPlayerService vimeoPlayerService;
private VideoSavedState videoSavedState;
private Unbinder unbinder;
private Typeface font;
private SimpleExoPlayer exoPlayer;
private CastContext castContext;
private PlaybackLocation location;
private CastSession castSession;
private PlaybackState playbackState;
private boolean isLocalVideoPrepared = false;
private long currentPosition = 0;
private SessionManagerListener<CastSession> sessionManagerListener;
// endregion
// region Listeners
@OnClick(R.id.exo_play)
public void onPlayButtonClicked(){
playbackState = PlaybackState.PLAYING;
exoPlayButton.setVisibility(View.GONE);
exoPauseButton.setVisibility(View.VISIBLE);
resumeLocalVideo();
if(location == PlaybackLocation.REMOTE){
resumeRemoteVideo();
castInfoTextView.setText(String.format("Casting to %s", castSession.getCastDevice().getFriendlyName()));
}
}
@OnClick(R.id.exo_pause)
public void onPauseButtonClicked(){
playbackState = PlaybackState.PAUSED;
exoPauseButton.setVisibility(View.GONE);
exoPlayButton.setVisibility(View.VISIBLE);
pauseLocalVideo();
if(location == PlaybackLocation.REMOTE){
pauseRemoteVideo();
castInfoTextView.setText(getString(R.string.cast_is_paused));
}
}
@OnClick(R.id.exo_replay)
public void onReplayButtonClicked() {
replayImageButton.setVisibility(View.GONE);
exoButtonsFrameLayout.setVisibility(View.VISIBLE);
switch (location){
case LOCAL:
updateLocalVideoPosition(0);
break;
case REMOTE:
updateLocalVideoPosition(0);
pauseLocalVideo();
playRemoteVideo(0, true);
break;
default:
break;
}
}
// @OnClick(R.id.reload_btn)
// public void onReloadButtonClicked() {
//// errorLinearLayout.setVisibility(View.GONE);
//// loadingImageView.setVisibility(View.VISIBLE);
//
// Call getVideoConfigCall = vimeoPlayerService.getVideoConfig(videoId);
// calls.add(getVideoConfigCall);
// getVideoConfigCall.enqueue(getVideoConfigCallback);
// }
private ExoPlayer.EventListener exoPlayerEventListener = new ExoPlayer.EventListener() {
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
}
@Override
public void onLoadingChanged(boolean isLoading) {
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
switch (playbackState){
case ExoPlayer.STATE_BUFFERING:
case ExoPlayer.STATE_READY:
replayImageButton.setVisibility(View.GONE);
exoButtonsFrameLayout.setVisibility(View.VISIBLE);
break;
case ExoPlayer.STATE_ENDED:
exoButtonsFrameLayout.setVisibility(View.GONE);
replayImageButton.setVisibility(View.VISIBLE);
break;
default:
break;
}
}
@Override
public void onPlayerError(ExoPlaybackException error) {
}
@Override
public void onPositionDiscontinuity() {
}
};
private PlaybackControlView.VisibilityListener playbackControlViewVisibilityListener = new PlaybackControlView.VisibilityListener() {
@Override
public void onVisibilityChange(int visibility) {
int orientation = getContext().getResources().getConfiguration().orientation;
switch (orientation){
case ORIENTATION_PORTRAIT:
if(visibility == View.GONE){
toolbar.animate().alpha(0.0f).setDuration(300);
hidePortraitSystemUI();
} else {
showPortraitSystemUI();
toolbar.animate().alpha(1.0f).setDuration(300);
}
break;
case ORIENTATION_LANDSCAPE:
if(visibility == View.GONE){
toolbar.animate().alpha(0.0f).setDuration(300);
hideLandscapeSystemUI();
} else {
showLandscapeSystemUI();
toolbar.animate().alpha(1.0f).setDuration(300);
}
break;
default:
break;
}
}
};
private SeekBar.OnSeekBarChangeListener seekBarOnSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(fromUser){
currentPosition = (long)(exoPlayer.getDuration() * (progress/1000.0D));
switch (location) {
case LOCAL:
updateLocalVideoPosition(currentPosition);
break;
case REMOTE:
updateLocalVideoPosition(currentPosition);
pauseLocalVideo();
castSession.getRemoteMediaClient().addListener(new RemoteMediaClient.Listener() {
@Override
public void onStatusUpdated() {
if(isResumed()){
if(isRemoteVideoPlaying()){
playbackState = PlaybackState.PLAYING;
resumeLocalVideo();
castInfoTextView.setText(String.format("Casting to %s", castSession.getCastDevice().getFriendlyName()));
} else {
// playbackState = PlaybackState.PAUSED;
pauseLocalVideo();
// castInfoTextView.setText(getString(R.string.cast_is_paused));
if(playbackState == PlaybackState.PLAYING){
castInfoTextView.setText(getString(R.string.cast_is_loading));
}
}
castInfoTextView.setVisibility(View.VISIBLE);
}
}
@Override
public void onMetadataUpdated() {
}
@Override
public void onQueueStatusUpdated() {
}
@Override
public void onPreloadStatusUpdated() {
}
@Override
public void onSendingRemoteMediaRequest() {
}
@Override
public void onAdBreakStatusUpdated() {
}
});
updateRemoteVideoPosition(currentPosition);
break;
default:
break;
}
} else {
// playbackState = PlaybackState.PLAYING;
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
// private RemoteMediaClient.Listener remoteMediaClientListener = new RemoteMediaClient.Listener() {
// @Override
// public void onStatusUpdated() {
// Timber.d("VideoDetailsFragment : remoteMediaClientListener : onStatusUpdated()");
// if(isResumed()){
// Timber.d("VideoDetailsFragment : remoteMediaClientListener : onStatusUpdated() : isResumed()");
//
// if(isRemoteVideoPlaying()){
// playbackState = PlaybackState.PLAYING;
//
// Timber.d("VideoDetailsFragment : loadRemoteMedia() : onStatusUpdated() : isResumed() : isRemoteVideoPlaying() : getFormattedPosition(position) - %s", getFormattedPosition(currentPosition));
// Timber.d("VideoDetailsFragment : loadRemoteMedia() : onStatusUpdated() : isResumed() : isRemoteVideoPlaying() : getFormattedPosition(castSession.getRemoteMediaClient().getApproximateStreamPosition()) - %s", getFormattedPosition(castSession.getRemoteMediaClient().getApproximateStreamPosition()));
//
// updateLocalVideoVolume(0.0f);
//
// if(!isLocalVideoPrepared)
// // Prepare the player with the source.
// exoPlayer.prepare(getMediaSource(videoUrl));
//
//// currentPosition = (exoPlayer.getDuration() * (exoSeekBar.getProgress()/1000L));
//// currentPosition = exoPlayer.getCurrentPosition();
// currentPosition = castSession.getRemoteMediaClient().getApproximateStreamPosition();
//
// updateLocalVideoPosition(currentPosition);
// resumeLocalVideo();
//
//// remoteMediaClient.removeListener(this);
// castInfoTextView.setText(String.format("Casting to %s", castSession.getCastDevice().getFriendlyName()));
//// remoteMediaClient.removeListener(this);
// } else {
//// playbackState = PlaybackState.PAUSED;
// pauseLocalVideo();
//
//// castInfoTextView.setText(getString(R.string.cast_is_paused));
// if(playbackState == PlaybackState.PLAYING){
// castInfoTextView.setText(getString(R.string.cast_is_loading));
// }
// }
// castInfoTextView.setVisibility(View.VISIBLE);
//
//// Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
//// startActivity(intent);
//// remoteMediaClient.removeListener(this);
// }
// }
//
// @Override
// public void onMetadataUpdated() {
// Timber.d("VideoDetailsFragment : remoteMediaClientListener : onMetadataUpdated()");
// }
//
// @Override
// public void onQueueStatusUpdated() {
// Timber.d("VideoDetailsFragment : remoteMediaClientListener : onQueueStatusUpdated()");
// }
//
// @Override
// public void onPreloadStatusUpdated() {
// Timber.d("VideoDetailsFragment : remoteMediaClientListener : onPreloadStatusUpdated()");
// }
//
// @Override
// public void onSendingRemoteMediaRequest() {
// Timber.d("VideoDetailsFragment : remoteMediaClientListener : onSendingRemoteMediaRequest()");
// }
//
// @Override
// public void onAdBreakStatusUpdated() {
// Timber.d("VideoDetailsFragment : remoteMediaClientListener : onAdBreakStatusUpdated()");
// }
// };
// endregion
// region Callbacks
private Callback<VideoConfig> getVideoConfigCallback = new Callback<VideoConfig>() {
@Override
public void onResponse(Call<VideoConfig> call, Response<VideoConfig> response) {
// loadingImageView.setVisibility(View.GONE);
if (!response.isSuccessful()) {
int responseCode = response.code();
switch (responseCode){
case 504: // 504 Unsatisfiable Request (only-if-cached)
// errorTextView.setText("Can't load data.\nCheck your network connection.");
// errorLinearLayout.setVisibility(View.VISIBLE);
break;
case 403: // Forbidden
Snackbar.make(getActivity().findViewById(R.id.main_content),
TrestleUtility.getFormattedText("Cannot play this video.", font, 16),
Snackbar.LENGTH_LONG)
.show();
break;
}
return;
}
VideoConfig videoConfig = response.body();
if (videoConfig != null) {
videoUrl = videoConfig.getVideoUrl();
if (!TextUtils.isEmpty(videoUrl)) {
playVideo(0);
}
}
}
@Override
public void onFailure(Call<VideoConfig> call, Throwable t) {
NetworkLogUtility.logFailure(call, t);
if (!call.isCanceled()){
// loadingImageView.setVisibility(View.GONE);
if(NetworkUtility.isKnownException(t)){
// errorTextView.setText("Can't load data.\nCheck your network connection.");
// errorLinearLayout.setVisibility(View.VISIBLE);
}
}
}
};
// endregion
// region Constructors
public VideoDetailsFragment() {
}
// endregion
// region Factory Methods
public static VideoDetailsFragment newInstance(Bundle extras) {
VideoDetailsFragment fragment = new VideoDetailsFragment();
fragment.setArguments(extras);
return fragment;
}
public static VideoDetailsFragment newInstance() {
VideoDetailsFragment fragment = new VideoDetailsFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
// endregion
// region Lifecycle Methods
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// // Retain this fragment across configuration changes.
setRetainInstance(true);
if (getArguments() != null) {
video = (Video) getArguments().get(LikedVideosFragment.KEY_VIDEO);
// mTransitionName = getArguments().getString("TRANSITION_KEY");
}
AccessToken token = LoopPrefs.getAccessToken(getActivity());
vimeoPlayerService = ServiceGenerator.createService(
VimeoPlayerService.class,
VimeoPlayerService.BASE_URL,
new AuthorizedNetworkInterceptor(token));
setHasOptionsMenu(true);
font = FontCache.getTypeface("Ubuntu-Medium.ttf", getContext());
castContext = CastContext.getSharedInstance(getContext());
castSession = castContext.getSessionManager().getCurrentCastSession();
playbackState = PlaybackState.PLAYING;
if (castSession != null && castSession.isConnected()) {
location = PlaybackLocation.REMOTE;
} else {
location = PlaybackLocation.LOCAL;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_video_details, container, false);
unbinder = ButterKnife.bind(this, rootView);
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle("");
}
if (video != null) {
setUpExoPlayer();
setUpSimpleExoPlayerView();
setUpThumbnail();
setUpCastButton();
exoSeekBar.setOnSeekBarChangeListener(seekBarOnSeekBarChangeListener);
setupCastListener();
}
int orientation = view.getResources().getConfiguration().orientation;
switch (orientation){
case ORIENTATION_PORTRAIT:
if (viewPager != null) {
setupViewPager(viewPager);
}
tabLayout.setupWithViewPager(viewPager);
tabLayout.setTabMode(TabLayout.MODE_FIXED);
updateTabLayout();
showPortraitSystemUI();
break;
case ORIENTATION_LANDSCAPE:
showLandscapeSystemUI();
break;
default:
break;
}
VideoSavedState videoSavedState = getVideoSavedState();
if(videoSavedState != null && !TextUtils.isEmpty(videoSavedState.getVideoUrl())){
// loadingImageView.setVisibility(View.GONE);
long currentPosition = videoSavedState.getCurrentPosition();
videoUrl = videoSavedState.getVideoUrl();
playbackState = videoSavedState.getPlaybackState();
castInfoTextView.setText(videoSavedState.getCastInfo());
castInfoTextView.setVisibility(View.VISIBLE);
boolean autoPlay = false;
switch (playbackState){
case PLAYING:
autoPlay = true;
break;
case PAUSED:
autoPlay = false;
break;
default:
break;
}
switch (location) {
case LOCAL:
playLocalVideo(currentPosition, autoPlay);
updateLocalVideoVolume(videoSavedState.getCurrentVolume());
break;
case REMOTE:
playLocalVideo(castSession.getRemoteMediaClient().getApproximateStreamPosition(), autoPlay);
updateLocalVideoVolume(videoSavedState.getCurrentVolume());
break;
default:
break;
}
} else {
Call getVideoConfigCall = vimeoPlayerService.getVideoConfig(video.getId());
calls.add(getVideoConfigCall);
getVideoConfigCall.enqueue(getVideoConfigCallback);
}
}
@Override
public void onResume() {
super.onResume();
castContext.getSessionManager().addSessionManagerListener(
sessionManagerListener, CastSession.class);
if (castSession != null && castSession.isConnected()) {
location = PlaybackLocation.REMOTE;
} else {
location = PlaybackLocation.LOCAL;
}
if(!isLocalVideoPlaying() && playbackState == PlaybackState.PLAYING) {
switch (location){
case LOCAL:
resumeLocalVideo();
break;
case REMOTE:
updateLocalVideoPosition(castSession.getRemoteMediaClient().getApproximateStreamPosition());
resumeLocalVideo();
break;
default:
break;
}
}
}
@Override
public void onPause() {
super.onPause();
castContext.getSessionManager().removeSessionManagerListener(
sessionManagerListener, CastSession.class);
switch (location) {
case LOCAL:
pauseLocalVideo();
break;
case REMOTE:
break;
default:
break;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
if(!TextUtils.isEmpty(videoUrl)){
VideoSavedState videoSavedState = new VideoSavedState();
videoSavedState.setVideoUrl(videoUrl);
videoSavedState.setCurrentPosition(exoPlayer.getCurrentPosition());
videoSavedState.setCurrentVolume(exoPlayer.getVolume());
videoSavedState.setPlaybackState(playbackState);
videoSavedState.setCastInfo(castInfoTextView.getText().toString());
setVideoSavedState(videoSavedState);
}
removeListeners();
unbinder.unbind();
}
@Override
public void onDestroy() {
super.onDestroy();
exoPlayer.release();
}
// endregion
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.video_details_menu, menu);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.share:
if (video != null) {
// EventLogger.fire(ProductShareEvent.start(mProduct.getId()));
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra(Intent.EXTRA_TEXT,
String.format("I found this on Loop. Check it out.\n\n%s\n\n%s", video.getName(), video.getLink()));
String title = getResources().getString(R.string.share_this_video);
Intent chooser = Intent.createChooser(sendIntent, title);
if (sendIntent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivityForResult(chooser, VIDEO_SHARE_REQUEST_CODE);
}
}
return true;
default:
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case VIDEO_SHARE_REQUEST_CODE:
if (resultCode == Activity.RESULT_OK) {
if (video != null) {
// EventLogger.fire(ProductShareEvent.submit(mProduct.getId()));
}
} else if (resultCode == Activity.RESULT_CANCELED) {
}
break;
default:
break;
}
}
// region Helper Methods
private void setUpExoPlayer(){
// Create a default TrackSelector
TrackSelector trackSelector = createTrackSelector();
// Create a default LoadControl
LoadControl loadControl = new DefaultLoadControl();
// Create the player
exoPlayer = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, loadControl);
resumeLocalVideo();
exoPlayer.addListener(exoPlayerEventListener);
}
private void setUpThumbnail() {
String thumbnailUrl = video.getThumbnailUrl();
if (!TextUtils.isEmpty(thumbnailUrl)) {
Glide.with(getActivity())
.load(thumbnailUrl)
// .placeholder(R.drawable.ic_placeholder)
// .error(R.drawable.ic_error)
.into(thumbnailImageView);
}
}
private void setUpSimpleExoPlayerView(){
simpleExoPlayerView.setPlayer(exoPlayer);
simpleExoPlayerView.setControllerVisibilityListener(playbackControlViewVisibilityListener);
}
// This snippet hides the system bars.
private void hidePortraitSystemUI() {
final View decorView = getActivity().getWindow().getDecorView();
// Set the IMMERSIVE flag.
// Set the content to appear under the system bars so that the content
// doesn't resize when the system bars hide and show.
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_IMMERSIVE);
}
// This snippet shows the system bars. It does this by removing all the flags
// except for the ones that make the content appear under the system bars.
private void showPortraitSystemUI() {
final View decorView = getActivity().getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
// This snippet hides the system bars.
private void hideLandscapeSystemUI() {
final View decorView = getActivity().getWindow().getDecorView();
// Set the IMMERSIVE flag.
// Set the content to appear under the system bars so that the content
// doesn't resize when the system bars hide and show.
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_IMMERSIVE);
}
// This snippet shows the system bars. It does this by removing all the flags
// except for the ones that make the content appear under the system bars.
private void showLandscapeSystemUI() {
final View decorView = getActivity().getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
public void setVideoSavedState(VideoSavedState videoSavedState) {
this.videoSavedState = videoSavedState;
}
public VideoSavedState getVideoSavedState() {
return videoSavedState;
}
private TrackSelector createTrackSelector(){
// Create a default TrackSelector
// Measures bandwidth during playback. Can be null if not required.
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
new DefaultTrackSelector(videoTrackSelectionFactory);
return trackSelector;
}
private MediaSource getMediaSource(String videoUrl){
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Produces DataSource instances through which media data is loaded.
// DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getContext(), Util.getUserAgent(getContext(), "Loop"), bandwidthMeter);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(LoopApplication.getInstance().getApplicationContext(), Util.getUserAgent(LoopApplication.getInstance().getApplicationContext(), "Loop"), bandwidthMeter);
// Produces Extractor instances for parsing the media data.
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
// This is the MediaSource representing the media to be played.
MediaSource mediaSource = new ExtractorMediaSource(Uri.parse(videoUrl),
dataSourceFactory, extractorsFactory, null, null);
// Loops the video indefinitely.
// LoopingMediaSource loopingSource = new LoopingMediaSource(mediaSource);
return mediaSource;
}
private void removeListeners() {
exoPlayer.removeListener(exoPlayerEventListener);
simpleExoPlayerView.setControllerVisibilityListener(null);
exoSeekBar.setOnSeekBarChangeListener(null);
}
private void setupViewPager(ViewPager viewPager) {
Adapter adapter = new Adapter(getChildFragmentManager());
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_VIDEO, video);
VideoDetailsInfoFragment videoDetailsInfoFragment = VideoDetailsInfoFragment.newInstance(bundle);
adapter.addFragment(videoDetailsInfoFragment, getString(R.string.info));
Bundle bundle2 = new Bundle();
bundle2.putLong(KEY_VIDEO_ID, video.getId());
RelatedVideosFragment relatedVideosFragment = RelatedVideosFragment.newInstance(bundle2);
adapter.addFragment(relatedVideosFragment, getString(R.string.related_videos));
viewPager.setAdapter(adapter);
}
private void updateTabLayout(){
ViewGroup vg = (ViewGroup) tabLayout.getChildAt(0);
int tabsCount = vg.getChildCount();
for (int j = 0; j < tabsCount; j++) {
ViewGroup vgTab = (ViewGroup) vg.getChildAt(j);
int tabChildsCount = vgTab.getChildCount();
for (int i = 0; i < tabChildsCount; i++) {
View tabViewChild = vgTab.getChildAt(i);
if (tabViewChild instanceof TextView) {
((TextView) tabViewChild).setTypeface(font);
}
}
}
}
private void setUpCastButton(){
Drawable remoteIndicatorDrawable = getRemoteIndicatorDrawable();
DrawableCompat.setTint(remoteIndicatorDrawable, ContextCompat.getColor(getContext(), android.R.color.white));
mediaRouteButton.setRemoteIndicatorDrawable(remoteIndicatorDrawable);
CastButtonFactory.setUpMediaRouteButton(getContext().getApplicationContext(), mediaRouteButton);
}
private Drawable getRemoteIndicatorDrawable(){
Context castContext = new ContextThemeWrapper(getContext(), android.support.v7.mediarouter.R.style.Theme_MediaRouter);
TypedArray a = castContext.obtainStyledAttributes(null,
android.support.v7.mediarouter.R.styleable.MediaRouteButton, android.support.v7.mediarouter.R.attr.mediaRouteButtonStyle, 0);
Drawable remoteIndicatorDrawable = a.getDrawable(
android.support.v7.mediarouter.R.styleable.MediaRouteButton_externalRouteEnabledDrawable);
a.recycle();
return remoteIndicatorDrawable;
}
private void setupCastListener() {
sessionManagerListener = new SessionManagerListener<CastSession>() {
@Override
public void onSessionEnded(CastSession session, int error) {
onApplicationDisconnected();
}
@Override
public void onSessionResumed(CastSession session, boolean wasSuspended) {
onApplicationConnected(session);
}
@Override
public void onSessionResumeFailed(CastSession session, int error) {
onApplicationDisconnected();
}
@Override
public void onSessionStarted(CastSession session, String sessionId) {
onApplicationConnected(session);
}
@Override
public void onSessionStartFailed(CastSession session, int error) {
onApplicationDisconnected();
}
@Override
public void onSessionStarting(CastSession session) {
}
@Override
public void onSessionEnding(CastSession session) {
}
@Override
public void onSessionResuming(CastSession session, String sessionId) {
}
@Override
public void onSessionSuspended(CastSession session, int reason) {
}
private void onApplicationConnected(CastSession session) {
castSession = session;
// castSession.getRemoteMediaClient().addListener(remoteMediaClientListener);
location = PlaybackLocation.REMOTE;
pauseLocalVideo();
thumbnailImageView.animate().alpha(1.0f).setDuration(1000);
castInfoTextView.setText(getString(R.string.connecting_to_receiver));
castInfoTextView.setVisibility(View.VISIBLE);
if(!TextUtils.isEmpty(videoUrl)){
if (playbackState == PlaybackState.PLAYING) {
loadRemoteMedia(exoPlayer.getCurrentPosition(), true);
} else if(playbackState == PlaybackState.PAUSED){
loadRemoteMedia(exoPlayer.getCurrentPosition(), false);
}
// else {
// playbackState = PlaybackState.IDLE;
// }
}
}
private void onApplicationDisconnected() {
castSession = null;
location = PlaybackLocation.LOCAL;
// playbackState = PlaybackState.IDLE;
thumbnailImageView.animate().alpha(0.0f).setDuration(1000);
castInfoTextView.setVisibility(View.GONE);
updateLocalVideoVolume(1.0f);
}
};
}
private void loadRemoteMedia(final long position, boolean autoPlay) {
currentPosition = position;
final RemoteMediaClient remoteMediaClient = castSession.getRemoteMediaClient();
remoteMediaClient.addListener(new RemoteMediaClient.Listener() {
@Override
public void onStatusUpdated() {
if(isResumed()){
if(isRemoteVideoPlaying()) {
playbackState = PlaybackState.PLAYING;
updateLocalVideoVolume(0.0f);
if(!isLocalVideoPrepared)
// Prepare the player with the source.
exoPlayer.prepare(getMediaSource(videoUrl));
updateLocalVideoPosition(position);
resumeLocalVideo();
castInfoTextView.setText(String.format("Casting to %s", castSession.getCastDevice().getFriendlyName()));
remoteMediaClient.removeListener(this);
} else {
if(playbackState == PlaybackState.PLAYING){
castInfoTextView.setText(getString(R.string.cast_is_loading));
}
}
castInfoTextView.setVisibility(View.VISIBLE);
// Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
// startActivity(intent);
// remoteMediaClient.removeListener(this);
}
}
@Override
public void onMetadataUpdated() {
}
@Override
public void onQueueStatusUpdated() {
}
@Override
public void onPreloadStatusUpdated() {
}
@Override
public void onSendingRemoteMediaRequest() {
}
@Override
public void onAdBreakStatusUpdated() {
}
});
castSession.getRemoteMediaClient().load(buildMediaInfo(), autoPlay, position);
}
private MediaInfo buildMediaInfo() {
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, video.getUser().getName());
movieMetadata.putString(MediaMetadata.KEY_TITLE, video.getName());
movieMetadata.addImage(new WebImage(Uri.parse(video.getThumbnailUrl())));
return new MediaInfo.Builder(videoUrl)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/mp4")
.setMetadata(movieMetadata)
.setStreamDuration(exoPlayer.getDuration() * 1000)
.build();
}
private void playVideo(long position){
switch (location) {
case LOCAL:
playLocalVideo(position, true);
break;
case REMOTE:
playRemoteVideo(position, true);
break;
default:
break;
}
}
private void playLocalVideo(long position, boolean autoPlay){
if(location == PlaybackLocation.LOCAL){
thumbnailImageView.animate().alpha(0.0f).setDuration(1000);
castInfoTextView.setVisibility(View.GONE);
}
updateLocalVideoPosition(position);
// Prepare the player with the source.
exoPlayer.prepare(getMediaSource(videoUrl));
isLocalVideoPrepared = true;
if(!autoPlay)
pauseLocalVideo();
}
private void playRemoteVideo(long position, boolean autoPlay){
updateRemoteVideoPosition(position);
loadRemoteMedia(position, autoPlay);
}
private void updateVideoPosition(int position){
switch (location) {
case LOCAL:
updateLocalVideoPosition(position);
break;
case REMOTE:
updateRemoteVideoPosition(position);
break;
default:
break;
}
}
private void updateLocalVideoPosition(long position){
exoPlayer.seekTo(position);
}
private void updateRemoteVideoPosition(long position){
castSession.getRemoteMediaClient().seek(position, RemoteMediaClient.RESUME_STATE_UNCHANGED);
}
private boolean isVideoPlaying(){
switch (location) {
case LOCAL:
return isLocalVideoPlaying();
case REMOTE:
return isRemoteVideoPlaying();
default:
return false;
}
}
private boolean isLocalVideoPlaying(){
return exoPlayer.getPlayWhenReady();
}
private boolean isRemoteVideoPlaying(){
if(castSession != null){
RemoteMediaClient remoteMediaClient = castSession.getRemoteMediaClient();
return remoteMediaClient.isPlaying();
}
return false;
}
private void resumeVideo(){
switch (location) {
case LOCAL:
resumeLocalVideo();
break;
case REMOTE:
resumeRemoteVideo();
break;
default:
break;
}
}
private void resumeLocalVideo(){
exoPlayer.setPlayWhenReady(true);
}
private void resumeRemoteVideo(){
if(castSession != null){
RemoteMediaClient remoteMediaClient = castSession.getRemoteMediaClient();
if(remoteMediaClient != null){
remoteMediaClient.play();
}
}
}
private void pauseVideo(){
switch (location) {
case LOCAL:
pauseLocalVideo();
break;
case REMOTE:
pauseRemoteVideo();
break;
default:
break;
}
}
private void pauseLocalVideo(){
exoPlayer.setPlayWhenReady(false);
}
private void pauseRemoteVideo(){
if(castSession != null){
RemoteMediaClient remoteMediaClient = castSession.getRemoteMediaClient();
if(remoteMediaClient != null){
remoteMediaClient.pause();
}
}
}
private void updateVideoVolume(float volume){
switch (location) {
case LOCAL:
updateLocalVideoVolume(volume);
break;
case REMOTE:
updateRemoteVideoVolume(volume);
break;
default:
break;
}
}
private void updateLocalVideoVolume(float volume){
exoPlayer.setVolume(volume);
}
private void updateRemoteVideoVolume(float volume){
}
// endregion
// region Inner Classes
public static class Adapter extends FragmentPagerAdapter {
private final List<Fragment> fragments = new ArrayList<>();
private final List<String> fragmentTitles = new ArrayList<>();
public Adapter(FragmentManager fm) {
super(fm);
}
public void addFragment(Fragment fragment, String title) {
fragments.add(fragment);
fragmentTitles.add(title);
}
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return fragmentTitles.get(position);
}
}
/**
* indicates whether we are doing a local or a remote playback
*/
public enum PlaybackLocation {
LOCAL,
REMOTE
}
/**
* List of various states that we can be in
*/
public enum PlaybackState {
PLAYING, PAUSED, BUFFERING, IDLE
}
// endregion
}