Add preferred player
@ -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,
|
||||
|
@ -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"
|
||||
|