1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-12-15 21:31:47 +01:00

merge RouterActivity and RouterVideoActivity

This commit is contained in:
Christian Schabesberger 2018-02-12 19:44:35 +01:00
parent ba0be665ae
commit 738e2ac344
17 changed files with 465 additions and 526 deletions

View File

@ -122,8 +122,12 @@
<activity
android:name=".RouterActivity"
android:excludeFromRecents="true"
android:label="@string/preferred_player_share_menu_title"
android:taskAffinity=""
android:theme="@style/RouterActivityThemeDark">
<!-- Youtube filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
@ -169,6 +173,41 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="vnd.youtube"/>
<data android:scheme="vnd.youtube.launch"/>
</intent-filter>
<!-- Hooktube filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="hooktube.com"/>
<data android:host="*.youtube.com"/>
<!-- video prefix -->
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
</intent-filter>
<!-- Soundcloud filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="soundcloud.com"/>
@ -176,17 +215,8 @@
<data android:host="www.soundcloud.com"/>
<data android:pathPrefix="/"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="vnd.youtube"/>
<data android:scheme="vnd.youtube.launch"/>
</intent-filter>
<!-- Share filter -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
@ -195,68 +225,7 @@
</activity>
<service
android:name=".RouterPlayerActivity$FetcherService"
android:name=".RouterActivity$FetcherService"
android:exported="false"/>
<activity
android:name=".RouterPlayerActivity"
android:excludeFromRecents="true"
android:label="@string/preferred_player_share_menu_title"
android:taskAffinity=""
android:theme="@style/RouterActivityThemeDark">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtube.com"/>
<data android:host="m.youtube.com"/>
<data android:host="www.youtube.com"/>
<!-- video prefix -->
<data android:pathPrefix="/v/"/>
<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"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtu.be"/>
<data android:pathPrefix="/"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="vnd.youtube"/>
<data android:scheme="vnd.youtube.launch"/>
</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>
</application>
</manifest>

View File

