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/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index f747ca9a5..b4fcb7f4e 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -25,7 +25,6 @@ import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
@@ -37,7 +36,6 @@ import android.view.View;
import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.extractor.StreamingService;
-import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.search.SearchFragment;
import org.schabi.newpipe.settings.SettingsActivity;
@@ -104,12 +102,10 @@ public class MainActivity extends AppCompatActivity {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (fragment instanceof VideoDetailFragment) if (((VideoDetailFragment) fragment).onActivityBackPressed()) return;
- super.onBackPressed();
- fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
- if (getSupportFragmentManager().getBackStackEntryCount() == 0 && !(fragment instanceof MainFragment)) {
- super.onBackPressed();
- }
+ if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
+ finish();
+ } else super.onBackPressed();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -148,11 +144,7 @@ public class MainActivity extends AppCompatActivity {
switch (id) {
case android.R.id.home: {
- Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
- if (fragment instanceof VideoDetailFragment) ((VideoDetailFragment) fragment).clearHistory();
-
- getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- NavigationHelper.openMainFragment(getSupportFragmentManager());
+ NavigationHelper.gotoMainFragment(getSupportFragmentManager());
return true;
}
case R.id.action_settings: {
@@ -178,9 +170,9 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/
private void initFragments() {
- if (getIntent() != null && getIntent().hasExtra(Constants.KEY_URL)) {
+ if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
handleIntent(getIntent());
- } else NavigationHelper.openMainFragment(getSupportFragmentManager());
+ } else NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
/*//////////////////////////////////////////////////////////////////////////
@@ -209,8 +201,7 @@ public class MainActivity extends AppCompatActivity {
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
NavigationHelper.openSearchFragment(getSupportFragmentManager(), serviceId, searchQuery);
} else {
- getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- NavigationHelper.openMainFragment(getSupportFragmentManager());
+ NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
index 375591a57..628e1e055 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
@@ -380,7 +380,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
public void onClick(View v) {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (TextUtils.isEmpty(searchEditText.getText())) {
- NavigationHelper.openMainFragment(getFragmentManager());
+ NavigationHelper.gotoMainFragment(getFragmentManager());
return;
}
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..0da8cdd86 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -24,6 +24,7 @@ import org.schabi.newpipe.player.VideoPlayer;
@SuppressWarnings({"unused", "WeakerAccess"})
public class NavigationHelper {
+ public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
/*//////////////////////////////////////////////////////////////////////////
// Players
@@ -51,7 +52,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) {
@@ -74,11 +76,19 @@ public class NavigationHelper {
// Through FragmentManager
//////////////////////////////////////////////////////////////////////////*/
- public static void openMainFragment(FragmentManager fragmentManager) {
+ public static void gotoMainFragment(FragmentManager fragmentManager) {
ImageLoader.getInstance().clearMemoryCache();
+
+ boolean popped = fragmentManager.popBackStackImmediate(MAIN_FRAGMENT_TAG, 0);
+ if (!popped) openMainFragment(fragmentManager);
+ }
+
+ private static void openMainFragment(FragmentManager fragmentManager) {
+ fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentManager.beginTransaction()
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
.replace(R.id.fragment_holder, new MainFragment())
+ .addToBackStack(MAIN_FRAGMENT_TAG)
.commit();
}
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"/>
+
+
+ android:layout_height="match_parent">
+
+
+
+ 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
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 06cadc2f2..53e309708 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip