2017-02-27 20:14:03 +00:00
|
|
|
package org.schabi.newpipe;
|
|
|
|
|
2021-10-02 17:21:25 +00:00
|
|
|
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
|
|
|
|
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;
|
|
|
|
|
2018-06-17 11:55:43 +00:00
|
|
|
import android.annotation.SuppressLint;
|
2018-02-12 18:44:35 +00:00
|
|
|
import android.app.IntentService;
|
2018-04-06 09:02:51 +00:00
|
|
|
import android.content.Context;
|
2018-02-12 18:44:35 +00:00
|
|
|
import android.content.DialogInterface;
|
2017-02-27 20:14:03 +00:00
|
|
|
import android.content.Intent;
|
2018-02-12 18:44:35 +00:00
|
|
|
import android.content.SharedPreferences;
|
2018-06-20 12:46:57 +00:00
|
|
|
import android.content.pm.PackageManager;
|
2017-02-27 20:14:03 +00:00
|
|
|
import android.os.Bundle;
|
2018-01-23 00:40:00 +00:00
|
|
|
import android.text.TextUtils;
|
2018-02-12 18:44:35 +00:00
|
|
|
import android.view.ContextThemeWrapper;
|
|
|
|
import android.view.LayoutInflater;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.ViewGroup;
|
|
|
|
import android.widget.Button;
|
|
|
|
import android.widget.RadioButton;
|
|
|
|
import android.widget.RadioGroup;
|
2017-02-27 20:14:03 +00:00
|
|
|
import android.widget.Toast;
|
|
|
|
|
2020-03-27 02:18:14 +00:00
|
|
|
import androidx.annotation.DrawableRes;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import androidx.annotation.Nullable;
|
2022-05-02 20:37:51 +00:00
|
|
|
import androidx.annotation.StringRes;
|
2020-03-27 02:18:14 +00:00
|
|
|
import androidx.appcompat.app.AlertDialog;
|
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
2020-04-01 13:15:38 +00:00
|
|
|
import androidx.appcompat.content.res.AppCompatResources;
|
2020-03-27 02:18:14 +00:00
|
|
|
import androidx.core.app.NotificationCompat;
|
2020-12-19 11:22:17 +00:00
|
|
|
import androidx.core.app.ServiceCompat;
|
2019-10-04 12:59:08 +00:00
|
|
|
import androidx.fragment.app.FragmentManager;
|
2020-10-22 00:15:27 +00:00
|
|
|
import androidx.preference.PreferenceManager;
|
2019-10-04 12:59:08 +00:00
|
|
|
|
2021-10-09 16:46:20 +00:00
|
|
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
2020-10-31 09:06:12 +00:00
|
|
|
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
|
|
|
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
2018-06-17 11:55:43 +00:00
|
|
|
import org.schabi.newpipe.download.DownloadDialog;
|
2020-12-11 13:55:47 +00:00
|
|
|
import org.schabi.newpipe.error.ErrorInfo;
|
2021-12-01 08:43:24 +00:00
|
|
|
import org.schabi.newpipe.error.ErrorUtil;
|
2020-12-11 13:55:47 +00:00
|
|
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
|
|
|
import org.schabi.newpipe.error.UserAction;
|
2018-02-12 18:44:35 +00:00
|
|
|
import org.schabi.newpipe.extractor.Info;
|
|
|
|
import org.schabi.newpipe.extractor.NewPipe;
|
|
|
|
import org.schabi.newpipe.extractor.StreamingService;
|
|
|
|
import org.schabi.newpipe.extractor.StreamingService.LinkType;
|
|
|
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
2020-12-11 13:55:47 +00:00
|
|
|
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
|
|
|
|
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
|
|
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
2018-01-23 00:40:00 +00:00
|
|
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
2020-12-11 13:55:47 +00:00
|
|
|
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
|
|
|
|
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
|
|
|
|
import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
|
|
|
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
|
|
|
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
|
|
|
|
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
|
2018-02-12 18:44:35 +00:00
|
|
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
|
|
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
2020-12-11 13:55:47 +00:00
|
|
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
2021-10-09 16:46:20 +00:00
|
|
|
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
2022-04-08 07:35:14 +00:00
|
|
|
import org.schabi.newpipe.player.PlayerService;
|
2020-09-09 18:45:42 +00:00
|
|
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
2020-10-18 18:33:08 +00:00
|
|
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
2018-04-21 21:10:01 +00:00
|
|
|
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
|
|
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
|
|
|
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
|
|
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
2018-12-24 18:11:21 +00:00
|
|
|
import org.schabi.newpipe.util.Constants;
|
2020-10-22 00:15:27 +00:00
|
|
|
import org.schabi.newpipe.util.DeviceUtils;
|
2018-01-23 00:40:00 +00:00
|
|
|
import org.schabi.newpipe.util.ExtractorHelper;
|
2022-05-04 17:09:41 +00:00
|
|
|
import org.schabi.newpipe.util.Localization;
|
2017-04-09 17:34:00 +00:00
|
|
|
import org.schabi.newpipe.util.NavigationHelper;
|
2018-02-12 18:44:35 +00:00
|
|
|
import org.schabi.newpipe.util.PermissionHelper;
|
|
|
|
import org.schabi.newpipe.util.ThemeHelper;
|
2021-10-02 17:21:25 +00:00
|
|
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
2020-03-27 02:18:14 +00:00
|
|
|
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
2020-05-01 18:13:01 +00:00
|
|
|
import org.schabi.newpipe.views.FocusOverlayView;
|
2017-02-27 20:14:03 +00:00
|
|
|
|
2018-02-12 18:44:35 +00:00
|
|
|
import java.io.Serializable;
|
2018-04-06 09:02:51 +00:00
|
|
|
import java.util.ArrayList;
|
2018-02-12 18:44:35 +00:00
|
|
|
import java.util.Arrays;
|
2021-10-09 16:46:20 +00:00
|
|
|
import java.util.Collections;
|
2018-04-06 09:02:51 +00:00
|
|
|
import java.util.List;
|
2017-02-27 20:14:03 +00:00
|
|
|
|
2018-01-23 00:40:00 +00:00
|
|
|
import icepick.Icepick;
|
|
|
|
import icepick.State;
|
2020-10-31 20:55:45 +00:00
|
|
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
|
|
|
import io.reactivex.rxjava3.core.Observable;
|
|
|
|
import io.reactivex.rxjava3.core.Single;
|
|
|
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
|
|
|
import io.reactivex.rxjava3.disposables.Disposable;
|
|
|
|
import io.reactivex.rxjava3.functions.Consumer;
|
|
|
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
2018-01-23 00:40:00 +00:00
|
|
|
|
2017-02-27 20:14:03 +00:00
|
|
|
/**
|
2020-03-31 17:20:15 +00:00
|
|
|
* Get the url from the intent and open it in the chosen preferred player.
|
2017-02-27 20:14:03 +00:00
|
|
|
*/
|
2017-09-03 06:04:18 +00:00
|
|
|
public class RouterActivity extends AppCompatActivity {
|
2020-03-31 17:20:15 +00:00
|
|
|
protected final CompositeDisposable disposables = new CompositeDisposable();
|
2019-08-15 02:00:11 +00:00
|
|
|
@State
|
|
|
|
protected int currentServiceId = -1;
|
|
|
|
@State
|
|
|
|
protected LinkType currentLinkType;
|
|
|
|
@State
|
|
|
|
protected int selectedRadioPosition = -1;
|
2018-02-12 18:44:35 +00:00
|
|
|
protected int selectedPreviously = -1;
|
2018-01-23 00:40:00 +00:00
|
|
|
protected String currentUrl;
|
2020-03-31 17:20:15 +00:00
|
|
|
private StreamingService currentService;
|
2018-06-20 12:46:57 +00:00
|
|
|
private boolean selectionIsDownload = false;
|
2021-10-02 17:21:25 +00:00
|
|
|
private boolean selectionIsAddToPlaylist = false;
|
2021-05-25 05:49:49 +00:00
|
|
|
private AlertDialog alertDialogChoice = null;
|
2018-06-17 11:55:43 +00:00
|
|
|
|
2017-02-27 20:14:03 +00:00
|
|
|
@Override
|
2020-03-31 17:20:15 +00:00
|
|
|
protected void onCreate(final Bundle savedInstanceState) {
|
2017-02-27 20:14:03 +00:00
|
|
|
super.onCreate(savedInstanceState);
|
2018-01-23 00:40:00 +00:00
|
|
|
Icepick.restoreInstanceState(this, savedInstanceState);
|
2017-06-05 19:33:01 +00:00
|
|
|
|
2018-01-23 00:40:00 +00:00
|
|
|
if (TextUtils.isEmpty(currentUrl)) {
|
|
|
|
currentUrl = getUrl(getIntent());
|
|
|
|
|
|
|
|
if (TextUtils.isEmpty(currentUrl)) {
|
2018-12-24 18:11:21 +00:00
|
|
|
handleText();
|
2018-01-23 00:40:00 +00:00
|
|
|
finish();
|
|
|
|
}
|
|
|
|
}
|
2018-02-12 18:44:35 +00:00
|
|
|
|
2022-05-01 19:58:20 +00:00
|
|
|
ThemeHelper.setDayNightMode(this);
|
2018-02-12 18:44:35 +00:00
|
|
|
setTheme(ThemeHelper.isLightThemeSelected(this)
|
2018-04-18 14:44:46 +00:00
|
|
|
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
2022-05-04 17:09:41 +00:00
|
|
|
Localization.assureCorrectAppLanguage(this);
|
2018-01-23 00:40:00 +00:00
|
|
|
}
|
|
|
|
|
2021-05-25 05:49:49 +00:00
|
|
|
@Override
|
|
|
|
protected void onStop() {
|
|
|
|
super.onStop();
|
|
|
|
// we need to dismiss the dialog before leaving the activity or we get leaks
|
|
|
|
if (alertDialogChoice != null) {
|
|
|
|
alertDialogChoice.dismiss();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-23 00:40:00 +00:00
|
|
|
@Override
|
2020-12-11 13:55:47 +00:00
|
|
|
protected void onSaveInstanceState(@NonNull final Bundle outState) {
|
2018-06-20 12:46:57 +00:00
|
|
|
super.onSaveInstanceState(outState);
|
|
|
|
Icepick.saveInstanceState(this, outState);
|
2018-01-23 00:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onStart() {
|
|
|
|
super.onStart();
|
2018-12-24 18:11:21 +00:00
|
|
|
|
2018-01-23 00:40:00 +00:00
|
|
|
handleUrl(currentUrl);
|
2017-02-27 20:14:03 +00:00
|
|
|
}
|
|
|
|
|
2018-02-12 18:44:35 +00:00
|
|
|
@Override
|
|
|
|
protected void onDestroy() {
|
|
|
|
super.onDestroy();
|
|
|
|
|
|
|
|
disposables.clear();
|
|
|
|
}
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
private void handleUrl(final String url) {
|
2018-01-23 00:40:00 +00:00
|
|
|
disposables.add(Observable
|
2018-02-12 18:44:35 +00:00
|
|
|
.fromCallable(() -> {
|
2021-03-07 17:59:17 +00:00
|
|
|
try {
|
|
|
|
if (currentServiceId == -1) {
|
|
|
|
currentService = NewPipe.getServiceByUrl(url);
|
|
|
|
currentServiceId = currentService.getServiceId();
|
|
|
|
currentLinkType = currentService.getLinkTypeByUrl(url);
|
|
|
|
currentUrl = url;
|
|
|
|
} else {
|
|
|
|
currentService = NewPipe.getService(currentServiceId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// return whether the url was found to be supported or not
|
|
|
|
return currentLinkType != LinkType.NONE;
|
|
|
|
} catch (final ExtractionException e) {
|
|
|
|
// this can be reached only when the url is completely unsupported
|
|
|
|
return false;
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
})
|
2018-01-23 00:40:00 +00:00
|
|
|
.subscribeOn(Schedulers.io())
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
2021-03-07 17:59:17 +00:00
|
|
|
.subscribe(isUrlSupported -> {
|
|
|
|
if (isUrlSupported) {
|
2018-02-12 18:44:35 +00:00
|
|
|
onSuccess();
|
|
|
|
} else {
|
2020-06-28 11:33:08 +00:00
|
|
|
showUnsupportedUrlDialog(url);
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
2021-03-07 17:59:17 +00:00
|
|
|
}, throwable -> handleError(this, new ErrorInfo(throwable,
|
|
|
|
UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url))));
|
2018-01-23 00:40:00 +00:00
|
|
|
}
|
|
|
|
|
2020-12-11 13:55:47 +00:00
|
|
|
/**
|
2021-03-07 17:59:17 +00:00
|
|
|
* @param context the context. It will be {@code finish()}ed at the end of the handling if it is
|
|
|
|
* an instance of {@link RouterActivity}.
|
|
|
|
* @param errorInfo the error information
|
2020-12-11 13:55:47 +00:00
|
|
|
*/
|
|
|
|
private static void handleError(final Context context, final ErrorInfo errorInfo) {
|
|
|
|
if (errorInfo.getThrowable() != null) {
|
|
|
|
errorInfo.getThrowable().printStackTrace();
|
|
|
|
}
|
2018-01-23 00:40:00 +00:00
|
|
|
|
2020-12-11 13:55:47 +00:00
|
|
|
if (errorInfo.getThrowable() instanceof ReCaptchaException) {
|
|
|
|
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
|
|
|
// Starting ReCaptcha Challenge Activity
|
|
|
|
final Intent intent = new Intent(context, ReCaptchaActivity.class);
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
context.startActivity(intent);
|
|
|
|
} else if (errorInfo.getThrowable() != null
|
|
|
|
&& ExceptionUtils.isNetworkRelated(errorInfo.getThrowable())) {
|
|
|
|
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
|
|
|
|
} else if (errorInfo.getThrowable() instanceof AgeRestrictedContentException) {
|
|
|
|
Toast.makeText(context, R.string.restricted_video_no_stream,
|
|
|
|
Toast.LENGTH_LONG).show();
|
|
|
|
} else if (errorInfo.getThrowable() instanceof GeographicRestrictionException) {
|
|
|
|
Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show();
|
|
|
|
} else if (errorInfo.getThrowable() instanceof PaidContentException) {
|
|
|
|
Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show();
|
|
|
|
} else if (errorInfo.getThrowable() instanceof PrivateContentException) {
|
|
|
|
Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show();
|
|
|
|
} else if (errorInfo.getThrowable() instanceof SoundCloudGoPlusContentException) {
|
|
|
|
Toast.makeText(context, R.string.soundcloud_go_plus_content,
|
|
|
|
Toast.LENGTH_LONG).show();
|
|
|
|
} else if (errorInfo.getThrowable() instanceof YoutubeMusicPremiumContentException) {
|
|
|
|
Toast.makeText(context, R.string.youtube_music_premium_content,
|
|
|
|
Toast.LENGTH_LONG).show();
|
|
|
|
} else if (errorInfo.getThrowable() instanceof ContentNotAvailableException) {
|
|
|
|
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
|
|
|
|
} else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) {
|
|
|
|
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
|
2018-01-23 00:40:00 +00:00
|
|
|
} else {
|
2021-12-01 08:43:24 +00:00
|
|
|
ErrorUtil.createNotification(context, errorInfo);
|
2020-12-11 13:55:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (context instanceof RouterActivity) {
|
|
|
|
((RouterActivity) context).finish();
|
2017-06-05 19:33:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-28 11:33:08 +00:00
|
|
|
private void showUnsupportedUrlDialog(final String url) {
|
|
|
|
final Context context = getThemeWrapperContext();
|
|
|
|
new AlertDialog.Builder(context)
|
|
|
|
.setTitle(R.string.unsupported_url)
|
|
|
|
.setMessage(R.string.unsupported_url_dialog_message)
|
2021-03-27 14:45:49 +00:00
|
|
|
.setIcon(R.drawable.ic_share)
|
2020-06-28 11:33:08 +00:00
|
|
|
.setPositiveButton(R.string.open_in_browser,
|
|
|
|
(dialog, which) -> ShareUtils.openUrlInBrowser(this, url))
|
|
|
|
.setNegativeButton(R.string.share,
|
2021-04-25 12:14:56 +00:00
|
|
|
(dialog, which) -> ShareUtils.shareText(this, "", url)) // no subject
|
2020-06-28 11:33:08 +00:00
|
|
|
.setNeutralButton(R.string.cancel, null)
|
|
|
|
.setOnDismissListener(dialog -> finish())
|
|
|
|
.show();
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
2018-01-23 00:40:00 +00:00
|
|
|
|
2018-02-12 18:44:35 +00:00
|
|
|
protected void onSuccess() {
|
2020-03-31 17:20:15 +00:00
|
|
|
final SharedPreferences preferences = PreferenceManager
|
|
|
|
.getDefaultSharedPreferences(this);
|
2022-05-02 20:37:51 +00:00
|
|
|
|
|
|
|
final ChoiceAvailabilityChecker choiceChecker = new ChoiceAvailabilityChecker(
|
|
|
|
getChoicesForService(currentService, currentLinkType),
|
|
|
|
preferences.getString(getString(R.string.preferred_open_action_key),
|
|
|
|
getString(R.string.preferred_open_action_default)));
|
|
|
|
|
|
|
|
// Check for non-player related choices
|
|
|
|
if (choiceChecker.isAvailableAndSelected(
|
|
|
|
R.string.show_info_key,
|
|
|
|
R.string.download_key,
|
|
|
|
R.string.add_to_playlist_key)) {
|
|
|
|
handleChoice(choiceChecker.getSelectedChoiceKey());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Check if the choice is player related
|
|
|
|
if (choiceChecker.isAvailableAndSelected(
|
|
|
|
R.string.video_player_key,
|
|
|
|
R.string.background_player_key,
|
|
|
|
R.string.popup_player_key)) {
|
|
|
|
|
|
|
|
final String selectedChoice = choiceChecker.getSelectedChoiceKey();
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
final boolean isExtVideoEnabled = preferences.getBoolean(
|
|
|
|
getString(R.string.use_external_video_player_key), false);
|
|
|
|
final boolean isExtAudioEnabled = preferences.getBoolean(
|
|
|
|
getString(R.string.use_external_audio_player_key), false);
|
2022-05-01 20:46:15 +00:00
|
|
|
final boolean isVideoPlayerSelected =
|
2022-05-02 20:37:51 +00:00
|
|
|
selectedChoice.equals(getString(R.string.video_player_key))
|
|
|
|
|| selectedChoice.equals(getString(R.string.popup_player_key));
|
2022-05-01 20:46:15 +00:00
|
|
|
final boolean isAudioPlayerSelected =
|
2022-05-02 20:37:51 +00:00
|
|
|
selectedChoice.equals(getString(R.string.background_player_key));
|
2018-04-18 14:44:46 +00:00
|
|
|
|
2022-05-01 20:46:15 +00:00
|
|
|
if (currentLinkType != LinkType.STREAM
|
2022-05-03 17:19:21 +00:00
|
|
|
&& ((isExtAudioEnabled && isAudioPlayerSelected)
|
|
|
|
|| (isExtVideoEnabled && isVideoPlayerSelected))
|
2022-05-01 20:46:15 +00:00
|
|
|
) {
|
|
|
|
Toast.makeText(this, R.string.external_player_unsupported_link_type,
|
|
|
|
Toast.LENGTH_LONG).show();
|
2022-05-02 20:37:51 +00:00
|
|
|
handleChoice(getString(R.string.show_info_key));
|
2022-05-01 20:46:15 +00:00
|
|
|
return;
|
2018-04-18 14:44:46 +00:00
|
|
|
}
|
|
|
|
|
2022-05-01 20:46:15 +00:00
|
|
|
final List<StreamingService.ServiceInfo.MediaCapability> capabilities =
|
|
|
|
currentService.getServiceInfo().getMediaCapabilities();
|
2018-04-18 14:44:46 +00:00
|
|
|
|
2022-05-02 20:37:51 +00:00
|
|
|
// Check if the service supports the choice
|
2022-05-03 17:19:21 +00:00
|
|
|
if ((isVideoPlayerSelected && capabilities.contains(VIDEO))
|
|
|
|
|| (isAudioPlayerSelected && capabilities.contains(AUDIO))) {
|
2022-05-02 20:37:51 +00:00
|
|
|
handleChoice(selectedChoice);
|
|
|
|
} else {
|
|
|
|
handleChoice(getString(R.string.show_info_key));
|
2018-04-18 14:44:46 +00:00
|
|
|
}
|
2022-05-02 20:37:51 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-04-18 14:44:46 +00:00
|
|
|
|
2022-05-02 20:37:51 +00:00
|
|
|
// Default / Ask always
|
|
|
|
final List<AdapterChoiceItem> availableChoices = choiceChecker.getAvailableChoices();
|
|
|
|
switch (availableChoices.size()) {
|
|
|
|
case 1:
|
|
|
|
handleChoice(availableChoices.get(0).key);
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
handleChoice(getString(R.string.show_info_key));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
showDialog(availableChoices);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is a helper class for checking if the choices are available and/or selected.
|
|
|
|
*/
|
|
|
|
class ChoiceAvailabilityChecker {
|
|
|
|
private final List<AdapterChoiceItem> availableChoices;
|
|
|
|
private final String selectedChoiceKey;
|
|
|
|
|
|
|
|
ChoiceAvailabilityChecker(
|
|
|
|
@NonNull final List<AdapterChoiceItem> availableChoices,
|
|
|
|
@NonNull final String selectedChoiceKey) {
|
|
|
|
this.availableChoices = availableChoices;
|
|
|
|
this.selectedChoiceKey = selectedChoiceKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
public List<AdapterChoiceItem> getAvailableChoices() {
|
|
|
|
return availableChoices;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getSelectedChoiceKey() {
|
|
|
|
return selectedChoiceKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isAvailableAndSelected(@StringRes final int... wantedKeys) {
|
|
|
|
return Arrays.stream(wantedKeys).anyMatch(this::isAvailableAndSelected);
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isAvailableAndSelected(@StringRes final int wantedKey) {
|
|
|
|
final String wanted = getString(wantedKey);
|
|
|
|
// Check if the wanted option is selected
|
|
|
|
if (!selectedChoiceKey.equals(wanted)) {
|
|
|
|
return false;
|
2018-04-18 14:44:46 +00:00
|
|
|
}
|
2022-05-02 20:37:51 +00:00
|
|
|
// Check if it's available
|
|
|
|
return availableChoices.stream().anyMatch(item -> wanted.equals(item.key));
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-18 14:44:46 +00:00
|
|
|
private void showDialog(final List<AdapterChoiceItem> choices) {
|
2018-04-06 09:02:51 +00:00
|
|
|
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
2022-05-27 22:39:02 +00:00
|
|
|
|
2018-04-18 14:44:46 +00:00
|
|
|
final Context themeWrapperContext = getThemeWrapperContext();
|
2022-05-27 22:39:02 +00:00
|
|
|
final LayoutInflater layoutInflater = LayoutInflater.from(themeWrapperContext);
|
2018-02-12 18:44:35 +00:00
|
|
|
|
2022-05-27 22:39:02 +00:00
|
|
|
final SingleChoiceDialogViewBinding binding =
|
|
|
|
SingleChoiceDialogViewBinding.inflate(layoutInflater);
|
|
|
|
final RadioGroup radioGroup = binding.list;
|
2018-02-12 18:44:35 +00:00
|
|
|
|
|
|
|
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
|
|
|
|
final int indexOfChild = radioGroup.indexOfChild(
|
|
|
|
radioGroup.findViewById(radioGroup.getCheckedRadioButtonId()));
|
2018-04-06 09:02:51 +00:00
|
|
|
final AdapterChoiceItem choice = choices.get(indexOfChild);
|
2018-02-12 18:44:35 +00:00
|
|
|
|
|
|
|
handleChoice(choice.key);
|
|
|
|
|
2020-09-09 18:45:42 +00:00
|
|
|
// open future streams always like this one, because "always" button was used by user
|
2018-02-12 18:44:35 +00:00
|
|
|
if (which == DialogInterface.BUTTON_POSITIVE) {
|
2020-03-31 17:20:15 +00:00
|
|
|
preferences.edit()
|
|
|
|
.putString(getString(R.string.preferred_open_action_key), choice.key)
|
|
|
|
.apply();
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-05-25 05:49:49 +00:00
|
|
|
alertDialogChoice = new AlertDialog.Builder(themeWrapperContext)
|
2018-04-07 18:36:52 +00:00
|
|
|
.setTitle(R.string.preferred_open_action_share_menu_title)
|
2022-05-27 22:39:02 +00:00
|
|
|
.setView(binding.getRoot())
|
2018-02-12 18:44:35 +00:00
|
|
|
.setCancelable(true)
|
|
|
|
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
|
|
|
|
.setPositiveButton(R.string.always, dialogButtonsClickListener)
|
2022-05-27 22:16:27 +00:00
|
|
|
.setOnDismissListener(dialog -> {
|
2021-10-02 17:21:25 +00:00
|
|
|
if (!selectionIsDownload && !selectionIsAddToPlaylist) {
|
2020-03-31 17:20:15 +00:00
|
|
|
finish();
|
|
|
|
}
|
2018-06-17 11:55:43 +00:00
|
|
|
})
|
2018-02-12 18:44:35 +00:00
|
|
|
.create();
|
|
|
|
|
2022-05-27 22:16:27 +00:00
|
|
|
alertDialogChoice.setOnShowListener(dialog -> setDialogButtonsState(
|
|
|
|
alertDialogChoice, radioGroup.getCheckedRadioButtonId() != -1));
|
2018-02-12 18:44:35 +00:00
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
radioGroup.setOnCheckedChangeListener((group, checkedId) ->
|
2021-05-25 05:49:49 +00:00
|
|
|
setDialogButtonsState(alertDialogChoice, true));
|
2018-02-12 18:44:35 +00:00
|
|
|
final View.OnClickListener radioButtonsClickListener = v -> {
|
|
|
|
final int indexOfChild = radioGroup.indexOfChild(v);
|
2020-03-31 17:20:15 +00:00
|
|
|
if (indexOfChild == -1) {
|
|
|
|
return;
|
|
|
|
}
|
2018-02-12 18:44:35 +00:00
|
|
|
|
|
|
|
selectedPreviously = selectedRadioPosition;
|
|
|
|
selectedRadioPosition = indexOfChild;
|
|
|
|
|
|
|
|
if (selectedPreviously == selectedRadioPosition) {
|
2018-04-06 09:02:51 +00:00
|
|
|
handleChoice(choices.get(selectedRadioPosition).key);
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
int id = 12345;
|
2020-08-16 08:24:58 +00:00
|
|
|
for (final AdapterChoiceItem item : choices) {
|
2022-05-27 22:39:02 +00:00
|
|
|
final RadioButton radioButton = ListRadioIconItemBinding.inflate(layoutInflater)
|
|
|
|
.getRoot();
|
2018-02-12 18:44:35 +00:00
|
|
|
radioButton.setText(item.description);
|
2022-05-10 02:15:01 +00:00
|
|
|
radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
2021-04-23 22:31:02 +00:00
|
|
|
AppCompatResources.getDrawable(themeWrapperContext, item.icon),
|
2020-04-01 13:15:38 +00:00
|
|
|
null, null, null);
|
2018-02-12 18:44:35 +00:00
|
|
|
radioButton.setChecked(false);
|
|
|
|
radioButton.setId(id++);
|
2020-03-31 17:20:15 +00:00
|
|
|
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
|
|
|
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
2018-02-12 18:44:35 +00:00
|
|
|
radioButton.setOnClickListener(radioButtonsClickListener);
|
|
|
|
radioGroup.addView(radioButton);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selectedRadioPosition == -1) {
|
2020-03-31 17:20:15 +00:00
|
|
|
final String lastSelectedPlayer = preferences.getString(
|
|
|
|
getString(R.string.preferred_open_action_last_selected_key), null);
|
2018-02-12 18:44:35 +00:00
|
|
|
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
|
2018-04-06 09:02:51 +00:00
|
|
|
for (int i = 0; i < choices.size(); i++) {
|
2020-08-16 08:24:58 +00:00
|
|
|
final AdapterChoiceItem c = choices.get(i);
|
2018-02-12 18:44:35 +00:00
|
|
|
if (lastSelectedPlayer.equals(c.key)) {
|
|
|
|
selectedRadioPosition = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-06 09:02:51 +00:00
|
|
|
selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.size() - 1);
|
2018-02-12 18:44:35 +00:00
|
|
|
if (selectedRadioPosition != -1) {
|
|
|
|
((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true);
|
|
|
|
}
|
|
|
|
selectedPreviously = selectedRadioPosition;
|
|
|
|
|
2021-05-25 05:49:49 +00:00
|
|
|
alertDialogChoice.show();
|
2019-09-23 07:17:03 +00:00
|
|
|
|
2020-07-20 22:43:49 +00:00
|
|
|
if (DeviceUtils.isTv(this)) {
|
2021-05-25 05:49:49 +00:00
|
|
|
FocusOverlayView.setupFocusObserver(alertDialogChoice);
|
2019-09-23 07:17:03 +00:00
|
|
|
}
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
private List<AdapterChoiceItem> getChoicesForService(final StreamingService service,
|
|
|
|
final LinkType linkType) {
|
2020-09-09 18:45:42 +00:00
|
|
|
final AdapterChoiceItem showInfo = new AdapterChoiceItem(
|
|
|
|
getString(R.string.show_info_key), getString(R.string.show_info),
|
2021-03-27 14:45:49 +00:00
|
|
|
R.drawable.ic_info_outline);
|
2022-05-02 20:37:51 +00:00
|
|
|
final AdapterChoiceItem videoPlayer = new AdapterChoiceItem(
|
|
|
|
getString(R.string.video_player_key), getString(R.string.video_player),
|
|
|
|
R.drawable.ic_play_arrow);
|
2020-09-09 18:45:42 +00:00
|
|
|
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
|
|
|
|
getString(R.string.background_player_key), getString(R.string.background_player),
|
2021-03-27 14:45:49 +00:00
|
|
|
R.drawable.ic_headset);
|
2022-05-02 20:37:51 +00:00
|
|
|
final AdapterChoiceItem popupPlayer = new AdapterChoiceItem(
|
|
|
|
getString(R.string.popup_player_key), getString(R.string.popup_player),
|
|
|
|
R.drawable.ic_picture_in_picture);
|
2021-10-02 17:21:25 +00:00
|
|
|
|
2022-05-02 20:37:51 +00:00
|
|
|
final List<AdapterChoiceItem> returnedItems = new ArrayList<>();
|
|
|
|
returnedItems.add(showInfo); // Always present
|
2022-05-03 17:19:21 +00:00
|
|
|
|
2022-05-02 20:37:51 +00:00
|
|
|
final List<StreamingService.ServiceInfo.MediaCapability> capabilities =
|
|
|
|
service.getServiceInfo().getMediaCapabilities();
|
2020-09-09 18:45:42 +00:00
|
|
|
|
|
|
|
if (linkType == LinkType.STREAM) {
|
|
|
|
if (capabilities.contains(VIDEO)) {
|
2022-05-02 20:37:51 +00:00
|
|
|
returnedItems.add(videoPlayer);
|
|
|
|
returnedItems.add(popupPlayer);
|
2020-09-09 18:45:42 +00:00
|
|
|
}
|
|
|
|
if (capabilities.contains(AUDIO)) {
|
2022-05-02 20:37:51 +00:00
|
|
|
returnedItems.add(backgroundPlayer);
|
2020-09-09 18:45:42 +00:00
|
|
|
}
|
2021-05-27 22:08:53 +00:00
|
|
|
// download is redundant for linkType CHANNEL AND PLAYLIST (till playlist downloading is
|
|
|
|
// not supported )
|
2022-05-02 20:37:51 +00:00
|
|
|
returnedItems.add(new AdapterChoiceItem(getString(R.string.download_key),
|
2021-05-27 22:08:53 +00:00
|
|
|
getString(R.string.download),
|
|
|
|
R.drawable.ic_file_download));
|
2020-09-09 18:45:42 +00:00
|
|
|
|
2021-10-02 17:21:25 +00:00
|
|
|
// Add to playlist is not necessary for CHANNEL and PLAYLIST linkType since those can
|
|
|
|
// not be added to a playlist
|
2022-05-02 20:37:51 +00:00
|
|
|
returnedItems.add(new AdapterChoiceItem(getString(R.string.add_to_playlist_key),
|
|
|
|
getString(R.string.add_to_playlist),
|
|
|
|
R.drawable.ic_add));
|
2020-09-09 18:45:42 +00:00
|
|
|
} else {
|
2022-05-03 17:19:21 +00:00
|
|
|
// LinkType.NONE is never present because it's filtered out before
|
|
|
|
// channels and playlist can be played as they contain a list of videos
|
2022-05-02 20:37:51 +00:00
|
|
|
final SharedPreferences preferences = PreferenceManager
|
|
|
|
.getDefaultSharedPreferences(this);
|
|
|
|
final boolean isExtVideoEnabled = preferences.getBoolean(
|
|
|
|
getString(R.string.use_external_video_player_key), false);
|
|
|
|
final boolean isExtAudioEnabled = preferences.getBoolean(
|
|
|
|
getString(R.string.use_external_audio_player_key), false);
|
|
|
|
|
2020-09-09 18:45:42 +00:00
|
|
|
if (capabilities.contains(VIDEO) && !isExtVideoEnabled) {
|
2022-05-02 20:37:51 +00:00
|
|
|
returnedItems.add(videoPlayer);
|
|
|
|
returnedItems.add(popupPlayer);
|
2020-09-09 18:45:42 +00:00
|
|
|
}
|
|
|
|
if (capabilities.contains(AUDIO) && !isExtAudioEnabled) {
|
2022-05-02 20:37:51 +00:00
|
|
|
returnedItems.add(backgroundPlayer);
|
2020-09-09 18:45:42 +00:00
|
|
|
}
|
2018-04-06 09:02:51 +00:00
|
|
|
}
|
|
|
|
|
2022-05-02 20:37:51 +00:00
|
|
|
return returnedItems;
|
2018-04-06 09:02:51 +00:00
|
|
|
}
|
|
|
|
|
2018-04-18 14:44:46 +00:00
|
|
|
private Context getThemeWrapperContext() {
|
2020-03-31 17:20:15 +00:00
|
|
|
return new ContextThemeWrapper(this, ThemeHelper.isLightThemeSelected(this)
|
|
|
|
? R.style.LightTheme : R.style.DarkTheme);
|
2018-04-18 14:44:46 +00:00
|
|
|
}
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
private void setDialogButtonsState(final AlertDialog dialog, final boolean state) {
|
2018-02-12 18:44:35 +00:00
|
|
|
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
|
|
|
|
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
2020-03-31 17:20:15 +00:00
|
|
|
if (negativeButton == null || positiveButton == null) {
|
|
|
|
return;
|
|
|
|
}
|
2018-02-12 18:44:35 +00:00
|
|
|
|
|
|
|
negativeButton.setEnabled(state);
|
|
|
|
positiveButton.setEnabled(state);
|
|
|
|
}
|
|
|
|
|
2019-08-15 02:00:11 +00:00
|
|
|
private void handleText() {
|
2020-08-16 08:24:58 +00:00
|
|
|
final String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
|
|
|
final int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
|
|
|
final Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
|
2018-12-24 18:11:21 +00:00
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
startActivity(intent);
|
2019-08-15 02:00:11 +00:00
|
|
|
NavigationHelper.openSearch(getThemeWrapperContext(), serviceId, searchString);
|
2018-12-24 18:11:21 +00:00
|
|
|
}
|
|
|
|
|
2018-04-18 14:44:46 +00:00
|
|
|
private void handleChoice(final String selectedChoiceKey) {
|
2020-03-31 17:20:15 +00:00
|
|
|
final List<String> validChoicesList = Arrays.asList(getResources()
|
|
|
|
.getStringArray(R.array.preferred_open_action_values_list));
|
2018-04-18 14:44:46 +00:00
|
|
|
if (validChoicesList.contains(selectedChoiceKey)) {
|
2018-02-12 18:44:35 +00:00
|
|
|
PreferenceManager.getDefaultSharedPreferences(this).edit()
|
2020-03-31 17:20:15 +00:00
|
|
|
.putString(getString(
|
|
|
|
R.string.preferred_open_action_last_selected_key), selectedChoiceKey)
|
2018-04-06 09:02:51 +00:00
|
|
|
.apply();
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
if (selectedChoiceKey.equals(getString(R.string.popup_player_key))
|
|
|
|
&& !PermissionHelper.isPopupEnabled(this)) {
|
2018-02-12 18:44:35 +00:00
|
|
|
PermissionHelper.showPopupEnablementToast(this);
|
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-06-17 11:55:43 +00:00
|
|
|
if (selectedChoiceKey.equals(getString(R.string.download_key))) {
|
2020-03-31 17:20:15 +00:00
|
|
|
if (PermissionHelper.checkStoragePermissions(this,
|
|
|
|
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
2019-08-15 02:00:11 +00:00
|
|
|
selectionIsDownload = true;
|
|
|
|
openDownloadDialog();
|
|
|
|
}
|
2018-06-17 11:55:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-02 17:21:25 +00:00
|
|
|
if (selectedChoiceKey.equals(getString(R.string.add_to_playlist_key))) {
|
|
|
|
selectionIsAddToPlaylist = true;
|
|
|
|
openAddToPlaylistDialog();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-12 18:44:35 +00:00
|
|
|
// stop and bypass FetcherService if InfoScreen was selected since
|
|
|
|
// StreamDetailFragment can fetch data itself
|
2022-05-02 20:37:51 +00:00
|
|
|
if (selectedChoiceKey.equals(getString(R.string.show_info_key))
|
|
|
|
|| canHandleChoiceLikeShowInfo(selectedChoiceKey)) {
|
2018-02-12 18:44:35 +00:00
|
|
|
disposables.add(Observable
|
2018-04-05 20:45:00 +00:00
|
|
|
.fromCallable(() -> NavigationHelper.getIntentByLink(this, currentUrl))
|
2018-02-12 18:44:35 +00:00
|
|
|
.subscribeOn(Schedulers.io())
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
|
|
.subscribe(intent -> {
|
|
|
|
startActivity(intent);
|
|
|
|
finish();
|
2021-03-07 17:59:17 +00:00
|
|
|
}, throwable -> handleError(this, new ErrorInfo(throwable,
|
|
|
|
UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl)))
|
2018-02-12 18:44:35 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final Intent intent = new Intent(this, FetcherService.class);
|
2020-03-31 17:20:15 +00:00
|
|
|
final Choice choice = new Choice(currentService.getServiceId(), currentLinkType,
|
|
|
|
currentUrl, selectedChoiceKey);
|
2018-04-06 09:02:51 +00:00
|
|
|
intent.putExtra(FetcherService.KEY_CHOICE, choice);
|
2018-02-12 18:44:35 +00:00
|
|
|
startService(intent);
|
|
|
|
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
2022-05-02 20:37:51 +00:00
|
|
|
private boolean canHandleChoiceLikeShowInfo(final String selectedChoiceKey) {
|
2022-05-03 17:19:21 +00:00
|
|
|
if (!selectedChoiceKey.equals(getString(R.string.video_player_key))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// "video player" can be handled like "show info" (because VideoDetailFragment can load
|
|
|
|
// the stream instead of FetcherService) when...
|
2022-05-02 20:37:51 +00:00
|
|
|
|
2022-05-03 17:19:21 +00:00
|
|
|
// ...Autoplay is enabled
|
|
|
|
if (!PlayerHelper.isAutoplayAllowedByUser(getThemeWrapperContext())) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-05-02 20:37:51 +00:00
|
|
|
|
2022-05-03 17:19:21 +00:00
|
|
|
final boolean isExtVideoEnabled = PreferenceManager.getDefaultSharedPreferences(this)
|
|
|
|
.getBoolean(getString(R.string.use_external_video_player_key), false);
|
|
|
|
// ...it's not done via an external player
|
|
|
|
if (isExtVideoEnabled) {
|
|
|
|
return false;
|
2022-05-02 20:37:51 +00:00
|
|
|
}
|
2022-05-03 17:19:21 +00:00
|
|
|
|
|
|
|
// ...the player is not running or in normal Video-mode/type
|
2022-04-08 07:35:14 +00:00
|
|
|
final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType();
|
|
|
|
return playerType == null || playerType == PlayerService.PlayerType.MAIN;
|
2022-05-02 20:37:51 +00:00
|
|
|
}
|
|
|
|
|
2021-10-02 17:21:25 +00:00
|
|
|
private void openAddToPlaylistDialog() {
|
2021-10-09 16:47:36 +00:00
|
|
|
// Getting the stream info usually takes a moment
|
|
|
|
// Notifying the user here to ensure that no confusion arises
|
|
|
|
Toast.makeText(
|
|
|
|
getApplicationContext(),
|
|
|
|
getString(R.string.processing_may_take_a_moment),
|
|
|
|
Toast.LENGTH_SHORT)
|
|
|
|
.show();
|
|
|
|
|
2021-10-02 17:21:25 +00:00
|
|
|
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
|
|
|
|
.subscribeOn(Schedulers.io())
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
2021-10-09 16:46:20 +00:00
|
|
|
.subscribe(
|
|
|
|
info -> PlaylistDialog.createCorrespondingDialog(
|
|
|
|
getThemeWrapperContext(),
|
|
|
|
Collections.singletonList(new StreamEntity(info)),
|
|
|
|
playlistDialog -> {
|
|
|
|
playlistDialog.setOnDismissListener(dialog -> finish());
|
|
|
|
|
|
|
|
playlistDialog.show(
|
|
|
|
this.getSupportFragmentManager(),
|
|
|
|
"addToPlaylistDialog"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
),
|
|
|
|
throwable -> handleError(this, new ErrorInfo(
|
|
|
|
throwable,
|
|
|
|
UserAction.REQUESTED_STREAM,
|
2021-10-02 17:21:25 +00:00
|
|
|
"Tried to add " + currentUrl + " to a playlist",
|
2021-10-09 16:46:20 +00:00
|
|
|
currentService.getServiceId())
|
|
|
|
)
|
|
|
|
)
|
2021-10-02 17:21:25 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-06-20 12:46:57 +00:00
|
|
|
@SuppressLint("CheckResult")
|
|
|
|
private void openDownloadDialog() {
|
2020-10-18 18:19:50 +00:00
|
|
|
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
|
2018-06-20 12:46:57 +00:00
|
|
|
.subscribeOn(Schedulers.io())
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
2020-11-19 23:54:27 +00:00
|
|
|
.subscribe(result -> {
|
2022-06-18 15:40:22 +00:00
|
|
|
final DownloadDialog downloadDialog = new DownloadDialog(this, result);
|
Add support of other delivery methods than progressive HTTP (in the player only)
Detailed changes:
- External players:
- Add a message instruction about stream selection;
- Add a message when there is no stream available for external players;
- Return now HLS, DASH and SmoothStreaming URL contents, in addition to progressive HTTP ones.
- Player:
- Support DASH, HLS and SmoothStreaming streams for videos, whether they are content URLs or the manifests themselves, in addition to progressive HTTP ones;
- Use a custom HttpDataSource to play YouTube contents, based of ExoPlayer's default one, which allows better spoofing of official clients (custom user-agent and headers (depending of the client used), use of range and rn (set dynamically by the DataSource) parameters);
- Fetch YouTube progressive contents as DASH streams, like official clients, support fully playback of livestreams which have ended recently and OTF streams;
- Use ExoPlayer's default retries count for contents on non-fatal errors (instead of Integer.MAX_VALUE for non-live contents and 5 for live contents).
- Download dialog:
- Add message about support of progressive HTTP streams only for downloading;
- Remove several duplicated code and update relevant usages;
- Support downloading of contents with an unknown media format.
- ListHelper:
- Catch NumberFormatException when trying to compare two video streams between them.
- Tests:
- Update ListHelperTest and StreamItemAdapterTest to fix breaking changes in the extractor.
- Other places:
- Fixes deprecation of changes made in the extractor;
- Improve some code related to the files changed.
- Issues fixed and/or improved with the changes:
- Seeking of PeerTube HLS streams (the duration shown was the one from the stream duration and not the one parsed, incomplete because HLS streams are fragmented MP4s with multiple sidx boxes, for which seeking is not supported by ExoPlayer) (the app now uses the HLS manifest returned for each quality, in the master playlist (not fetched and computed by the extractor));
- Crash when loading PeerTube streams with a separated audio;
- Lack of some streams on some YouTube videos (OTF streams);
- Loading times of YouTube streams, after a quality change or a playback start;
- View count of YouTube ended livestreams interpreted as watching count (this type of streams is not interpreted anymore as livestreams);
- Watchable time of YouTube ended livestreams;
- Playback of SoundCloud HLS-only tracks (which cannot be downloaded anymore because the workaround which was used is being removed by SoundCloud, so it has been removed from the extractor).
2022-06-16 09:13:19 +00:00
|
|
|
downloadDialog.setOnDismissListener(dialog -> finish());
|
2018-06-20 12:46:57 +00:00
|
|
|
|
2020-08-16 08:24:58 +00:00
|
|
|
final FragmentManager fm = getSupportFragmentManager();
|
2018-06-20 12:46:57 +00:00
|
|
|
downloadDialog.show(fm, "downloadDialog");
|
|
|
|
fm.executePendingTransactions();
|
Add support of other delivery methods than progressive HTTP (in the player only)
Detailed changes:
- External players:
- Add a message instruction about stream selection;
- Add a message when there is no stream available for external players;
- Return now HLS, DASH and SmoothStreaming URL contents, in addition to progressive HTTP ones.
- Player:
- Support DASH, HLS and SmoothStreaming streams for videos, whether they are content URLs or the manifests themselves, in addition to progressive HTTP ones;
- Use a custom HttpDataSource to play YouTube contents, based of ExoPlayer's default one, which allows better spoofing of official clients (custom user-agent and headers (depending of the client used), use of range and rn (set dynamically by the DataSource) parameters);
- Fetch YouTube progressive contents as DASH streams, like official clients, support fully playback of livestreams which have ended recently and OTF streams;
- Use ExoPlayer's default retries count for contents on non-fatal errors (instead of Integer.MAX_VALUE for non-live contents and 5 for live contents).
- Download dialog:
- Add message about support of progressive HTTP streams only for downloading;
- Remove several duplicated code and update relevant usages;
- Support downloading of contents with an unknown media format.
- ListHelper:
- Catch NumberFormatException when trying to compare two video streams between them.
- Tests:
- Update ListHelperTest and StreamItemAdapterTest to fix breaking changes in the extractor.
- Other places:
- Fixes deprecation of changes made in the extractor;
- Improve some code related to the files changed.
- Issues fixed and/or improved with the changes:
- Seeking of PeerTube HLS streams (the duration shown was the one from the stream duration and not the one parsed, incomplete because HLS streams are fragmented MP4s with multiple sidx boxes, for which seeking is not supported by ExoPlayer) (the app now uses the HLS manifest returned for each quality, in the master playlist (not fetched and computed by the extractor));
- Crash when loading PeerTube streams with a separated audio;
- Lack of some streams on some YouTube videos (OTF streams);
- Loading times of YouTube streams, after a quality change or a playback start;
- View count of YouTube ended livestreams interpreted as watching count (this type of streams is not interpreted anymore as livestreams);
- Watchable time of YouTube ended livestreams;
- Playback of SoundCloud HLS-only tracks (which cannot be downloaded anymore because the workaround which was used is being removed by SoundCloud, so it has been removed from the extractor).
2022-06-16 09:13:19 +00:00
|
|
|
}, throwable -> showUnsupportedUrlDialog(currentUrl)));
|
2018-06-20 12:46:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-03-31 17:20:15 +00:00
|
|
|
public void onRequestPermissionsResult(final int requestCode,
|
|
|
|
@NonNull final String[] permissions,
|
|
|
|
@NonNull final int[] grantResults) {
|
2021-06-16 02:08:41 +00:00
|
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
2020-08-16 08:24:58 +00:00
|
|
|
for (final int i : grantResults) {
|
2019-08-15 02:00:11 +00:00
|
|
|
if (i == PackageManager.PERMISSION_DENIED) {
|
2018-06-20 12:46:57 +00:00
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2019-08-15 02:00:11 +00:00
|
|
|
if (requestCode == PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE) {
|
|
|
|
openDownloadDialog();
|
|
|
|
}
|
2018-06-20 12:46:57 +00:00
|
|
|
}
|
|
|
|
|
2018-02-12 18:44:35 +00:00
|
|
|
private static class AdapterChoiceItem {
|
2020-03-31 17:20:15 +00:00
|
|
|
final String description;
|
|
|
|
final String key;
|
2019-08-15 02:00:11 +00:00
|
|
|
@DrawableRes
|
|
|
|
final int icon;
|
2018-02-12 18:44:35 +00:00
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
AdapterChoiceItem(final String key, final String description, final int icon) {
|
2018-02-12 18:44:35 +00:00
|
|
|
this.key = key;
|
2022-05-02 20:37:51 +00:00
|
|
|
this.description = description;
|
2018-02-12 18:44:35 +00:00
|
|
|
this.icon = icon;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class Choice implements Serializable {
|
|
|
|
final int serviceId;
|
2020-03-31 17:20:15 +00:00
|
|
|
final String url;
|
|
|
|
final String playerChoice;
|
2018-02-12 18:44:35 +00:00
|
|
|
final LinkType linkType;
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
Choice(final int serviceId, final LinkType linkType,
|
|
|
|
final String url, final String playerChoice) {
|
2018-02-12 18:44:35 +00:00
|
|
|
this.serviceId = serviceId;
|
|
|
|
this.linkType = linkType;
|
|
|
|
this.url = url;
|
|
|
|
this.playerChoice = playerChoice;
|
|
|
|
}
|
|
|
|
|
2020-12-11 13:55:47 +00:00
|
|
|
@NonNull
|
2018-02-12 18:44:35 +00:00
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class FetcherService extends IntentService {
|
|
|
|
|
|
|
|
public static final String KEY_CHOICE = "key_choice";
|
2020-03-31 17:20:15 +00:00
|
|
|
private static final int ID = 456;
|
2018-02-12 18:44:35 +00:00
|
|
|
private Disposable fetcher;
|
|
|
|
|
|
|
|
public FetcherService() {
|
|
|
|
super(FetcherService.class.getSimpleName());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCreate() {
|
|
|
|
super.onCreate();
|
|
|
|
startForeground(ID, createNotification().build());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-03-31 17:20:15 +00:00
|
|
|
protected void onHandleIntent(@Nullable final Intent intent) {
|
|
|
|
if (intent == null) {
|
|
|
|
return;
|
|
|
|
}
|
2018-02-12 18:44:35 +00:00
|
|
|
|
|
|
|
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
|
2020-03-31 17:20:15 +00:00
|
|
|
if (!(serializable instanceof Choice)) {
|
|
|
|
return;
|
|
|
|
}
|
2020-08-16 08:24:58 +00:00
|
|
|
final Choice playerChoice = (Choice) serializable;
|
2018-02-12 18:44:35 +00:00
|
|
|
handleChoice(playerChoice);
|
|
|
|
}
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
public void handleChoice(final Choice choice) {
|
2018-02-12 18:44:35 +00:00
|
|
|
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);
|
2020-03-31 17:20:15 +00:00
|
|
|
if (fetcher != null) {
|
|
|
|
fetcher.dispose();
|
|
|
|
}
|
2020-12-11 13:55:47 +00:00
|
|
|
}, throwable -> handleError(this, new ErrorInfo(throwable, finalUserAction,
|
|
|
|
choice.url + " opened with " + choice.playerChoice,
|
|
|
|
choice.serviceId)));
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
public Consumer<Info> getResultHandler(final Choice choice) {
|
2018-02-12 18:44:35 +00:00
|
|
|
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);
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
final SharedPreferences preferences = PreferenceManager
|
|
|
|
.getDefaultSharedPreferences(this);
|
2020-08-16 08:24:58 +00:00
|
|
|
final boolean isExtVideoEnabled = preferences.getBoolean(
|
2020-03-31 17:20:15 +00:00
|
|
|
getString(R.string.use_external_video_player_key), false);
|
2020-08-16 08:24:58 +00:00
|
|
|
final boolean isExtAudioEnabled = preferences.getBoolean(
|
2020-03-31 17:20:15 +00:00
|
|
|
getString(R.string.use_external_audio_player_key), false);
|
2018-02-12 18:44:35 +00:00
|
|
|
|
2020-10-18 18:33:08 +00:00
|
|
|
final PlayQueue playQueue;
|
2018-02-12 18:44:35 +00:00
|
|
|
if (info instanceof StreamInfo) {
|
2020-10-18 18:33:08 +00:00
|
|
|
if (choice.playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
|
2018-02-12 18:44:35 +00:00
|
|
|
NavigationHelper.playOnExternalAudioPlayer(this, (StreamInfo) info);
|
2020-10-18 18:33:08 +00:00
|
|
|
return;
|
|
|
|
} else if (choice.playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
|
2018-02-12 18:44:35 +00:00
|
|
|
NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info);
|
2020-10-18 18:33:08 +00:00
|
|
|
return;
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
2020-10-18 18:33:08 +00:00
|
|
|
playQueue = new SinglePlayQueue((StreamInfo) info);
|
|
|
|
} else if (info instanceof ChannelInfo) {
|
|
|
|
playQueue = new ChannelPlayQueue((ChannelInfo) info);
|
|
|
|
} else if (info instanceof PlaylistInfo) {
|
|
|
|
playQueue = new PlaylistPlayQueue((PlaylistInfo) info);
|
|
|
|
} else {
|
|
|
|
return;
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
|
2020-10-18 18:33:08 +00:00
|
|
|
if (choice.playerChoice.equals(videoPlayerKey)) {
|
2020-10-31 19:26:09 +00:00
|
|
|
NavigationHelper.playOnMainPlayer(this, playQueue, false);
|
2020-10-18 18:33:08 +00:00
|
|
|
} else if (choice.playerChoice.equals(backgroundPlayerKey)) {
|
|
|
|
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
|
|
|
|
} else if (choice.playerChoice.equals(popupPlayerKey)) {
|
|
|
|
NavigationHelper.playOnPopupPlayer(this, playQueue, true);
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDestroy() {
|
|
|
|
super.onDestroy();
|
2020-12-19 11:22:17 +00:00
|
|
|
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
|
2020-03-31 17:20:15 +00:00
|
|
|
if (fetcher != null) {
|
|
|
|
fetcher.dispose();
|
|
|
|
}
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2020-03-31 17:20:15 +00:00
|
|
|
.setContentTitle(
|
|
|
|
getString(R.string.preferred_player_fetcher_notification_title))
|
|
|
|
.setContentText(
|
|
|
|
getString(R.string.preferred_player_fetcher_notification_message));
|
2018-02-12 18:44:35 +00:00
|
|
|
}
|
2018-01-23 00:40:00 +00:00
|
|
|
}
|
|
|
|
|
2017-06-05 19:33:01 +00:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Utils
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2020-03-27 02:18:14 +00:00
|
|
|
@Nullable
|
2020-03-31 17:20:15 +00:00
|
|
|
private String getUrl(final Intent intent) {
|
2020-03-27 02:18:14 +00:00
|
|
|
String foundUrl = null;
|
2017-04-09 17:34:00 +00:00
|
|
|
if (intent.getData() != null) {
|
2020-03-27 02:18:14 +00:00
|
|
|
// Called from another app
|
|
|
|
foundUrl = intent.getData().toString();
|
2017-04-09 17:34:00 +00:00
|
|
|
} else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
|
2020-03-27 02:18:14 +00:00
|
|
|
// Called from the share menu
|
|
|
|
final String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
|
|
|
foundUrl = UrlFinder.firstUrlFromInput(extraText);
|
2017-04-09 17:34:00 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 02:18:14 +00:00
|
|
|
return foundUrl;
|
2017-02-27 20:14:03 +00:00
|
|
|
}
|
|
|
|
}
|