From 7b6064842456043225b1c0e60560a169daee3f67 Mon Sep 17 00:00:00 2001 From: Marco Biscaro Date: Sun, 25 Jun 2017 23:41:52 -0300 Subject: [PATCH] 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. --- app/build.gradle | 2 +- .../org/schabi/newpipe/player/BasePlayer.java | 22 ++++++++ .../schabi/newpipe/player/VideoPlayer.java | 55 ++++++++++++++++--- .../schabi/newpipe/util/NavigationHelper.java | 3 +- .../main/res/layout/activity_main_player.xml | 16 +++++- app/src/main/res/layout/player_popup.xml | 11 ++++ 6 files changed, 98 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1101abe32..6dfd5aa11 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,5 +50,5 @@ dependencies { compile 'de.hdodenhof:circleimageview:2.0.0' compile 'com.github.nirhart:parallaxscroll:1.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' } diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 9a4d49fda..31caddca5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -22,6 +22,7 @@ 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.PlaybackParameters; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; 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 java.io.File; +import java.text.DecimalFormat; +import java.text.NumberFormat; import java.util.Formatter; import java.util.Locale; 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 START_POSITION = "start_position"; public static final String CHANNEL_NAME = "channel_name"; + public static final String PLAYBACK_SPEED = "playback_speed"; protected Bitmap videoThumbnail = null; protected String videoUrl = ""; @@ -176,6 +180,7 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage videoThumbnailUrl = intent.getStringExtra(VIDEO_THUMBNAIL_URL); videoStartPos = intent.getIntExtra(START_POSITION, -1); channelName = intent.getStringExtra(CHANNEL_NAME); + setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed())); initThumbnail(); //play(getSelectedVideoStream(), true); @@ -438,6 +443,10 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + } + @Override public void onLoadingChanged(boolean 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 Formatter formatter = new Formatter(stringBuilder, Locale.getDefault()); + private final NumberFormat speedFormatter = new DecimalFormat("0.##x"); public String getTimeString(int milliSeconds) { long seconds = (milliSeconds % 60000L) / 1000L; @@ -569,6 +579,10 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage : formatter.format("%02d:%02d", minutes, seconds).toString(); } + protected String formatSpeed(float speed) { + return speedFormatter.format(speed); + } + protected void startProgressLoop() { progressLoop.removeCallbacksAndMessages(null); isProgressLoopRunning.set(true); @@ -714,4 +728,12 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage public void setVideoThumbnailUrl(String videoThumbnailUrl) { this.videoThumbnailUrl = videoThumbnailUrl; } + + public float getPlaybackSpeed() { + return simpleExoPlayer.getPlaybackParameters().speed; + } + + public void setPlaybackSpeed(float speed) { + simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, 1f)); + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 84f2192f9..a8db51c40 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -74,6 +74,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. //////////////////////////////////////////////////////////////////////////*/ 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 wasPlaying = false; @@ -99,6 +100,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. private SeekBar playbackSeekBar; private TextView playbackCurrentTime; private TextView playbackEndTime; + private TextView playbackSpeed; private View topControlsRoot; private TextView qualityTextView; @@ -112,6 +114,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. private int qualityPopupMenuGroupId = 69; private PopupMenu qualityPopupMenu; + private int playbackSpeedPopupMenuGroupId = 79; + private PopupMenu playbackSpeedPopupMenu; + /////////////////////////////////////////////////////////////////////////// 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.playbackCurrentTime = (TextView) rootView.findViewById(R.id.playbackCurrentTime); this.playbackEndTime = (TextView) rootView.findViewById(R.id.playbackEndTime); + this.playbackSpeed = (TextView) rootView.findViewById(R.id.playbackSpeed); this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls); this.topControlsRoot = rootView.findViewById(R.id.topControls); 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.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); @@ -158,6 +165,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. public void initListeners() { super.initListeners(); playbackSeekBar.setOnSeekBarChangeListener(this); + playbackSpeed.setOnClickListener(this); fullScreenButton.setOnClickListener(this); qualityTextView.setOnClickListener(this); } @@ -208,6 +216,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId); buildQualityMenu(qualityPopupMenu); + playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId); + buildPlaybackSpeedMenu(playbackSpeedPopupMenu); + super.playUrl(url, format, autoPlay); } @@ -230,6 +241,15 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. 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 //////////////////////////////////////////////////////////////////////////*/ @@ -346,6 +366,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. playbackSeekBar.setMax((int) simpleExoPlayer.getDuration()); playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration())); + playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); super.onPrepared(playWhenReady); } @@ -412,24 +433,37 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. onFullScreenButtonClicked(); } else if (v.getId() == qualityTextView.getId()) { 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 public boolean onMenuItemClick(MenuItem menuItem) { if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]"); - if (selectedIndexStream == menuItem.getItemId()) return true; - setVideoStartPos((int) simpleExoPlayer.getCurrentPosition()); - selectedIndexStream = menuItem.getItemId(); - if (!(getCurrentState() == STATE_COMPLETED)) play(wasPlaying); - else qualityChanged = true; + if (qualityPopupMenuGroupId == menuItem.getGroupId()) { + if (selectedIndexStream == menuItem.getItemId()) return true; + setVideoStartPos((int) simpleExoPlayer.getCurrentPosition()); - qualityTextView.setText(menuItem.getTitle()); - return true; + selectedIndexStream = menuItem.getItemId(); + if (!(getCurrentState() == STATE_COMPLETED)) play(wasPlaying); + else qualityChanged = true; + + qualityTextView.setText(menuItem.getTitle()); + 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(); } + private void onPlaybackSpeedClicked() { + if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called"); + playbackSpeedPopupMenu.show(); + } + /*////////////////////////////////////////////////////////////////////////// // SeekBar Listener //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 56a44cf55..32c89881e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -51,7 +51,8 @@ public class NavigationHelper { .putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex()) .putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList()) .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) { diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index 07363f7e0..e3ef022f9 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -107,7 +107,7 @@ android:layout_height="35dp" android:layout_marginLeft="2dp" android:layout_marginRight="2dp" - android:layout_toLeftOf="@+id/screenRotationButton" + android:layout_toLeftOf="@+id/playbackSpeed" android:gravity="center" android:minWidth="50dp" android:text="720p" @@ -115,6 +115,20 @@ android:textStyle="bold" tools:ignore="HardcodedText,RtlHardcoded"/> + + + +