/*
* This file is part of Popcorn Time.
*
* Popcorn Time is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Popcorn Time is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Popcorn Time. If not, see <http://www.gnu.org/licenses/>.
*/
package pct.droid.fragments;
import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import com.connectsdk.device.ConnectableDevice;
import com.connectsdk.service.capability.MediaControl;
import com.connectsdk.service.capability.MediaPlayer;
import com.connectsdk.service.capability.VolumeControl;
import com.connectsdk.service.capability.listeners.ResponseListener;
import com.connectsdk.service.command.ServiceCommandError;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.github.sv244.torrentstream.StreamStatus;
import com.github.sv244.torrentstream.Torrent;
import com.github.sv244.torrentstream.listeners.TorrentListener;
import pct.droid.R;
import pct.droid.activities.BeamPlayerActivity;
import pct.droid.activities.VideoPlayerActivity;
import pct.droid.base.beaming.BeamDeviceListener;
import pct.droid.base.beaming.BeamManager;
import pct.droid.base.beaming.BeamPlayerNotificationService;
import pct.droid.base.torrent.StreamInfo;
import pct.droid.base.utils.AnimUtils;
import pct.droid.base.utils.FragmentUtil;
import pct.droid.base.utils.PixelUtils;
import pct.droid.base.utils.VersionUtils;
import pct.droid.fragments.dialog.LoadingBeamingDialogFragment;
import pct.droid.fragments.dialog.OptionDialogFragment;
import pct.droid.widget.SeekBar;
import timber.log.Timber;
public class BeamPlayerFragment extends Fragment implements TorrentListener {
public static final int REFRESH_INTERVAL_MS = (int) TimeUnit.SECONDS.toMillis(1);
private StreamInfo mStreamInfo;
private Long mResumePosition;
private BeamPlayerActivity mActivity;
private BeamManager mBeamManager = BeamManager.getInstance(getActivity());
private MediaControl mMediaControl;
private VolumeControl mVolumeControl;
private boolean mHasVolumeControl = true, mHasSeekControl = true, mIsPlaying = false, mIsUserSeeking = false, mProcessingSeeking = false;
private int mRetries = 0;
private long mTotalTimeDuration = 0;
private Float mDownloadProgress = 0f;
private LoadingBeamingDialogFragment mLoadingDialog;
private ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(2);
private ScheduledFuture mTask;
View mRootView;
@Bind(R.id.toolbar)
Toolbar mToolbar;
@Bind(R.id.sliding_layout)
SlidingUpPanelLayout mPanel;
@Bind(R.id.play_button)
ImageButton mPlayButton;
@Bind(R.id.cover_image)
ImageView mCoverImage;
@Bind(R.id.seekbar)
SeekBar mSeekBar;
@Bind(R.id.volumebar)
SeekBar mVolumeBar;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return mRootView = inflater.inflate(R.layout.fragment_beamplayer, container, false);
}
@Override
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
ButterKnife.bind(this, v);
mToolbar.getBackground().setAlpha(0);
mToolbar.setNavigationIcon(R.drawable.abc_ic_clear_mtrl_alpha);
mSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
mVolumeBar.setOnSeekBarChangeListener(mVolumeBarChangeListener);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mActivity = (BeamPlayerActivity) getActivity();
mActivity.setSupportActionBar(mToolbar);
mLoadingDialog = LoadingBeamingDialogFragment.newInstance();
mLoadingDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
closePlayer();
}
});
mLoadingDialog.show(getChildFragmentManager(), "overlay_fragment");
mResumePosition = mActivity.getResumePosition();
mStreamInfo = mActivity.getInfo();
int paletteColor = mStreamInfo.getPaletteColor();
if (paletteColor == -1) {
paletteColor = getResources().getColor(R.color.primary);
}
LayerDrawable progressDrawable;
LayerDrawable volumeDrawable;
if (!VersionUtils.isLollipop()) {
progressDrawable = (LayerDrawable) getResources().getDrawable(R.drawable.scrubber_progress_horizontal_bigtrack);
volumeDrawable = (LayerDrawable) getResources().getDrawable(R.drawable.scrubber_progress_horizontal);
} else {
if (mVolumeBar.getProgressDrawable() instanceof StateListDrawable) {
StateListDrawable stateListDrawable = (StateListDrawable) mVolumeBar.getProgressDrawable();
volumeDrawable = (LayerDrawable) stateListDrawable.getCurrent();
} else {
volumeDrawable = (LayerDrawable) mVolumeBar.getProgressDrawable();
}
progressDrawable = (LayerDrawable) getResources().getDrawable(R.drawable.progress_horizontal_material);
}
if(volumeDrawable == null) {
volumeDrawable = (LayerDrawable) progressDrawable.mutate();
}
progressDrawable.findDrawableByLayerId(android.R.id.background).setColorFilter(getResources().getColor(R.color.beamplayer_seekbar_track), PorterDuff.Mode.SRC_IN);
progressDrawable.findDrawableByLayerId(android.R.id.progress).setColorFilter(paletteColor, PorterDuff.Mode.SRC_IN);
progressDrawable.findDrawableByLayerId(android.R.id.secondaryProgress).setColorFilter(paletteColor, PorterDuff.Mode.SRC_IN);
volumeDrawable.findDrawableByLayerId(android.R.id.background).setColorFilter(getResources().getColor(R.color.beamplayer_seekbar_track), PorterDuff.Mode.SRC_IN);
volumeDrawable.findDrawableByLayerId(android.R.id.progress).setColorFilter(paletteColor, PorterDuff.Mode.SRC_IN);
volumeDrawable.findDrawableByLayerId(android.R.id.secondaryProgress).setColorFilter(paletteColor, PorterDuff.Mode.SRC_IN);
mSeekBar.setProgressDrawable(progressDrawable);
mSeekBar.getThumbDrawable().setColorFilter(paletteColor, PorterDuff.Mode.SRC_IN);
mVolumeBar.setProgressDrawable(volumeDrawable);
mVolumeBar.getThumbDrawable().setColorFilter(paletteColor, PorterDuff.Mode.SRC_IN);
if (!VersionUtils.isJellyBean()) {
mPlayButton.setBackgroundDrawable(PixelUtils.changeDrawableColor(mPlayButton.getContext(), R.drawable.play_button_circle, paletteColor));
} else {
mPlayButton.setBackground(PixelUtils.changeDrawableColor(mPlayButton.getContext(), R.drawable.play_button_circle, paletteColor));
}
if (mStreamInfo.getImageUrl() != null && !mStreamInfo.getImageUrl().equals("")) {
Picasso.with(mCoverImage.getContext()).load(mStreamInfo.getImageUrl())
.into(mCoverImage, new Callback() {
@Override
public void onSuccess() {
AnimUtils.fadeIn(mCoverImage);
}
@Override
public void onError() {
}
});
}
mActivity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mActivity.getSupportActionBar().setTitle("");
try {
if (!mBeamManager.getConnectedDevice().hasCapability(MediaControl.Position) || !mBeamManager.getConnectedDevice().hasCapability(MediaControl.Seek) || !mBeamManager.getConnectedDevice().hasCapability(MediaControl.Duration)) {
mHasSeekControl = false;
mSeekBar.setVisibility(View.INVISIBLE);
}
if (!mBeamManager.getConnectedDevice().hasCapability(VolumeControl.Volume_Get) || !mBeamManager.getConnectedDevice().hasCapability(VolumeControl.Volume_Set) || !mBeamManager.getConnectedDevice().hasCapability(VolumeControl.Volume_Subscribe)) {
mHasVolumeControl = false;
mPanel.setEnabled(false);
mPanel.setTouchEnabled(false);
}
if (!mBeamManager.getConnectedDevice().hasCapability(MediaControl.Pause)) {
mPlayButton.setEnabled(false);
}
startVideo();
} catch (Exception e) {
Snackbar.make(mRootView, R.string.unknown_error, Snackbar.LENGTH_SHORT).show();
getActivity().finish();
}
Intent intent = new Intent( getActivity(), BeamPlayerNotificationService.class );
intent.setAction(mIsPlaying ? BeamPlayerNotificationService.ACTION_PLAY : BeamPlayerNotificationService.ACTION_PAUSE);
getActivity().startService(intent);
}
@Override
public void onResume() {
super.onResume();
BeamManager.getInstance(getActivity()).addDeviceListener(mDeviceListener);
}
@Override
public void onPause() {
super.onPause();
BeamManager manager = BeamManager.getInstance(getActivity());
manager.removeDeviceListener(mDeviceListener);
}
@Override
public void onDestroy() {
super.onDestroy();
Intent intent = new Intent( getActivity(), BeamPlayerNotificationService.class );
getActivity().stopService(intent);
}
private void startVideo() {
mBeamManager.playVideo(mStreamInfo, new MediaPlayer.LaunchListener() {
@Override
public void onSuccess(MediaPlayer.MediaLaunchObject object) {
mMediaControl = object.mediaControl;
mMediaControl.subscribePlayState(mPlayStateListener);
mMediaControl.getPlayState(mPlayStateListener);
if (mHasVolumeControl) {
mVolumeControl = BeamManager.getInstance(getActivity()).getVolumeControl();
mVolumeControl.subscribeVolume(mVolumeListener);
mVolumeControl.getVolume(mVolumeListener);
}
if (mHasSeekControl) {
startUpdating();
mMediaControl.getDuration(mDurationListener);
}
if(mResumePosition > 0) {
mMediaControl.seek(mResumePosition, null);
}
}
@Override
public void onError(ServiceCommandError error) {
Timber.e(error.getCause(), error.getMessage());
if (mRetries > 2 && !isDetached()) {
if (mLoadingDialog.isVisible() && !getActivity().isFinishing()) {
mLoadingDialog.dismiss();
}
OptionDialogFragment.show(mActivity, getChildFragmentManager(), R.string.unknown_error, R.string.beaming_failed, android.R.string.yes, android.R.string.no, new OptionDialogFragment.Listener() {
@Override
public void onSelectionPositive() {
startVideo();
}
@Override
public void onSelectionNegative() {
closePlayer();
}
});
return;
}
startVideo();
mRetries++;
}
});
}
@OnClick(R.id.play_button)
public void playPauseClick(View v) {
if(mMediaControl == null) return;
ResponseListener<Object> responseListener = new ResponseListener<Object>() {
@Override
public void onSuccess(Object object) {
mMediaControl.getPlayState(mPlayStateListener);
}
@Override
public void onError(ServiceCommandError error) {
mMediaControl.getPlayState(mPlayStateListener);
}
};
if (mIsPlaying) {
mIsPlaying = false;
mMediaControl.pause(responseListener);
} else {
mIsPlaying = true;
mMediaControl.play(responseListener);
}
mPlayButton.setImageResource(mIsPlaying ? R.drawable.ic_av_pause : R.drawable.ic_av_play);
mPlayButton.setContentDescription(mIsPlaying ? getString(R.string.pause) : getString(R.string.play));
}
@OnClick(R.id.forward_button)
public void forwardClick(View v) {
int newProgress = mSeekBar.getProgress() + 10000;
if (newProgress > mTotalTimeDuration) newProgress = (int) mTotalTimeDuration;
mMediaControl.seek(newProgress, null);
}
@OnClick(R.id.backward_button)
public void backwardClick(View v) {
int newProgress = mSeekBar.getProgress() - 10000;
if (newProgress < 0) newProgress = 0;
mMediaControl.seek(newProgress, null);
}
private void startUpdating() {
mTask = mExecutor.scheduleAtFixedRate(mPositionRunnable, 0, REFRESH_INTERVAL_MS, TimeUnit.MILLISECONDS);
}
private void stopUpdating() {
if (mTask != null) {
mTask.cancel(false);
}
if (mExecutor != null) {
for (Runnable r : mExecutor.getQueue()) {
mExecutor.remove(r);
}
}
}
private void closePlayer() {
if (mActivity != null && mActivity.getService() != null) {
mActivity.getService().stopStreaming();
}
mBeamManager.stopVideo();
getActivity().finish();
}
private MediaControl.PlayStateListener mPlayStateListener = new MediaControl.PlayStateListener() {
@Override
public void onSuccess(MediaControl.PlayStateStatus state) {
if(!FragmentUtil.isAdded(BeamPlayerFragment.this)) {
return;
}
mIsPlaying = state.equals(MediaControl.PlayStateStatus.Playing);
mPlayButton.setImageResource(mIsPlaying ? R.drawable.ic_av_pause : R.drawable.ic_av_play);
mPlayButton.setContentDescription(mIsPlaying ? getString(R.string.pause) : getString(R.string.play));
if (mLoadingDialog.isVisible() && mIsPlaying && !getActivity().isFinishing()) {
mLoadingDialog.dismiss();
}
if (mIsPlaying) {
mMediaControl.getDuration(mDurationListener);
}
}
@Override
public void onError(ServiceCommandError error) {
if(!FragmentUtil.isAdded(BeamPlayerFragment.this)) {
return;
}
if (mLoadingDialog.isVisible() && error.getCode() == 500 && !getActivity().isFinishing()) {
mLoadingDialog.dismiss();
OptionDialogFragment.show(mActivity, getChildFragmentManager(), R.string.unknown_error, R.string.beaming_failed, android.R.string.yes, android.R.string.no, new OptionDialogFragment.Listener() {
@Override
public void onSelectionPositive() {
startVideo();
}
@Override
public void onSelectionNegative() {
closePlayer();
}
});
}
}
};
private MediaControl.DurationListener mDurationListener = new MediaControl.DurationListener() {
@Override
public void onError(ServiceCommandError error) {
}
@Override
public void onSuccess(Long duration) {
if(mTotalTimeDuration != duration) {
mTotalTimeDuration = duration;
mSeekBar.setMax(duration.intValue());
}
//durationTextView.setText(formatTime(duration.intValue()));
}
};
private VolumeControl.VolumeListener mVolumeListener = new VolumeControl.VolumeListener() {
@Override
public void onSuccess(Float volume) {
mVolumeBar.setProgress((int) (volume * 100.0f));
}
@Override
public void onError(ServiceCommandError error) {
}
};
private Runnable mPositionRunnable = new Runnable() {
@Override
public void run() {
mMediaControl.getPosition(new MediaControl.PositionListener() {
@Override
public void onSuccess(Long position) {
if (!mIsUserSeeking) {
mSeekBar.setProgress(position.intValue());
mSeekBar.setSecondaryProgress(0); // hack to make the secondary progress appear on Android 5.0
mSeekBar.setSecondaryProgress(mDownloadProgress.intValue());
}
if (mLoadingDialog.isVisible() && !getActivity().isFinishing() && position > 0) {
mLoadingDialog.dismiss();
}
}
@Override
public void onError(ServiceCommandError error) {
}
});
}
};
private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(android.widget.SeekBar seekBar, int progress, boolean fromUser) {
if(fromUser && !mProcessingSeeking && mIsUserSeeking) {
if (progress <= mDownloadProgress) {
mSeekBar.setProgress(progress);
mSeekBar.setSecondaryProgress(0); // hack to make the secondary progress appear on Android 5.0
mSeekBar.setSecondaryProgress(mDownloadProgress.intValue());
mProcessingSeeking = true;
mMediaControl.seek(mSeekBar.getProgress(), new ResponseListener<Object>() {
@Override
public void onSuccess(Object response) {
mProcessingSeeking = false;
startUpdating();
}
@Override
public void onError(ServiceCommandError error) {
mProcessingSeeking = false;
startUpdating();
}
});
} else {
mSeekBar.setProgress(mDownloadProgress.intValue());
mSeekBar.setSecondaryProgress(0); // hack to make the secondary progress appear on Android 5.0
mSeekBar.setSecondaryProgress(mDownloadProgress.intValue());
}
}
}
@Override
public void onStartTrackingTouch(android.widget.SeekBar seekBar) {
mIsUserSeeking = true;
stopUpdating();
}
@Override
public void onStopTrackingTouch(android.widget.SeekBar seekBar) {
mIsUserSeeking = false;
}
};
public SeekBar.OnSeekBarChangeListener mVolumeBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(android.widget.SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(android.widget.SeekBar seekBar) {
}
@Override
public void onProgressChanged(android.widget.SeekBar seekBar, int position, boolean fromUser) {
if (fromUser)
mVolumeControl.setVolume((float) mVolumeBar.getProgress() / 100.0f, null);
}
};
@Override
public void onStreamStarted(Torrent torrent) { }
@Override
public void onStreamPrepared(Torrent torrent) { }
@Override
public void onStreamError(Torrent torrent, Exception e) { }
@Override
public void onStreamReady(Torrent torrent) { }
@Override
public void onStreamProgress(Torrent torrent, StreamStatus status) {
mDownloadProgress = mTotalTimeDuration / 100 * status.progress;
mSeekBar.setSecondaryProgress(0); // hack to make the secondary progress appear on Android 5.0
mSeekBar.setSecondaryProgress(mDownloadProgress.intValue());
}
@Override
public void onStreamStopped() {}
BeamDeviceListener mDeviceListener = new BeamDeviceListener() {
@Override
public void onDeviceDisconnected(ConnectableDevice device) {
super.onDeviceDisconnected(device);
VideoPlayerActivity.startActivity(getActivity(), mStreamInfo, mSeekBar.getProgress());
getActivity().finish();
}
};
}