Merge branch 'play-queue-enhancement' of https://github.com/karyogamy/NewPipe into pqe
This commit is contained in:
commit
6d60e6698a
|
@ -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;
|
||||
|
@ -459,6 +461,11 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> 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);
|
||||
|
@ -476,6 +483,32 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
detailControlsPopup.setOnTouchListener(getOnControlsTouchListener());
|
||||
}
|
||||
|
||||
private void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
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
|
||||
|
@ -792,16 +825,16 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> 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() {
|
||||
|
@ -820,13 +853,11 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -866,8 +897,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> 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);
|
||||
|
|
|
@ -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<I, N> extends BaseStateFragment<I> 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<ChannelInfoItem>() {
|
||||
|
@ -149,6 +157,9 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
|||
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
|
||||
selectedItem.service_id, selectedItem.url, selectedItem.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(ChannelInfoItem selectedItem) {}
|
||||
});
|
||||
|
||||
infoListAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<PlaylistInfoItem>() {
|
||||
|
@ -159,6 +170,9 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
|||
useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(),
|
||||
selectedItem.service_id, selectedItem.url, selectedItem.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(PlaylistInfoItem selectedItem) {}
|
||||
});
|
||||
|
||||
itemsList.clearOnScrollListeners();
|
||||
|
@ -176,6 +190,31 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
|||
}
|
||||
}
|
||||
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -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;
|
||||
|
@ -69,6 +81,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
private TextView headerSubscribersTextView;
|
||||
private Button headerSubscribeButton;
|
||||
|
||||
private LinearLayout headerPlayAllButton;
|
||||
private LinearLayout headerPopupButton;
|
||||
private LinearLayout headerBackgroundButton;
|
||||
|
||||
private MenuItem menuRssButton;
|
||||
|
||||
public static ChannelFragment getInstance(int serviceId, String url, String name) {
|
||||
|
@ -125,9 +141,52 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
|
||||
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
|
||||
|
||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.channel_play_all_button);
|
||||
headerPopupButton = headerRootLayout.findViewById(R.id.channel_play_popup_button);
|
||||
headerBackgroundButton = headerRootLayout.findViewById(R.id.channel_play_bg_button);
|
||||
|
||||
return headerRootLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -391,6 +450,46 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
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
|
||||
|
|
|
@ -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,8 +14,8 @@ 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.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -23,12 +24,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;
|
||||
|
@ -51,9 +52,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
private ImageView headerUploaderAvatar;
|
||||
private TextView headerStreamCount;
|
||||
|
||||
private Button headerPlayAllButton;
|
||||
private Button headerPopupButton;
|
||||
private Button headerBackgroundButton;
|
||||
private LinearLayout headerPlayAllButton;
|
||||
private LinearLayout headerPopupButton;
|
||||
private LinearLayout headerBackgroundButton;
|
||||
|
||||
public static PlaylistFragment getInstance(int serviceId, String url, String name) {
|
||||
PlaylistFragment instance = new PlaylistFragment();
|
||||
|
@ -103,6 +104,45 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
inflater.inflate(R.menu.menu_playlist, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -160,7 +200,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
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 +213,29 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
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
|
||||
|
|
|
@ -19,8 +19,6 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
|
|||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -134,6 +132,9 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
|||
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(ChannelInfoItem selectedItem) {}
|
||||
});
|
||||
|
||||
headerRootLayout.setOnClickListener(new View.OnClickListener() {
|
||||
|
|
|
@ -44,6 +44,7 @@ public class InfoItemBuilder {
|
|||
|
||||
public interface OnInfoItemSelectedListener<T extends InfoItem> {
|
||||
void selected(T selectedItem);
|
||||
void held(T selectedItem);
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
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.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
|
||||
public class InfoItemDialog {
|
||||
private final AlertDialog dialog;
|
||||
|
||||
public InfoItemDialog(@NonNull final Activity activity,
|
||||
@NonNull final InfoItem item,
|
||||
@NonNull final String[] commands,
|
||||
@NonNull final DialogInterface.OnClickListener actions) {
|
||||
|
||||
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(item.name);
|
||||
TextView typeView = bannerView.findViewById(R.id.itemTypeView);
|
||||
typeView.setText(item.info_type.name());
|
||||
|
||||
dialog = new AlertDialog.Builder(activity)
|
||||
.setCustomTitle(bannerView)
|
||||
.setItems(commands, actions)
|
||||
.create();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
dialog.show();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -193,7 +193,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.filter(new Predicate<Long>() {
|
||||
@Override
|
||||
public boolean test(@NonNull Long aLong) throws Exception {
|
||||
public boolean test(Long aLong) throws Exception {
|
||||
return isProgressLoopRunning();
|
||||
}
|
||||
})
|
||||
|
@ -235,7 +235,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);
|
||||
|
@ -514,11 +514,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
}
|
||||
break;
|
||||
case Player.STATE_READY: //3
|
||||
recover();
|
||||
|
||||
if (!isPrepared) {
|
||||
isPrepared = true;
|
||||
onPrepared(playWhenReady);
|
||||
recover();
|
||||
break;
|
||||
}
|
||||
if (currentState == STATE_PAUSED_SEEK) break;
|
||||
|
@ -591,12 +590,12 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with window index = [" + newWindowIndex + "]");
|
||||
|
||||
// If the user selects a new track, then the discontinuity occurs after the index is changed.
|
||||
// Therefore, the only source that causes a discrepancy would be autoplay,
|
||||
// Therefore, the only source that causes a discrepancy would be gapless transition,
|
||||
// which can only offset the current track by +1.
|
||||
if (newWindowIndex != playQueue.getIndex() && playbackManager != null) {
|
||||
if (newWindowIndex == playQueue.getIndex() + 1) {
|
||||
playQueue.offsetIndex(+1);
|
||||
playbackManager.load();
|
||||
}
|
||||
playbackManager.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -613,6 +612,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Blocking...");
|
||||
|
||||
currentItem = null;
|
||||
currentInfo = null;
|
||||
simpleExoPlayer.stop();
|
||||
isPrepared = false;
|
||||
|
||||
|
@ -631,17 +632,21 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
}
|
||||
|
||||
@Override
|
||||
public void sync(@android.support.annotation.NonNull final PlayQueueItem item,
|
||||
public void sync(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info) {
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Syncing...");
|
||||
|
||||
if (currentItem == item && currentInfo == info) return;
|
||||
currentItem = item;
|
||||
currentInfo = info;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Syncing...");
|
||||
if (simpleExoPlayer == null) return;
|
||||
|
||||
// Check if on wrong window
|
||||
final int currentSourceIndex = playQueue.getIndex();
|
||||
if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) {
|
||||
final int currentSourceIndex = playQueue.indexOf(item);
|
||||
if (currentSourceIndex != playQueue.getIndex()) {
|
||||
Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex +
|
||||
"], queue index=[" + playQueue.getIndex() + "]");
|
||||
} else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) {
|
||||
final long startPos = info != null ? info.start_position : 0;
|
||||
if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos));
|
||||
simpleExoPlayer.seekTo(currentSourceIndex, startPos);
|
||||
|
@ -756,10 +761,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
} else {
|
||||
playQueue.setIndex(index);
|
||||
}
|
||||
|
||||
if (!isPlaying()) {
|
||||
onVideoPlayPause();
|
||||
}
|
||||
}
|
||||
|
||||
public void seekBy(int milliSeconds) {
|
||||
|
@ -826,15 +827,15 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
}
|
||||
|
||||
public String getVideoUrl() {
|
||||
return currentItem == null ? null : currentItem.getUrl();
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl();
|
||||
}
|
||||
|
||||
public String getVideoTitle() {
|
||||
return currentItem == null ? null : currentItem.getTitle();
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle();
|
||||
}
|
||||
|
||||
public String getUploaderName() {
|
||||
return currentItem == null ? null : currentItem.getUploader();
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
|
@ -870,8 +871,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
}
|
||||
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
final PlaybackParameters defaultParameters = new PlaybackParameters(1f, 1f);
|
||||
if (simpleExoPlayer == null) return defaultParameters;
|
||||
final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters();
|
||||
return parameters == null ? new PlaybackParameters(1f, 1f) : parameters;
|
||||
return parameters == null ? defaultParameters : parameters;
|
||||
}
|
||||
|
||||
public void setPlaybackParameters(float speed, float pitch) {
|
||||
|
@ -900,7 +903,9 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
final int queuePos = playQueue.getIndex();
|
||||
final long windowPos = simpleExoPlayer.getCurrentPosition();
|
||||
|
||||
setRecovery(queuePos, windowPos);
|
||||
if (windowPos > 0 && windowPos <= simpleExoPlayer.getDuration()) {
|
||||
setRecovery(queuePos, windowPos);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRecovery(final int queuePos, final long windowPos) {
|
||||
|
|
|
@ -48,6 +48,7 @@ import com.google.android.exoplayer2.Player;
|
|||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||
|
@ -397,7 +398,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
getControlsRoot().setVisibility(View.INVISIBLE);
|
||||
queueLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
itemsList.smoothScrollToPosition(playQueue.getIndex());
|
||||
itemsList.scrollToPosition(playQueue.getIndex());
|
||||
}
|
||||
|
||||
private void onQueueClosed() {
|
||||
|
@ -565,6 +566,9 @@ public final class MainVideoPlayer extends Activity {
|
|||
itemsList.setClickable(true);
|
||||
itemsList.setLongClickable(true);
|
||||
|
||||
itemsList.clearOnScrollListeners();
|
||||
itemsList.addOnScrollListener(getQueueScrollListener());
|
||||
|
||||
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||
itemTouchHelper.attachToRecyclerView(itemsList);
|
||||
|
||||
|
@ -578,6 +582,19 @@ public final class MainVideoPlayer extends Activity {
|
|||
});
|
||||
}
|
||||
|
||||
private OnScrollBelowItemsListener getQueueScrollListener() {
|
||||
return new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
public void onScrolledDown(RecyclerView recyclerView) {
|
||||
if (playQueue != null && !playQueue.isComplete()) {
|
||||
playQueue.fetch();
|
||||
} else if (itemsList != null) {
|
||||
itemsList.clearOnScrollListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
|
||||
@Override
|
||||
|
|
|
@ -65,6 +65,7 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
|||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
|
@ -96,7 +97,6 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
|||
public final class PopupVideoPlayer extends Service {
|
||||
private static final String TAG = ".PopupVideoPlayer";
|
||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
private static final int SHUTDOWN_FLING_VELOCITY = 10000;
|
||||
|
||||
private static final int NOTIFICATION_ID = 40028922;
|
||||
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
|
||||
|
@ -112,6 +112,9 @@ public final class PopupVideoPlayer extends Service {
|
|||
private WindowManager.LayoutParams windowLayoutParams;
|
||||
private GestureDetector gestureDetector;
|
||||
|
||||
private int shutdownFlingVelocity;
|
||||
private int tossFlingVelocity;
|
||||
|
||||
private float screenWidth, screenHeight;
|
||||
private float popupWidth, popupHeight;
|
||||
|
||||
|
@ -211,12 +214,14 @@ public final class PopupVideoPlayer extends Service {
|
|||
View rootView = View.inflate(this, R.layout.player_popup, null);
|
||||
playerImpl.setup(rootView);
|
||||
|
||||
shutdownFlingVelocity = PlayerHelper.getShutdownFlingVelocity(this);
|
||||
tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this);
|
||||
|
||||
updateScreenSize();
|
||||
|
||||
final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(this);
|
||||
final float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean popupRememberSizeAndPos = sharedPreferences.getBoolean(getString(R.string.popup_remember_size_pos_key), true);
|
||||
|
||||
float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
|
||||
popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
|
||||
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
|
@ -313,15 +318,6 @@ public final class PopupVideoPlayer extends Service {
|
|||
stopSelf();
|
||||
}
|
||||
|
||||
public void openControl(final Context context) {
|
||||
Intent intent = new Intent(context, PopupVideoPlayerActivity.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));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -366,6 +362,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
private void updatePopupSize(int width, int height) {
|
||||
if (playerImpl == null) return;
|
||||
if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]");
|
||||
|
||||
width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width);
|
||||
|
@ -577,6 +574,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public void sync(@NonNull PlayQueueItem item, @Nullable StreamInfo info) {
|
||||
if (currentItem == item && currentInfo == info) return;
|
||||
super.sync(item, info);
|
||||
updateMetadata();
|
||||
}
|
||||
|
@ -617,7 +615,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
onVideoPlayPause();
|
||||
break;
|
||||
case ACTION_OPEN_CONTROLS:
|
||||
openControl(getApplicationContext());
|
||||
NavigationHelper.openPopupPlayerControl(getApplicationContext());
|
||||
break;
|
||||
case ACTION_REPEAT:
|
||||
onRepeatClicked();
|
||||
|
@ -791,11 +789,20 @@ public final class PopupVideoPlayer extends Service {
|
|||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
if (DEBUG) Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]");
|
||||
if (playerImpl == null) return false;
|
||||
if (Math.abs(velocityX) > SHUTDOWN_FLING_VELOCITY) {
|
||||
if (DEBUG) Log.d(TAG, "Popup close fling velocity= " + velocityX);
|
||||
|
||||
final float absVelocityX = Math.abs(velocityX);
|
||||
final float absVelocityY = Math.abs(velocityY);
|
||||
if (absVelocityX > shutdownFlingVelocity) {
|
||||
onClose();
|
||||
return true;
|
||||
} else if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
|
||||
if (absVelocityX > tossFlingVelocity) windowLayoutParams.x = (int) velocityX;
|
||||
if (absVelocityY > tossFlingVelocity) windowLayoutParams.y = (int) velocityY;
|
||||
checkPositionBounds();
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.google.android.exoplayer2.Player;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||
|
@ -57,6 +58,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
private static final int PLAYBACK_SPEED_POPUP_MENU_GROUP_ID = 61;
|
||||
private static final int PLAYBACK_PITCH_POPUP_MENU_GROUP_ID = 97;
|
||||
|
||||
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
|
||||
|
||||
private View rootView;
|
||||
|
||||
private RecyclerView itemsList;
|
||||
|
@ -225,6 +228,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
itemsList.setAdapter(player.getPlayQueueAdapter());
|
||||
itemsList.setClickable(true);
|
||||
itemsList.setLongClickable(true);
|
||||
itemsList.clearOnScrollListeners();
|
||||
itemsList.addOnScrollListener(getQueueScrollListener());
|
||||
|
||||
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||
itemTouchHelper.attachToRecyclerView(itemsList);
|
||||
|
@ -286,6 +291,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if (player == null) return false;
|
||||
|
||||
player.setPlaybackSpeed(playbackSpeed);
|
||||
return true;
|
||||
}
|
||||
|
@ -304,6 +311,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if (player == null) return false;
|
||||
|
||||
player.setPlaybackPitch(playbackPitch);
|
||||
return true;
|
||||
}
|
||||
|
@ -317,6 +326,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
remove.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if (player == null) return false;
|
||||
|
||||
final int index = player.getPlayQueue().indexOf(item);
|
||||
if (index != -1) player.getPlayQueue().remove(index);
|
||||
return true;
|
||||
|
@ -339,6 +350,19 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
// Component Helpers
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private OnScrollBelowItemsListener getQueueScrollListener() {
|
||||
return new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
public void onScrolledDown(RecyclerView recyclerView) {
|
||||
if (player != null && player.getPlayQueue() != null && !player.getPlayQueue().isComplete()) {
|
||||
player.getPlayQueue().fetch();
|
||||
} else if (itemsList != null) {
|
||||
itemsList.clearOnScrollListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
|
||||
@Override
|
||||
|
@ -349,7 +373,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
final int sourceIndex = source.getLayoutPosition();
|
||||
final int targetIndex = target.getLayoutPosition();
|
||||
player.getPlayQueue().move(sourceIndex, targetIndex);
|
||||
if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -372,11 +396,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
return new PlayQueueItemBuilder.OnSelectedListener() {
|
||||
@Override
|
||||
public void selected(PlayQueueItem item, View view) {
|
||||
player.onSelected(item);
|
||||
if (player != null) player.onSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(PlayQueueItem item, View view) {
|
||||
if (player == null) return;
|
||||
|
||||
final int index = player.getPlayQueue().indexOf(item);
|
||||
if (index != -1) buildItemPopupMenu(item, view);
|
||||
}
|
||||
|
@ -393,7 +419,23 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
private void scrollToSelected() {
|
||||
itemsList.smoothScrollToPosition(player.getPlayQueue().getIndex());
|
||||
if (player == null) return;
|
||||
|
||||
final int currentPlayingIndex = player.getPlayQueue().getIndex();
|
||||
final int currentVisibleIndex;
|
||||
if (itemsList.getLayoutManager() instanceof LinearLayoutManager) {
|
||||
final LinearLayoutManager layout = ((LinearLayoutManager) itemsList.getLayoutManager());
|
||||
currentVisibleIndex = layout.findFirstVisibleItemPosition();
|
||||
} else {
|
||||
currentVisibleIndex = 0;
|
||||
}
|
||||
|
||||
final int distance = Math.abs(currentPlayingIndex - currentVisibleIndex);
|
||||
if (distance < SMOOTH_SCROLL_MAXIMUM_DISTANCE) {
|
||||
itemsList.smoothScrollToPosition(currentPlayingIndex);
|
||||
} else {
|
||||
itemsList.scrollToPosition(currentPlayingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -402,6 +444,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (player == null) return;
|
||||
|
||||
if (view.getId() == repeatButton.getId()) {
|
||||
player.onRepeatClicked();
|
||||
|
||||
|
@ -450,7 +494,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
player.simpleExoPlayer.seekTo(seekBar.getProgress());
|
||||
if (player != null) player.simpleExoPlayer.seekTo(seekBar.getProgress());
|
||||
seekDisplay.setVisibility(View.GONE);
|
||||
seeking = false;
|
||||
}
|
||||
|
|
|
@ -272,17 +272,18 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
|
||||
|
||||
final VideoStream video;
|
||||
final int index;
|
||||
if (playbackQuality == null) {
|
||||
final int index = getDefaultResolutionIndex(videos);
|
||||
video = videos.get(index);
|
||||
index = getDefaultResolutionIndex(videos);
|
||||
} else {
|
||||
final int index = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
||||
video = videos.get(index);
|
||||
index = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
||||
}
|
||||
if (index < 0 || index >= videos.size()) return null;
|
||||
final VideoStream video = videos.get(index);
|
||||
|
||||
final MediaSource streamSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
|
||||
final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams);
|
||||
|
|
|
@ -12,6 +12,8 @@ import java.text.NumberFormat;
|
|||
import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class PlayerHelper {
|
||||
private PlayerHelper() {}
|
||||
|
||||
|
@ -56,6 +58,10 @@ public class PlayerHelper {
|
|||
return isUsingOldPlayer(context, false);
|
||||
}
|
||||
|
||||
public static boolean isRememberingPopupDimensions(@Nonnull final Context context) {
|
||||
return isRememberingPopupDimensions(context, true);
|
||||
}
|
||||
|
||||
public static long getPreferredCacheSize(@NonNull final Context context) {
|
||||
return 64 * 1024 * 1024L;
|
||||
}
|
||||
|
@ -83,6 +89,15 @@ public class PlayerHelper {
|
|||
public static boolean isUsingDSP(@NonNull final Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int getShutdownFlingVelocity(@Nonnull final Context context) {
|
||||
return 10000;
|
||||
}
|
||||
|
||||
public static int getTossFlingVelocity(@Nonnull final Context context) {
|
||||
return 2500;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Private helpers
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -103,4 +118,8 @@ public class PlayerHelper {
|
|||
private static boolean isUsingOldPlayer(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.use_old_player_key), b);
|
||||
}
|
||||
|
||||
private static boolean isRememberingPopupDimensions(@Nonnull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import io.reactivex.subjects.PublishSubject;
|
|||
public class MediaSourceManager {
|
||||
private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode());
|
||||
// One-side rolling window size for default loading
|
||||
// Effectively loads windowSize * 2 + 1 streams, must be greater than 0
|
||||
// Effectively loads windowSize * 2 + 1 streams per call to load, must be greater than 0
|
||||
private final int windowSize;
|
||||
private final PlaybackListener playbackListener;
|
||||
private final PlayQueue playQueue;
|
||||
|
@ -38,7 +38,7 @@ public class MediaSourceManager {
|
|||
// The higher it is, the less loading occurs during rapid noncritical timeline changes
|
||||
// Not recommended to go below 100ms
|
||||
private final long loadDebounceMillis;
|
||||
private final PublishSubject<Long> loadSignal;
|
||||
private final PublishSubject<Long> debouncedLoadSignal;
|
||||
private final Disposable debouncedLoader;
|
||||
|
||||
private final DeferredMediaSource.Callback sourceBuilder;
|
||||
|
@ -52,7 +52,7 @@ public class MediaSourceManager {
|
|||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this(listener, playQueue, 1, 1000L);
|
||||
this(listener, playQueue, 1, 400L);
|
||||
}
|
||||
|
||||
private MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
|
@ -69,7 +69,7 @@ public class MediaSourceManager {
|
|||
this.loadDebounceMillis = loadDebounceMillis;
|
||||
|
||||
this.syncReactor = new SerialDisposable();
|
||||
this.loadSignal = PublishSubject.create();
|
||||
this.debouncedLoadSignal = PublishSubject.create();
|
||||
this.debouncedLoader = getDebouncedLoader();
|
||||
|
||||
this.sourceBuilder = getSourceBuilder();
|
||||
|
@ -101,7 +101,7 @@ public class MediaSourceManager {
|
|||
* Dispose the manager and releases all message buses and loaders.
|
||||
* */
|
||||
public void dispose() {
|
||||
if (loadSignal != null) loadSignal.onComplete();
|
||||
if (debouncedLoadSignal != null) debouncedLoadSignal.onComplete();
|
||||
if (debouncedLoader != null) debouncedLoader.dispose();
|
||||
if (playQueueReactor != null) playQueueReactor.cancel();
|
||||
if (syncReactor != null) syncReactor.dispose();
|
||||
|
@ -118,7 +118,7 @@ public class MediaSourceManager {
|
|||
* Unblocks the player once the item at the current index is loaded.
|
||||
* */
|
||||
public void load() {
|
||||
loadSignal.onNext(System.currentTimeMillis());
|
||||
loadDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,12 +157,12 @@ public class MediaSourceManager {
|
|||
}
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueEvent event) {
|
||||
if (playQueue.isEmpty()) {
|
||||
if (playQueue.isEmpty() && playQueue.isComplete()) {
|
||||
playbackListener.shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// why no pattern matching in Java =(
|
||||
// Event specific action
|
||||
switch (event.type()) {
|
||||
case INIT:
|
||||
case REORDER:
|
||||
|
@ -172,37 +172,34 @@ public class MediaSourceManager {
|
|||
case APPEND:
|
||||
populateSources();
|
||||
break;
|
||||
case SELECT:
|
||||
sync();
|
||||
break;
|
||||
case REMOVE:
|
||||
final RemoveEvent removeEvent = (RemoveEvent) event;
|
||||
remove(removeEvent.getRemoveIndex());
|
||||
// Sync only when the currently playing is removed
|
||||
if (removeEvent.getQueueIndex() == removeEvent.getRemoveIndex()) sync();
|
||||
break;
|
||||
case MOVE:
|
||||
final MoveEvent moveEvent = (MoveEvent) event;
|
||||
move(moveEvent.getFromIndex(), moveEvent.getToIndex());
|
||||
break;
|
||||
case SELECT:
|
||||
case RECOVERY:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Loading and Syncing
|
||||
switch (event.type()) {
|
||||
case INIT:
|
||||
case REORDER:
|
||||
case ERROR:
|
||||
case APPEND:
|
||||
loadInternal(); // low frequency, critical events
|
||||
loadImmediate(); // low frequency, critical events
|
||||
break;
|
||||
case APPEND:
|
||||
case REMOVE:
|
||||
case SELECT:
|
||||
case MOVE:
|
||||
case RECOVERY:
|
||||
default:
|
||||
load(); // high frequency or noncritical events
|
||||
loadDebounced(); // high frequency or noncritical events
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -262,7 +259,11 @@ public class MediaSourceManager {
|
|||
syncReactor.set(currentItem.getStream().subscribe(syncPlayback, onError));
|
||||
}
|
||||
|
||||
private void loadInternal() {
|
||||
private void loadDebounced() {
|
||||
debouncedLoadSignal.onNext(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private void loadImmediate() {
|
||||
// The current item has higher priority
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
|
||||
|
@ -290,7 +291,9 @@ public class MediaSourceManager {
|
|||
|
||||
final DeferredMediaSource mediaSource = (DeferredMediaSource) sources.getMediaSource(playQueue.indexOf(item));
|
||||
if (mediaSource.state() == DeferredMediaSource.STATE_PREPARED) mediaSource.load();
|
||||
if (tryUnblock()) sync();
|
||||
|
||||
tryUnblock();
|
||||
if (!isBlocked) sync();
|
||||
}
|
||||
|
||||
private void resetSources() {
|
||||
|
@ -307,13 +310,13 @@ public class MediaSourceManager {
|
|||
}
|
||||
|
||||
private Disposable getDebouncedLoader() {
|
||||
return loadSignal
|
||||
return debouncedLoadSignal
|
||||
.debounce(loadDebounceMillis, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Long>() {
|
||||
@Override
|
||||
public void accept(Long timestamp) throws Exception {
|
||||
loadInternal();
|
||||
loadImmediate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ public interface PlaybackListener {
|
|||
*
|
||||
* May be called at any time.
|
||||
* */
|
||||
@Nullable
|
||||
MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info);
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
|
||||
boolean isInitial;
|
||||
boolean isComplete;
|
||||
|
||||
int serviceId;
|
||||
String baseUrl;
|
||||
String nextUrl;
|
||||
|
||||
transient Disposable fetchReactor;
|
||||
|
||||
AbstractInfoPlayQueue(final U item) {
|
||||
this(item.service_id, item.url, null, Collections.<InfoItem>emptyList(), 0);
|
||||
}
|
||||
|
||||
AbstractInfoPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
final int index) {
|
||||
super(index, extractListItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isInitial = streams.isEmpty();
|
||||
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
|
||||
}
|
||||
|
||||
abstract protected String getTag();
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
SingleObserver<T> getHeadListObserver() {
|
||||
return new SingleObserver<T>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || !isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull T result) {
|
||||
isInitial = false;
|
||||
if (!result.has_more_streams) isComplete = true;
|
||||
nextUrl = result.next_streams_url;
|
||||
|
||||
append(extractListItems(result.related_streams));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SingleObserver<ListExtractor.NextItemsResult> getNextItemsObserver() {
|
||||
return new SingleObserver<ListExtractor.NextItemsResult>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull ListExtractor.NextItemsResult result) {
|
||||
if (!result.hasMoreStreams()) isComplete = true;
|
||||
nextUrl = result.nextItemsUrl;
|
||||
|
||||
append(extractListItems(result.nextItemsList));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (fetchReactor != null) fetchReactor.dispose();
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> extractListItems(final List<InfoItem> infos) {
|
||||
List<PlayQueueItem> result = new ArrayList<>();
|
||||
for (final InfoItem stream : infos) {
|
||||
if (stream instanceof StreamInfoItem) {
|
||||
result.add(new PlayQueueItem((StreamInfoItem) stream));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, ChannelInfoItem> {
|
||||
public ChannelPlayQueue(final ChannelInfoItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTag() {
|
||||
return "ChannelPlayQueue@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
if (this.isInitial) {
|
||||
ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextItemsObserver());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class ExternalPlayQueue extends PlayQueue {
|
||||
private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode());
|
||||
|
||||
private boolean isComplete;
|
||||
|
||||
private int serviceId;
|
||||
private String baseUrl;
|
||||
private String nextUrl;
|
||||
|
||||
private transient Disposable fetchReactor;
|
||||
|
||||
public ExternalPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
final int index) {
|
||||
super(index, extractPlaylistItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isComplete = nextPageUrl == null || nextPageUrl.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return isComplete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getPlaylistObserver());
|
||||
}
|
||||
|
||||
private SingleObserver<ListExtractor.NextItemsResult> getPlaylistObserver() {
|
||||
return new SingleObserver<ListExtractor.NextItemsResult>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) {
|
||||
d.dispose();
|
||||
} else {
|
||||
fetchReactor = d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull ListExtractor.NextItemsResult result) {
|
||||
if (!result.hasMoreStreams()) isComplete = true;
|
||||
nextUrl = result.nextItemsUrl;
|
||||
|
||||
append(extractPlaylistItems(result.nextItemsList));
|
||||
|
||||
fetchReactor.dispose();
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e);
|
||||
isComplete = true;
|
||||
append(); // Notify change
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (fetchReactor != null) fetchReactor.dispose();
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> extractPlaylistItems(final List<InfoItem> infos) {
|
||||
List<PlayQueueItem> result = new ArrayList<>();
|
||||
for (final InfoItem stream : infos) {
|
||||
if (stream instanceof StreamInfoItem) {
|
||||
result.add(new PlayQueueItem((StreamInfoItem) stream));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -123,7 +123,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
* May throw {@link IndexOutOfBoundsException}.
|
||||
* */
|
||||
public PlayQueueItem getItem(int index) {
|
||||
if (index >= streams.size() || streams.get(index) == null) return null;
|
||||
if (index < 0 || index >= streams.size() || streams.get(index) == null) return null;
|
||||
return streams.get(index);
|
||||
}
|
||||
|
||||
|
@ -279,7 +279,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
queueIndex.set(currentIndex % (size - 1));
|
||||
|
||||
} else if (currentIndex == removeIndex && currentIndex == size - 1){
|
||||
queueIndex.set(removeIndex - 1);
|
||||
queueIndex.set(0);
|
||||
}
|
||||
|
||||
if (backup != null) {
|
||||
|
|
|
@ -107,6 +107,8 @@ public class PlayQueueItemBuilder {
|
|||
.bitmapConfig(Bitmap.Config.RGB_565) // Users won't be able to see much anyways
|
||||
.preProcessor(bitmapProcessor)
|
||||
.imageScaleType(ImageScaleType.EXACTLY)
|
||||
.cacheInMemory(true)
|
||||
.cacheOnDisk(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo, PlaylistInfoItem> {
|
||||
public PlaylistPlayQueue(final PlaylistInfoItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
public PlaylistPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final List<InfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTag() {
|
||||
return "PlaylistPlayQueue@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch() {
|
||||
if (this.isInitial) {
|
||||
ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextItemsObserver());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,21 @@
|
|||
package org.schabi.newpipe.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public final class SinglePlayQueue extends PlayQueue {
|
||||
public SinglePlayQueue(final StreamInfoItem item) {
|
||||
this(new PlayQueueItem(item));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final StreamInfo info) {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
this(new PlayQueueItem(info));
|
||||
}
|
||||
|
||||
private SinglePlayQueue(final PlayQueueItem playQueueItem) {
|
||||
super(0, Collections.singletonList(playQueueItem));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,10 +5,11 @@ import android.content.ActivityNotFoundException;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
|
@ -28,7 +29,12 @@ import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
|||
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||
import org.schabi.newpipe.history.HistoryActivity;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.BackgroundPlayerActivity;
|
||||
import org.schabi.newpipe.player.BasePlayer;
|
||||
import org.schabi.newpipe.player.MainVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayerActivity;
|
||||
import org.schabi.newpipe.player.VideoPlayer;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
|
@ -77,6 +83,29 @@ public class NavigationHelper {
|
|||
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch);
|
||||
}
|
||||
|
||||
public static void playOnMainPlayer(final Context context, final PlayQueue queue) {
|
||||
context.startActivity(getPlayerIntent(context, MainVideoPlayer.class, queue));
|
||||
}
|
||||
|
||||
public static void playOnPopupPlayer(final Context context, final PlayQueue queue) {
|
||||
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
context.startService(getPlayerIntent(context, PopupVideoPlayer.class, queue));
|
||||
}
|
||||
|
||||
public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue) {
|
||||
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
context.startService(getPlayerIntent(context, BackgroundPlayer.class, queue));
|
||||
}
|
||||
|
||||
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue) {
|
||||
Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
|
||||
context.startService(getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue));
|
||||
}
|
||||
|
||||
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue) {
|
||||
Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show();
|
||||
context.startService(getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue));
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Through FragmentManager
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -230,6 +259,23 @@ public class NavigationHelper {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static void openBackgroundPlayerControl(final Context context) {
|
||||
openServicePlayerControl(context, BackgroundPlayerActivity.class);
|
||||
}
|
||||
|
||||
public static void openPopupPlayerControl(final Context context) {
|
||||
openServicePlayerControl(context, PopupVideoPlayerActivity.class);
|
||||
}
|
||||
|
||||
private static void openServicePlayerControl(final Context context, final Class clazz) {
|
||||
final Intent intent = new Intent(context, clazz);
|
||||
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));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Link handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -6,75 +6,166 @@
|
|||
android:id="@+id/channel_header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp">
|
||||
android:background="?attr/contrast_background_color">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/channel_banner_image"
|
||||
<RelativeLayout
|
||||
android:id="@+id/channel_metadata"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="70dp"
|
||||
android:background="@android:color/black"
|
||||
android:fitsSystemWindows="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/channel_banner"
|
||||
tools:ignore="ContentDescription"/>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/channel_avatar_view"
|
||||
android:layout_width="@dimen/channel_avatar_size"
|
||||
android:layout_height="@dimen/channel_avatar_size"
|
||||
android:layout_alignTop="@id/channel_banner_image"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:src="@drawable/buddy"
|
||||
app:civ_border_color="#ffffff"
|
||||
app:civ_border_width="2dp"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
<ImageView
|
||||
android:id="@+id/channel_banner_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="70dp"
|
||||
android:background="@android:color/black"
|
||||
android:fitsSystemWindows="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/channel_banner"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channel_title_view"
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/channel_avatar_view"
|
||||
android:layout_width="@dimen/channel_avatar_size"
|
||||
android:layout_height="@dimen/channel_avatar_size"
|
||||
android:layout_alignTop="@id/channel_banner_image"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:src="@drawable/buddy"
|
||||
app:civ_border_color="#ffffff"
|
||||
app:civ_border_width="2dp"
|
||||
tools:ignore="RtlHardcoded"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channel_title_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/channel_banner_image"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_toLeftOf="@+id/channel_subscribe_button"
|
||||
android:layout_toRightOf="@+id/channel_avatar_view"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_detail_title_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Lorem ipsum dolor"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channel_subscriber_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/channel_title_view"
|
||||
android:layout_alignRight="@+id/channel_title_view"
|
||||
android:layout_below="@+id/channel_title_view"
|
||||
android:ellipsize="end"
|
||||
android:gravity="left|center"
|
||||
android:lines="1"
|
||||
android:textSize="@dimen/channel_subscribers_text_size"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="123,141,411 subscribers"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatButton
|
||||
android:id="@+id/channel_subscribe_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/channel_banner_image"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginRight="2dp"
|
||||
android:text="@string/subscribe_button_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:theme="@style/RedButton"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/channel_banner_image"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_toLeftOf="@+id/channel_subscribe_button"
|
||||
android:layout_toRightOf="@+id/channel_avatar_view"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_detail_title_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Lorem ipsum dolor"/>
|
||||
android:layout_height="25dp"
|
||||
android:id="@+id/play_control"
|
||||
android:layout_margin="@dimen/software_component_item_padding"
|
||||
android:layout_below="@+id/channel_metadata">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channel_subscriber_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/channel_title_view"
|
||||
android:layout_alignRight="@+id/channel_title_view"
|
||||
android:layout_below="@+id/channel_title_view"
|
||||
android:ellipsize="end"
|
||||
android:gravity="left|center"
|
||||
android:lines="1"
|
||||
android:textSize="@dimen/channel_subscribers_text_size"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="123,141,411 subscribers"
|
||||
tools:visibility="visible"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:id="@+id/channel_play_bg_button">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/controls_background_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:drawablePadding="4dp"
|
||||
android:drawableLeft="?attr/audio"
|
||||
android:drawableStart="?attr/audio"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View android:id="@+id/anchorLeft"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:clickable="false"
|
||||
android:background="?attr/colorAccent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:id="@+id/channel_play_all_button">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/play_all"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View android:id="@+id/anchorRight"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:clickable="false"
|
||||
android:background="?attr/colorAccent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:id="@+id/channel_play_popup_button">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/controls_popup_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:drawablePadding="4dp"
|
||||
android:drawableLeft="?attr/popup"
|
||||
android:drawableStart="?attr/popup"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.v7.widget.AppCompatButton
|
||||
android:id="@+id/channel_subscribe_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/channel_banner_image"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginRight="2dp"
|
||||
android:text="@string/subscribe_button_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:theme="@style/RedButton"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/itemRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. "/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemTypeView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemTitleView"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
tools:text="TYPE" />
|
||||
</RelativeLayout>
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="@dimen/video_item_search_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
|
@ -28,11 +29,12 @@
|
|||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Channel Title, Lorem ipsum"/>
|
||||
tools:text="Channel Title, Lorem ipsum" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemChannelDescriptionView"
|
||||
|
@ -41,11 +43,12 @@
|
|||
android:layout_above="@+id/itemAdditionalDetails"
|
||||
android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:lines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit"/>
|
||||
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemAdditionalDetails"
|
||||
|
@ -53,6 +56,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="@dimen/video_item_search_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<ImageView
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="@dimen/video_item_search_height"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<ImageView
|
||||
|
@ -48,6 +49,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
|
@ -60,6 +62,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemVideoTitleView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
|
@ -71,6 +74,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/video_item_search_padding">
|
||||
|
||||
<ImageView
|
||||
|
@ -48,11 +49,13 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemUploaderView"
|
||||
|
@ -60,8 +63,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemVideoTitleView"
|
||||
android:layout_toRightOf="@+id/itemThumbnailView"
|
||||
android:layout_toEndOf="@+id/itemThumbnailView"
|
||||
android:lines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
tools:text="Uploader"/>
|
||||
tools:text="Uploader" />
|
||||
</RelativeLayout>
|
|
@ -79,8 +79,7 @@
|
|||
android:layout_toLeftOf="@id/itemHandle"
|
||||
android:layout_toStartOf="@id/itemHandle"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. "/>
|
||||
|
@ -94,7 +93,8 @@
|
|||
android:layout_toEndOf="@id/itemThumbnailView"
|
||||
android:layout_toLeftOf="@id/itemHandle"
|
||||
android:layout_toStartOf="@id/itemHandle"
|
||||
android:lines="1"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||
tools:text="Uploader"/>
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/contrast_background_color"
|
||||
android:paddingBottom="6dp">
|
||||
android:background="?attr/contrast_background_color">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_title_view"
|
||||
|
@ -81,64 +80,90 @@
|
|||
android:textSize="@dimen/playlist_detail_subtext_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="234 videos"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="25dp"
|
||||
android:id="@+id/play_control"
|
||||
android:paddingLeft="5dp"
|
||||
android:paddingRight="5dp"
|
||||
android:layout_margin="@dimen/software_component_item_padding"
|
||||
android:layout_below="@+id/playlist_meta">
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_play_bg_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/playlist_play_all_button"
|
||||
android:layout_toStartOf="@+id/playlist_play_all_button"
|
||||
android:text="@string/controls_background_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:theme="@style/RedButton"
|
||||
android:drawableLeft="?attr/audio"
|
||||
android:drawablePadding="4dp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:id="@+id/playlist_play_bg_button">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/controls_background_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:drawablePadding="4dp"
|
||||
android:drawableLeft="?attr/audio"
|
||||
android:drawableStart="?attr/audio"/>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_play_all_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/playlist_play_popup_button"
|
||||
android:layout_toStartOf="@+id/playlist_play_popup_button"
|
||||
android:text="@string/play_all"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:theme="@style/RedButton"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible"/>
|
||||
<View android:id="@+id/anchorLeft"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:clickable="false"
|
||||
android:background="?attr/colorAccent"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_play_popup_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:layout_alignParentRight="true"
|
||||
android:text="@string/controls_popup_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:theme="@style/RedButton"
|
||||
android:drawableLeft="?attr/popup"
|
||||
android:drawablePadding="4dp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible" />
|
||||
</RelativeLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:id="@+id/playlist_play_all_button">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/play_all"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View android:id="@+id/anchorRight"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:clickable="false"
|
||||
android:background="?attr/colorAccent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:id="@+id/playlist_play_popup_button">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/controls_popup_title"
|
||||
android:textSize="@dimen/channel_rss_title_size"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:drawablePadding="4dp"
|
||||
android:drawableLeft="?attr/popup"
|
||||
android:drawableStart="?attr/popup"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -122,6 +122,8 @@
|
|||
<string name="notification_channel_name">NewPipe Notification</string>
|
||||
<string name="notification_channel_description">Notifications for NewPipe Background and Popup Players</string>
|
||||
|
||||
<string name="unknown_content">[Unknown]</string>
|
||||
|
||||
<!-- error strings -->
|
||||
<string name="general_error">Error</string>
|
||||
<string name="network_error">Network error</string>
|
||||
|
@ -308,4 +310,9 @@
|
|||
<string name="play_queue_stream_detail">Details</string>
|
||||
<string name="play_queue_audio_settings">Audio Settings</string>
|
||||
<string name="hold_to_append">Hold To Enqueue</string>
|
||||
<string name="enqueue_on_background">Enqueue on Background</string>
|
||||
<string name="enqueue_on_popup">Enqueue on Popup</string>
|
||||
<string name="start_here_on_main">Start Playing Here</string>
|
||||
<string name="start_here_on_background">Start Here on Background</string>
|
||||
<string name="start_here_on_popup">Start Here on Popup</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue