Merge pull request #2713 from mauriciocolli/compatibility-extractor

Use new Localization and Downloader implementations from extractor
This commit is contained in:
Tobias Groza 2019-11-18 10:05:45 +01:00 committed by GitHub
commit a8e326ceea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 367 additions and 406 deletions

View File

@ -62,7 +62,7 @@ dependencies {
exclude module: 'support-annotations' exclude module: 'support-annotations'
}) })
implementation 'com.github.teamnewpipe:NewPipeExtractor:v0.17.4' implementation 'com.github.TeamNewPipe:NewPipeExtractor:5c420340ceb39'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.23.0' testImplementation 'org.mockito:mockito-core:2.23.0'
@ -94,6 +94,7 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxjava:2.2.2' implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1' implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
implementation 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
implementation "androidx.room:room-runtime:${roomDbLibVersion}" implementation "androidx.room:room-runtime:${roomDbLibVersion}"
implementation "androidx.room:room-rxjava2:${roomDbLibVersion}" implementation "androidx.room:room-rxjava2:${roomDbLibVersion}"

View File

@ -17,6 +17,9 @@
#} #}
-dontobfuscate -dontobfuscate
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
-keep class org.ocpsoft.prettytime.i18n.** { *; }
-keep class org.mozilla.javascript.** { *; } -keep class org.mozilla.javascript.** { *; }
-keep class org.mozilla.classfile.ClassFileWriter -keep class org.mozilla.classfile.ClassFileWriter

View File

@ -15,7 +15,7 @@ import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.LeakDirectoryProvider; import com.squareup.leakcanary.LeakDirectoryProvider;
import com.squareup.leakcanary.RefWatcher; import com.squareup.leakcanary.RefWatcher;
import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.downloader.Downloader;
import java.io.File; import java.io.File;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -39,7 +39,7 @@ public class DebugApp extends App {
@Override @Override
protected Downloader getDownloader() { protected Downloader getDownloader() {
return org.schabi.newpipe.Downloader.init(new OkHttpClient.Builder() return DownloaderImpl.init(new OkHttpClient.Builder()
.addNetworkInterceptor(new StethoInterceptor())); .addNetworkInterceptor(new StethoInterceptor()));
} }

View File

@ -21,13 +21,14 @@ import org.acra.config.ACRAConfiguration;
import org.acra.config.ACRAConfigurationException; import org.acra.config.ACRAConfigurationException;
import org.acra.config.ConfigurationBuilder; import org.acra.config.ConfigurationBuilder;
import org.acra.sender.ReportSenderFactory; import org.acra.sender.ReportSenderFactory;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.report.AcraReportSenderFactory; import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StateSaver;
import java.io.IOException; import java.io.IOException;
@ -95,7 +96,10 @@ public class App extends Application {
SettingsActivity.initSettings(this); SettingsActivity.initSettings(this);
NewPipe.init(getDownloader(), NewPipe.init(getDownloader(),
org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(this)); Localization.getPreferredLocalization(this),
Localization.getPreferredContentCountry(this));
Localization.init();
StateSaver.init(this); StateSaver.init(this);
initNotificationChannel(); initNotificationChannel();
@ -109,7 +113,7 @@ public class App extends Application {
} }
protected Downloader getDownloader() { protected Downloader getDownloader() {
return org.schabi.newpipe.Downloader.init(null); return DownloaderImpl.init(null);
} }
private void configureRxJavaErrorHandler() { private void configureRxJavaErrorHandler() {

View File

@ -1,296 +0,0 @@
package org.schabi.newpipe;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import org.schabi.newpipe.extractor.DownloadRequest;
import org.schabi.newpipe.extractor.DownloadResponse;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.utils.Localization;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
/*
* Created by Christian Schabesberger on 28.01.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* Downloader.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/>.
*/
public class Downloader implements org.schabi.newpipe.extractor.Downloader {
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0";
private static Downloader instance;
private String mCookies;
private final OkHttpClient client;
private Downloader(OkHttpClient.Builder builder) {
this.client = builder
.readTimeout(30, TimeUnit.SECONDS)
//.cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024))
.build();
}
/**
* It's recommended to call exactly once in the entire lifetime of the application.
*
* @param builder if null, default builder will be used
*/
public static Downloader init(@Nullable OkHttpClient.Builder builder) {
return instance = new Downloader(builder != null ? builder : new OkHttpClient.Builder());
}
public static Downloader getInstance() {
return instance;
}
public String getCookies() {
return mCookies;
}
public void setCookies(String cookies) {
mCookies = cookies;
}
/**
* Get the size of the content that the url is pointing by firing a HEAD request.
*
* @param url an url pointing to the content
* @return the size of the content, in bytes
*/
public long getContentLength(String url) throws IOException {
Response response = null;
try {
final Request request = new Request.Builder()
.head().url(url)
.addHeader("User-Agent", USER_AGENT)
.build();
response = client.newCall(request).execute();
String contentLength = response.header("Content-Length");
return contentLength == null ? -1 : Long.parseLong(contentLength);
} catch (NumberFormatException e) {
throw new IOException("Invalid content length", e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Download the text file at the supplied URL as in download(String),
* but set the HTTP header field "Accept-Language" to the supplied string.
*
* @param siteUrl the URL of the text file to return the contents of
* @param localization the language and country (usually a 2-character code) to set
* @return the contents of the specified text file
*/
@Override
public String download(String siteUrl, Localization localization) throws IOException, ReCaptchaException {
Map<String, String> requestProperties = new HashMap<>();
requestProperties.put("Accept-Language", localization.getLanguage());
return download(siteUrl, requestProperties);
}
/**
* Download the text file at the supplied URL as in download(String),
* but set the HTTP headers included in the customProperties map.
*
* @param siteUrl the URL of the text file to return the contents of
* @param customProperties set request header properties
* @return the contents of the specified text file
* @throws IOException
*/
@Override
public String download(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
return getBody(siteUrl, customProperties).string();
}
public InputStream stream(String siteUrl) throws IOException {
try {
return getBody(siteUrl, Collections.emptyMap()).byteStream();
} catch (ReCaptchaException e) {
throw new IOException(e.getMessage(), e.getCause());
}
}
private ResponseBody getBody(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
final Request.Builder requestBuilder = new Request.Builder()
.method("GET", null).url(siteUrl);
for (Map.Entry<String, String> header : customProperties.entrySet()) {
requestBuilder.addHeader(header.getKey(), header.getValue());
}
if (!customProperties.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
final Request request = requestBuilder.build();
final Response response = client.newCall(request).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
}
if (body == null) {
response.close();
return null;
}
return body;
}
/**
* Download (via HTTP) the text file located at the supplied URL, and return its contents.
* Primarily intended for downloading web pages.
*
* @param siteUrl the URL of the text file to download
* @return the contents of the specified text file
*/
@Override
public String download(String siteUrl) throws IOException, ReCaptchaException {
return download(siteUrl, Collections.emptyMap());
}
@Override
public DownloadResponse get(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
final Request.Builder requestBuilder = new Request.Builder()
.method("GET", null).url(siteUrl);
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
// set custom headers in request
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
for(String value : pair.getValue()){
requestBuilder.addHeader(pair.getKey(), value);
}
}
if (!requestHeaders.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
final Request okRequest = requestBuilder.build();
final Response response = client.newCall(okRequest).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
}
if (body == null) {
response.close();
return null;
}
return new DownloadResponse(response.code(), body.string(), response.headers().toMultimap());
}
@Override
public DownloadResponse get(String siteUrl) throws IOException, ReCaptchaException {
return get(siteUrl, DownloadRequest.emptyRequest);
}
@Override
public DownloadResponse post(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
if(null == requestHeaders.get("Content-Type") || requestHeaders.get("Content-Type").isEmpty()){
// content type header is required. maybe throw an exception here
return null;
}
String contentType = requestHeaders.get("Content-Type").get(0);
RequestBody okRequestBody = null;
if (null != request.getRequestBody()) {
okRequestBody = RequestBody.create(MediaType.parse(contentType), request.getRequestBody());
}
final Request.Builder requestBuilder = new Request.Builder()
.method("POST", okRequestBody).url(siteUrl);
// set custom headers in request
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
for(String value : pair.getValue()){
requestBuilder.addHeader(pair.getKey(), value);
}
}
if (!requestHeaders.containsKey("User-Agent")) {
requestBuilder.header("User-Agent", USER_AGENT);
}
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
final Request okRequest = requestBuilder.build();
final Response response = client.newCall(okRequest).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
}
if (body == null) {
response.close();
return null;
}
return new DownloadResponse(response.code(), body.string(), response.headers().toMultimap());
}
@Override
public DownloadResponse head(String siteUrl) throws IOException, ReCaptchaException {
final Request request = new Request.Builder()
.head().url(siteUrl)
.addHeader("User-Agent", USER_AGENT)
.build();
final Response response = client.newCall(request).execute();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
}
return new DownloadResponse(response.code(), null, response.headers().toMultimap());
}
}

View File

@ -0,0 +1,156 @@
package org.schabi.newpipe;
import android.text.TextUtils;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.downloader.Request;
import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import androidx.annotation.Nullable;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
public class DownloaderImpl extends Downloader {
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0";
private static DownloaderImpl instance;
private String mCookies;
private OkHttpClient client;
private DownloaderImpl(OkHttpClient.Builder builder) {
this.client = builder
.readTimeout(30, TimeUnit.SECONDS)
//.cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024))
.build();
}
/**
* It's recommended to call exactly once in the entire lifetime of the application.
*
* @param builder if null, default builder will be used
*/
public static DownloaderImpl init(@Nullable OkHttpClient.Builder builder) {
return instance = new DownloaderImpl(builder != null ? builder : new OkHttpClient.Builder());
}
public static DownloaderImpl getInstance() {
return instance;
}
public String getCookies() {
return mCookies;
}
public void setCookies(String cookies) {
mCookies = cookies;
}
/**
* Get the size of the content that the url is pointing by firing a HEAD request.
*
* @param url an url pointing to the content
* @return the size of the content, in bytes
*/
public long getContentLength(String url) throws IOException {
try {
final Response response = head(url);
return Long.parseLong(response.getHeader("Content-Length"));
} catch (NumberFormatException e) {
throw new IOException("Invalid content length", e);
} catch (ReCaptchaException e) {
throw new IOException(e);
}
}
public InputStream stream(String siteUrl) throws IOException {
try {
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
.method("GET", null).url(siteUrl)
.addHeader("User-Agent", USER_AGENT);
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
final okhttp3.Request request = requestBuilder.build();
final okhttp3.Response response = client.newCall(request).execute();
final ResponseBody body = response.body();
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
}
if (body == null) {
response.close();
return null;
}
return body.byteStream();
} catch (ReCaptchaException e) {
throw new IOException(e.getMessage(), e.getCause());
}
}
@Override
public Response execute(@Nonnull Request request) throws IOException, ReCaptchaException {
final String httpMethod = request.httpMethod();
final String url = request.url();
final Map<String, List<String>> headers = request.headers();
final byte[] dataToSend = request.dataToSend();
RequestBody requestBody = null;
if (dataToSend != null) {
requestBody = RequestBody.create(null, dataToSend);
}
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
.method(httpMethod, requestBody).url(url)
.addHeader("User-Agent", USER_AGENT);
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
}
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {
final String headerName = pair.getKey();
final List<String> headerValueList = pair.getValue();
if (headerValueList.size() > 1) {
requestBuilder.removeHeader(headerName);
for (String headerValue : headerValueList) {
requestBuilder.addHeader(headerName, headerValue);
}
} else if (headerValueList.size() == 1) {
requestBuilder.header(headerName, headerValueList.get(0));
}
}
final okhttp3.Response response = client.newCall(requestBuilder.build()).execute();
if (response.code() == 429) {
response.close();
throw new ReCaptchaException("reCaptcha Challenge requested", url);
}
final ResponseBody body = response.body();
String responseBodyToReturn = null;
if (body != null) {
responseBodyToReturn = body.string();
}
return new Response(response.code(), response.message(), response.headers().toMultimap(), responseBodyToReturn);
}
}

View File

@ -40,7 +40,7 @@ public class ImageDownloader extends BaseImageDownloader {
} }
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException { protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
final Downloader downloader = (Downloader) NewPipe.getDownloader(); final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader();
return downloader.stream(imageUri); return downloader.stream(imageUri);
} }
} }

View File

@ -112,7 +112,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
// find cookies : s_gl & goojf and Add cookies to Downloader // find cookies : s_gl & goojf and Add cookies to Downloader
if (find_access_cookies(cookies)) { if (find_access_cookies(cookies)) {
// Give cookies to Downloader class // Give cookies to Downloader class
Downloader.getInstance().setCookies(mCookies); DownloaderImpl.getInstance().setCookies(mCookies);
// Closing activity and return to parent // Closing activity and return to parent
setResult(RESULT_OK); setResult(RESULT_OK);

View File

@ -40,12 +40,12 @@ import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.utils.Localization;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
@ -488,35 +488,24 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} }
private int getSubtitleIndexBy(List<SubtitlesStream> streams) { private int getSubtitleIndexBy(List<SubtitlesStream> streams) {
Localization loc = NewPipe.getPreferredLocalization(); final Localization preferredLocalization = NewPipe.getPreferredLocalization();
int candidate = 0;
for (int i = 0; i < streams.size(); i++) { for (int i = 0; i < streams.size(); i++) {
Locale streamLocale = streams.get(i).getLocale(); final Locale streamLocale = streams.get(i).getLocale();
String tag = streamLocale.getLanguage().concat("-").concat(streamLocale.getCountry());
if (tag.equalsIgnoreCase(loc.getLanguage())) { final boolean languageEquals = streamLocale.getLanguage() != null && preferredLocalization.getLanguageCode() != null &&
return i; streamLocale.getLanguage().equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage());
final boolean countryEquals = streamLocale.getCountry() != null && streamLocale.getCountry().equals(preferredLocalization.getCountryCode());
if (languageEquals) {
if (countryEquals) return i;
candidate = i;
} }
} }
// fallback return candidate;
// 1st loop match country & language
// 2nd loop match language only
int index = loc.getLanguage().indexOf("-");
String lang = index > 0 ? loc.getLanguage().substring(0, index) : loc.getLanguage();
for (int j = 0; j < 2; j++) {
for (int i = 0; i < streams.size(); i++) {
Locale streamLocale = streams.get(i).getLocale();
if (streamLocale.getLanguage().equalsIgnoreCase(lang)) {
if (j > 0 || streamLocale.getCountry().equalsIgnoreCase(loc.getCountry())) {
return i;
}
}
}
}
return 0;
} }
StoredDirectoryHelper mainStorageAudio = null; StoredDirectoryHelper mainStorageAudio = null;

