diff --git a/README.md b/README.md
index 374c56d00..08d3fb49e 100644
--- a/README.md
+++ b/README.md
@@ -18,16 +18,16 @@ WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR
## Screenshots
-[](screenshots/shot_1.png)
-[](screenshots/shot_2.png)
-[](screenshots/shot_3.png)
-[](screenshots/shot_4.png)
-[](screenshots/shot_5.png)
-[](screenshots/shot_6.png)
-[](screenshots/shot_7.png)
-[](screenshots/shot_8.png)
-[](screenshots/shot_9.png)
-[](screenshots/shot_10.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
## Description
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4f97a7201..dab6fb2ec 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -185,7 +185,7 @@
android:name=".RouterPopupActivity"
android:label="@string/popup_mode_share_menu_title"
android:taskAffinity=""
- android:theme="@android:style/Theme.NoDisplay">
+ android:theme="@style/PopupPermissionsTheme">
diff --git a/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java b/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java
index 1cff0ca76..2e7089300 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java
@@ -21,6 +21,7 @@ public class RouterPopupActivity extends RouterActivity {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !PermissionHelper.checkSystemAlertWindowPermission(this)) {
Toast.makeText(this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
+ finish();
return;
}
StreamingService service;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
index 80f05585b..a85a536db 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
@@ -50,6 +50,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC
protected Button errorButtonRetry;
protected TextView errorTextView;
+ @State
protected boolean useAsFrontPage = false;
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index f225b5d85..c741aca31 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -36,6 +36,7 @@ import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.PopupMenu;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
@@ -62,9 +63,10 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.history.HistoryListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
-import org.schabi.newpipe.player.BackgroundPlayer;
+import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
+import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
@@ -463,6 +465,11 @@ public class VideoDetailFragment extends BaseStateFragment implement
public void selected(StreamInfoItem selectedItem) {
selectAndLoadVideo(selectedItem.service_id, selectedItem.url, selectedItem.name);
}
+
+ @Override
+ public void held(StreamInfoItem selectedItem) {
+ showStreamDialog(selectedItem);
+ }
});
videoTitleRoot.setOnClickListener(this);
@@ -480,6 +487,34 @@ public class VideoDetailFragment extends BaseStateFragment implement
detailControlsPopup.setOnTouchListener(getOnControlsTouchListener());
}
+ private void showStreamDialog(final StreamInfoItem item) {
+ final Context context = getContext();
+ if (context == null || context.getResources() == null || getActivity() == null) return;
+
+ final String[] commands = new String[]{
+ context.getResources().getString(R.string.enqueue_on_background),
+ context.getResources().getString(R.string.enqueue_on_popup)
+ };
+
+ final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ switch (i) {
+ case 0:
+ NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
+ break;
+ case 1:
+ NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(item));
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ new InfoItemDialog(getActivity(), item, commands, actions).show();
+ }
+
private View.OnTouchListener getOnControlsTouchListener() {
return new View.OnTouchListener() {
@Override
@@ -796,16 +831,16 @@ public class VideoDetailFragment extends BaseStateFragment implement
((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream());
}
- final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
- final Intent intent;
+ final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
if (append) {
- Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
- intent = NavigationHelper.getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, playQueue);
+ NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue);
} else {
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
- intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, getSelectedVideoStream().resolution);
+ final Intent intent = NavigationHelper.getPlayerIntent(
+ activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution
+ );
+ activity.startService(intent);
}
- activity.startService(intent);
}
private void openVideoPlayer() {
@@ -824,13 +859,11 @@ public class VideoDetailFragment extends BaseStateFragment implement
private void openNormalBackgroundPlayer(final boolean append) {
- final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
+ final PlayQueue itemQueue = new SinglePlayQueue(currentInfo);
if (append) {
- activity.startService(NavigationHelper.getPlayerEnqueueIntent(activity, BackgroundPlayer.class, playQueue));
- Toast.makeText(activity, R.string.background_player_append, Toast.LENGTH_SHORT).show();
+ NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue);
} else {
- activity.startService(NavigationHelper.getPlayerIntent(activity, BackgroundPlayer.class, playQueue));
- Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
+ NavigationHelper.playOnBackgroundPlayer(activity, itemQueue);
}
}
@@ -870,8 +903,7 @@ public class VideoDetailFragment extends BaseStateFragment implement
private void openNormalPlayer(VideoStream selectedVideoStream) {
Intent mIntent;
- boolean useOldPlayer = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.use_old_player_key), false)
- || (Build.VERSION.SDK_INT < 16);
+ boolean useOldPlayer = PlayerHelper.isUsingOldPlayer(activity) || (Build.VERSION.SDK_INT < 16);
if (!useOldPlayer) {
// ExoPlayer
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
index 35f6a08d3..ae17dafff 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.fragments.list;
import android.content.Context;
+import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
@@ -19,7 +20,9 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
+import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.info_list.InfoListAdapter;
+import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.StateSaver;
@@ -139,6 +142,11 @@ public abstract class BaseListFragment extends BaseStateFragment implem
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
selectedItem.service_id, selectedItem.url, selectedItem.name);
}
+
+ @Override
+ public void held(StreamInfoItem selectedItem) {
+ showStreamDialog(selectedItem);
+ }
});
infoListAdapter.setOnChannelSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@@ -149,6 +157,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
selectedItem.service_id, selectedItem.url, selectedItem.name);
}
+
+ @Override
+ public void held(ChannelInfoItem selectedItem) {}
});
infoListAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@@ -159,6 +170,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
selectedItem.service_id, selectedItem.url, selectedItem.name);
}
+
+ @Override
+ public void held(PlaylistInfoItem selectedItem) {}
});
itemsList.clearOnScrollListeners();
@@ -176,6 +190,33 @@ public abstract class BaseListFragment extends BaseStateFragment implem
}
}
+ protected void showStreamDialog(final StreamInfoItem item) {
+ final Context context = getContext();
+ if (context == null || context.getResources() == null || getActivity() == null) return;
+
+ final String[] commands = new String[]{
+ context.getResources().getString(R.string.enqueue_on_background),
+ context.getResources().getString(R.string.enqueue_on_popup)
+ };
+
+ final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ switch (i) {
+ case 0:
+ NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
+ break;
+ case 1:
+ NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(item));
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ new InfoItemDialog(getActivity(), item, commands, actions).show();
+ }
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
index 64875b17f..857fb81e0 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
@@ -1,8 +1,10 @@
package org.schabi.newpipe.fragments.list.channel;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -10,6 +12,7 @@ import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
import android.util.Log;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -18,7 +21,9 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
+import android.widget.Toast;
import com.jakewharton.rxbinding2.view.RxView;
@@ -28,12 +33,19 @@ import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.fragments.subscription.SubscriptionService;
+import org.schabi.newpipe.info_list.InfoItemDialog;
+import org.schabi.newpipe.playlist.ChannelPlayQueue;
+import org.schabi.newpipe.playlist.PlayQueue;
+import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.PermissionHelper;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -68,6 +80,11 @@ public class ChannelFragment extends BaseListInfoFragment {
private TextView headerTitleView;
private TextView headerSubscribersTextView;
private Button headerSubscribeButton;
+ private View playlistCtrl;
+
+ private LinearLayout headerPlayAllButton;
+ private LinearLayout headerPopupButton;
+ private LinearLayout headerBackgroundButton;
private MenuItem menuRssButton;
@@ -124,10 +141,57 @@ public class ChannelFragment extends BaseListInfoFragment {
headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
+ playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
+
+
+ headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
+ headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
+ headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
return headerRootLayout;
}
+ @Override
+ protected void showStreamDialog(final StreamInfoItem item) {
+ final Context context = getContext();
+ if (context == null || context.getResources() == null || getActivity() == null) return;
+
+ final String[] commands = new String[]{
+ context.getResources().getString(R.string.enqueue_on_background),
+ context.getResources().getString(R.string.enqueue_on_popup),
+ context.getResources().getString(R.string.start_here_on_main),
+ context.getResources().getString(R.string.start_here_on_background),
+ context.getResources().getString(R.string.start_here_on_popup),
+ };
+
+ final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
+ switch (i) {
+ case 0:
+ NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
+ break;
+ case 1:
+ NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(item));
+ break;
+ case 2:
+ NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
+ break;
+ case 3:
+ NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
+ break;
+ case 4:
+ NavigationHelper.playOnPopupPlayer(context, getPlayQueue(index));
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ new InfoItemDialog(getActivity(), item, commands, actions).show();
+ }
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@@ -382,6 +446,7 @@ public class ChannelFragment extends BaseListInfoFragment {
} else headerSubscribersTextView.setVisibility(View.GONE);
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.feed_url));
+ playlistCtrl.setVisibility(View.VISIBLE);
if (!result.errors.isEmpty()) {
showSnackBarError(result.errors, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.service_id), result.url, 0);
@@ -391,6 +456,46 @@ public class ChannelFragment extends BaseListInfoFragment {
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
updateSubscription(result);
monitorSubscription(result);
+
+ headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ NavigationHelper.playOnMainPlayer(activity, getPlayQueue());
+ }
+ });
+ headerPopupButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
+ Toast toast = Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG);
+ TextView messageView = toast.getView().findViewById(android.R.id.message);
+ if (messageView != null) messageView.setGravity(Gravity.CENTER);
+ toast.show();
+ return;
+ }
+ NavigationHelper.playOnPopupPlayer(activity, getPlayQueue());
+ }
+ });
+ headerBackgroundButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue());
+ }
+ });
+ }
+
+ private PlayQueue getPlayQueue() {
+ return getPlayQueue(0);
+ }
+
+ private PlayQueue getPlayQueue(final int index) {
+ return new ChannelPlayQueue(
+ currentInfo.service_id,
+ currentInfo.url,
+ currentInfo.next_streams_url,
+ infoListAdapter.getItemsList(),
+ index
+ );
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java
index a9d1cda76..3e224efdc 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java
@@ -27,6 +27,7 @@ import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.NavigationHelper;
+import icepick.State;
import io.reactivex.Single;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -53,7 +54,8 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class KioskFragment extends BaseListInfoFragment {
- private String kioskId = "";
+ @State
+ protected String kioskId = "";
/*//////////////////////////////////////////////////////////////////////////
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index b88d54524..e7f7e9968 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.fragments.list.playlist;
-import android.content.Intent;
+import android.content.Context;
+import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
@@ -13,7 +14,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@@ -23,12 +23,12 @@ import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
+import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
-import org.schabi.newpipe.player.BackgroundPlayer;
-import org.schabi.newpipe.player.MainVideoPlayer;
-import org.schabi.newpipe.player.PopupVideoPlayer;
-import org.schabi.newpipe.playlist.ExternalPlayQueue;
+import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.playlist.PlayQueue;
+import org.schabi.newpipe.playlist.PlaylistPlayQueue;
+import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
@@ -50,10 +50,11 @@ public class PlaylistFragment extends BaseListInfoFragment {
private TextView headerUploaderName;
private ImageView headerUploaderAvatar;
private TextView headerStreamCount;
+ private View playlistCtrl;
- private Button headerPlayAllButton;
- private Button headerPopupButton;
- private Button headerBackgroundButton;
+ private View headerPlayAllButton;
+ private View headerPopupButton;
+ private View headerBackgroundButton;
public static PlaylistFragment getInstance(int serviceId, String url, String name) {
PlaylistFragment instance = new PlaylistFragment();
@@ -81,10 +82,11 @@ public class PlaylistFragment extends BaseListInfoFragment {
headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name);
headerUploaderAvatar = headerRootLayout.findViewById(R.id.uploader_avatar_view);
headerStreamCount = headerRootLayout.findViewById(R.id.playlist_stream_count);
+ playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
- headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_play_all_button);
- headerPopupButton = headerRootLayout.findViewById(R.id.playlist_play_popup_button);
- headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_play_bg_button);
+ headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
+ headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
+ headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
return headerRootLayout;
}
@@ -103,6 +105,47 @@ public class PlaylistFragment extends BaseListInfoFragment {
inflater.inflate(R.menu.menu_playlist, menu);
}
+ @Override
+ protected void showStreamDialog(final StreamInfoItem item) {
+ final Context context = getContext();
+ if (context == null || context.getResources() == null || getActivity() == null) return;
+
+ final String[] commands = new String[]{
+ context.getResources().getString(R.string.enqueue_on_background),
+ context.getResources().getString(R.string.enqueue_on_popup),
+ context.getResources().getString(R.string.start_here_on_main),
+ context.getResources().getString(R.string.start_here_on_background),
+ context.getResources().getString(R.string.start_here_on_popup),
+ };
+
+ final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
+ switch (i) {
+ case 0:
+ NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
+ break;
+ case 1:
+ NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(item));
+ break;
+ case 2:
+ NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
+ break;
+ case 3:
+ NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
+ break;
+ case 4:
+ NavigationHelper.playOnPopupPlayer(context, getPlayQueue(index));
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ new InfoItemDialog(getActivity(), item, commands, actions).show();
+ }
/*//////////////////////////////////////////////////////////////////////////
// Load and handle
//////////////////////////////////////////////////////////////////////////*/
@@ -150,6 +193,8 @@ public class PlaylistFragment extends BaseListInfoFragment {
}
}
+ playlistCtrl.setVisibility(View.VISIBLE);
+
imageLoader.displayImage(result.uploader_avatar_url, headerUploaderAvatar, DISPLAY_AVATAR_OPTIONS);
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, (int) result.stream_count, (int) result.stream_count));
@@ -160,7 +205,7 @@ public class PlaylistFragment extends BaseListInfoFragment {
headerPlayAllButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- startActivity(buildPlaylistIntent(MainVideoPlayer.class));
+ NavigationHelper.playOnMainPlayer(activity, getPlayQueue());
}
});
headerPopupButton.setOnClickListener(new View.OnClickListener() {
@@ -173,26 +218,29 @@ public class PlaylistFragment extends BaseListInfoFragment {
toast.show();
return;
}
- activity.startService(buildPlaylistIntent(PopupVideoPlayer.class));
+ NavigationHelper.playOnPopupPlayer(activity, getPlayQueue());
}
});
headerBackgroundButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- activity.startService(buildPlaylistIntent(BackgroundPlayer.class));
+ NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue());
}
});
}
- private Intent buildPlaylistIntent(final Class targetClazz) {
- final PlayQueue playQueue = new ExternalPlayQueue(
+ private PlayQueue getPlayQueue() {
+ return getPlayQueue(0);
+ }
+
+ private PlayQueue getPlayQueue(final int index) {
+ return new PlaylistPlayQueue(
currentInfo.service_id,
currentInfo.url,
currentInfo.next_streams_url,
infoListAdapter.getItemsList(),
- 0
+ index
);
- return NavigationHelper.getPlayerIntent(activity, targetClazz, playQueue);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index fae97bb7b..fb54a15c3 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -165,7 +165,7 @@ public class SearchFragment extends BaseListFragment items = new ArrayList<>();
private final Context context;
private OnSuggestionItemSelected listener;
- private boolean showSugestinHistory = true;
+ private boolean showSuggestionHistory = true;
public interface OnSuggestionItemSelected {
void onSuggestionItemSelected(SuggestionItem item);
+ void onSuggestionItemInserted(SuggestionItem item);
void onSuggestionItemLongClick(SuggestionItem item);
}
@@ -32,7 +33,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter items) {
this.items.clear();
- if (showSugestinHistory) {
+ if (showSuggestionHistory) {
this.items.addAll(items);
} else {
// remove history items if history is disabled
@@ -49,8 +50,8 @@ public class SuggestionListAdapter extends RecyclerView.Adapter {
void selected(T selectedItem);
+ void held(T selectedItem);
}
private final Context context;
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java
new file mode 100644
index 000000000..1cc2ca19e
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java
@@ -0,0 +1,55 @@
+package org.schabi.newpipe.info_list;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.stream.StreamInfoItem;
+
+public class InfoItemDialog {
+ private final AlertDialog dialog;
+
+ public InfoItemDialog(@NonNull final Activity activity,
+ @NonNull final StreamInfoItem info,
+ @NonNull final String[] commands,
+ @NonNull final DialogInterface.OnClickListener actions) {
+ this(activity, commands, actions, info.name, info.uploader_name);
+ }
+
+ public InfoItemDialog(@NonNull final Activity activity,
+ @NonNull final String[] commands,
+ @NonNull final DialogInterface.OnClickListener actions,
+ @NonNull final String title,
+ @Nullable final String additionalDetail) {
+
+ final LayoutInflater inflater = activity.getLayoutInflater();
+ final View bannerView = inflater.inflate(R.layout.dialog_title, null);
+ bannerView.setSelected(true);
+
+ TextView titleView = bannerView.findViewById(R.id.itemTitleView);
+ titleView.setText(title);
+
+ TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
+ if (additionalDetail != null) {
+ detailsView.setText(additionalDetail);
+ detailsView.setVisibility(View.VISIBLE);
+ } else {
+ detailsView.setVisibility(View.GONE);
+ }
+
+ dialog = new AlertDialog.Builder(activity)
+ .setCustomTitle(bannerView)
+ .setItems(commands, actions)
+ .create();
+ }
+
+ public void show() {
+ dialog.show();
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
index 138503d39..48f775070 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
@@ -67,6 +67,38 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
}
});
+
+ switch (item.stream_type) {
+ case AUDIO_STREAM:
+ case VIDEO_STREAM:
+ case FILE:
+ enableLongClick(item);
+ break;
+ case LIVE_STREAM:
+ case AUDIO_LIVE_STREAM:
+ case NONE:
+ default:
+ disableLongClick();
+ break;
+ }
+ }
+
+ private void enableLongClick(final StreamInfoItem item) {
+ itemView.setLongClickable(true);
+ itemView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ if (itemBuilder.getOnStreamSelectedListener() != null) {
+ itemBuilder.getOnStreamSelectedListener().held(item);
+ }
+ return true;
+ }
+ });
+ }
+
+ private void disableLongClick() {
+ itemView.setLongClickable(false);
+ itemView.setOnLongClickListener(null);
}
/**
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
index 863eaf3e8..443da74d4 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
@@ -48,6 +48,7 @@ import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.util.ListHelper;
+import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
@@ -68,6 +69,10 @@ public final class BackgroundPlayer extends Service {
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
public static final String ACTION_PLAY_NEXT = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
public static final String ACTION_PLAY_PREVIOUS = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
+ public static final String ACTION_FAST_REWIND = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
+ public static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
+
+ public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
private BasePlayerImpl basePlayerImpl;
private LockManager lockManager;
@@ -130,16 +135,6 @@ public final class BackgroundPlayer extends Service {
/*//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////*/
-
- public void openControl(final Context context) {
- Intent intent = new Intent(context, BackgroundPlayerActivity.class);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- context.startActivity(intent);
- context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
- }
-
private void onClose() {
if (DEBUG) Log.d(TAG, "onClose() called");
@@ -191,6 +186,8 @@ public final class BackgroundPlayer extends Service {
}
private void setupNotification(RemoteViews remoteViews) {
+ if (basePlayerImpl == null) return;
+
remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
@@ -203,10 +200,21 @@ public final class BackgroundPlayer extends Service {
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
- remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
- remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
+ if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) {
+ remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous);
+ remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next);
+ remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
+ remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
+ } else {
+ remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind);
+ remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward);
+ remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
+ remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
+ }
setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode());
}
@@ -241,17 +249,15 @@ public final class BackgroundPlayer extends Service {
//////////////////////////////////////////////////////////////////////////*/
private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
- final String methodName = "setImageResource";
-
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
- remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_off);
+ remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_off);
break;
case Player.REPEAT_MODE_ONE:
- remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_one);
+ remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_one);
break;
case Player.REPEAT_MODE_ALL:
- remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_all);
+ remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all);
break;
}
}
@@ -372,6 +378,7 @@ public final class BackgroundPlayer extends Service {
@Override
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
+ if (currentItem == item && currentInfo == info) return;
super.sync(item, info);
resetNotification();
@@ -380,9 +387,10 @@ public final class BackgroundPlayer extends Service {
}
@Override
+ @Nullable
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams);
- if (index < 0) return null;
+ if (index < 0 || index >= info.audio_streams.size()) return null;
final AudioStream audio = info.audio_streams.get(index);
return buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
@@ -449,6 +457,8 @@ public final class BackgroundPlayer extends Service {
intentFilter.addAction(ACTION_REPEAT);
intentFilter.addAction(ACTION_PLAY_PREVIOUS);
intentFilter.addAction(ACTION_PLAY_NEXT);
+ intentFilter.addAction(ACTION_FAST_REWIND);
+ intentFilter.addAction(ACTION_FAST_FORWARD);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
@@ -469,7 +479,7 @@ public final class BackgroundPlayer extends Service {
onVideoPlayPause();
break;
case ACTION_OPEN_CONTROLS:
- openControl(getApplicationContext());
+ NavigationHelper.openBackgroundPlayerControl(getApplicationContext());
break;
case ACTION_REPEAT:
onRepeatClicked();
@@ -480,6 +490,12 @@ public final class BackgroundPlayer extends Service {
case ACTION_PLAY_PREVIOUS:
onPlayPrevious();
break;
+ case ACTION_FAST_FORWARD:
+ onFastForward();
+ break;
+ case ACTION_FAST_REWIND:
+ onFastRewind();
+ break;
case Intent.ACTION_SCREEN_ON:
onScreenOnOff(true);
break;
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 8508bb237..427c97741 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
@@ -26,6 +26,7 @@ import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
@@ -76,7 +77,6 @@ import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
@@ -134,6 +134,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
protected final static int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
protected final static int PLAY_PREV_ACTIVATION_LIMIT = 5000; // 5 seconds
protected final static int PROGRESS_LOOP_INTERVAL = 500;
+ protected final static int RECOVERY_SKIP_THRESHOLD = 3000; // 3 seconds
protected SimpleExoPlayer simpleExoPlayer;
protected AudioReactor audioReactor;
@@ -193,7 +194,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
.observeOn(AndroidSchedulers.mainThread())
.filter(new Predicate() {
@Override
- public boolean test(@NonNull Long aLong) throws Exception {
+ public boolean test(Long aLong) throws Exception {
return isProgressLoopRunning();
}
})
@@ -235,7 +236,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
initPlayback(queue);
}
- protected void initPlayback(@NonNull final PlayQueue queue) {
+ protected void initPlayback(final PlayQueue queue) {
playQueue = queue;
playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue);
@@ -453,16 +454,20 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
final PlayQueueItem currentSourceItem = playQueue.getItem();
// Check if already playing correct window
- final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
+ final boolean isCurrentWindowCorrect =
+ simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
// Check if recovering
- if (isCurrentWindowCorrect && currentSourceItem != null &&
- currentSourceItem.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
+ if (isCurrentWindowCorrect && currentSourceItem != null) {
/* Recovering with sub-second position may cause a long buffer delay in ExoPlayer,
* rounding this position to the nearest second will help alleviate this.*/
final long position = currentSourceItem.getRecoveryPosition();
- if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)position));
+ /* Skip recovering if the recovery position is not set.*/
+ if (position == PlayQueueItem.RECOVERY_UNSET) return;
+
+ if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex +
+ " at: " + getTimeString((int)position));
simpleExoPlayer.seekTo(currentSourceItem.getRecoveryPosition());
playQueue.unsetRecovery(currentSourceIndex);
}
@@ -515,7 +520,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
break;
case Player.STATE_READY: //3
recover();
-
if (!isPrepared) {
isPrepared = true;
onPrepared(playWhenReady);
@@ -545,14 +549,18 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
* an error to the play queue based on if the current error can be skipped.
*
* This is done because ExoPlayer reports the source exceptions before window is
- * transitioned on seamless playback.
+ * transitioned on seamless playback. Because player error causes ExoPlayer to go
+ * back to {@link Player#STATE_IDLE STATE_IDLE}, we reset and prepare the media source
+ * again to resume playback.
*
- * Because player error causes ExoPlayer to go back to {@link Player#STATE_IDLE STATE_IDLE},
- * we reset and prepare the media source again to resume playback.
+ * In the event that this error is produced during a valid stream playback, we save the
+ * current position so the playback may be recovered and resumed manually by the user. This
+ * happens only if the playback is {@link #RECOVERY_SKIP_THRESHOLD} milliseconds until complete.
+ *
* If a runtime error occurred, then we can try to recover it by restarting the playback
- * after setting the timestamp recovery.
+ * after setting the timestamp recovery.