diff --git a/app/build.gradle b/app/build.gradle index bc8f124cf..4aa6359a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,5 +57,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/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 7e20d4940..547d6447e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -266,7 +266,7 @@ public class MainVideoPlayer extends Activity { animateView(playerImpl.getControlsRoot(), true, 300, 0, new Runnable() { @Override public void run() { - if (getCurrentState() == STATE_PLAYING && !playerImpl.isQualityMenuVisible()) { + if (getCurrentState() == STATE_PLAYING && !playerImpl.isSomePopupMenuVisible()) { hideControls(300, DEFAULT_CONTROLS_HIDE_TIME); } } 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..c16acd766 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; @@ -107,11 +109,14 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. private ValueAnimator controlViewAnimator; private Handler controlsVisibilityHandler = new Handler(); - private boolean isQualityPopupMenuVisible = false; + private boolean isSomePopupMenuVisible = false; private boolean qualityChanged = false; 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,40 +433,53 @@ 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; } /** - * Called when the quality selector is dismissed + * Called when some popup menu is dismissed */ @Override public void onDismiss(PopupMenu menu) { if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); - isQualityPopupMenuVisible = false; + isSomePopupMenuVisible = false; qualityTextView.setText(getSelectedVideoStream().resolution); } public void onQualitySelectorClicked() { if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called"); qualityPopupMenu.show(); - isQualityPopupMenuVisible = true; + isSomePopupMenuVisible = true; showControls(300); VideoStream videoStream = getSelectedVideoStream(); @@ -453,6 +487,13 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. wasPlaying = isPlaying(); } + private void onPlaybackSpeedClicked() { + if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called"); + playbackSpeedPopupMenu.show(); + isSomePopupMenuVisible = true; + showControls(300); + } + /*////////////////////////////////////////////////////////////////////////// // SeekBar Listener //////////////////////////////////////////////////////////////////////////*/ @@ -553,8 +594,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. controlViewAnimator.start(); } - public boolean isQualityMenuVisible() { - return isQualityPopupMenuVisible; + public boolean isSomePopupMenuVisible() { + return isSomePopupMenuVisible; } public void showControlsThenHide() { 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 8c47879a3..68314afdd 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"/> + + + tools:layout_height="84dp" + tools:layout_width="@dimen/popup_minimum_width"> + android:paddingTop="4dp" + tools:ignore="RtlHardcoded"> + tools:ignore="RtlHardcoded,RtlSymmetry" + tools:text="1080p60"/> + + + android:weightSum="5.5"> + tools:visibility="gone"/> \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index 8ab553bb6..724e16681 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -1,7 +1,7 @@ 230dp - 140dp + 160dp 18sp diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 9351d9e07..0d353795b 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -22,7 +22,7 @@ 4dp 180dp - 120dp + 150dp 16sp