View File

@ -1067,7 +1067,13 @@ public class VideoDetailFragment
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy)); uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
if (info.getViewCount() >= 0) { if (info.getViewCount() >= 0) {
if (info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
videoCountView.setText(Localization.listeningCount(activity, info.getViewCount()));
} else if (info.getStreamType().equals(StreamType.LIVE_STREAM)) {
videoCountView.setText(Localization.watchingCount(activity, info.getViewCount()));
} else {
videoCountView.setText(Localization.localizeViewCount(activity, info.getViewCount())); videoCountView.setText(Localization.localizeViewCount(activity, info.getViewCount()));
}
videoCountView.setVisibility(View.VISIBLE); videoCountView.setVisibility(View.VISIBLE);
} else { } else {
videoCountView.setVisibility(View.GONE); videoCountView.setVisibility(View.GONE);
@ -1120,9 +1126,15 @@ public class VideoDetailFragment
videoTitleToggleArrow.setVisibility(View.VISIBLE); videoTitleToggleArrow.setVisibility(View.VISIBLE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoDescriptionRootLayout.setVisibility(View.GONE); videoDescriptionRootLayout.setVisibility(View.GONE);
if (!TextUtils.isEmpty(info.getUploadDate())) {
videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate())); if (info.getUploadDate() != null) {
videoUploadDateView.setText(Localization.localizeUploadDate(activity, info.getUploadDate().date().getTime()));
videoUploadDateView.setVisibility(View.VISIBLE);
} else {
videoUploadDateView.setText(null);
videoUploadDateView.setVisibility(View.GONE);
} }
prepareDescription(info.getDescription()); prepareDescription(info.getDescription());
updateProgressInfo(info); updateProgressInfo(info);

View File

@ -14,6 +14,7 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -101,10 +102,17 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
ellipsize(); ellipsize();
} }
if (null != item.getLikeCount()) { if (item.getLikeCount() >= 0) {
itemLikesCountView.setText(String.valueOf(item.getLikeCount())); itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
} else {
itemLikesCountView.setText("-");
}
if (item.getPublishedTime() != null) {
itemPublishedTime.setText(Localization.relativeTime(item.getPublishedTime().date()));
} else {
itemPublishedTime.setText(item.getTextualPublishedTime());
} }
itemPublishedTime.setText(item.getPublishedTime());
itemView.setOnClickListener(view -> { itemView.setOnClickListener(view -> {
toggleEllipsize(); toggleEllipsize();

View File

@ -1,5 +1,6 @@
package org.schabi.newpipe.info_list.holder; package org.schabi.newpipe.info_list.holder;
import android.preference.PreferenceManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
@ -7,10 +8,13 @@ import android.widget.TextView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import static org.schabi.newpipe.MainActivity.DEBUG;
/* /*
* Created by Christian Schabesberger on 01.08.16. * Created by Christian Schabesberger on 01.08.16.
* <p> * <p>
@ -53,15 +57,38 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
private String getStreamInfoDetailLine(final StreamInfoItem infoItem) { private String getStreamInfoDetailLine(final StreamInfoItem infoItem) {
String viewsAndDate = ""; String viewsAndDate = "";
if (infoItem.getViewCount() >= 0) { if (infoItem.getViewCount() >= 0) {
if (infoItem.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
viewsAndDate = Localization.listeningCount(itemBuilder.getContext(), infoItem.getViewCount());
} else if (infoItem.getStreamType().equals(StreamType.LIVE_STREAM)) {
viewsAndDate = Localization.watchingCount(itemBuilder.getContext(), infoItem.getViewCount());
} else {
viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount());
} }
if (!TextUtils.isEmpty(infoItem.getUploadDate())) { }
final String uploadDate = getFormattedRelativeUploadDate(infoItem);
if (!TextUtils.isEmpty(uploadDate)) {
if (viewsAndDate.isEmpty()) { if (viewsAndDate.isEmpty()) {
viewsAndDate = infoItem.getUploadDate(); return uploadDate;
} else {
viewsAndDate += "" + infoItem.getUploadDate();
} }
return Localization.concatenateStrings(viewsAndDate, uploadDate);
} }
return viewsAndDate; return viewsAndDate;
} }
private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
if (infoItem.getUploadDate() != null) {
String formattedRelativeTime = Localization.relativeTime(infoItem.getUploadDate().date());
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
.getBoolean(itemBuilder.getContext().getString(R.string.show_original_time_ago_key), false)) {
formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")";
}
return formattedRelativeTime;
} else {
return infoItem.getTextualUploadDate();
}
}
} }

View File

@ -55,7 +55,7 @@ import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.Downloader; import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -209,7 +209,7 @@ public abstract class BasePlayer implements
this.progressUpdateReactor = new SerialDisposable(); this.progressUpdateReactor = new SerialDisposable();
this.databaseUpdateReactor = new CompositeDisposable(); this.databaseUpdateReactor = new CompositeDisposable();
final String userAgent = Downloader.USER_AGENT; final String userAgent = DownloaderImpl.USER_AGENT;
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build(); final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter); this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);

View File

@ -17,8 +17,8 @@ import com.nononsenseapps.filepicker.Utils;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.utils.Localization; import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.FilePickerActivityHelper;
@ -53,10 +53,16 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private String thumbnailLoadToggleKey; private String thumbnailLoadToggleKey;
private Localization initialSelectedLocalization;
private ContentCountry initialSelectedContentCountry;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key); thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
initialSelectedLocalization = org.schabi.newpipe.util.Localization.getPreferredLocalization(requireContext());
initialSelectedContentCountry = org.schabi.newpipe.util.Localization.getPreferredContentCountry(requireContext());
} }
@Override @Override
@ -108,20 +114,21 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
startActivityForResult(i, REQUEST_EXPORT_PATH); startActivityForResult(i, REQUEST_EXPORT_PATH);
return true; return true;
}); });
}
Preference setPreferredLanguage = findPreference(getString(R.string.content_language_key)); @Override
setPreferredLanguage.setOnPreferenceChangeListener((Preference p, Object newLanguage) -> { public void onDestroy() {
Localization oldLocal = org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(getActivity()); super.onDestroy();
NewPipe.setLocalization(new Localization(oldLocal.getCountry(), (String) newLanguage));
return true;
});
Preference setPreferredCountry = findPreference(getString(R.string.content_country_key)); final Localization selectedLocalization = org.schabi.newpipe.util.Localization
setPreferredCountry.setOnPreferenceChangeListener((Preference p, Object newCountry) -> { .getPreferredLocalization(requireContext());
Localization oldLocal = org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(getActivity()); final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
NewPipe.setLocalization(new Localization((String) newCountry, oldLocal.getLanguage())); .getPreferredContentCountry(requireContext());
return true;
}); if (!selectedLocalization.equals(initialSelectedLocalization)
|| !selectedContentCountry.equals(initialSelectedContentCountry)) {
Toast.makeText(requireContext(), R.string.localization_changes_requires_app_restart, Toast.LENGTH_LONG).show();
}
} }
@Override @Override

View File

@ -32,7 +32,7 @@ import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.SuggestionExtractor; import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfo; import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;

View File

@ -2,24 +2,26 @@ package org.schabi.newpipe.util;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;
import android.text.TextUtils; import android.text.TextUtils;
import org.ocpsoft.prettytime.PrettyTime;
import org.ocpsoft.prettytime.units.Decade;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.localization.ContentCountry;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;
/* /*
* Created by chschtsch on 12/29/15. * Created by chschtsch on 12/29/15.
* *
@ -42,11 +44,16 @@ import java.util.Locale;
public class Localization { public class Localization {
public final static String DOT_SEPARATOR = ""; private static PrettyTime prettyTime;
private static final String DOT_SEPARATOR = "";
private Localization() { private Localization() {
} }
public static void init() {
initPrettyTime();
}
@NonNull @NonNull
public static String concatenateStrings(final String... strings) { public static String concatenateStrings(final String... strings) {
return concatenateStrings(Arrays.asList(strings)); return concatenateStrings(Arrays.asList(strings));
@ -69,16 +76,18 @@ public class Localization {
return stringBuilder.toString(); return stringBuilder.toString();
} }
public static org.schabi.newpipe.extractor.utils.Localization getPreferredExtractorLocal(Context context) { public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization(final Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); final String contentLanguage = PreferenceManager
.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.content_language_key), context.getString(R.string.default_language_value));
return org.schabi.newpipe.extractor.localization.Localization.fromLocalizationCode(contentLanguage);
}
String languageCode = sp.getString(context.getString(R.string.content_language_key), public static ContentCountry getPreferredContentCountry(final Context context) {
context.getString(R.string.default_language_value)); final String contentCountry = PreferenceManager
.getDefaultSharedPreferences(context)
String countryCode = sp.getString(context.getString(R.string.content_country_key), .getString(context.getString(R.string.content_country_key), context.getString(R.string.default_country_value));
context.getString(R.string.default_country_value)); return new ContentCountry(contentCountry);
return new org.schabi.newpipe.extractor.utils.Localization(countryCode, languageCode);
} }
public static Locale getPreferredLocale(Context context) { public static Locale getPreferredLocale(Context context) {
@ -106,27 +115,12 @@ public class Localization {
return nf.format(number); return nf.format(number);
} }
private static String formatDate(Context context, String date) { public static String formatDate(Date date) {
Locale locale = getPreferredLocale(context); return DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(date);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
Date datum = null;
try {
datum = formatter.parse(date);
} catch (ParseException e) {
e.printStackTrace();
} }
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); public static String localizeUploadDate(Context context, Date date) {
return context.getString(R.string.upload_date_text, formatDate(date));
return df.format(datum);
}
public static String localizeDate(Context context, String date) {
Resources res = context.getResources();
String dateString = res.getString(R.string.upload_date_text);
String formattedDate = formatDate(context, date);
return String.format(dateString, formattedDate);
} }
public static String localizeViewCount(Context context, long viewCount) { public static String localizeViewCount(Context context, long viewCount) {
@ -153,6 +147,14 @@ public class Localization {
} }
} }
public static String listeningCount(Context context, long listeningCount) {
return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount, shortCount(context, listeningCount));
}
public static String watchingCount(Context context, long watchingCount) {
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, shortCount(context, watchingCount));
}
public static String shortViewCount(Context context, long viewCount) { public static String shortViewCount(Context context, long viewCount) {
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, shortCount(context, viewCount)); return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, shortCount(context, viewCount));
} }
@ -192,4 +194,26 @@ public class Localization {
} }
return output; return output;
} }
/*//////////////////////////////////////////////////////////////////////////
// Pretty Time
//////////////////////////////////////////////////////////////////////////*/
private static void initPrettyTime() {
prettyTime = new PrettyTime(Locale.getDefault());
// Do not use decades as YouTube doesn't either.
prettyTime.removeUnit(Decade.class);
}
private static PrettyTime getPrettyTime() {
// If pretty time's Locale is different, init again with the new one.
if (!prettyTime.getLocale().equals(Locale.getDefault())) {
initPrettyTime();
}
return prettyTime;
}
public static String relativeTime(Calendar calendarTime) {
return getPrettyTime().formatUnrounded(calendarTime);
}
} }

View File

@ -10,7 +10,7 @@ import android.widget.ImageView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import org.schabi.newpipe.Downloader; import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.Stream;
@ -182,7 +182,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
continue; continue;
} }
final long contentLength = Downloader.getInstance().getContentLength(stream.getUrl()); final long contentLength = DownloaderImpl.getInstance().getContentLength(stream.getUrl());
streamsWrapper.setSize(stream, contentLength); streamsWrapper.setSize(stream, contentLength);
hasChanged = true; hasChanged = true;
} }

