diff --git a/app/src/debug/java/org/schabi/newpipe/DebugApp.kt b/app/src/debug/java/org/schabi/newpipe/DebugApp.kt index 70b9ec2807b..24a0bfa053b 100644 --- a/app/src/debug/java/org/schabi/newpipe/DebugApp.kt +++ b/app/src/debug/java/org/schabi/newpipe/DebugApp.kt @@ -26,7 +26,8 @@ class DebugApp : App() { override fun getDownloader(): Downloader { val downloader = DownloaderImpl.init( OkHttpClient.Builder() - .addNetworkInterceptor(StethoInterceptor()) + .addNetworkInterceptor(StethoInterceptor()), + this ) setCookiesToDownloader(downloader) return downloader diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index a8827c33e73..5b8c96d3e97 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -131,7 +131,7 @@ public void onTerminate() { } protected Downloader getDownloader() { - final DownloaderImpl downloader = DownloaderImpl.init(null); + final DownloaderImpl downloader = DownloaderImpl.init(null, this); setCookiesToDownloader(downloader); return downloader; } diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 6ccc2437a2e..23e18d0349f 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -11,6 +11,7 @@ import org.schabi.newpipe.extractor.downloader.Request; import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.settings.ProxyManager; import org.schabi.newpipe.util.InfoCache; import java.io.IOException; @@ -52,11 +53,18 @@ private DownloaderImpl(final OkHttpClient.Builder builder) { * It's recommended to call exactly once in the entire lifetime of the application. * * @param builder if null, default builder will be used + * @param context the context to use * @return a new instance of {@link DownloaderImpl} */ - public static DownloaderImpl init(@Nullable final OkHttpClient.Builder builder) { - instance = new DownloaderImpl( - builder != null ? builder : new OkHttpClient.Builder()); + public static DownloaderImpl init(@Nullable final OkHttpClient.Builder builder, + final Context context) { + final OkHttpClient.Builder builderToUse = builder != null ? builder + : new OkHttpClient.Builder(); + final ProxyManager proxyManager = new ProxyManager(context); + if (proxyManager.isProxyEnabled()) { + builderToUse.proxy(proxyManager.getProxy()); + } + instance = new DownloaderImpl(builderToUse); return instance; } diff --git a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java index d4658d1ded4..be894110361 100644 --- a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java @@ -22,6 +22,7 @@ import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isWebEmbeddedPlayerStreamingUrl; import static java.lang.Math.min; +import android.content.Context; import android.net.Uri; import androidx.annotation.NonNull; @@ -47,6 +48,7 @@ import com.google.common.net.HttpHeaders; import org.schabi.newpipe.DownloaderImpl; +import org.schabi.newpipe.settings.ProxyManager; import java.io.IOException; import java.io.InputStream; @@ -56,6 +58,7 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.NoRouteToHostException; +import java.net.Proxy; import java.net.URL; import java.util.HashMap; import java.util.List; @@ -86,6 +89,7 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD */ public static final class Factory implements HttpDataSource.Factory { + private final Context context; private final RequestProperties defaultRequestProperties; @Nullable @@ -102,8 +106,10 @@ public static final class Factory implements HttpDataSource.Factory { /** * Creates an instance. + * @param context the context to use */ - public Factory() { + public Factory(final Context context) { + this.context = context; defaultRequestProperties = new RequestProperties(); connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLIS; readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLIS; @@ -222,7 +228,6 @@ public Factory setContentTypePredicate( *

The default is {@code null}. * *

See {@link DataSource#addTransferListener(TransferListener)}. - * * @param transferListenerToUse The listener that will be used. * @return This factory. */ @@ -249,6 +254,7 @@ public Factory setKeepPostFor302Redirects(final boolean keepPostFor302RedirectsV @Override public YoutubeHttpDataSource createDataSource() { final YoutubeHttpDataSource dataSource = new YoutubeHttpDataSource( + context, connectTimeoutMs, readTimeoutMs, allowCrossProtocolRedirects, @@ -274,6 +280,7 @@ public YoutubeHttpDataSource createDataSource() { private static final String YOUTUBE_BASE_URL = "https://www.youtube.com"; private static final byte[] POST_BODY = new byte[] {0x78, 0}; + private final Context context; private final boolean allowCrossProtocolRedirects; private final boolean rangeParameterEnabled; private final boolean rnParameterEnabled; @@ -301,7 +308,8 @@ public YoutubeHttpDataSource createDataSource() { private long requestNumber; @SuppressWarnings("checkstyle:ParameterNumber") - private YoutubeHttpDataSource(final int connectTimeoutMillis, + private YoutubeHttpDataSource(final Context context, + final int connectTimeoutMillis, final int readTimeoutMillis, final boolean allowCrossProtocolRedirects, final boolean rangeParameterEnabled, @@ -310,6 +318,7 @@ private YoutubeHttpDataSource(final int connectTimeoutMillis, @Nullable final Predicate contentTypePredicate, final boolean keepPostFor302Redirects) { super(true); + this.context = context; this.connectTimeoutMillis = connectTimeoutMillis; this.readTimeoutMillis = readTimeoutMillis; this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; @@ -716,6 +725,11 @@ private HttpURLConnection makeConnection( * @return an {@link HttpURLConnection} created with the {@code url} */ private HttpURLConnection openConnection(@NonNull final URL url) throws IOException { + final ProxyManager proxyManager = new ProxyManager(context); + final Proxy proxy = proxyManager.getProxy(); + if (proxy != null) { + return (HttpURLConnection) url.openConnection(proxy); + } return (HttpURLConnection) url.openConnection(); } @@ -1014,4 +1028,3 @@ public int hashCode() { } } } - diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index 0530d56e921..6b4f470e82f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -95,11 +95,11 @@ public PlayerDataSource(final Context context, // YouTube-specific data source factories use getYoutubeHttpDataSourceFactory() ytHlsCacheDataSourceFactory = new CacheFactory(context, transferListener, cache, - getYoutubeHttpDataSourceFactory(false, false)); + getYoutubeHttpDataSourceFactory(context, false, false)); ytDashCacheDataSourceFactory = new CacheFactory(context, transferListener, cache, - getYoutubeHttpDataSourceFactory(true, true)); + getYoutubeHttpDataSourceFactory(context, true, true)); ytProgressiveDashCacheDataSourceFactory = new CacheFactory(context, transferListener, cache, - getYoutubeHttpDataSourceFactory(false, true)); + getYoutubeHttpDataSourceFactory(context, false, true)); // set the maximum size to manifest creators YoutubeProgressiveDashManifestCreator.getCache().setMaximumSize(MAX_MANIFEST_CACHE_SIZE); @@ -191,9 +191,10 @@ private static DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory( } private static YoutubeHttpDataSource.Factory getYoutubeHttpDataSourceFactory( + final Context context, final boolean rangeParameterEnabled, final boolean rnParameterEnabled) { - return new YoutubeHttpDataSource.Factory() + return new YoutubeHttpDataSource.Factory(context) .setRangeParameterEnabled(rangeParameterEnabled) .setRnParameterEnabled(rnParameterEnabled); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/ProxyManager.java b/app/src/main/java/org/schabi/newpipe/settings/ProxyManager.java new file mode 100644 index 00000000000..5e22fccd373 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/ProxyManager.java @@ -0,0 +1,79 @@ +package org.schabi.newpipe.settings; + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.preference.PreferenceManager; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +/** + * A class to manage proxy settings. + */ +public class ProxyManager { + + private final SharedPreferences sharedPreferences; + + /** + * Creates a new ProxyManager. + * @param context the context to use + */ + public ProxyManager(final Context context) { + this.sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + } + + /** + * Checks if the proxy is enabled. + * @return true if the proxy is enabled, false otherwise + */ + public boolean isProxyEnabled() { + return sharedPreferences.getBoolean("use_proxy", false); + } + + /** + * Gets the proxy host. + * @return the proxy host + */ + public String getProxyHost() { + return sharedPreferences.getString("proxy_host", "127.0.0.1"); + } + + /** + * Gets the proxy port. + * @return the proxy port + */ + public int getProxyPort() { + final String portString = sharedPreferences.getString("proxy_port", "1080"); + try { + return Integer.parseInt(portString); + } catch (final NumberFormatException e) { + return 1080; + } + } + + /** + * Gets the proxy type. + * @return the proxy type + */ + public Proxy.Type getProxyType() { + final String type = sharedPreferences.getString("proxy_type", "SOCKS"); + if ("SOCKS".equals(type)) { + return Proxy.Type.SOCKS; + } else { + return Proxy.Type.HTTP; + } + } + + /** + * Gets the proxy. + * @return the proxy, or null if the proxy is not enabled + */ + public Proxy getProxy() { + if (!isProxyEnabled()) { + return null; + } + return new Proxy(getProxyType(), new InetSocketAddress(getProxyHost(), getProxyPort())); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/ProxySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ProxySettingsFragment.java new file mode 100644 index 00000000000..f181909bd0b --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/ProxySettingsFragment.java @@ -0,0 +1,26 @@ +package org.schabi.newpipe.settings; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.preference.ListPreference; + +import org.schabi.newpipe.R; + +/** + * A fragment that displays proxy settings. + */ +public class ProxySettingsFragment extends BasePreferenceFragment { + + @Override + public void onCreatePreferences(@Nullable final Bundle savedInstanceState, + @Nullable final String rootKey) { + addPreferencesFromResource(R.xml.proxy_settings); + + final ListPreference proxyTypePreference = findPreference("proxy_type"); + if (proxyTypePreference != null) { + proxyTypePreference.setSummaryProvider( + ListPreference.SimpleSummaryProvider.getInstance()); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java index 4b116bdf906..77679fa4857 100644 --- a/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java @@ -23,6 +23,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.Image; +import org.schabi.newpipe.settings.ProxyManager; import java.io.File; import java.io.IOException; @@ -49,12 +50,17 @@ private PicassoHelper() { public static void init(final Context context) { picassoCache = new LruCache(10 * 1024 * 1024); - picassoDownloaderClient = new OkHttpClient.Builder() + final ProxyManager proxyManager = new ProxyManager(context); + final OkHttpClient.Builder builder = new OkHttpClient.Builder() .cache(new okhttp3.Cache(new File(context.getExternalCacheDir(), "picasso"), 50L * 1024L * 1024L)) // this should already be the default timeout in OkHttp3, but just to be sure... - .callTimeout(15, TimeUnit.SECONDS) - .build(); + .callTimeout(15, TimeUnit.SECONDS); + + if (proxyManager.isProxyEnabled()) { + builder.proxy(proxyManager.getProxy()); + } + picassoDownloaderClient = builder.build(); picassoInstance = new Picasso.Builder(context) .memoryCache(picassoCache) // memory cache diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml new file mode 100644 index 00000000000..9f4698a6032 --- /dev/null +++ b/app/src/main/res/menu/main_menu.xml @@ -0,0 +1,9 @@ + +

+ + diff --git a/app/src/main/res/values-ru/strings_proxy.xml b/app/src/main/res/values-ru/strings_proxy.xml new file mode 100644 index 00000000000..65e4eb562de --- /dev/null +++ b/app/src/main/res/values-ru/strings_proxy.xml @@ -0,0 +1,11 @@ + + + Настройки прокси + Использовать прокси + Перенаправлять трафик через прокси + Хост прокси + Имя хоста или IP-адрес прокси + Порт прокси + Номер порта прокси + Введите номер порта прокси + \ No newline at end of file diff --git a/app/src/main/res/values/strings_proxy.xml b/app/src/main/res/values/strings_proxy.xml new file mode 100644 index 00000000000..117c8cca5aa --- /dev/null +++ b/app/src/main/res/values/strings_proxy.xml @@ -0,0 +1,20 @@ + + + Proxy Settings + Use proxy + Redirect traffic through a proxy + Proxy host + Hostname or IP address of the proxy + Proxy port + Port number of the proxy + Enter the proxy port number + Proxy type + + HTTP + SOCKS + + + HTTP + SOCKS + + \ No newline at end of file diff --git a/app/src/main/res/xml/main_settings.xml b/app/src/main/res/xml/main_settings.xml index 5f96989f979..bdb02355fd8 100644 --- a/app/src/main/res/xml/main_settings.xml +++ b/app/src/main/res/xml/main_settings.xml @@ -16,6 +16,12 @@ android:title="@string/settings_category_downloads_title" app:iconSpaceReserved="false" /> + + + + + + + + + + + + + \ No newline at end of file