@ -1,51 +1,80 @@
package org.schabi.newpipe;
import android.app.IntentService;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.StreamingService.LinkType;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.playlist.ChannelPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlaylistPlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import icepick.Icepick;
import icepick.State;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
/*
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
* RouterActivity.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
/**
* This Acitivty is designed to route share/open intents to the specified service, and
* to the part of the service which can handle the url.
* Get the url from the intent and open it in the chosen preferred player
*/
public class RouterActivity extends AppCompatActivity {
@State
protected int currentServiceId = -1;
private StreamingService currentService;
@State
protected LinkType currentLinkType;
@State
protected int selectedRadioPosition = -1;
protected int selectedPreviously = -1;
protected String currentUrl;
protected CompositeDisposable disposables = new CompositeDisposable();
@ -62,6 +91,10 @@ public class RouterActivity extends AppCompatActivity {
finish();
}
}
setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight
: R.style.RouterActivityThemeDark);
}
@Override
@ -73,25 +106,43 @@ public class RouterActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
handleUrl(currentUrl);
}
protected void handleUrl(String url) {
disposables.add(Observable
.fromCallable(() -> NavigationHelper.getIntentByLink(this, url))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
@Override
protected void onDestroy() {
super.onDestroy();
finish();
}, this::handleError)
);
disposables.clear();
}
protected void handleError(Throwable error) {
private void handleUrl(String url) {
disposables.add(Observable
.fromCallable(() -> {
if (currentServiceId == -1) {
currentService = NewPipe.getServiceByUrl(url);
currentServiceId = currentService.getServiceId();
currentLinkType = currentService.getLinkTypeByUrl(url);
currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType);
} else {
currentService = NewPipe.getService(currentServiceId);
}
return currentLinkType != LinkType.NONE;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (result) {
onSuccess();
} else {
onError();
}
}, this::handleError));
}
private void handleError(Throwable error) {
error.printStackTrace();
if (error instanceof ExtractionException) {
@ -103,11 +154,339 @@ public class RouterActivity extends AppCompatActivity {
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
private void onError() {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
finish();
}
disposables.clear();
protected void onSuccess() {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
if ((isExtAudioEnabled || isExtVideoEnabled) && currentLinkType != LinkType.STREAM) {
Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
finish();
return;
}
// TODO: Add some sort of "capabilities" field to services (audio only, video and audio, etc.)
if (currentService == ServiceList.SoundCloud.getService()) {
handleChoice(getString(R.string.background_player_key));
return;
}
final String playerChoiceKey = preferences.getString(getString(R.string.preferred_player_key), getString(R.string.preferred_player_default));
final String alwaysAskKey = getString(R.string.always_ask_player_key);
if (playerChoiceKey.equals(alwaysAskKey)) {
showDialog();
} else {
handleChoice(playerChoiceKey);
}
}
private void showDialog() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this,
ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
LayoutInflater inflater = LayoutInflater.from(themeWrapper);
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
final AdapterChoiceItem[] choices = {
new AdapterChoiceItem(getString(R.string.info_screen_key), getString(R.string.info_screen),
resolveResourceIdFromAttr(themeWrapper, R.attr.info)),
new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.play)),
new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.audio)),
new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.popup))
};
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
final int indexOfChild = radioGroup.indexOfChild(
radioGroup.findViewById(radioGroup.getCheckedRadioButtonId()));
final AdapterChoiceItem choice = choices[indexOfChild];
handleChoice(choice.key);
if (which == DialogInterface.BUTTON_POSITIVE) {
preferences.edit().putString(getString(R.string.preferred_player_key), choice.key).apply();
}
};
final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper)
.setTitle(R.string.preferred_player_share_menu_title)
.setView(radioGroup)
.setCancelable(true)
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
.setPositiveButton(R.string.always, dialogButtonsClickListener)
.setOnDismissListener((dialog) -> finish())
.create();
alertDialog.setOnShowListener(dialog -> {
setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
});
radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true));
final View.OnClickListener radioButtonsClickListener = v -> {
final int indexOfChild = radioGroup.indexOfChild(v);
if (indexOfChild == -1) return;
selectedPreviously = selectedRadioPosition;
selectedRadioPosition = indexOfChild;
if (selectedPreviously == selectedRadioPosition) {
handleChoice(choices[selectedRadioPosition].key);
}
};
int id = 12345;
for (AdapterChoiceItem item : choices) {
final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
radioButton.setText(item.description);
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
radioButton.setChecked(false);
radioButton.setId(id++);
radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener);
radioGroup.addView(radioButton);
}
if (selectedRadioPosition == -1) {
final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_player_last_selected_key), null);
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
for (int i = 0; i < choices.length; i++) {
AdapterChoiceItem c = choices[i];
if (lastSelectedPlayer.equals(c.key)) {
selectedRadioPosition = i;
break;
}
}
}
}
selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1);
if (selectedRadioPosition != -1) {
((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true);
}
selectedPreviously = selectedRadioPosition;
alertDialog.show();
}
private void setDialogButtonsState(AlertDialog dialog, boolean state) {
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (negativeButton == null || positiveButton == null) return;
negativeButton.setEnabled(state);
positiveButton.setEnabled(state);
}
private void handleChoice(final String playerChoiceKey) {
if (Arrays.asList(getResources().getStringArray(R.array.preferred_player_values_list)).contains(playerChoiceKey)) {
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(getString(R.string.preferred_player_last_selected_key), playerChoiceKey).apply();
}
if (playerChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this);
finish();
return;
}
// stop and bypass FetcherService if InfoScreen was selected since
// StreamDetailFragment can fetch data itself
if(playerChoiceKey.equals(getString(R.string.info_screen_key))) {
disposables.add(Observable
.fromCallable(() -> NavigationHelper.getIntentByLink(this, currentUrl))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}, this::handleError)
);
return;
}
final Intent intent = new Intent(this, FetcherService.class);
intent.putExtra(FetcherService.KEY_CHOICE,
new Choice(currentService.getServiceId(),
currentLinkType,
currentUrl,
playerChoiceKey));
startService(intent);
finish();
}
private static class AdapterChoiceItem {
final String description, key;
@DrawableRes
final int icon;
AdapterChoiceItem(String key, String description, int icon) {
this.description = description;
this.key = key;
this.icon = icon;
}
}
private static class Choice implements Serializable {
final int serviceId;
final String url, playerChoice;
final LinkType linkType;
Choice(int serviceId, LinkType linkType, String url, String playerChoice) {
this.serviceId = serviceId;
this.linkType = linkType;
this.url = url;
this.playerChoice = playerChoice;
}
@Override
public String toString() {
return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Service Fetcher
//////////////////////////////////////////////////////////////////////////*/
public static class FetcherService extends IntentService {
private static final int ID = 456;
public static final String KEY_CHOICE = "key_choice";
private Disposable fetcher;
public FetcherService() {
super(FetcherService.class.getSimpleName());
}
@Override
public void onCreate() {
super.onCreate();
startForeground(ID, createNotification().build());
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent == null) return;
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
if (!(serializable instanceof Choice)) return;
Choice playerChoice = (Choice) serializable;
handleChoice(playerChoice);
}
public void handleChoice(Choice choice) {
Single<? extends Info> single = null;
UserAction userAction = UserAction.SOMETHING_ELSE;
switch (choice.linkType) {
case STREAM:
single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_STREAM;
break;
case CHANNEL:
single = ExtractorHelper.getChannelInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_CHANNEL;
break;
case PLAYLIST:
single = ExtractorHelper.getPlaylistInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_PLAYLIST;
break;
}
if (single != null) {
final UserAction finalUserAction = userAction;
final Consumer<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));
}
}
/*//////////////////////////////////////////////////////////////////////////
@ -119,9 +498,9 @@ public class RouterActivity extends AppCompatActivity {
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
* more details.
*/
protected final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
protected String getUrl(Intent intent) {
private String getUrl(Intent intent) {
// first gather data and find service
String videoUrl = null;
if (intent.getData() != null) {
@ -137,7 +516,7 @@ public class RouterActivity extends AppCompatActivity {
return videoUrl;
}
protected String removeHeadingGibberish(final String input) {
private String removeHeadingGibberish(final String input) {
int start = 0;
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
if (!input.substring(i, i + 1).matches("\\p{L}")) {
@ -148,7 +527,7 @@ public class RouterActivity extends AppCompatActivity {
return input.substring(start, input.length());
}
protected String trim(final String input) {
private String trim(final String input) {
if (input == null || input.length() < 1) {
return input;
} else {
@ -188,5 +567,4 @@ public class RouterActivity extends AppCompatActivity {
}
return result.toArray(new String[result.size()]);
}
}

View File

@ -1,413 +0,0 @@
package org.schabi.newpipe;
import android.app.IntentService;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.StreamingService.LinkType;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.playlist.ChannelPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlaylistPlayQueue;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.Arrays;
import icepick.State;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
/**
* Get the url from the intent and open it in the chosen preferred player
*/
public class RouterPlayerActivity extends RouterActivity {
@State
protected int currentServiceId = -1;
private StreamingService currentService;
@State
protected LinkType currentLinkType;
@State
protected int selectedRadioPosition = -1;
protected int selectedPreviously = -1;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
setTheme(ThemeHelper.isLightThemeSelected(this) ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
}
@Override
protected void handleUrl(String url) {
disposables.add(Observable
.fromCallable(() -> {
if (currentServiceId == -1) {
currentService = NewPipe.getServiceByUrl(url);
currentServiceId = currentService.getServiceId();
currentLinkType = currentService.getLinkTypeByUrl(url);
currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType);
} else {
currentService = NewPipe.getService(currentServiceId);
}
return currentLinkType != LinkType.NONE;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (result) {
onSuccess();
} else {
onError();
}
}, this::handleError));
}
protected void onError() {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
finish();
}
protected void onSuccess() {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
if ((isExtAudioEnabled || isExtVideoEnabled) && currentLinkType != LinkType.STREAM) {
Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
finish();
return;
}
// TODO: Add some sort of "capabilities" field to services (audio only, video and audio, etc.)
if (currentService == ServiceList.SoundCloud.getService()) {
handleChoice(getString(R.string.background_player_key));
return;
}
final String playerChoiceKey = preferences.getString(getString(R.string.preferred_player_key), getString(R.string.preferred_player_default));
final String alwaysAskKey = getString(R.string.always_ask_player_key);
if (playerChoiceKey.equals(alwaysAskKey)) {
showDialog();
} else {
handleChoice(playerChoiceKey);
}
}
private void showDialog() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this,
ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
LayoutInflater inflater = LayoutInflater.from(themeWrapper);
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
final AdapterChoiceItem[] choices = {
new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.play)),
new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.audio)),
new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player),
resolveResourceIdFromAttr(themeWrapper, R.attr.popup))
};
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
final int indexOfChild = radioGroup.indexOfChild(radioGroup.findViewById(radioGroup.getCheckedRadioButtonId()));
final AdapterChoiceItem choice = choices[indexOfChild];
handleChoice(choice.key);
if (which == DialogInterface.BUTTON_POSITIVE) {
preferences.edit().putString(getString(R.string.preferred_player_key), choice.key).apply();
}
};
final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper)
.setTitle(R.string.preferred_player_share_menu_title)
.setView(radioGroup)
.setCancelable(true)
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
.setPositiveButton(R.string.always, dialogButtonsClickListener)
.setOnDismissListener((dialog) -> finish())
.create();
alertDialog.setOnShowListener(dialog -> {
setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
});
radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true));
final View.OnClickListener radioButtonsClickListener = v -> {
final int indexOfChild = radioGroup.indexOfChild(v);
if (indexOfChild == -1) return;
selectedPreviously = selectedRadioPosition;
selectedRadioPosition = indexOfChild;
if (selectedPreviously == selectedRadioPosition) {
handleChoice(choices[selectedRadioPosition].key);
}
};
int id = 12345;
for (AdapterChoiceItem item : choices) {
final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
radioButton.setText(item.description);
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
radioButton.setChecked(false);
radioButton.setId(id++);
radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener);
radioGroup.addView(radioButton);
}
if (selectedRadioPosition == -1) {
final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_player_last_selected_key), null);
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
for (int i = 0; i < choices.length; i++) {
AdapterChoiceItem c = choices[i];
if (lastSelectedPlayer.equals(c.key)) {
selectedRadioPosition = i;
break;
}
}
}
}
selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1);
if (selectedRadioPosition != -1) {
((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true);
}
selectedPreviously = selectedRadioPosition;
alertDialog.show();
}
private void setDialogButtonsState(AlertDialog dialog, boolean state) {
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (negativeButton == null || positiveButton == null) return;
negativeButton.setEnabled(state);
positiveButton.setEnabled(state);
}
private void handleChoice(final String playerChoiceKey) {
if (Arrays.asList(getResources().getStringArray(R.array.preferred_player_values_list)).contains(playerChoiceKey)) {
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(getString(R.string.preferred_player_last_selected_key), playerChoiceKey).apply();
}
if (playerChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this);
finish();
return;
}
final Intent intent = new Intent(this, FetcherService.class);
intent.putExtra(FetcherService.KEY_CHOICE, new Choice(currentService.getServiceId(), currentLinkType, currentUrl, playerChoiceKey));
startService(intent);
finish();
}
private static class AdapterChoiceItem {
final String description, key;
@DrawableRes
final int icon;
AdapterChoiceItem(String key, String description, int icon) {
this.description = description;
this.key = key;
this.icon = icon;
}
}
private static class Choice implements Serializable {
final int serviceId;
final String url, playerChoice;
final LinkType linkType;
Choice(int serviceId, LinkType linkType, String url, String playerChoice) {
this.serviceId = serviceId;
this.linkType = linkType;
this.url = url;
this.playerChoice = playerChoice;
}
@Override
public String toString() {
return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Service Fetcher
//////////////////////////////////////////////////////////////////////////*/
public static class FetcherService extends IntentService {
private static final int ID = 456;
public static final String KEY_CHOICE = "key_choice";
private Disposable fetcher;
public FetcherService() {
super(FetcherService.class.getSimpleName());
}
@Override
public void onCreate() {
super.onCreate();
startForeground(ID, createNotification().build());
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent == null) return;
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
if (!(serializable instanceof Choice)) return;
Choice playerChoice = (Choice) serializable;
handleChoice(playerChoice);
}
public void handleChoice(Choice choice) {
Single<? extends Info> single = null;
UserAction userAction = UserAction.SOMETHING_ELSE;
switch (choice.linkType) {
case STREAM:
single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_STREAM;
break;
case CHANNEL:
single = ExtractorHelper.getChannelInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_CHANNEL;
break;
case PLAYLIST:
single = ExtractorHelper.getPlaylistInfo(choice.serviceId, choice.url, false);
userAction = UserAction.REQUESTED_PLAYLIST;
break;
}
if (single != null) {
final UserAction finalUserAction = userAction;
final Consumer<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));
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -3,6 +3,7 @@
<!--TODO: preffix these with "ic_"-->
<attr name="thumbs_up" format="reference"/>
<attr name="thumbs_down" format="reference"/>
<attr name="info" format="reference"/>
<attr name="audio" format="reference"/>
<attr name="download" format="reference"/>
<attr name="share" format="reference"/>

View File

@ -155,6 +155,7 @@
<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="info_screen_key" translatable="false">info_screen</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>

View File

@ -30,6 +30,7 @@
<string name="channel_unsubscribed">Channel unsubscribed</string>
<string name="subscription_change_failed">Unable to change subscription</string>
<string name="subscription_update_failed">Unable to update subscription</string>
<string name="info_screen">Info Screen</string>
<string name="tab_main">Main</string>
<string name="tab_subscriptions">Subscriptions</string>

View File

@ -18,6 +18,7 @@
<item name="thumbs_up">@drawable/ic_thumb_up_black_24dp</item>
<item name="thumbs_down">@drawable/ic_thumb_down_black_24dp</item>
<item name="info">@drawable/ic_info_outline_black_24dp</item>
<item name="audio">@drawable/ic_headset_black_24dp</item>
<item name="clear_history">@drawable/ic_delete_sweep_white_24dp</item>
<item name="download">@drawable/ic_file_download_black_24dp</item>
@ -69,6 +70,7 @@
<item name="thumbs_up">@drawable/ic_thumb_up_white_24dp</item>
<item name="thumbs_down">@drawable/ic_thumb_down_white_24dp</item>
<item name="audio">@drawable/ic_headset_white_24dp</item>
<item name="info">@drawable/ic_info_outline_white_24dp</item>
<item name="clear_history">@drawable/ic_delete_sweep_black_24dp</item>
<item name="download">@drawable/ic_file_download_white_24dp</item>
<item name="share">@drawable/ic_share_white_24dp</item>