Adds support for adjustable playback speed
ExoPlayer was updated to 2.4.2, which supports playback speed change. A speed selector was also added in the MainPlayer and PlayerPopup. Fixes #153.
This commit is contained in:
parent
3cb4952281
commit
7b60648424
6 changed files with 98 additions and 11 deletions
|
@ -50,5 +50,5 @@ dependencies {
|
||||||
compile 'de.hdodenhof:circleimageview:2.0.0'
|
compile 'de.hdodenhof:circleimageview:2.0.0'
|
||||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||||
compile 'com.google.android.exoplayer:exoplayer:r2.3.1'
|
compile 'com.google.android.exoplayer:exoplayer:r2.4.2'
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import com.google.android.exoplayer2.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
|
@ -47,6 +48,8 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.NumberFormat;
|
||||||
import java.util.Formatter;
|
import java.util.Formatter;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
@ -77,6 +80,7 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage
|
||||||
public static final String VIDEO_THUMBNAIL_URL = "video_thumbnail_url";
|
public static final String VIDEO_THUMBNAIL_URL = "video_thumbnail_url";
|
||||||
public static final String START_POSITION = "start_position";
|
public static final String START_POSITION = "start_position";
|
||||||
public static final String CHANNEL_NAME = "channel_name";
|
public static final String CHANNEL_NAME = "channel_name";
|
||||||
|
public static final String PLAYBACK_SPEED = "playback_speed";
|
||||||
|
|
||||||
protected Bitmap videoThumbnail = null;
|
protected Bitmap videoThumbnail = null;
|
||||||
protected String videoUrl = "";
|
protected String videoUrl = "";
|
||||||
|
@ -176,6 +180,7 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage
|
||||||
videoThumbnailUrl = intent.getStringExtra(VIDEO_THUMBNAIL_URL);
|
videoThumbnailUrl = intent.getStringExtra(VIDEO_THUMBNAIL_URL);
|
||||||
videoStartPos = intent.getIntExtra(START_POSITION, -1);
|
videoStartPos = intent.getIntExtra(START_POSITION, -1);
|
||||||
channelName = intent.getStringExtra(CHANNEL_NAME);
|
channelName = intent.getStringExtra(CHANNEL_NAME);
|
||||||
|
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()));
|
||||||
|
|
||||||
initThumbnail();
|
initThumbnail();
|
||||||
//play(getSelectedVideoStream(), true);
|
//play(getSelectedVideoStream(), true);
|
||||||
|
@ -438,6 +443,10 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage
|
||||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadingChanged(boolean isLoading) {
|
public void onLoadingChanged(boolean isLoading) {
|
||||||
if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
|
if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
|
||||||
|
@ -556,6 +565,7 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage
|
||||||
|
|
||||||
private final StringBuilder stringBuilder = new StringBuilder();
|
private final StringBuilder stringBuilder = new StringBuilder();
|
||||||
private final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
|
private final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
|
||||||
|
private final NumberFormat speedFormatter = new DecimalFormat("0.##x");
|
||||||
|
|
||||||
public String getTimeString(int milliSeconds) {
|
public String getTimeString(int milliSeconds) {
|
||||||
long seconds = (milliSeconds % 60000L) / 1000L;
|
long seconds = (milliSeconds % 60000L) / 1000L;
|
||||||
|
@ -569,6 +579,10 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage
|
||||||
: formatter.format("%02d:%02d", minutes, seconds).toString();
|
: formatter.format("%02d:%02d", minutes, seconds).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String formatSpeed(float speed) {
|
||||||
|
return speedFormatter.format(speed);
|
||||||
|
}
|
||||||
|
|
||||||
protected void startProgressLoop() {
|
protected void startProgressLoop() {
|
||||||
progressLoop.removeCallbacksAndMessages(null);
|
progressLoop.removeCallbacksAndMessages(null);
|
||||||
isProgressLoopRunning.set(true);
|
isProgressLoopRunning.set(true);
|
||||||
|
@ -714,4 +728,12 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage
|
||||||
public void setVideoThumbnailUrl(String videoThumbnailUrl) {
|
public void setVideoThumbnailUrl(String videoThumbnailUrl) {
|
||||||
this.videoThumbnailUrl = videoThumbnailUrl;
|
this.videoThumbnailUrl = videoThumbnailUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getPlaybackSpeed() {
|
||||||
|
return simpleExoPlayer.getPlaybackParameters().speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaybackSpeed(float speed) {
|
||||||
|
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, 1f));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public static final int DEFAULT_CONTROLS_HIDE_TIME = 3000; // 3 Seconds
|
public static final int DEFAULT_CONTROLS_HIDE_TIME = 3000; // 3 Seconds
|
||||||
|
private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f};
|
||||||
|
|
||||||
private boolean startedFromNewPipe = true;
|
private boolean startedFromNewPipe = true;
|
||||||
private boolean wasPlaying = false;
|
private boolean wasPlaying = false;
|
||||||
|
@ -99,6 +100,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
private SeekBar playbackSeekBar;
|
private SeekBar playbackSeekBar;
|
||||||
private TextView playbackCurrentTime;
|
private TextView playbackCurrentTime;
|
||||||
private TextView playbackEndTime;
|
private TextView playbackEndTime;
|
||||||
|
private TextView playbackSpeed;
|
||||||
|
|
||||||
private View topControlsRoot;
|
private View topControlsRoot;
|
||||||
private TextView qualityTextView;
|
private TextView qualityTextView;
|
||||||
|
@ -112,6 +114,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
private int qualityPopupMenuGroupId = 69;
|
private int qualityPopupMenuGroupId = 69;
|
||||||
private PopupMenu qualityPopupMenu;
|
private PopupMenu qualityPopupMenu;
|
||||||
|
|
||||||
|
private int playbackSpeedPopupMenuGroupId = 79;
|
||||||
|
private PopupMenu playbackSpeedPopupMenu;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public VideoPlayer(String debugTag, Context context) {
|
public VideoPlayer(String debugTag, Context context) {
|
||||||
|
@ -138,6 +143,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
this.playbackSeekBar = (SeekBar) rootView.findViewById(R.id.playbackSeekBar);
|
this.playbackSeekBar = (SeekBar) rootView.findViewById(R.id.playbackSeekBar);
|
||||||
this.playbackCurrentTime = (TextView) rootView.findViewById(R.id.playbackCurrentTime);
|
this.playbackCurrentTime = (TextView) rootView.findViewById(R.id.playbackCurrentTime);
|
||||||
this.playbackEndTime = (TextView) rootView.findViewById(R.id.playbackEndTime);
|
this.playbackEndTime = (TextView) rootView.findViewById(R.id.playbackEndTime);
|
||||||
|
this.playbackSpeed = (TextView) rootView.findViewById(R.id.playbackSpeed);
|
||||||
this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
|
this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
|
||||||
this.topControlsRoot = rootView.findViewById(R.id.topControls);
|
this.topControlsRoot = rootView.findViewById(R.id.topControls);
|
||||||
this.qualityTextView = (TextView) rootView.findViewById(R.id.qualityTextView);
|
this.qualityTextView = (TextView) rootView.findViewById(R.id.qualityTextView);
|
||||||
|
@ -149,6 +155,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
||||||
|
|
||||||
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
|
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
|
||||||
|
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeed);
|
||||||
|
|
||||||
((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel)).getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
|
((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel)).getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
|
||||||
|
|
||||||
|
@ -158,6 +165,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
public void initListeners() {
|
public void initListeners() {
|
||||||
super.initListeners();
|
super.initListeners();
|
||||||
playbackSeekBar.setOnSeekBarChangeListener(this);
|
playbackSeekBar.setOnSeekBarChangeListener(this);
|
||||||
|
playbackSpeed.setOnClickListener(this);
|
||||||
fullScreenButton.setOnClickListener(this);
|
fullScreenButton.setOnClickListener(this);
|
||||||
qualityTextView.setOnClickListener(this);
|
qualityTextView.setOnClickListener(this);
|
||||||
}
|
}
|
||||||
|
@ -208,6 +216,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
||||||
buildQualityMenu(qualityPopupMenu);
|
buildQualityMenu(qualityPopupMenu);
|
||||||
|
|
||||||
|
playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId);
|
||||||
|
buildPlaybackSpeedMenu(playbackSpeedPopupMenu);
|
||||||
|
|
||||||
super.playUrl(url, format, autoPlay);
|
super.playUrl(url, format, autoPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,6 +241,15 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
popupMenu.setOnDismissListener(this);
|
popupMenu.setOnDismissListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void buildPlaybackSpeedMenu(PopupMenu popupMenu) {
|
||||||
|
for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) {
|
||||||
|
popupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, formatSpeed(PLAYBACK_SPEEDS[i]));
|
||||||
|
}
|
||||||
|
playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
|
||||||
|
popupMenu.setOnMenuItemClickListener(this);
|
||||||
|
popupMenu.setOnDismissListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// States Implementation
|
// States Implementation
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -346,6 +366,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
|
|
||||||
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
|
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
|
||||||
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
|
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
|
||||||
|
playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
|
||||||
|
|
||||||
super.onPrepared(playWhenReady);
|
super.onPrepared(playWhenReady);
|
||||||
}
|
}
|
||||||
|
@ -412,15 +433,19 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
onFullScreenButtonClicked();
|
onFullScreenButtonClicked();
|
||||||
} else if (v.getId() == qualityTextView.getId()) {
|
} else if (v.getId() == qualityTextView.getId()) {
|
||||||
onQualitySelectorClicked();
|
onQualitySelectorClicked();
|
||||||
|
} else if (v.getId() == playbackSpeed.getId()) {
|
||||||
|
onPlaybackSpeedClicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an item of the quality selector is selected
|
* Called when an item of the quality selector or the playback speed selector is selected
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||||
if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
|
if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
|
||||||
|
|
||||||
|
if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
|
||||||
if (selectedIndexStream == menuItem.getItemId()) return true;
|
if (selectedIndexStream == menuItem.getItemId()) return true;
|
||||||
setVideoStartPos((int) simpleExoPlayer.getCurrentPosition());
|
setVideoStartPos((int) simpleExoPlayer.getCurrentPosition());
|
||||||
|
|
||||||
|
@ -430,6 +455,15 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
|
|
||||||
qualityTextView.setText(menuItem.getTitle());
|
qualityTextView.setText(menuItem.getTitle());
|
||||||
return true;
|
return true;
|
||||||
|
} else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
|
||||||
|
int speedIndex = menuItem.getItemId();
|
||||||
|
float speed = PLAYBACK_SPEEDS[speedIndex];
|
||||||
|
|
||||||
|
setPlaybackSpeed(speed);
|
||||||
|
playbackSpeed.setText(formatSpeed(speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -453,6 +487,11 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
wasPlaying = isPlaying();
|
wasPlaying = isPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onPlaybackSpeedClicked() {
|
||||||
|
if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called");
|
||||||
|
playbackSpeedPopupMenu.show();
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// SeekBar Listener
|
// SeekBar Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
@ -51,7 +51,8 @@ public class NavigationHelper {
|
||||||
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
|
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
|
||||||
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
|
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
|
||||||
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
|
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
|
||||||
.putExtra(BasePlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition()));
|
.putExtra(BasePlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition()))
|
||||||
|
.putExtra(BasePlayer.PLAYBACK_SPEED, instance.getPlaybackSpeed());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) {
|
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) {
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
android:layout_height="35dp"
|
android:layout_height="35dp"
|
||||||
android:layout_marginLeft="2dp"
|
android:layout_marginLeft="2dp"
|
||||||
android:layout_marginRight="2dp"
|
android:layout_marginRight="2dp"
|
||||||
android:layout_toLeftOf="@+id/screenRotationButton"
|
android:layout_toLeftOf="@+id/playbackSpeed"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:minWidth="50dp"
|
android:minWidth="50dp"
|
||||||
android:text="720p"
|
android:text="720p"
|
||||||
|
@ -115,6 +115,20 @@
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:ignore="HardcodedText,RtlHardcoded"/>
|
tools:ignore="HardcodedText,RtlHardcoded"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/playbackSpeed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="2dp"
|
||||||
|
android:layout_toLeftOf="@+id/screenRotationButton"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minHeight="35dp"
|
||||||
|
android:minWidth="40dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||||
|
tools:text="1x" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/screenRotationButton"
|
android:id="@+id/screenRotationButton"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
|
|
|
@ -72,6 +72,17 @@
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry"/>
|
tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/playbackSpeed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_toRightOf="@id/qualityTextView"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||||
|
tools:text="1x" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/fullScreenButton"
|
android:id="@+id/fullScreenButton"
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
|
|
Loading…
Reference in a new issue