diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bc3dc62e6..f286ee76c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -122,8 +122,12 @@
+
+
@@ -169,6 +173,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -176,17 +215,8 @@
-
-
-
-
-
-
-
-
-
-
+
@@ -195,68 +225,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index 8aaa248dd..0586a86be 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -1,51 +1,80 @@
package org.schabi.newpipe;
+import android.app.IntentService;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.preference.PreferenceManager;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.v4.app.NotificationCompat;
+import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
import android.widget.Toast;
+import org.schabi.newpipe.extractor.Info;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.StreamingService.LinkType;
+import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
+import org.schabi.newpipe.extractor.stream.StreamInfo;
+import org.schabi.newpipe.player.helper.PlayerHelper;
+import org.schabi.newpipe.playlist.ChannelPlayQueue;
+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;
+import org.schabi.newpipe.util.PermissionHelper;
+import org.schabi.newpipe.util.ThemeHelper;
+import java.io.Serializable;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import icepick.Icepick;
import icepick.State;
import io.reactivex.Observable;
+import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
-/*
- * Copyright (C) Christian Schabesberger 2017
- * RouterActivity.java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe. If not, see .
- */
+import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
/**
- * This Acitivty is designed to route share/open intents to the specified service, and
- * to the part of the service which can handle the url.
+ * Get the url from the intent and open it in the chosen preferred player
*/
public class RouterActivity extends AppCompatActivity {
@State
+ protected int currentServiceId = -1;
+ private StreamingService currentService;
+ @State
+ protected LinkType currentLinkType;
+ @State
+ protected int selectedRadioPosition = -1;
+ protected int selectedPreviously = -1;
+
protected String currentUrl;
protected CompositeDisposable disposables = new CompositeDisposable();
@@ -62,6 +91,10 @@ public class RouterActivity extends AppCompatActivity {
finish();
}
}
+
+ setTheme(ThemeHelper.isLightThemeSelected(this)
+ ? R.style.RouterActivityThemeLight
+ : R.style.RouterActivityThemeDark);
}
@Override
@@ -73,25 +106,43 @@ public class RouterActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
+
handleUrl(currentUrl);
}
- protected void handleUrl(String url) {
- disposables.add(Observable
- .fromCallable(() -> NavigationHelper.getIntentByLink(this, url))
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(intent -> {
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity(intent);
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
- finish();
- }, this::handleError)
- );
+ disposables.clear();
}
- protected void handleError(Throwable error) {
+ private void handleUrl(String url) {
+ disposables.add(Observable
+ .fromCallable(() -> {
+ if (currentServiceId == -1) {
+ currentService = NewPipe.getServiceByUrl(url);
+ currentServiceId = currentService.getServiceId();
+ currentLinkType = currentService.getLinkTypeByUrl(url);
+ currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType);
+ } else {
+ currentService = NewPipe.getService(currentServiceId);
+ }
+
+ return currentLinkType != LinkType.NONE;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(result -> {
+ if (result) {
+ onSuccess();
+ } else {
+ onError();
+ }
+ }, this::handleError));
+ }
+
+ private void handleError(Throwable error) {
error.printStackTrace();
if (error instanceof ExtractionException) {
@@ -103,11 +154,339 @@ public class RouterActivity extends AppCompatActivity {
finish();
}
- @Override
- protected void onDestroy() {
- super.onDestroy();
+ private void onError() {
+ Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
+ finish();
+ }
- disposables.clear();
+ protected void onSuccess() {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
+ boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
+
+ if ((isExtAudioEnabled || isExtVideoEnabled) && currentLinkType != LinkType.STREAM) {
+ Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ // TODO: Add some sort of "capabilities" field to services (audio only, video and audio, etc.)
+ if (currentService == ServiceList.SoundCloud.getService()) {
+ handleChoice(getString(R.string.background_player_key));
+ return;
+ }
+
+ final String playerChoiceKey = preferences.getString(getString(R.string.preferred_player_key), getString(R.string.preferred_player_default));
+ final String alwaysAskKey = getString(R.string.always_ask_player_key);
+
+ if (playerChoiceKey.equals(alwaysAskKey)) {
+ showDialog();
+ } else {
+ handleChoice(playerChoiceKey);
+ }
+ }
+
+ private void showDialog() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this,
+ ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
+
+ LayoutInflater inflater = LayoutInflater.from(themeWrapper);
+ final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false);
+ final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
+
+ final AdapterChoiceItem[] choices = {
+ new AdapterChoiceItem(getString(R.string.info_screen_key), getString(R.string.info_screen),
+ resolveResourceIdFromAttr(themeWrapper, R.attr.info)),
+ new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player),
+ resolveResourceIdFromAttr(themeWrapper, R.attr.play)),
+ new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player),
+ resolveResourceIdFromAttr(themeWrapper, R.attr.audio)),
+ new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player),
+ resolveResourceIdFromAttr(themeWrapper, R.attr.popup))
+ };
+
+ final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
+ final int indexOfChild = radioGroup.indexOfChild(
+ radioGroup.findViewById(radioGroup.getCheckedRadioButtonId()));
+ final AdapterChoiceItem choice = choices[indexOfChild];
+
+ handleChoice(choice.key);
+
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ preferences.edit().putString(getString(R.string.preferred_player_key), choice.key).apply();
+ }
+ };
+
+ final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper)
+ .setTitle(R.string.preferred_player_share_menu_title)
+ .setView(radioGroup)
+ .setCancelable(true)
+ .setNegativeButton(R.string.just_once, dialogButtonsClickListener)
+ .setPositiveButton(R.string.always, dialogButtonsClickListener)
+ .setOnDismissListener((dialog) -> finish())
+ .create();
+
+ alertDialog.setOnShowListener(dialog -> {
+ setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
+ });
+
+ radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true));
+ final View.OnClickListener radioButtonsClickListener = v -> {
+ final int indexOfChild = radioGroup.indexOfChild(v);
+ if (indexOfChild == -1) return;
+
+ selectedPreviously = selectedRadioPosition;
+ selectedRadioPosition = indexOfChild;
+
+ if (selectedPreviously == selectedRadioPosition) {
+ handleChoice(choices[selectedRadioPosition].key);
+ }
+ };
+
+ int id = 12345;
+ for (AdapterChoiceItem item : choices) {
+ final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
+ radioButton.setText(item.description);
+ radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
+ radioButton.setChecked(false);
+ radioButton.setId(id++);
+ radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ radioButton.setOnClickListener(radioButtonsClickListener);
+ radioGroup.addView(radioButton);
+ }
+
+ if (selectedRadioPosition == -1) {
+ final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_player_last_selected_key), null);
+ if (!TextUtils.isEmpty(lastSelectedPlayer)) {
+ for (int i = 0; i < choices.length; i++) {
+ AdapterChoiceItem c = choices[i];
+ if (lastSelectedPlayer.equals(c.key)) {
+ selectedRadioPosition = i;
+ break;
+ }
+ }
+ }
+ }
+
+ selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1);
+ if (selectedRadioPosition != -1) {
+ ((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true);
+ }
+ selectedPreviously = selectedRadioPosition;
+
+ alertDialog.show();
+ }
+
+ private void setDialogButtonsState(AlertDialog dialog, boolean state) {
+ final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
+ final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ if (negativeButton == null || positiveButton == null) return;
+
+ negativeButton.setEnabled(state);
+ positiveButton.setEnabled(state);
+ }
+
+ private void handleChoice(final String playerChoiceKey) {
+ if (Arrays.asList(getResources().getStringArray(R.array.preferred_player_values_list)).contains(playerChoiceKey)) {
+ PreferenceManager.getDefaultSharedPreferences(this).edit()
+ .putString(getString(R.string.preferred_player_last_selected_key), playerChoiceKey).apply();
+ }
+
+ if (playerChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) {
+ PermissionHelper.showPopupEnablementToast(this);
+ finish();
+ return;
+ }
+
+ // stop and bypass FetcherService if InfoScreen was selected since
+ // StreamDetailFragment can fetch data itself
+ if(playerChoiceKey.equals(getString(R.string.info_screen_key))) {
+ disposables.add(Observable
+ .fromCallable(() -> NavigationHelper.getIntentByLink(this, currentUrl))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(intent -> {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+
+ finish();
+ }, this::handleError)
+ );
+ return;
+ }
+
+ final Intent intent = new Intent(this, FetcherService.class);
+ intent.putExtra(FetcherService.KEY_CHOICE,
+ new Choice(currentService.getServiceId(),
+ currentLinkType,
+ currentUrl,
+ playerChoiceKey));
+ startService(intent);
+
+ finish();
+ }
+
+ private static class AdapterChoiceItem {
+ final String description, key;
+ @DrawableRes
+ final int icon;
+
+ AdapterChoiceItem(String key, String description, int icon) {
+ this.description = description;
+ this.key = key;
+ this.icon = icon;
+ }
+ }
+
+ private static class Choice implements Serializable {
+ final int serviceId;
+ final String url, playerChoice;
+ final LinkType linkType;
+
+ Choice(int serviceId, LinkType linkType, String url, String playerChoice) {
+ this.serviceId = serviceId;
+ this.linkType = linkType;
+ this.url = url;
+ this.playerChoice = playerChoice;
+ }
+
+ @Override
+ public String toString() {
+ return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Service Fetcher
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static class FetcherService extends IntentService {
+
+ private static final int ID = 456;
+ public static final String KEY_CHOICE = "key_choice";
+ private Disposable fetcher;
+
+ public FetcherService() {
+ super(FetcherService.class.getSimpleName());
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ startForeground(ID, createNotification().build());
+ }
+
+ @Override
+ protected void onHandleIntent(@Nullable Intent intent) {
+ if (intent == null) return;
+
+ final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
+ if (!(serializable instanceof Choice)) return;
+ Choice playerChoice = (Choice) serializable;
+ handleChoice(playerChoice);
+ }
+
+ public void handleChoice(Choice choice) {
+ Single extends Info> single = null;
+ UserAction userAction = UserAction.SOMETHING_ELSE;
+
+ switch (choice.linkType) {
+ case STREAM:
+ single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false);
+ userAction = UserAction.REQUESTED_STREAM;
+ break;
+ case CHANNEL:
+ single = ExtractorHelper.getChannelInfo(choice.serviceId, choice.url, false);
+ userAction = UserAction.REQUESTED_CHANNEL;
+ break;
+ case PLAYLIST:
+ single = ExtractorHelper.getPlaylistInfo(choice.serviceId, choice.url, false);
+ userAction = UserAction.REQUESTED_PLAYLIST;
+ break;
+ }
+
+
+ if (single != null) {
+ final UserAction finalUserAction = userAction;
+ final Consumer resultHandler = getResultHandler(choice);
+ fetcher = single
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(info -> {
+ resultHandler.accept(info);
+ if (fetcher != null) fetcher.dispose();
+ }, throwable -> ExtractorHelper.handleGeneralException(this,
+ choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice));
+ }
+ }
+
+ public Consumer getResultHandler(Choice choice) {
+ return info -> {
+ final String videoPlayerKey = getString(R.string.video_player_key);
+ final String backgroundPlayerKey = getString(R.string.background_player_key);
+ final String popupPlayerKey = getString(R.string.popup_player_key);
+
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
+ boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
+ boolean useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this);
+
+ PlayQueue playQueue;
+ String playerChoice = choice.playerChoice;
+
+ if (info instanceof StreamInfo) {
+ if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
+ NavigationHelper.playOnExternalAudioPlayer(this, (StreamInfo) info);
+
+ } else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
+ NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info);
+
+ } else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) {
+ NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info);
+
+ } else {
+ playQueue = new SinglePlayQueue((StreamInfo) info);
+
+ if (playerChoice.equals(videoPlayerKey)) {
+ NavigationHelper.playOnMainPlayer(this, playQueue);
+ } else if (playerChoice.equals(backgroundPlayerKey)) {
+ NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
+ } else if (playerChoice.equals(popupPlayerKey)) {
+ NavigationHelper.enqueueOnPopupPlayer(this, playQueue, true);
+ }
+ }
+ }
+
+ if (info instanceof ChannelInfo || info instanceof PlaylistInfo) {
+ playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info);
+
+ if (playerChoice.equals(videoPlayerKey)) {
+ NavigationHelper.playOnMainPlayer(this, playQueue);
+ } else if (playerChoice.equals(backgroundPlayerKey)) {
+ NavigationHelper.playOnBackgroundPlayer(this, playQueue);
+ } else if (playerChoice.equals(popupPlayerKey)) {
+ NavigationHelper.playOnPopupPlayer(this, playQueue);
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ stopForeground(true);
+ if (fetcher != null) fetcher.dispose();
+ }
+
+ private NotificationCompat.Builder createNotification() {
+ return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.ic_newpipe_triangle_white)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setContentTitle(getString(R.string.preferred_player_fetcher_notification_title))
+ .setContentText(getString(R.string.preferred_player_fetcher_notification_message));
+ }
}
/*//////////////////////////////////////////////////////////////////////////
@@ -119,9 +498,9 @@ public class RouterActivity extends AppCompatActivity {
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
* more details.
*/
- protected final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
+ private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
- protected String getUrl(Intent intent) {
+ private String getUrl(Intent intent) {
// first gather data and find service
String videoUrl = null;
if (intent.getData() != null) {
@@ -137,7 +516,7 @@ public class RouterActivity extends AppCompatActivity {
return videoUrl;
}
- protected String removeHeadingGibberish(final String input) {
+ private String removeHeadingGibberish(final String input) {
int start = 0;
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
if (!input.substring(i, i + 1).matches("\\p{L}")) {
@@ -148,7 +527,7 @@ public class RouterActivity extends AppCompatActivity {
return input.substring(start, input.length());
}
- protected String trim(final String input) {
+ private String trim(final String input) {
if (input == null || input.length() < 1) {
return input;
} else {
@@ -188,5 +567,4 @@ public class RouterActivity extends AppCompatActivity {
}
return result.toArray(new String[result.size()]);
}
-
}
diff --git a/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java
deleted file mode 100644
index 7196e413d..000000000
--- a/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package org.schabi.newpipe;
-
-import android.app.IntentService;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.preference.PreferenceManager;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.Nullable;
-import android.support.v4.app.NotificationCompat;
-import android.support.v7.app.AlertDialog;
-import android.text.TextUtils;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.Toast;
-
-import org.schabi.newpipe.extractor.Info;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.ServiceList;
-import org.schabi.newpipe.extractor.StreamingService;
-import org.schabi.newpipe.extractor.StreamingService.LinkType;
-import org.schabi.newpipe.extractor.channel.ChannelInfo;
-import org.schabi.newpipe.extractor.exceptions.ExtractionException;
-import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
-import org.schabi.newpipe.extractor.stream.StreamInfo;
-import org.schabi.newpipe.player.helper.PlayerHelper;
-import org.schabi.newpipe.playlist.ChannelPlayQueue;
-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;
-import org.schabi.newpipe.util.PermissionHelper;
-import org.schabi.newpipe.util.ThemeHelper;
-
-import java.io.Serializable;
-import java.util.Arrays;
-
-import icepick.State;
-import io.reactivex.Observable;
-import io.reactivex.Single;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.functions.Consumer;
-import io.reactivex.schedulers.Schedulers;
-
-import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
-
-/**
- * Get the url from the intent and open it in the chosen preferred player
- */
-public class RouterPlayerActivity extends RouterActivity {
-
- @State
- protected int currentServiceId = -1;
- private StreamingService currentService;
- @State
- protected LinkType currentLinkType;
- @State
- protected int selectedRadioPosition = -1;
- protected int selectedPreviously = -1;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
- super.onCreate(savedInstanceState, persistentState);
- setTheme(ThemeHelper.isLightThemeSelected(this) ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
- }
-
- @Override
- protected void handleUrl(String url) {
- disposables.add(Observable
- .fromCallable(() -> {
- if (currentServiceId == -1) {
- currentService = NewPipe.getServiceByUrl(url);
- currentServiceId = currentService.getServiceId();
- currentLinkType = currentService.getLinkTypeByUrl(url);
- currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType);
- } else {
- currentService = NewPipe.getService(currentServiceId);
- }
-
- return currentLinkType != LinkType.NONE;
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(result -> {
- if (result) {
- onSuccess();
- } else {
- onError();
- }
- }, this::handleError));
- }
-
- protected void onError() {
- Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
- finish();
- }
-
- protected void onSuccess() {
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
- boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
-
- if ((isExtAudioEnabled || isExtVideoEnabled) && currentLinkType != LinkType.STREAM) {
- Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
- finish();
- return;
- }
-
- // TODO: Add some sort of "capabilities" field to services (audio only, video and audio, etc.)
- if (currentService == ServiceList.SoundCloud.getService()) {
- handleChoice(getString(R.string.background_player_key));
- return;
- }
-
- final String playerChoiceKey = preferences.getString(getString(R.string.preferred_player_key), getString(R.string.preferred_player_default));
- final String alwaysAskKey = getString(R.string.always_ask_player_key);
-
- if (playerChoiceKey.equals(alwaysAskKey)) {
- showDialog();
- } else {
- handleChoice(playerChoiceKey);
- }
- }
-
- private void showDialog() {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this,
- ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
-
- LayoutInflater inflater = LayoutInflater.from(themeWrapper);
- final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false);
- final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
-
- final AdapterChoiceItem[] choices = {
- new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player),
- resolveResourceIdFromAttr(themeWrapper, R.attr.play)),
- new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player),
- resolveResourceIdFromAttr(themeWrapper, R.attr.audio)),
- new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player),
- resolveResourceIdFromAttr(themeWrapper, R.attr.popup))
- };
-
- final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
- final int indexOfChild = radioGroup.indexOfChild(radioGroup.findViewById(radioGroup.getCheckedRadioButtonId()));
- final AdapterChoiceItem choice = choices[indexOfChild];
-
- handleChoice(choice.key);
-
- if (which == DialogInterface.BUTTON_POSITIVE) {
- preferences.edit().putString(getString(R.string.preferred_player_key), choice.key).apply();
- }
- };
-
- final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper)
- .setTitle(R.string.preferred_player_share_menu_title)
- .setView(radioGroup)
- .setCancelable(true)
- .setNegativeButton(R.string.just_once, dialogButtonsClickListener)
- .setPositiveButton(R.string.always, dialogButtonsClickListener)
- .setOnDismissListener((dialog) -> finish())
- .create();
-
- alertDialog.setOnShowListener(dialog -> {
- setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
- });
-
- radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true));
- final View.OnClickListener radioButtonsClickListener = v -> {
- final int indexOfChild = radioGroup.indexOfChild(v);
- if (indexOfChild == -1) return;
-
- selectedPreviously = selectedRadioPosition;
- selectedRadioPosition = indexOfChild;
-
- if (selectedPreviously == selectedRadioPosition) {
- handleChoice(choices[selectedRadioPosition].key);
- }
- };
-
- int id = 12345;
- for (AdapterChoiceItem item : choices) {
- final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
- radioButton.setText(item.description);
- radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
- radioButton.setChecked(false);
- radioButton.setId(id++);
- radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- radioButton.setOnClickListener(radioButtonsClickListener);
- radioGroup.addView(radioButton);
- }
-
- if (selectedRadioPosition == -1) {
- final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_player_last_selected_key), null);
- if (!TextUtils.isEmpty(lastSelectedPlayer)) {
- for (int i = 0; i < choices.length; i++) {
- AdapterChoiceItem c = choices[i];
- if (lastSelectedPlayer.equals(c.key)) {
- selectedRadioPosition = i;
- break;
- }
- }
- }
- }
-
- selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1);
- if (selectedRadioPosition != -1) {
- ((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true);
- }
- selectedPreviously = selectedRadioPosition;
-
- alertDialog.show();
- }
-
- private void setDialogButtonsState(AlertDialog dialog, boolean state) {
- final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
- final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- if (negativeButton == null || positiveButton == null) return;
-
- negativeButton.setEnabled(state);
- positiveButton.setEnabled(state);
- }
-
- private void handleChoice(final String playerChoiceKey) {
- if (Arrays.asList(getResources().getStringArray(R.array.preferred_player_values_list)).contains(playerChoiceKey)) {
- PreferenceManager.getDefaultSharedPreferences(this).edit()
- .putString(getString(R.string.preferred_player_last_selected_key), playerChoiceKey).apply();
- }
-
- if (playerChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) {
- PermissionHelper.showPopupEnablementToast(this);
- finish();
- return;
- }
-
- final Intent intent = new Intent(this, FetcherService.class);
- intent.putExtra(FetcherService.KEY_CHOICE, new Choice(currentService.getServiceId(), currentLinkType, currentUrl, playerChoiceKey));
- startService(intent);
-
- finish();
- }
-
- private static class AdapterChoiceItem {
- final String description, key;
- @DrawableRes
- final int icon;
-
- AdapterChoiceItem(String key, String description, int icon) {
- this.description = description;
- this.key = key;
- this.icon = icon;
- }
- }
-
- private static class Choice implements Serializable {
- final int serviceId;
- final String url, playerChoice;
- final LinkType linkType;
-
- Choice(int serviceId, LinkType linkType, String url, String playerChoice) {
- this.serviceId = serviceId;
- this.linkType = linkType;
- this.url = url;
- this.playerChoice = playerChoice;
- }
-
- @Override
- public String toString() {
- return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Service Fetcher
- //////////////////////////////////////////////////////////////////////////*/
-
- public static class FetcherService extends IntentService {
-
- private static final int ID = 456;
- public static final String KEY_CHOICE = "key_choice";
- private Disposable fetcher;
-
- public FetcherService() {
- super(FetcherService.class.getSimpleName());
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- startForeground(ID, createNotification().build());
- }
-
- @Override
- protected void onHandleIntent(@Nullable Intent intent) {
- if (intent == null) return;
-
- final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
- if (!(serializable instanceof Choice)) return;
- Choice playerChoice = (Choice) serializable;
- handleChoice(playerChoice);
- }
-
- public void handleChoice(Choice choice) {
- Single extends Info> single = null;
- UserAction userAction = UserAction.SOMETHING_ELSE;
-
- switch (choice.linkType) {
- case STREAM:
- single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false);
- userAction = UserAction.REQUESTED_STREAM;
- break;
- case CHANNEL:
- single = ExtractorHelper.getChannelInfo(choice.serviceId, choice.url, false);
- userAction = UserAction.REQUESTED_CHANNEL;
- break;
- case PLAYLIST:
- single = ExtractorHelper.getPlaylistInfo(choice.serviceId, choice.url, false);
- userAction = UserAction.REQUESTED_PLAYLIST;
- break;
- }
-
-
- if (single != null) {
- final UserAction finalUserAction = userAction;
- final Consumer resultHandler = getResultHandler(choice);
- fetcher = single
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(info -> {
- resultHandler.accept(info);
- if (fetcher != null) fetcher.dispose();
- }, throwable -> ExtractorHelper.handleGeneralException(this,
- choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice));
- }
- }
-
- public Consumer getResultHandler(Choice choice) {
- return info -> {
- final String videoPlayerKey = getString(R.string.video_player_key);
- final String backgroundPlayerKey = getString(R.string.background_player_key);
- final String popupPlayerKey = getString(R.string.popup_player_key);
-
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
- boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
- boolean useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this);
-
- PlayQueue playQueue;
- String playerChoice = choice.playerChoice;
-
- if (info instanceof StreamInfo) {
- if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
- NavigationHelper.playOnExternalAudioPlayer(this, (StreamInfo) info);
-
- } else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
- NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info);
-
- } else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) {
- NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info);
-
- } else {
- playQueue = new SinglePlayQueue((StreamInfo) info);
-
- if (playerChoice.equals(videoPlayerKey)) {
- NavigationHelper.playOnMainPlayer(this, playQueue);
- } else if (playerChoice.equals(backgroundPlayerKey)) {
- NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
- } else if (playerChoice.equals(popupPlayerKey)) {
- NavigationHelper.enqueueOnPopupPlayer(this, playQueue, true);
- }
- }
- }
-
- if (info instanceof ChannelInfo || info instanceof PlaylistInfo) {
- playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info);
-
- if (playerChoice.equals(videoPlayerKey)) {
- NavigationHelper.playOnMainPlayer(this, playQueue);
- } else if (playerChoice.equals(backgroundPlayerKey)) {
- NavigationHelper.playOnBackgroundPlayer(this, playQueue);
- } else if (playerChoice.equals(popupPlayerKey)) {
- NavigationHelper.playOnPopupPlayer(this, playQueue);
- }
- }
- };
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- stopForeground(true);
- if (fetcher != null) fetcher.dispose();
- }
-
- private NotificationCompat.Builder createNotification() {
- return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
- .setOngoing(true)
- .setSmallIcon(R.drawable.ic_newpipe_triangle_white)
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- .setContentTitle(getString(R.string.preferred_player_fetcher_notification_title))
- .setContentText(getString(R.string.preferred_player_fetcher_notification_message));
- }
- }
-}
diff --git a/app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png
new file mode 100644
index 000000000..4b5ab06e1
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png
new file mode 100644
index 000000000..c7b1113cf
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_info_outline_black_24dp.png
new file mode 100644
index 000000000..e0c9fe0eb
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_info_outline_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png
new file mode 100644
index 000000000..353e06495
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png
new file mode 100644
index 000000000..b706f0d06
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png
new file mode 100644
index 000000000..c571b2e3e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png
new file mode 100644
index 000000000..3847a9fe7
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png
new file mode 100644
index 000000000..c41a5fcff
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png
new file mode 100644
index 000000000..c1e2a03a4
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png
new file mode 100644
index 000000000..3a82cab3b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png differ
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 794365a3d..31eda4fbc 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -3,6 +3,7 @@
+
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index 372b917e0..8f33f9297 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -155,6 +155,7 @@
@string/always_ask_player_key
preferred_player_last_selected
+ info_screen
video_player
background_player
popup_player
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ea8f0fce8..0e9f6e7ac 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -30,6 +30,7 @@
Channel unsubscribed
Unable to change subscription
Unable to update subscription
+ Info Screen
Main
Subscriptions
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index b16958ae6..dcf8f9268 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -18,6 +18,7 @@
- @drawable/ic_thumb_up_black_24dp
- @drawable/ic_thumb_down_black_24dp
+ - @drawable/ic_info_outline_black_24dp
- @drawable/ic_headset_black_24dp
- @drawable/ic_delete_sweep_white_24dp
- @drawable/ic_file_download_black_24dp
@@ -69,6 +70,7 @@
- @drawable/ic_thumb_up_white_24dp
- @drawable/ic_thumb_down_white_24dp
- @drawable/ic_headset_white_24dp
+ - @drawable/ic_info_outline_white_24dp
- @drawable/ic_delete_sweep_black_24dp
- @drawable/ic_file_download_white_24dp
- @drawable/ic_share_white_24dp