View File

@ -5,7 +5,7 @@ import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.schabi.newpipe.Downloader; import org.schabi.newpipe.DownloaderImpl;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -212,7 +212,7 @@ public class DownloadMission extends Mission {
HttpURLConnection openConnection(String url, int threadId, long rangeStart, long rangeEnd) throws IOException { HttpURLConnection openConnection(String url, int threadId, long rangeStart, long rangeEnd) throws IOException {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(true); conn.setInstanceFollowRedirects(true);
conn.setRequestProperty("User-Agent", Downloader.USER_AGENT); conn.setRequestProperty("User-Agent", DownloaderImpl.USER_AGENT);
conn.setRequestProperty("Accept", "*/*"); conn.setRequestProperty("Accept", "*/*");
// BUG workaround: switching between networks can freeze the download forever // BUG workaround: switching between networks can freeze the download forever

View File

@ -111,8 +111,8 @@
<!-- DEBUG ONLY --> <!-- DEBUG ONLY -->
<string name="debug_pref_screen_key" translatable="false">debug_pref_screen_key</string> <string name="debug_pref_screen_key" translatable="false">debug_pref_screen_key</string>
<string name="allow_heap_dumping_key" translatable="false">allow_heap_dumping_key</string> <string name="allow_heap_dumping_key" translatable="false">allow_heap_dumping_key</string>
<string name="allow_disposed_exceptions_key" translatable="false">allow_disposed_exceptions_key</string> <string name="allow_disposed_exceptions_key" translatable="false">allow_disposed_exceptions_key</string>
<string name="show_original_time_ago_key" translatable="false">show_original_time_ago_text_key</string>
<!-- THEMES --> <!-- THEMES -->
<string name="theme_key" translatable="false">theme</string> <string name="theme_key" translatable="false">theme</string>

View File

@ -263,6 +263,19 @@
<item quantity="one">%s view</item> <item quantity="one">%s view</item>
<item quantity="other">%s views</item> <item quantity="other">%s views</item>
</plurals> </plurals>
<string name="no_one_watching">No one is watching</string>
<plurals name="watching">
<item quantity="one">%s watching</item>
<item quantity="other">%s watching</item>
</plurals>
<string name="no_one_listening">No one is listening</string>
<plurals name="listening">
<item quantity="one">%s listener</item>
<item quantity="other">%s listeners</item>
</plurals>
<string name="no_videos">No videos</string> <string name="no_videos">No videos</string>
<plurals name="videos"> <plurals name="videos">
<item quantity="one">%s video</item> <item quantity="one">%s video</item>
@ -380,6 +393,8 @@
<string name="override_current_data">This will override your current setup.</string> <string name="override_current_data">This will override your current setup.</string>
<string name="import_settings">Do you want to also import settings?</string> <string name="import_settings">Do you want to also import settings?</string>
<string name="error_unable_to_load_comments">Could not load comments</string> <string name="error_unable_to_load_comments">Could not load comments</string>
<string name="localization_changes_requires_app_restart">Localization changes will not take effect until the app is restarted</string>
<!-- Kiosk Names --> <!-- Kiosk Names -->
<string name="kiosk">Kiosk</string> <string name="kiosk">Kiosk</string>
<string name="trending">Trending</string> <string name="trending">Trending</string>
@ -443,6 +458,10 @@
<string name="enable_leak_canary_summary">Memory leak monitoring may cause the app to become unresponsive when heap dumping</string> <string name="enable_leak_canary_summary">Memory leak monitoring may cause the app to become unresponsive when heap dumping</string>
<string name="enable_disposed_exceptions_title">Report out-of-lifecycle errors</string> <string name="enable_disposed_exceptions_title">Report out-of-lifecycle errors</string>
<string name="enable_disposed_exceptions_summary">Force reporting of undeliverable Rx exceptions outside of fragment or activity lifecycle after disposal</string> <string name="enable_disposed_exceptions_summary">Force reporting of undeliverable Rx exceptions outside of fragment or activity lifecycle after disposal</string>
<string name="show_original_time_ago_title" translatable="false">Show original time ago on items</string>
<string name="show_original_time_ago_summary" translatable="false">Original texts from services will be visible in stream items</string>
<!-- Subscriptions import/export --> <!-- Subscriptions import/export -->
<string name="import_export_title">Import/export</string> <string name="import_export_title">Import/export</string>
<string name="import_title">Import</string> <string name="import_title">Import</string>

View File

@ -3,15 +3,6 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/content"> android:title="@string/content">
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="@string/default_country_value"
android:entries="@array/country_names"
android:entryValues="@array/country_codes"
android:key="@string/content_country_key"
android:summary="%s"
android:title="@string/default_content_country_title"/>
<ListPreference <ListPreference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
android:defaultValue="@string/default_language_value" android:defaultValue="@string/default_language_value"
@ -21,6 +12,15 @@
android:summary="%s" android:summary="%s"
android:title="@string/content_language_title"/> android:title="@string/content_language_title"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="@string/default_country_value"
android:entries="@array/country_names"
android:entryValues="@array/country_codes"
android:key="@string/content_country_key"
android:summary="%s"
android:title="@string/default_content_country_title"/>
<SwitchPreference <SwitchPreference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
android:defaultValue="false" android:defaultValue="false"

View File

@ -18,4 +18,11 @@
android:key="@string/allow_disposed_exceptions_key" android:key="@string/allow_disposed_exceptions_key"
android:title="@string/enable_disposed_exceptions_title" android:title="@string/enable_disposed_exceptions_title"
android:summary="@string/enable_disposed_exceptions_summary"/> android:summary="@string/enable_disposed_exceptions_summary"/>
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="false"
android:key="@string/show_original_time_ago_key"
android:title="@string/show_original_time_ago_title"
android:summary="@string/show_original_time_ago_summary"/>
</PreferenceScreen> </PreferenceScreen>