diff --git a/app/build.gradle b/app/build.gradle index 273616f91..efc3b69f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.TeamNewPipe:NewPipeExtractor:4fb49d54b5' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:e51bc58a856dcf3' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:1.10.19' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bc3dc62e6..e15d9abf8 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..c32b924b0 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) { + 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.show_info), + 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 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 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/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 4512e316f..abc150e7d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -47,7 +47,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte // Constants //////////////////////////////////////////////////////////////////////////*/ - private static final int FALLBACK_SERVICE_ID = ServiceList.YouTube.getId(); + private static final int FALLBACK_SERVICE_ID = ServiceList.YouTube.getServiceId(); private static final String FALLBACK_CHANNEL_URL = "https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ"; private static final String FALLBACK_CHANNEL_NAME = "Music"; private static final String FALLBACK_KIOSK_ID = "Trending"; diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 4161f96c1..855594503 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -73,7 +73,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { .putString(getString(R.string.main_page_selectd_kiosk_id), kioskId).apply(); String serviceName = ""; try { - serviceName = NewPipe.getService(service_id).getServiceInfo().name; + serviceName = NewPipe.getService(service_id).getServiceInfo().getName(); } catch (ExtractionException e) { onError(e); } @@ -245,7 +245,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { String summary = String.format(getString(R.string.service_kiosk_string), - service.getServiceInfo().name, + service.getServiceInfo().getName(), kioskName); mainPagePref.setSummary(summary); diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java index 167b6f31b..5ab1ed1f2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -122,11 +122,11 @@ public class SelectKioskFragment extends DialogFragment { for(StreamingService service : NewPipe.getServices()) { //TODO: Multi-service support - if (service.getServiceId() != ServiceList.YouTube.getId()) continue; + if (service.getServiceId() != ServiceList.YouTube.getServiceId()) continue; for(String kioskId : service.getKioskList().getAvailableKiosks()) { String name = String.format(getString(R.string.service_kiosk_string), - service.getServiceInfo().name, + service.getServiceInfo().getName(), KioskTranslator.getTranslatedKioskName(kioskId, getContext())); kioskList.add(new Entry( ServiceHelper.getIcon(service.getServiceId()), diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index ce1491ba4..55c6e68f2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -12,7 +12,7 @@ import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; public class ServiceHelper { - private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube.getService(); + private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube; @DrawableRes public static int getIcon(int serviceId) { @@ -45,9 +45,9 @@ public class ServiceHelper { public static void setSelectedServiceId(Context context, int serviceId) { String serviceName; try { - serviceName = NewPipe.getService(serviceId).getServiceInfo().name; + serviceName = NewPipe.getService(serviceId).getServiceInfo().getName(); } catch (ExtractionException e) { - serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().name; + serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName(); } setSelectedServicePreferences(context, serviceName); @@ -55,7 +55,7 @@ public class ServiceHelper { public static void setSelectedServiceId(Context context, String serviceName) { int serviceId = NewPipe.getIdOfService(serviceName); - if (serviceId == -1) serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().name; + if (serviceId == -1) serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName(); setSelectedServicePreferences(context, serviceName); } diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index b0e00465a..824ac4a9d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -73,7 +73,7 @@ public class ThemeHelper { else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme"; else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme"; - themeName += "." + service.getServiceInfo().name; + themeName += "." + service.getServiceInfo().getName(); int resourceId = context.getResources().getIdentifier(themeName, "style", context.getPackageName()); if (resourceId > 0) { 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..caf792186 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 + Show info Main Subscriptions @@ -368,7 +369,7 @@ - @string/preferred_player_settings_title + NewPipe Open with preferred player Preferred player 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 diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml index ceacfb142..b32bff6f8 100644 --- a/app/src/main/res/xml/video_audio_settings.xml +++ b/app/src/main/res/xml/video_audio_settings.xml @@ -74,12 +74,6 @@ android:layout="@layout/settings_category_header_layout" android:title="@string/settings_category_player_behavior_title"> - -