Merge remote-tracking branch 'origin/dev' into dev
| @@ -124,7 +124,7 @@ | ||||
|         <activity | ||||
|             android:name=".RouterActivity" | ||||
|             android:taskAffinity="" | ||||
|             android:theme="@android:style/Theme.NoDisplay"> | ||||
|             android:theme="@style/RouterActivityThemeDark"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.VIEW"/> | ||||
|                 <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/> | ||||
| @@ -175,17 +175,21 @@ | ||||
|             </intent-filter> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.SEND"/> | ||||
|  | ||||
|                 <category android:name="android.intent.category.DEFAULT"/> | ||||
|  | ||||
|                 <data android:mimeType="text/plain"/> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|  | ||||
|         <service | ||||
|             android:name=".RouterPlayerActivity$FetcherService" | ||||
|             android:exported="false"/> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".RouterPopupActivity" | ||||
|             android:label="@string/popup_mode_share_menu_title" | ||||
|             android:name=".RouterPlayerActivity" | ||||
|             android:excludeFromRecents="true" | ||||
|             android:label="@string/preferred_player_share_menu_title" | ||||
|             android:taskAffinity="" | ||||
|             android:theme="@style/PopupPermissionsTheme"> | ||||
|             android:theme="@style/RouterActivityThemeDark"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.VIEW"/> | ||||
|                 <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/> | ||||
| @@ -204,6 +208,11 @@ | ||||
|                 <data android:pathPrefix="/embed/"/> | ||||
|                 <data android:pathPrefix="/watch"/> | ||||
|                 <data android:pathPrefix="/attribution_link"/> | ||||
|                 <!-- channel prefix --> | ||||
|                 <data android:pathPrefix="/channel/"/> | ||||
|                 <data android:pathPrefix="/user/"/> | ||||
|                 <!-- playlist prefix --> | ||||
|                 <data android:pathPrefix="/playlist"/> | ||||
|             </intent-filter> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.VIEW"/> | ||||
|   | ||||
| @@ -77,17 +77,6 @@ public abstract class BaseFragment extends Fragment { | ||||
|     protected void initListeners() { | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Utils | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     protected final int resolveResourceIdFromAttr(@AttrRes int attr) { | ||||
|         TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr}); | ||||
|         int attributeResourceId = a.getResourceId(0, 0); | ||||
|         a.recycle(); | ||||
|         return attributeResourceId; | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // DisplayImageOptions default configurations | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|   | ||||
| @@ -3,14 +3,25 @@ package org.schabi.newpipe; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| import android.text.TextUtils; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||
| import org.schabi.newpipe.report.UserAction; | ||||
| import org.schabi.newpipe.util.ExtractorHelper; | ||||
| import org.schabi.newpipe.util.NavigationHelper; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.HashSet; | ||||
|  | ||||
| /** | ||||
| import icepick.Icepick; | ||||
| import icepick.State; | ||||
| import io.reactivex.Observable; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.disposables.CompositeDisposable; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
|  | ||||
| /* | ||||
|  * Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org> | ||||
|  * RouterActivity.java is part of NewPipe. | ||||
|  * | ||||
| @@ -34,23 +45,71 @@ import java.util.HashSet; | ||||
|  */ | ||||
| public class RouterActivity extends AppCompatActivity { | ||||
|  | ||||
|     @State | ||||
|     protected String currentUrl; | ||||
|     protected CompositeDisposable disposables = new CompositeDisposable(); | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         Icepick.restoreInstanceState(this, savedInstanceState); | ||||
|  | ||||
|         String videoUrl = getUrl(getIntent()); | ||||
|         handleUrl(videoUrl); | ||||
|         if (TextUtils.isEmpty(currentUrl)) { | ||||
|             currentUrl = getUrl(getIntent()); | ||||
|  | ||||
|             if (TextUtils.isEmpty(currentUrl)) { | ||||
|                 Toast.makeText(this, R.string.invalid_url_toast, Toast.LENGTH_LONG).show(); | ||||
|                 finish(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onSaveInstanceState(Bundle outState) { | ||||
|         super.onSaveInstanceState(outState); | ||||
|         Icepick.saveInstanceState(this, outState); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onStart() { | ||||
|         super.onStart(); | ||||
|         handleUrl(currentUrl); | ||||
|     } | ||||
|  | ||||
|     protected void handleUrl(String url) { | ||||
|         boolean success = NavigationHelper.openByLink(this, url); | ||||
|         if (!success) { | ||||
|         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); | ||||
|  | ||||
|                     finish(); | ||||
|                 }, this::handleError) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     protected void handleError(Throwable error) { | ||||
|         error.printStackTrace(); | ||||
|  | ||||
|         if (error instanceof ExtractionException) { | ||||
|             Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); | ||||
|         } else { | ||||
|             ExtractorHelper.handleGeneralException(this, -1, null, error, UserAction.SOMETHING_ELSE, null); | ||||
|         } | ||||
|  | ||||
|         finish(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|  | ||||
|         disposables.clear(); | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Utils | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
| @@ -71,7 +130,8 @@ public class RouterActivity extends AppCompatActivity { | ||||
|         } else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) { | ||||
|             //this means that vidoe was called through share menu | ||||
|             String extraText = intent.getStringExtra(Intent.EXTRA_TEXT); | ||||
|             videoUrl = getUris(extraText)[0]; | ||||
|             final String[] uris = getUris(extraText); | ||||
|             videoUrl = uris.length > 0 ? uris[0] : null; | ||||
|         } | ||||
|  | ||||
|         return videoUrl; | ||||
|   | ||||
							
								
								
									
										413
									
								
								app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,413 @@ | ||||
| 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<Info> 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<Info> 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)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| package org.schabi.newpipe; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.os.Build; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.StreamingService; | ||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||
| import org.schabi.newpipe.player.PopupVideoPlayer; | ||||
| import org.schabi.newpipe.util.Constants; | ||||
| import org.schabi.newpipe.util.PermissionHelper; | ||||
|  | ||||
| /** | ||||
|  * Get the url from the intent and open a popup player | ||||
|  */ | ||||
| public class RouterPopupActivity extends RouterActivity { | ||||
|  | ||||
|     @Override | ||||
|     protected void handleUrl(String url) { | ||||
|         if (!PermissionHelper.isPopupEnabled(this)) { | ||||
|             PermissionHelper.showPopupEnablementToast(this); | ||||
|             finish(); | ||||
|             return; | ||||
|         } | ||||
|         StreamingService service; | ||||
|         try { | ||||
|             service = NewPipe.getServiceByUrl(url); | ||||
|         } catch (ExtractionException e) { | ||||
|             Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Intent callIntent = new Intent(this, PopupVideoPlayer.class); | ||||
|         switch (service.getLinkTypeByUrl(url)) { | ||||
|             case STREAM: | ||||
|                 break; | ||||
|             default: | ||||
|                 Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); | ||||
|                 return; | ||||
|         } | ||||
|  | ||||
|         callIntent.putExtra(Constants.KEY_URL, url); | ||||
|         callIntent.putExtra(Constants.KEY_SERVICE_ID, service.getServiceId()); | ||||
|         startService(callIntent); | ||||
|  | ||||
|         finish(); | ||||
|     } | ||||
| } | ||||
| @@ -13,7 +13,6 @@ import android.support.annotation.DrawableRes; | ||||
| import android.support.annotation.FloatRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.support.v4.text.TextUtilsCompat; | ||||
| import android.support.v4.view.animation.FastOutSlowInInterpolator; | ||||
| import android.support.v7.app.ActionBar; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| @@ -25,7 +24,6 @@ import android.text.util.Linkify; | ||||
| import android.util.DisplayMetrics; | ||||
| import android.util.Log; | ||||
| import android.util.TypedValue; | ||||
| import android.view.Gravity; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| @@ -37,7 +35,6 @@ 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; | ||||
| @@ -51,7 +48,6 @@ import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.ReCaptchaActivity; | ||||
| import org.schabi.newpipe.download.DownloadDialog; | ||||
| import org.schabi.newpipe.extractor.InfoItem; | ||||
| import org.schabi.newpipe.extractor.MediaFormat; | ||||
| import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; | ||||
| import org.schabi.newpipe.extractor.exceptions.ParsingException; | ||||
| @@ -80,6 +76,7 @@ import org.schabi.newpipe.util.ListHelper; | ||||
| import org.schabi.newpipe.util.Localization; | ||||
| 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.ArrayList; | ||||
| @@ -394,7 +391,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | ||||
|  | ||||
|         if (relatedStreamsView.getChildCount() > initialCount) { | ||||
|             relatedStreamsView.removeViews(initialCount, relatedStreamsView.getChildCount() - (initialCount)); | ||||
|             relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.expand))); | ||||
|             relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @@ -404,7 +401,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | ||||
|             //Log.d(TAG, "i = " + i); | ||||
|             relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item)); | ||||
|         } | ||||
|         relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.collapse))); | ||||
|         relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse))); | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
| @@ -579,7 +576,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | ||||
|             relatedStreamRootLayout.setVisibility(View.VISIBLE); | ||||
|             relatedStreamExpandButton.setVisibility(View.VISIBLE); | ||||
|  | ||||
|             relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.expand))); | ||||
|             relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); | ||||
|         } else { | ||||
|             if (info.getNextVideo() == null) relatedStreamRootLayout.setVisibility(View.GONE); | ||||
|             relatedStreamExpandButton.setVisibility(View.GONE); | ||||
| @@ -807,7 +804,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | ||||
|         if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) { | ||||
|             openNormalBackgroundPlayer(append); | ||||
|         } else { | ||||
|             openExternalBackgroundPlayer(audioStream); | ||||
|             NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), audioStream); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -841,13 +838,12 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | ||||
|         } | ||||
|  | ||||
|         if (PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(this.getString(R.string.use_external_video_player_key), false)) { | ||||
|             openExternalVideoPlayer(selectedVideoStream); | ||||
|             NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), selectedVideoStream); | ||||
|         } else { | ||||
|             openNormalPlayer(selectedVideoStream); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private void openNormalBackgroundPlayer(final boolean append) { | ||||
|         final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); | ||||
|         if (append) { | ||||
| @@ -857,40 +853,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void openExternalBackgroundPlayer(AudioStream audioStream) { | ||||
|         Intent intent; | ||||
|         intent = new Intent(); | ||||
|         try { | ||||
|             intent.setAction(Intent.ACTION_VIEW); | ||||
|             intent.setDataAndType(Uri.parse(audioStream.getUrl()), audioStream.getFormat().getMimeType()); | ||||
|             intent.putExtra(Intent.EXTRA_TITLE, currentInfo.getName()); | ||||
|             intent.putExtra("title", currentInfo.getName()); | ||||
|             activity.startActivity(intent); | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|             AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||||
|             builder.setMessage(R.string.no_player_found) | ||||
|                     .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { | ||||
|                         @Override | ||||
|                         public void onClick(DialogInterface dialog, int which) { | ||||
|                             Intent intent = new Intent(); | ||||
|                             intent.setAction(Intent.ACTION_VIEW); | ||||
|                             intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url))); | ||||
|                             activity.startActivity(intent); | ||||
|                         } | ||||
|                     }) | ||||
|                     .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { | ||||
|                         @Override | ||||
|                         public void onClick(DialogInterface dialog, int which) { | ||||
|                             Log.i(TAG, "You unlocked a secret unicorn."); | ||||
|                         } | ||||
|                     }); | ||||
|             builder.create().show(); | ||||
|             Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:"); | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void openNormalPlayer(VideoStream selectedVideoStream) { | ||||
|         Intent mIntent; | ||||
|         boolean useOldPlayer = PlayerHelper.isUsingOldPlayer(activity) || (Build.VERSION.SDK_INT < 16); | ||||
| @@ -909,33 +871,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | ||||
|         startActivity(mIntent); | ||||
|     } | ||||
|  | ||||
|     private void openExternalVideoPlayer(VideoStream selectedVideoStream) { | ||||
|         // External Player | ||||
|         Intent intent = new Intent(); | ||||
|         try { | ||||
|             intent.setAction(Intent.ACTION_VIEW) | ||||
|                     .setDataAndType(Uri.parse(selectedVideoStream.getUrl()), selectedVideoStream.getFormat().getMimeType()) | ||||
|                     .putExtra(Intent.EXTRA_TITLE, currentInfo.getName()) | ||||
|                     .putExtra("title", currentInfo.getName()); | ||||
|             this.startActivity(intent); | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|             AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||||
|             builder.setMessage(R.string.no_player_found) | ||||
|                     .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { | ||||
|                         @Override | ||||
|                         public void onClick(DialogInterface dialog, int which) { | ||||
|                             Intent intent = new Intent() | ||||
|                                     .setAction(Intent.ACTION_VIEW) | ||||
|                                     .setData(Uri.parse(getString(R.string.fdroid_vlc_url))); | ||||
|                             startActivity(intent); | ||||
|                         } | ||||
|                     }) | ||||
|                     .setNegativeButton(R.string.cancel, null); | ||||
|             builder.create().show(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Utils | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
| @@ -1212,4 +1147,4 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | ||||
|  | ||||
|         showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema); | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -111,6 +111,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | ||||
|     public static final String PLAYBACK_QUALITY = "playback_quality"; | ||||
|     public static final String PLAY_QUEUE = "play_queue"; | ||||
|     public static final String APPEND_ONLY = "append_only"; | ||||
|     public static final String SELECT_ON_APPEND = "select_on_append"; | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Playback | ||||
| @@ -218,7 +219,13 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | ||||
|  | ||||
|         // Resolve append intents | ||||
|         if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) { | ||||
|             int sizeBeforeAppend = playQueue.size(); | ||||
|             playQueue.append(queue.getStreams()); | ||||
|  | ||||
|             if (intent.getBooleanExtra(SELECT_ON_APPEND, false) && queue.getStreams().size() > 0) { | ||||
|                 playQueue.setIndex(sizeBeforeAppend); | ||||
|             } | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,6 @@ import android.content.res.Configuration; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.PixelFormat; | ||||
| import android.os.Build; | ||||
| import android.os.Handler; | ||||
| import android.os.IBinder; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.NonNull; | ||||
| @@ -49,20 +48,12 @@ import android.widget.PopupMenu; | ||||
| import android.widget.RemoteViews; | ||||
| import android.widget.SeekBar; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.google.android.exoplayer2.PlaybackParameters; | ||||
| import com.google.android.exoplayer2.Player; | ||||
|  | ||||
| import org.schabi.newpipe.BuildConfig; | ||||
| import org.schabi.newpipe.MainActivity; | ||||
| import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.ReCaptchaActivity; | ||||
| import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; | ||||
| import org.schabi.newpipe.extractor.exceptions.ParsingException; | ||||
| import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; | ||||
| 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; | ||||
| @@ -70,22 +61,12 @@ import org.schabi.newpipe.player.helper.LockManager; | ||||
| import org.schabi.newpipe.player.helper.PlayerHelper; | ||||
| import org.schabi.newpipe.player.old.PlayVideoActivity; | ||||
| import org.schabi.newpipe.playlist.PlayQueueItem; | ||||
| import org.schabi.newpipe.playlist.SinglePlayQueue; | ||||
| import org.schabi.newpipe.report.ErrorActivity; | ||||
| import org.schabi.newpipe.report.UserAction; | ||||
| import org.schabi.newpipe.util.Constants; | ||||
| import org.schabi.newpipe.util.ExtractorHelper; | ||||
| import org.schabi.newpipe.util.ListHelper; | ||||
| import org.schabi.newpipe.util.NavigationHelper; | ||||
| import org.schabi.newpipe.util.ThemeHelper; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
|  | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.disposables.Disposable; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
|  | ||||
| import static org.schabi.newpipe.player.helper.PlayerHelper.isUsingOldPlayer; | ||||
| import static org.schabi.newpipe.util.AnimationUtils.animateView; | ||||
|  | ||||
| @@ -125,8 +106,8 @@ public final class PopupVideoPlayer extends Service { | ||||
|     private RemoteViews notRemoteView; | ||||
|  | ||||
|     private VideoPlayerImpl playerImpl; | ||||
|     private Disposable currentWorker; | ||||
|     private LockManager lockManager; | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Service-Activity Binder | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
| @@ -157,21 +138,8 @@ public final class PopupVideoPlayer extends Service { | ||||
|         if (playerImpl.getPlayer() == null) initPopup(); | ||||
|         if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true); | ||||
|  | ||||
|         if (intent != null && intent.getStringExtra(Constants.KEY_URL) != null) { | ||||
|             final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0); | ||||
|             final String url = intent.getStringExtra(Constants.KEY_URL); | ||||
|         playerImpl.handleIntent(intent); | ||||
|  | ||||
|             playerImpl.setStartedFromNewPipe(false); | ||||
|  | ||||
|             final FetcherHandler fetcherRunnable = new FetcherHandler(this, serviceId, url); | ||||
|             currentWorker = ExtractorHelper.getStreamInfo(serviceId,url,false) | ||||
|                     .subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|                     .subscribe(fetcherRunnable::onReceive, fetcherRunnable::onError); | ||||
|         } else { | ||||
|             playerImpl.setStartedFromNewPipe(true); | ||||
|             playerImpl.handleIntent(intent); | ||||
|         } | ||||
|         return START_NOT_STICKY; | ||||
|     } | ||||
|  | ||||
| @@ -302,7 +270,6 @@ public final class PopupVideoPlayer extends Service { | ||||
|         } | ||||
|         if (lockManager != null) lockManager.releaseWifiAndCpu(); | ||||
|         if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); | ||||
|         if (currentWorker != null) currentWorker.dispose(); | ||||
|         mBinder = null; | ||||
|         playerImpl = null; | ||||
|  | ||||
| @@ -452,7 +419,6 @@ public final class PopupVideoPlayer extends Service { | ||||
|                         this.getPlaybackPitch(), | ||||
|                         this.getPlaybackQuality() | ||||
|                 ); | ||||
|                 if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false); | ||||
|                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             } else { | ||||
|                 intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class) | ||||
| @@ -862,63 +828,4 @@ public final class PopupVideoPlayer extends Service { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Fetcher handler used if open by a link out of NewPipe | ||||
|      */ | ||||
|     private class FetcherHandler { | ||||
|         private final int serviceId; | ||||
|         private final String url; | ||||
|  | ||||
|         private final Context context; | ||||
|         private final Handler mainHandler; | ||||
|  | ||||
|         private FetcherHandler(Context context, int serviceId, String url) { | ||||
|             this.mainHandler = new Handler(PopupVideoPlayer.this.getMainLooper()); | ||||
|             this.context = context; | ||||
|             this.url = url; | ||||
|             this.serviceId = serviceId; | ||||
|         } | ||||
|  | ||||
|         private void onReceive(final StreamInfo info) { | ||||
|             mainHandler.post(() -> { | ||||
|                 final Intent intent = NavigationHelper.getPlayerIntent(getApplicationContext(), | ||||
|                         PopupVideoPlayer.class, new SinglePlayQueue(info)); | ||||
|                 playerImpl.handleIntent(intent); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         private void onError(final Throwable exception) { | ||||
|             if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]"); | ||||
|             exception.printStackTrace(); | ||||
|             mainHandler.post(() -> { | ||||
|                 if (exception instanceof ReCaptchaException) { | ||||
|                     onReCaptchaException(); | ||||
|                 } else if (exception instanceof IOException) { | ||||
|                     Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); | ||||
|                 } else if (exception instanceof YoutubeStreamExtractor.GemaException) { | ||||
|                     Toast.makeText(context, R.string.blocked_by_gema, Toast.LENGTH_LONG).show(); | ||||
|                 } else if (exception instanceof YoutubeStreamExtractor.LiveStreamException) { | ||||
|                     Toast.makeText(context, R.string.live_streams_not_supported, Toast.LENGTH_LONG).show(); | ||||
|                 } else if (exception instanceof ContentNotAvailableException) { | ||||
|                     Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); | ||||
|                 } else { | ||||
|                     int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error : | ||||
|                             exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error; | ||||
|                     ErrorActivity.reportError(mainHandler, context, exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, errorId)); | ||||
|                 } | ||||
|             }); | ||||
|             stopSelf(); | ||||
|         } | ||||
|  | ||||
|         private void onReCaptchaException() { | ||||
|             Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); | ||||
|             // Starting ReCaptcha Challenge Activity | ||||
|             Intent intent = new Intent(context, ReCaptchaActivity.class); | ||||
|             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             context.startActivity(intent); | ||||
|             stopSelf(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -39,7 +39,6 @@ import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.SurfaceView; | ||||
| import android.view.View; | ||||
| import android.widget.ImageButton; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.PopupMenu; | ||||
| import android.widget.ProgressBar; | ||||
| @@ -85,12 +84,6 @@ public abstract class VideoPlayer extends BasePlayer | ||||
|     public static final boolean DEBUG = BasePlayer.DEBUG; | ||||
|     public final String TAG; | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Intent | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe"; | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Player | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
| @@ -102,7 +95,6 @@ public abstract class VideoPlayer extends BasePlayer | ||||
|  | ||||
|     protected String playbackQuality; | ||||
|  | ||||
|     private boolean startedFromNewPipe = true; | ||||
|     protected boolean wasPlaying = false; | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
| @@ -695,14 +687,6 @@ public abstract class VideoPlayer extends BasePlayer | ||||
|         return availableStreams.get(selectedStreamIndex); | ||||
|     } | ||||
|  | ||||
|     public boolean isStartedFromNewPipe() { | ||||
|         return startedFromNewPipe; | ||||
|     } | ||||
|  | ||||
|     public void setStartedFromNewPipe(boolean startedFromNewPipe) { | ||||
|         this.startedFromNewPipe = startedFromNewPipe; | ||||
|     } | ||||
|  | ||||
|     public Handler getControlsVisibilityHandler() { | ||||
|         return controlsVisibilityHandler; | ||||
|     } | ||||
|   | ||||
| @@ -15,6 +15,10 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C | ||||
|         super(item); | ||||
|     } | ||||
|  | ||||
|     public ChannelPlayQueue(final ChannelInfo info) { | ||||
|         this(info.getServiceId(), info.getUrl(), info.getNextStreamsUrl(), info.getRelatedStreams(), 0); | ||||
|     } | ||||
|  | ||||
|     public ChannelPlayQueue(final int serviceId, | ||||
|                             final String url, | ||||
|                             final String nextPageUrl, | ||||
|   | ||||
| @@ -15,6 +15,10 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo, | ||||
|         super(item); | ||||
|     } | ||||
|  | ||||
|     public PlaylistPlayQueue(final PlaylistInfo info) { | ||||
|         this(info.getServiceId(), info.getUrl(), info.getNextStreamsUrl(), info.getRelatedStreams(), 0); | ||||
|     } | ||||
|  | ||||
|     public PlaylistPlayQueue(final int serviceId, | ||||
|                              final String url, | ||||
|                              final String nextPageUrl, | ||||
|   | ||||
| @@ -72,7 +72,9 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         int id = item.getItemId(); | ||||
|         if (id == android.R.id.home) { | ||||
|             finish(); | ||||
|             if (getSupportFragmentManager().getBackStackEntryCount() == 0) { | ||||
|                 finish(); | ||||
|             } else getSupportFragmentManager().popBackStack(); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|   | ||||
| @@ -19,29 +19,38 @@ | ||||
|  | ||||
| package org.schabi.newpipe.util; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Handler; | ||||
| import android.util.Log; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import org.schabi.newpipe.MainActivity; | ||||
| import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.ReCaptchaActivity; | ||||
| import org.schabi.newpipe.extractor.Info; | ||||
| import org.schabi.newpipe.extractor.ListExtractor.NextItemsResult; | ||||
| import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.channel.ChannelInfo; | ||||
| import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; | ||||
| import org.schabi.newpipe.extractor.exceptions.ParsingException; | ||||
| import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; | ||||
| import org.schabi.newpipe.extractor.kiosk.KioskInfo; | ||||
| import org.schabi.newpipe.extractor.playlist.PlaylistInfo; | ||||
| import org.schabi.newpipe.extractor.search.SearchEngine; | ||||
| import org.schabi.newpipe.extractor.search.SearchResult; | ||||
| import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; | ||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||
| import org.schabi.newpipe.report.ErrorActivity; | ||||
| import org.schabi.newpipe.report.UserAction; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InterruptedIOException; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.Callable; | ||||
|  | ||||
| import io.reactivex.Maybe; | ||||
| import io.reactivex.MaybeSource; | ||||
| import io.reactivex.Single; | ||||
| import io.reactivex.annotations.NonNull; | ||||
| import io.reactivex.functions.Consumer; | ||||
| import io.reactivex.functions.Function; | ||||
|  | ||||
| public final class ExtractorHelper { | ||||
|     private static final String TAG = ExtractorHelper.class.getSimpleName(); | ||||
| @@ -198,6 +207,37 @@ public final class ExtractorHelper { | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A simple and general error handler that show a Toast for known exceptions, and for others, opens the report error activity with the (optional) error message. | ||||
|      */ | ||||
|     public static void handleGeneralException(Context context, int serviceId, String url, Throwable exception, UserAction userAction, String optionalErrorMessage) { | ||||
|         final Handler handler = new Handler(context.getMainLooper()); | ||||
|  | ||||
|         handler.post(() -> { | ||||
|             if (exception instanceof ReCaptchaException) { | ||||
|                 Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); | ||||
|                 // Starting ReCaptcha Challenge Activity | ||||
|                 Intent intent = new Intent(context, ReCaptchaActivity.class); | ||||
|                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|                 context.startActivity(intent); | ||||
|             } else if (exception instanceof IOException) { | ||||
|                 Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); | ||||
|             } else if (exception instanceof YoutubeStreamExtractor.GemaException) { | ||||
|                 Toast.makeText(context, R.string.blocked_by_gema, Toast.LENGTH_LONG).show(); | ||||
|             } else if (exception instanceof YoutubeStreamExtractor.LiveStreamException) { | ||||
|                 Toast.makeText(context, R.string.live_streams_not_supported, Toast.LENGTH_LONG).show(); | ||||
|             } else if (exception instanceof ContentNotAvailableException) { | ||||
|                 Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); | ||||
|             } else { | ||||
|                 int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error : | ||||
|                         exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error; | ||||
|                 ErrorActivity.reportError(handler, context, exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, | ||||
|                         serviceId == -1 ? "none" : NewPipe.getNameOfService(serviceId), url + (optionalErrorMessage == null ? "" : optionalErrorMessage), errorId)); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check if throwable have the cause that can be assignable from the causes to check. | ||||
|      * | ||||
| @@ -263,17 +303,4 @@ public final class ExtractorHelper { | ||||
|                 InterruptedIOException.class, | ||||
|                 InterruptedException.class); | ||||
|     } | ||||
|  | ||||
|     public static String toUpperCase(String value) { | ||||
|         StringBuilder sb = new StringBuilder(value); | ||||
|         for (int index = 0; index < sb.length(); index++) { | ||||
|             char c = sb.charAt(index); | ||||
|             if (Character.isLowerCase(c)) { | ||||
|                 sb.setCharAt(index, Character.toUpperCase(c)); | ||||
|             } else { | ||||
|                 sb.setCharAt(index, Character.toLowerCase(c)); | ||||
|             } | ||||
|         } | ||||
|         return sb.toString(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| /** | ||||
| /* | ||||
|  * Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com> | ||||
|  * InfoCache.java is part of NewPipe | ||||
|  * | ||||
|   | ||||
| @@ -9,6 +9,8 @@ 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.AlertDialog; | ||||
| import android.util.Log; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.nostra13.universalimageloader.core.ImageLoader; | ||||
| @@ -21,6 +23,10 @@ import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.ServiceList; | ||||
| import org.schabi.newpipe.extractor.StreamingService; | ||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||
| import org.schabi.newpipe.extractor.stream.AudioStream; | ||||
| import org.schabi.newpipe.extractor.stream.Stream; | ||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||
| import org.schabi.newpipe.extractor.stream.VideoStream; | ||||
| import org.schabi.newpipe.fragments.MainFragment; | ||||
| import org.schabi.newpipe.fragments.detail.VideoDetailFragment; | ||||
| import org.schabi.newpipe.fragments.list.channel.ChannelFragment; | ||||
| @@ -36,9 +42,12 @@ 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.player.old.PlayVideoActivity; | ||||
| import org.schabi.newpipe.playlist.PlayQueue; | ||||
| import org.schabi.newpipe.settings.SettingsActivity; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
|  | ||||
| @SuppressWarnings({"unused", "WeakerAccess"}) | ||||
| public class NavigationHelper { | ||||
|     public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; | ||||
| @@ -46,6 +55,7 @@ public class NavigationHelper { | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Players | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     public static Intent getPlayerIntent(final Context context, | ||||
|                                          final Class targetClazz, | ||||
|                                          final PlayQueue playQueue, | ||||
| @@ -65,9 +75,11 @@ public class NavigationHelper { | ||||
|  | ||||
|     public static Intent getPlayerEnqueueIntent(final Context context, | ||||
|                                                 final Class targetClazz, | ||||
|                                                 final PlayQueue playQueue) { | ||||
|                                                 final PlayQueue playQueue, | ||||
|                                                 final boolean selectOnAppend) { | ||||
|         return getPlayerIntent(context, targetClazz, playQueue) | ||||
|                 .putExtra(BasePlayer.APPEND_ONLY, true); | ||||
|                 .putExtra(BasePlayer.APPEND_ONLY, true) | ||||
|                 .putExtra(BasePlayer.SELECT_ON_APPEND, selectOnAppend); | ||||
|     } | ||||
|  | ||||
|     public static Intent getPlayerIntent(final Context context, | ||||
| @@ -84,16 +96,39 @@ public class NavigationHelper { | ||||
|     } | ||||
|  | ||||
|     public static void playOnMainPlayer(final Context context, final PlayQueue queue) { | ||||
|         context.startActivity(getPlayerIntent(context, MainVideoPlayer.class, queue)); | ||||
|         final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue); | ||||
|         playerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|         context.startActivity(playerIntent); | ||||
|     } | ||||
|  | ||||
|     public static void playOnPopupPlayer(final Activity activity, final PlayQueue queue) { | ||||
|         if (!PermissionHelper.isPopupEnabled(activity)) { | ||||
|             PermissionHelper.showPopupEnablementToast(activity); | ||||
|     public static void playOnOldVideoPlayer(Context context, StreamInfo info) { | ||||
|         ArrayList<VideoStream> videoStreamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false)); | ||||
|         int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList); | ||||
|  | ||||
|         if (index == -1) { | ||||
|             Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show(); | ||||
|             return; | ||||
|         } | ||||
|         Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); | ||||
|         activity.startService(getPlayerIntent(activity, PopupVideoPlayer.class, queue)); | ||||
|  | ||||
|         VideoStream videoStream = videoStreamsList.get(index); | ||||
|         Intent intent = new Intent(context, PlayVideoActivity.class) | ||||
|                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | ||||
|                 .putExtra(PlayVideoActivity.VIDEO_TITLE, info.getName()) | ||||
|                 .putExtra(PlayVideoActivity.STREAM_URL, videoStream.getUrl()) | ||||
|                 .putExtra(PlayVideoActivity.VIDEO_URL, info.getUrl()) | ||||
|                 .putExtra(PlayVideoActivity.START_POSITION, info.getStartPosition()); | ||||
|  | ||||
|         context.startActivity(intent); | ||||
|     } | ||||
|  | ||||
|     public static void playOnPopupPlayer(final Context context, final PlayQueue queue) { | ||||
|         if (!PermissionHelper.isPopupEnabled(context)) { | ||||
|             PermissionHelper.showPopupEnablementToast(context); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         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) { | ||||
| @@ -101,19 +136,92 @@ public class NavigationHelper { | ||||
|         context.startService(getPlayerIntent(context, BackgroundPlayer.class, queue)); | ||||
|     } | ||||
|  | ||||
|     public static void enqueueOnPopupPlayer(final Activity activity, final PlayQueue queue) { | ||||
|         if (!PermissionHelper.isPopupEnabled(activity)) { | ||||
|             PermissionHelper.showPopupEnablementToast(activity); | ||||
|     public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue) { | ||||
|         enqueueOnPopupPlayer(context, queue, false); | ||||
|     } | ||||
|  | ||||
|     public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) { | ||||
|         if (!PermissionHelper.isPopupEnabled(context)) { | ||||
|             PermissionHelper.showPopupEnablementToast(context); | ||||
|             return; | ||||
|         } | ||||
|         Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); | ||||
|         activity.startService(getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, queue)); | ||||
|  | ||||
|         Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); | ||||
|         context.startService(getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend)); | ||||
|     } | ||||
|  | ||||
|     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)); | ||||
|         enqueueOnBackgroundPlayer(context, queue, false); | ||||
|     } | ||||
|  | ||||
|     public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) { | ||||
|         Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show(); | ||||
|         context.startService(getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend)); | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // External Players | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     public static void playOnExternalAudioPlayer(Context context, StreamInfo info) { | ||||
|         final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams()); | ||||
|  | ||||
|         if (index == -1) { | ||||
|             Toast.makeText(context, R.string.audio_streams_empty, Toast.LENGTH_SHORT).show(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         AudioStream audioStream = info.getAudioStreams().get(index); | ||||
|         playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream); | ||||
|     } | ||||
|  | ||||
|     public static void playOnExternalVideoPlayer(Context context, StreamInfo info) { | ||||
|         ArrayList<VideoStream> videoStreamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false)); | ||||
|         int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList); | ||||
|  | ||||
|         if (index == -1) { | ||||
|             Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         VideoStream videoStream = videoStreamsList.get(index); | ||||
|         playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream); | ||||
|     } | ||||
|  | ||||
|     public static void playOnExternalPlayer(Context context, String name, String artist, Stream stream) { | ||||
|         Intent intent = new Intent(); | ||||
|         intent.setAction(Intent.ACTION_VIEW); | ||||
|         intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType()); | ||||
|         intent.putExtra(Intent.EXTRA_TITLE, name); | ||||
|         intent.putExtra("title", name); | ||||
|         intent.putExtra("artist", artist); | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|  | ||||
|         resolveActivityOrAskToInstall(context, intent); | ||||
|     } | ||||
|  | ||||
|     public static void resolveActivityOrAskToInstall(Context context, Intent intent) { | ||||
|         if (intent.resolveActivity(context.getPackageManager()) != null) { | ||||
|             context.startActivity(intent); | ||||
|         } else { | ||||
|             if (context instanceof Activity) { | ||||
|                 new AlertDialog.Builder(context) | ||||
|                         .setMessage(R.string.no_player_found) | ||||
|                         .setPositiveButton(R.string.install, (dialog, which) -> { | ||||
|                             Intent i = new Intent(); | ||||
|                             i.setAction(Intent.ACTION_VIEW); | ||||
|                             i.setData(Uri.parse(context.getString(R.string.fdroid_vlc_url))); | ||||
|                             context.startActivity(i); | ||||
|                         }) | ||||
|                         .setNegativeButton(R.string.cancel, (dialog, which) -> Log.i("NavigationHelper", "You unlocked a secret unicorn.")) | ||||
|                         .show(); | ||||
|                 //Log.e("NavigationHelper", "Either no Streaming player for audio was installed, or something important crashed:"); | ||||
|             } else { | ||||
|                 Toast.makeText(context, R.string.no_player_found_toast, Toast.LENGTH_LONG).show(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Through FragmentManager | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
| @@ -287,19 +395,6 @@ public class NavigationHelper { | ||||
|     // Link handling | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     public static boolean openByLink(Context context, String url) { | ||||
|         Intent intentByLink; | ||||
|         try { | ||||
|             intentByLink = getIntentByLink(context, url); | ||||
|         } catch (ExtractionException e) { | ||||
|             return false; | ||||
|         } | ||||
|         intentByLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|         intentByLink.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); | ||||
|         context.startActivity(intentByLink); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) { | ||||
|         Intent mIntent = new Intent(context, MainActivity.class); | ||||
|         mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId); | ||||
| @@ -317,15 +412,14 @@ public class NavigationHelper { | ||||
|             throw new ExtractionException("Service not supported at the moment"); | ||||
|         } | ||||
|  | ||||
|         int serviceId = service.getServiceId(); | ||||
|         StreamingService.LinkType linkType = service.getLinkTypeByUrl(url); | ||||
|  | ||||
|         if (linkType == StreamingService.LinkType.NONE) { | ||||
|             throw new ExtractionException("Url not known to service. service=" + serviceId + " url=" + url); | ||||
|             throw new ExtractionException("Url not known to service. service=" + service + " url=" + url); | ||||
|         } | ||||
|  | ||||
|         url = getCleanUrl(service, url, linkType); | ||||
|         Intent rIntent = getOpenIntent(context, url, serviceId, linkType); | ||||
|         Intent rIntent = getOpenIntent(context, url, service.getServiceId(), linkType); | ||||
|  | ||||
|         switch (linkType) { | ||||
|             case STREAM: | ||||
| @@ -337,7 +431,7 @@ public class NavigationHelper { | ||||
|         return rIntent; | ||||
|     } | ||||
|  | ||||
|     private static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws ExtractionException { | ||||
|     public static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws ExtractionException { | ||||
|         switch (linkType) { | ||||
|             case STREAM: | ||||
|                 return service.getStreamUrlIdHandler().cleanUrl(dirtyUrl); | ||||
| @@ -351,7 +445,6 @@ public class NavigationHelper { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private static Uri openMarketUrl(String packageName) { | ||||
|         return Uri.parse("market://details") | ||||
|                 .buildUpon() | ||||
|   | ||||
| @@ -20,7 +20,6 @@ import org.schabi.newpipe.R; | ||||
| public class PermissionHelper { | ||||
|     public static final int PERMISSION_WRITE_STORAGE = 778; | ||||
|     public static final int PERMISSION_READ_STORAGE = 777; | ||||
|     public static final int PERMISSION_SYSTEM_ALERT_WINDOW = 779; | ||||
|  | ||||
|  | ||||
|     public static boolean checkStoragePermissions(Activity activity) { | ||||
| @@ -80,27 +79,25 @@ public class PermissionHelper { | ||||
|      * In order to be able to draw over other apps, the permission android.permission.SYSTEM_ALERT_WINDOW have to be granted. | ||||
|      * <p> | ||||
|      * On < API 23 (MarshMallow) the permission was granted when the user installed the application (via AndroidManifest), | ||||
|      * on > 23, however, it have to start a activity asking the user if he agree. | ||||
|      * on > 23, however, it have to start a activity asking the user if he agrees. | ||||
|      * <p> | ||||
|      * This method just return if canDraw over other apps, if it doesn't, try to get the permission, | ||||
|      * it does not get the result of the startActivityForResult, if the user accept, the next time that he tries to open | ||||
|      * it will return true. | ||||
|      * This method just return if the app has permission to draw over other apps, and if it doesn't, it will try to get the permission. | ||||
|      * | ||||
|      * @param activity context to startActivityForResult | ||||
|      * @return returns {@link Settings#canDrawOverlays(Context)} | ||||
|      **/ | ||||
|     @RequiresApi(api = Build.VERSION_CODES.M) | ||||
|     public static boolean checkSystemAlertWindowPermission(Activity activity) { | ||||
|         if (!Settings.canDrawOverlays(activity)) { | ||||
|             Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity.getPackageName())); | ||||
|             activity.startActivityForResult(i, PERMISSION_SYSTEM_ALERT_WINDOW); | ||||
|     public static boolean checkSystemAlertWindowPermission(Context context) { | ||||
|         if (!Settings.canDrawOverlays(context)) { | ||||
|             Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName())); | ||||
|             i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             context.startActivity(i); | ||||
|             return false; | ||||
|         }else return true; | ||||
|     } | ||||
|  | ||||
|     public static boolean isPopupEnabled(Activity activity) { | ||||
|     public static boolean isPopupEnabled(Context context) { | ||||
|         return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || | ||||
|                 PermissionHelper.checkSystemAlertWindowPermission(activity); | ||||
|                 PermissionHelper.checkSystemAlertWindowPermission(context); | ||||
|     } | ||||
|  | ||||
|     public static void showPopupEnablementToast(Context context) { | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| package org.schabi.newpipe.util; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.res.TypedArray; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.AttrRes; | ||||
| import android.support.annotation.StyleRes; | ||||
|  | ||||
| import org.schabi.newpipe.R; | ||||
|  | ||||
| @@ -13,17 +16,7 @@ public class ThemeHelper { | ||||
|      * @param context context that the theme will be applied | ||||
|      */ | ||||
|     public static void setTheme(Context context) { | ||||
|         String lightTheme = context.getResources().getString(R.string.light_theme_key); | ||||
|         String darkTheme = context.getResources().getString(R.string.dark_theme_key); | ||||
|         String blackTheme = context.getResources().getString(R.string.black_theme_key); | ||||
|  | ||||
|         String selectedTheme = getSelectedTheme(context); | ||||
|  | ||||
|         if (selectedTheme.equals(lightTheme)) context.setTheme(R.style.LightTheme); | ||||
|         else if (selectedTheme.equals(blackTheme)) context.setTheme(R.style.BlackTheme); | ||||
|         else if (selectedTheme.equals(darkTheme)) context.setTheme(R.style.DarkTheme); | ||||
|             // Fallback | ||||
|         else context.setTheme(R.style.DarkTheme); | ||||
|         context.setTheme(getSelectedThemeStyle(context)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -35,9 +28,34 @@ public class ThemeHelper { | ||||
|         return getSelectedTheme(context).equals(context.getResources().getString(R.string.light_theme_key)); | ||||
|     } | ||||
|  | ||||
|     @StyleRes | ||||
|     public static int getSelectedThemeStyle(Context context) { | ||||
|         String lightTheme = context.getResources().getString(R.string.light_theme_key); | ||||
|         String darkTheme = context.getResources().getString(R.string.dark_theme_key); | ||||
|         String blackTheme = context.getResources().getString(R.string.black_theme_key); | ||||
|  | ||||
|         String selectedTheme = getSelectedTheme(context); | ||||
|  | ||||
|         if (selectedTheme.equals(lightTheme)) return R.style.LightTheme; | ||||
|         else if (selectedTheme.equals(blackTheme)) return R.style.BlackTheme; | ||||
|         else if (selectedTheme.equals(darkTheme)) return R.style.DarkTheme; | ||||
|             // Fallback | ||||
|         else return R.style.DarkTheme; | ||||
|     } | ||||
|  | ||||
|     public static String getSelectedTheme(Context context) { | ||||
|         String themeKey = context.getString(R.string.theme_key); | ||||
|         String defaultTheme = context.getResources().getString(R.string.default_theme_value); | ||||
|         return PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, defaultTheme); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get a resource id from a resource styled according to the the context's theme. | ||||
|      */ | ||||
|     public static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) { | ||||
|         TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); | ||||
|         int attributeResourceId = a.getResourceId(0, 0); | ||||
|         a.recycle(); | ||||
|         return attributeResourceId; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 194 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 195 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-mdpi/ic_play_arrow_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 150 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 157 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 208 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 220 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 265 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 283 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 320 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 343 B | 
							
								
								
									
										7
									
								
								app/src/main/res/drawable/dark_checked_selector.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android" android:enterFadeDuration="200" android:exitFadeDuration="200"> | ||||
|     <item android:drawable="@color/dark_ripple_color" android:state_checked="true" android:state_pressed="true" /> | ||||
|     <item android:drawable="@color/dark_ripple_color" android:state_pressed="true" /> | ||||
|     <item android:drawable="@color/dark_selected_color" android:state_checked="true" /> | ||||
|     <item android:drawable="@android:color/transparent" /> | ||||
| </selector> | ||||
							
								
								
									
										7
									
								
								app/src/main/res/drawable/light_checked_selector.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android" android:enterFadeDuration="200" android:exitFadeDuration="200"> | ||||
|     <item android:drawable="@color/light_ripple_color" android:state_checked="true" android:state_pressed="true" /> | ||||
|     <item android:drawable="@color/light_ripple_color" android:state_pressed="true" /> | ||||
|     <item android:drawable="@color/light_selected_color" android:state_checked="true" /> | ||||
|     <item android:drawable="@android:color/transparent" /> | ||||
| </selector> | ||||
							
								
								
									
										19
									
								
								app/src/main/res/layout/list_radio_icon_item.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RadioButton xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:button="@null" | ||||
|     android:drawablePadding="24dp" | ||||
|     android:ellipsize="marquee" | ||||
|     android:gravity="center_vertical" | ||||
|     android:maxLines="2" | ||||
|     android:minHeight="?attr/listPreferredItemHeightSmall" | ||||
|     android:paddingEnd="?attr/listPreferredItemPaddingRight" | ||||
|     android:paddingLeft="?attr/listPreferredItemPaddingLeft" | ||||
|     android:paddingRight="?attr/listPreferredItemPaddingRight" | ||||
|     android:paddingStart="?attr/listPreferredItemPaddingLeft" | ||||
|     android:background="?attr/checked_selector_drawable" | ||||
|     android:textColor="?attr/textColorAlertDialogListItem" | ||||
|     tools:drawableLeft="?attr/play" | ||||
|     tools:text="Lorem ipsum dolor sit amet" /> | ||||
							
								
								
									
										6
									
								
								app/src/main/res/layout/preferred_player_dialog_view.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:id="@android:id/list" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:paddingTop="?attr/listPreferredItemPaddingLeft" /> | ||||
| @@ -22,10 +22,12 @@ | ||||
|     <attr name="selected" format="reference"/> | ||||
|     <attr name="search_add" format="reference"/> | ||||
|     <attr name="options" format="reference"/> | ||||
|     <attr name="play" format="reference"/> | ||||
|  | ||||
|     <!-- Can't refer to colors directly into drawable's xml--> | ||||
|     <attr name="toolbar_shadow_drawable" format="reference"/> | ||||
|     <attr name="selector_drawable" format="reference"/> | ||||
|     <attr name="checked_selector_drawable" format="reference"/> | ||||
|  | ||||
|     <attr name="separator_color" format="color"/> | ||||
|     <attr name="queue_background_color" format="color"/> | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
|     <color name="light_youtube_accent_color">#000000</color> | ||||
|     <color name="light_separator_color">#32000000</color> | ||||
|     <color name="light_ripple_color">#48868686</color> | ||||
|     <color name="light_selected_color">#2a868686</color> | ||||
|     <color name="light_contrast_background_color">#1fa6a6a6</color> | ||||
|     <color name="light_shadow_start_color">#5a000000</color> | ||||
|     <color name="light_license_background_color">#ffffff</color> | ||||
| @@ -21,6 +22,7 @@ | ||||
|     <color name="dark_youtube_accent_color">#FFFFFF</color> | ||||
|     <color name="dark_separator_color">#0affffff</color> | ||||
|     <color name="dark_ripple_color">#48ffffff</color> | ||||
|     <color name="dark_selected_color">#2affffff</color> | ||||
|     <color name="dark_contrast_background_color">#1f717171</color> | ||||
|     <color name="dark_shadow_start_color">#82000000</color> | ||||
|     <color name="dark_license_background_color">#424242</color> | ||||
|   | ||||
| @@ -149,6 +149,29 @@ | ||||
|  | ||||
|     <string name="default_file_charset_value" translatable="false">@string/charset_most_special_characters_value</string> | ||||
|  | ||||
|     <!-- Preferred player --> | ||||
|     <string name="preferred_player_key" translatable="false">preferred_player_key</string> | ||||
|     <string name="preferred_player_default" translatable="false">@string/always_ask_player_key</string> | ||||
|     <string name="preferred_player_last_selected_key" translatable="false">preferred_player_last_selected</string> | ||||
|  | ||||
|     <string name="video_player_key" translatable="false">video_player</string> | ||||
|     <string name="background_player_key" translatable="false">background_player</string> | ||||
|     <string name="popup_player_key" translatable="false">popup_player</string> | ||||
|     <string name="always_ask_player_key" translatable="false">always_ask_player</string> | ||||
|  | ||||
|     <string-array name="preferred_player_description_list" translatable="false"> | ||||
|         <item>@string/video_player</item> | ||||
|         <item>@string/background_player</item> | ||||
|         <item>@string/popup_player</item> | ||||
|         <item>@string/always_ask_player</item> | ||||
|     </string-array> | ||||
|     <string-array name="preferred_player_values_list" translatable="false"> | ||||
|         <item>@string/video_player_key</item> | ||||
|         <item>@string/background_player_key</item> | ||||
|         <item>@string/popup_player_key</item> | ||||
|         <item>@string/always_ask_player_key</item> | ||||
|     </string-array> | ||||
|  | ||||
|     <!-- alternatively, load these from some local android data store --> | ||||
|     <string-array name="language_codes" translatable="false"> | ||||
|         <item>af</item> | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|     <string name="view_count_text">%1$s views</string> | ||||
|     <string name="upload_date_text">Published on %1$s</string> | ||||
|     <string name="no_player_found">No stream player found. Do you want to install VLC?</string> | ||||
|     <string name="no_player_found_toast">No stream player found (you can install VLC to play it)</string> | ||||
|     <string name="install">Install</string> | ||||
|     <string name="cancel">Cancel</string> | ||||
|     <string name="fdroid_vlc_url" translatable="false">https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc</string> | ||||
| @@ -119,6 +120,8 @@ | ||||
|     <string name="best_resolution">Best resolution</string> | ||||
|     <string name="undo">Undo</string> | ||||
|     <string name="play_all">Play All</string> | ||||
|     <string name="always">Always</string> | ||||
|     <string name="just_once">Just Once</string> | ||||
|  | ||||
|     <string name="notification_channel_id" translatable="false">newpipe</string> | ||||
|     <string name="notification_channel_name">NewPipe Notification</string> | ||||
| @@ -148,6 +151,10 @@ | ||||
|     <string name="player_stream_failure">Failed to play this stream</string> | ||||
|     <string name="player_unrecoverable_failure">Unrecoverable player error occurred</string> | ||||
|     <string name="player_recoverable_failure">Recovering from player error</string> | ||||
|     <string name="external_player_unsupported_link_type">External players don\'t support these types of links</string> | ||||
|     <string name="invalid_url_toast">Invalid URL</string> | ||||
|     <string name="video_streams_empty">No video streams found</string> | ||||
|     <string name="audio_streams_empty">No audio streams found</string> | ||||
|  | ||||
|     <!-- error activity --> | ||||
|     <string name="sorry_string">Sorry, that should not have happened.</string> | ||||
| @@ -336,4 +343,17 @@ | ||||
|     <string name="drawer_close">Close Drawer</string> | ||||
|     <string name="youtube" translatable="false">YouTube</string> | ||||
|     <string name="soundcloud" translatable="false">SoundCloud</string> | ||||
|  | ||||
|     <!-- Preferred player --> | ||||
|     <string name="preferred_player_share_menu_title">@string/preferred_player_settings_title</string> | ||||
|     <string name="preferred_player_share_menu_dialog_title">Open with preferred player</string> | ||||
|     <string name="preferred_player_settings_title">Preferred player</string> | ||||
|  | ||||
|     <string name="video_player">Video player</string> | ||||
|     <string name="background_player">Background player</string> | ||||
|     <string name="popup_player">Popup player</string> | ||||
|     <string name="always_ask_player">Always ask</string> | ||||
|      | ||||
|     <string name="preferred_player_fetcher_notification_title">Getting info…</string> | ||||
|     <string name="preferred_player_fetcher_notification_message">"The requested content is loading"</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -29,9 +29,11 @@ | ||||
|         <item name="selected">@drawable/ic_fiber_manual_record_black_24dp</item> | ||||
|         <item name="search_add">@drawable/ic_arrow_top_left_black_24dp</item> | ||||
|         <item name="options">@drawable/ic_more_vert_black_24dp</item> | ||||
|         <item name="play">@drawable/ic_play_arrow_black_24dp</item> | ||||
|  | ||||
|         <item name="separator_color">@color/light_separator_color</item> | ||||
|         <item name="contrast_background_color">@color/light_contrast_background_color</item> | ||||
|         <item name="checked_selector_drawable">@drawable/light_checked_selector</item> | ||||
|         <item name="queue_background_color">@color/light_queue_background_color</item> | ||||
|         <item name="toolbar_shadow_drawable">@drawable/toolbar_shadow_light</item> | ||||
|         <item name="selector_drawable">@drawable/light_selector</item> | ||||
| @@ -69,9 +71,11 @@ | ||||
|         <item name="selected">@drawable/ic_fiber_manual_record_white_24dp</item> | ||||
|         <item name="search_add">@drawable/ic_arrow_top_left_white_24dp</item> | ||||
|         <item name="options">@drawable/ic_more_vert_white_24dp</item> | ||||
|         <item name="play">@drawable/ic_play_arrow_white_24dp</item> | ||||
|  | ||||
|         <item name="separator_color">@color/dark_separator_color</item> | ||||
|         <item name="contrast_background_color">@color/dark_contrast_background_color</item> | ||||
|         <item name="checked_selector_drawable">@drawable/dark_checked_selector</item> | ||||
|         <item name="queue_background_color">@color/dark_queue_background_color</item> | ||||
|         <item name="toolbar_shadow_drawable">@drawable/toolbar_shadow_dark</item> | ||||
|         <item name="selector_drawable">@drawable/dark_selector</item> | ||||
| @@ -164,12 +168,27 @@ | ||||
|         <item name="android:background">@color/dark_youtube_primary_color</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="PopupPermissionsTheme" parent="Theme.AppCompat.NoActionBar"> | ||||
|     <style name="RouterActivityThemeLight" parent="LightTheme"> | ||||
|         <item name="colorPrimary">@android:color/transparent</item> | ||||
|         <item name="colorPrimaryDark">@android:color/transparent</item> | ||||
|         <item name="colorAccent">@android:color/transparent</item> | ||||
|  | ||||
|         <item name="android:windowNoTitle">true</item> | ||||
|         <item name="android:windowBackground">@android:color/transparent</item> | ||||
|         <item name="android:colorBackgroundCacheHint">@null</item> | ||||
|         <item name="android:windowIsTranslucent">true</item> | ||||
|         <item name="android:windowAnimationStyle">@android:style/Animation</item> | ||||
|         <item name="android:windowNoDisplay">true</item> | ||||
|         <item name="android:windowAnimationStyle">@null</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="RouterActivityThemeDark" parent="DarkTheme"> | ||||
|         <item name="colorPrimary">@android:color/transparent</item> | ||||
|         <item name="colorPrimaryDark">@android:color/transparent</item> | ||||
|         <item name="colorAccent">@android:color/transparent</item> | ||||
|  | ||||
|         <item name="android:windowNoTitle">true</item> | ||||
|         <item name="android:windowBackground">@android:color/transparent</item> | ||||
|         <item name="android:colorBackgroundCacheHint">@null</item> | ||||
|         <item name="android:windowIsTranslucent">true</item> | ||||
|         <item name="android:windowAnimationStyle">@null</item> | ||||
|     </style> | ||||
| </resources> | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <PreferenceScreen | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:title="@string/settings_category_video_audio_title"> | ||||
|  | ||||
|     <ListPreference | ||||
| @@ -81,6 +80,14 @@ | ||||
|             android:summary="@string/autoplay_by_calling_app_summary" | ||||
|             android:title="@string/autoplay_by_calling_app_title"/> | ||||
|  | ||||
|         <ListPreference | ||||
|             android:defaultValue="@string/preferred_player_default" | ||||
|             android:entries="@array/preferred_player_description_list" | ||||
|             android:entryValues="@array/preferred_player_values_list" | ||||
|             android:key="@string/preferred_player_key" | ||||
|             android:summary="%s" | ||||
|             android:title="@string/preferred_player_settings_title"/> | ||||
|  | ||||
|         <SwitchPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/resume_on_audio_focus_gain_key" | ||||
|   | ||||
 Weblate
					Weblate