From fc31458cc408e0f3cfba310553aee0e54f46b6af Mon Sep 17 00:00:00 2001 From: Ritvik Saraf <13ritvik@gmail.com> Date: Fri, 12 Oct 2018 02:34:30 +0530 Subject: [PATCH 001/270] added peertube --- app/build.gradle | 2 +- app/src/main/java/org/schabi/newpipe/App.java | 3 ++- app/src/main/java/org/schabi/newpipe/Downloader.java | 7 ++++--- .../java/org/schabi/newpipe/util/ExtractorHelper.java | 8 +++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2104d2023..5e634d9b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.yausername:NewPipeExtractor:d1ff1c7' + implementation 'com.github.yausername:NewPipeExtractor:c9fba9c' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.8.9' diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index dfce8f100..c0af70efb 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -21,6 +21,7 @@ import org.acra.config.ConfigurationBuilder; import org.acra.sender.ReportSenderFactory; import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.utils.Localization; import org.schabi.newpipe.report.AcraReportSenderFactory; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; @@ -88,7 +89,7 @@ public class App extends Application { // Initialize settings first because others inits can use its values SettingsActivity.initSettings(this); - NewPipe.init(getDownloader()); + NewPipe.init(getDownloader(), new Localization("GB", "en")); StateSaver.init(this); initNotificationChannel(); diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java index 16f2ed4ae..8ff2b839a 100644 --- a/app/src/main/java/org/schabi/newpipe/Downloader.java +++ b/app/src/main/java/org/schabi/newpipe/Downloader.java @@ -6,6 +6,7 @@ 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; @@ -109,13 +110,13 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { * 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 language the language (usually a 2-character code) to set as the preferred language + * @param localization the language and country (usually a 2-character code for both values) * @return the contents of the specified text file */ @Override - public String download(String siteUrl, String language) throws IOException, ReCaptchaException { + public String download(String siteUrl, Localization localization) throws IOException, ReCaptchaException { Map requestProperties = new HashMap<>(); - requestProperties.put("Accept-Language", language); + requestProperties.put("Accept-Language", localization.getLanguage()); return download(siteUrl, requestProperties); } diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 5a9911a67..e328ad23e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -76,8 +76,7 @@ public final class ExtractorHelper { SearchInfo.getInfo(NewPipe.getService(serviceId), NewPipe.getService(serviceId) .getSearchQHFactory() - .fromQuery(searchString, contentFilter, sortFilter), - contentCountry)); + .fromQuery(searchString, contentFilter, sortFilter))); } public static Single getMoreSearchItems(final int serviceId, @@ -92,7 +91,6 @@ public final class ExtractorHelper { NewPipe.getService(serviceId) .getSearchQHFactory() .fromQuery(searchString, contentFilter, sortFilter), - contentCountry, pageUrl)); } @@ -104,7 +102,7 @@ public final class ExtractorHelper { return Single.fromCallable(() -> NewPipe.getService(serviceId) .getSuggestionExtractor() - .suggestionList(query, contentCountry)); + .suggestionList(query)); } public static Single getStreamInfo(final int serviceId, @@ -128,7 +126,7 @@ public final class ExtractorHelper { final String nextStreamsUrl) { checkServiceId(serviceId); return Single.fromCallable(() -> - ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); + ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl, NewPipe.getLocalization())); } public static Single getCommentsInfo(final int serviceId, From cc6989b4f961aa2199c38ce31430d231a9649f3a Mon Sep 17 00:00:00 2001 From: Ritvik Saraf <13ritvik@gmail.com> Date: Fri, 12 Oct 2018 13:16:16 +0530 Subject: [PATCH 002/270] updated extractor --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5e634d9b9..7ba0ef803 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.yausername:NewPipeExtractor:c9fba9c' + implementation 'com.github.yausername:NewPipeExtractor:4883b6f' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.8.9' From 845663f80fe6aa5aee0c1e43ee35c7d599a564ba Mon Sep 17 00:00:00 2001 From: Ritvik Saraf <13ritvik@gmail.com> Date: Sat, 29 Dec 2018 23:06:39 +0530 Subject: [PATCH 003/270] added themeing for peertube, change peertube instance --- app/build.gradle | 2 +- .../java/org/schabi/newpipe/MainActivity.java | 3 ++ .../settings/ContentSettingsFragment.java | 39 ++++++++++++++++++ .../schabi/newpipe/util/KioskTranslator.java | 4 ++ .../schabi/newpipe/util/ServiceHelper.java | 24 ++++++++++- .../org/schabi/newpipe/util/ThemeHelper.java | 9 +++- .../ic_kiosklocal_black_24dp.png | Bin 0 -> 208 bytes .../ic_kiosklocal_white_24dp.png | Bin 0 -> 206 bytes .../ic_kioskrecent_black_24dp.png | Bin 0 -> 475 bytes .../ic_kioskrecent_white_24dp.png | Bin 0 -> 460 bytes .../ic_kiosklocal_black_24dp.png | Bin 0 -> 166 bytes .../ic_kiosklocal_white_24dp.png | Bin 0 -> 166 bytes .../ic_kioskrecent_black_24dp.png | Bin 0 -> 311 bytes .../ic_kioskrecent_white_24dp.png | Bin 0 -> 312 bytes .../res/drawable-nodpi/place_holder_cloud.png | Bin 0 -> 8032 bytes .../drawable-nodpi/place_holder_peertube.png | Bin 0 -> 15722 bytes .../ic_kiosklocal_black_24dp.png | Bin 0 -> 235 bytes .../ic_kiosklocal_white_24dp.png | Bin 0 -> 226 bytes .../ic_kioskrecent_black_24dp.png | Bin 0 -> 597 bytes .../ic_kioskrecent_white_24dp.png | Bin 0 -> 588 bytes .../ic_kiosklocal_black_24dp.png | Bin 0 -> 291 bytes .../ic_kiosklocal_white_24dp.png | Bin 0 -> 284 bytes .../ic_kioskrecent_black_24dp.png | Bin 0 -> 856 bytes .../ic_kioskrecent_white_24dp.png | Bin 0 -> 835 bytes .../ic_kiosklocal_black_24dp.png | Bin 0 -> 344 bytes .../ic_kiosklocal_white_24dp.png | Bin 0 -> 345 bytes .../ic_kioskrecent_black_24dp.png | Bin 0 -> 1178 bytes .../ic_kioskrecent_white_24dp.png | Bin 0 -> 1157 bytes .../main/res/values-v21/styles_services.xml | 19 +++++++++ app/src/main/res/values/attrs.xml | 2 + app/src/main/res/values/colors_services.xml | 11 +++++ app/src/main/res/values/settings_keys.xml | 2 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 4 ++ app/src/main/res/values/styles_services.xml | 19 +++++++++ app/src/main/res/xml/content_settings.xml | 5 +++ 36 files changed, 140 insertions(+), 4 deletions(-) create mode 100755 app/src/main/res/drawable-hdpi/ic_kiosklocal_black_24dp.png create mode 100755 app/src/main/res/drawable-hdpi/ic_kiosklocal_white_24dp.png create mode 100755 app/src/main/res/drawable-hdpi/ic_kioskrecent_black_24dp.png create mode 100755 app/src/main/res/drawable-hdpi/ic_kioskrecent_white_24dp.png create mode 100755 app/src/main/res/drawable-mdpi/ic_kiosklocal_black_24dp.png create mode 100755 app/src/main/res/drawable-mdpi/ic_kiosklocal_white_24dp.png create mode 100755 app/src/main/res/drawable-mdpi/ic_kioskrecent_black_24dp.png create mode 100755 app/src/main/res/drawable-mdpi/ic_kioskrecent_white_24dp.png create mode 100644 app/src/main/res/drawable-nodpi/place_holder_cloud.png create mode 100644 app/src/main/res/drawable-nodpi/place_holder_peertube.png create mode 100755 app/src/main/res/drawable-xhdpi/ic_kiosklocal_black_24dp.png create mode 100755 app/src/main/res/drawable-xhdpi/ic_kiosklocal_white_24dp.png create mode 100755 app/src/main/res/drawable-xhdpi/ic_kioskrecent_black_24dp.png create mode 100755 app/src/main/res/drawable-xhdpi/ic_kioskrecent_white_24dp.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_kiosklocal_black_24dp.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_kiosklocal_white_24dp.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_kioskrecent_black_24dp.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_kioskrecent_white_24dp.png create mode 100755 app/src/main/res/drawable-xxxhdpi/ic_kiosklocal_black_24dp.png create mode 100755 app/src/main/res/drawable-xxxhdpi/ic_kiosklocal_white_24dp.png create mode 100755 app/src/main/res/drawable-xxxhdpi/ic_kioskrecent_black_24dp.png create mode 100755 app/src/main/res/drawable-xxxhdpi/ic_kioskrecent_white_24dp.png diff --git a/app/build.gradle b/app/build.gradle index 31ec4776a..f88a43ffc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,7 +54,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.yausername:NewPipeExtractor:df0db84' + implementation 'com.github.yausername:NewPipeExtractor:b1a77fa' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index a9f2e9622..729bb2bd4 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -50,6 +50,7 @@ import android.widget.ImageView; import android.widget.TextView; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.fragments.BackPressable; @@ -95,6 +96,8 @@ public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); + ServiceHelper.initServices(this); + ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); super.onCreate(savedInstanceState); diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 82604f7da..5388d1821 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -18,9 +18,11 @@ import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.utils.Localization; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.ZipHelper; @@ -39,6 +41,9 @@ import java.util.Map; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; +import io.reactivex.Single; +import io.reactivex.schedulers.Schedulers; + public class ContentSettingsFragment extends BasePreferenceFragment { private static final int REQUEST_IMPORT_PATH = 8945; @@ -122,6 +127,40 @@ public class ContentSettingsFragment extends BasePreferenceFragment { NewPipe.setLocalization(new Localization((String) newCountry, oldLocal.getLanguage())); return true; }); + + + Preference peerTubeInstance = findPreference(getString(R.string.peertube_instance_url_key)); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); + peerTubeInstance.setDefaultValue(sharedPreferences.getString(getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl())); + peerTubeInstance.setSummary(sharedPreferences.getString(getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl())); + + peerTubeInstance.setOnPreferenceChangeListener((Preference p, Object newInstance) -> { + String url = (String) newInstance; + if(!url.startsWith("https://")){ + Toast.makeText(getActivity(), "instance url should start with https://", + Toast.LENGTH_SHORT).show(); + return false; + }else{ + boolean shouldUpdate = Single.fromCallable(() -> { + ServiceList.PeerTube.setInstance(url); + return true; + }).subscribeOn(Schedulers.io()) + .onErrorReturnItem(false) + .blockingGet(); + + if (shouldUpdate) { + p.setSummary(url); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()).apply(); + editor.putString(getString(R.string.current_service_key), ServiceList.PeerTube.getServiceInfo().getName()).apply(); + editor.putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); + }else{ + Toast.makeText(getActivity(), "unable to update instance", + Toast.LENGTH_SHORT).show(); + } + return shouldUpdate; + } + }); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java index 392892cef..61bd2323d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java +++ b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java @@ -44,6 +44,10 @@ public class KioskTranslator { return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); case "New & hot": return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); + case "Local": + return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local); + case "Recently added": + return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent); default: return 0; } diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index 7c781eb14..ab2d7c6b9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.util; import android.content.Context; +import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; @@ -10,7 +11,9 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; +import java.io.IOException; import java.util.concurrent.TimeUnit; import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; @@ -24,9 +27,11 @@ public class ServiceHelper { case 0: return R.drawable.place_holder_youtube; case 1: - return R.drawable.place_holder_circle; + return R.drawable.place_holder_cloud; + case 2: + return R.drawable.place_holder_peertube; default: - return R.drawable.service; + return R.drawable.place_holder_circle; } } @@ -127,4 +132,19 @@ public class ServiceHelper { default: return true; } } + + public static void initService(Context context, int serviceId) { + if(serviceId == 2){ + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + String peerTubeInstanceUrl = sharedPreferences.getString(context.getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl()); + String peerTubeInstanceName = sharedPreferences.getString(context.getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()); + ServiceList.PeerTube.setInstance(peerTubeInstanceUrl, peerTubeInstanceName); + } + } + + public static void initServices(Context context){ + for(StreamingService s : ServiceList.all()){ + initService(context, s.getServiceId()); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index 1d1b66bf9..b8fba6ce5 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -136,7 +136,14 @@ public class ThemeHelper { else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme"; else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme"; - themeName += "." + service.getServiceInfo().getName(); + switch (serviceId) { + case 2: + //service name for peertube depends on the instance + themeName += ".PeerTube"; + break; + default: + themeName += "." + service.getServiceInfo().getName(); + } int resourceId = context.getResources().getIdentifier(themeName, "style", context.getPackageName()); if (resourceId > 0) { diff --git a/app/src/main/res/drawable-hdpi/ic_kiosklocal_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_kiosklocal_black_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..a9e2993ebeaea19899781fcac3bd118f00f38e66 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8LpbWaz@kP61Pmlg^(J4m=Z6mLAZ zSkRNP#6eV~FgAc;>IGKO3rte=Y@0hBQj9qN$1)r|Q+s}W6yL-dIncC2Q> zkeYOEy`3{({8{*|k0wldT1B8LpR8JSjkP61PmjroQ9b_6lR!?|R z_+j4rm~*w3^M6m%FF#j%T5J7_%c2C^MSaXU~e@mSHPv-ZfPWDEx?)w8B$l&Sf=d#Wz Gp$P!p(pG=~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_kioskrecent_black_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..13813ff829c92dc913fb2f6cf003ef768f6cf74a GIT binary patch literal 475 zcmV<10VMv3P)< z18A;c7>2Le%QgiCwLi?ZvzwQ-Wh`^H?JZ4DPk4TOd%s%T^``f}?_Pte zNjCKiGQ|=5Ofg6u+4v-ppGzg=3MU?Ag;zL`%ZZkCT6xM%Qc2?m&uL`? z^$6e%jT)Th8*x}5p0CVmq7e@YG}P^}4vBPVs(=W**FY5)QLV}M;d@#WvyUjF-zGqS z+eV9`N3yFV5kihwbEIGigk9WZ=0wjSgher4gHuEBTJ%M*WQsgVv~xqzdSo*(6p2^~ z&J97OI7JxxMSSF|p@-u1V;B+fy4fiy42Uy;)XWZE7iSp5zKH2&6k&=PUAolvFzkw$ z>Z_qtsqJ3Y)J8B!oQJ*^`+;_0C>F8YS3~9Dwm(}Bq>qO5KxScxVnO7W&JDd3eSs(} z{MSpH?09MCMgLC-Nvw+1?-5#Jz<2%yMF1ORH;Ozl2L RuKfT2002ovPDHLkV1iXF)xH1# literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_kioskrecent_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_kioskrecent_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..9054e0042dcc677cf55075666fd1b71d4d452060 GIT binary patch literal 460 zcmV;-0W41wT{E^(#Lgn9-PoyZ$LbAg+p$wO*4`K!v$pLvd}{nVli8W1;l3yQPVycj z8ItIAM87+}_~?#)hxL|KG+X7CEN)q?xu|BgXYzPvb`i}c056>~%_!|Uj5Nb3uK-jB z(;NfbwOm80w$U>80mt$*TLBj|=gi+(z}6%i3jq6*L2v-DAiJd(;Ic}{$Ed0r6S9hn z;JKExXd`%_E#|6OldIJOurV!q4CaI|h$7~I#{=VTaNDrLqG4`Z}MvejR04p=p*P1y+{*|hfeSzir$0vQlfV7{(lYa zS`^j5q*9YT5!7P11MDd!+LN{Xz2J5!(Jjyu!-_{>R#A~z;E~F}iVfg_)`ExjA=r@a zXp86IVxeo~a=bSlQ=%z%MzijW2GLcov$O0vx|U_mR@2~k|K+3H?bywIvWjI^QeR{=N-j9xJ@~0TM3+>Q=Zc_59|is#ZjbTE*yy^`rrii= OF@vY8pUXO@geCy$(K~bi literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_kiosklocal_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..23d8145f5d6749cc189d6ab14ce2072cbd9ebbc0 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iTu&FrkP5~trxpq}C@?q#b{)+t zV>L_QYD1Wg)XTAxqPW%616Wcw{T*h4Hw%7fSHa5S*?K!S69bYrw zC2C^v71hppEpJL)(^MAUTBmtNq~tEIc27mJQw%e+muZ4mf0l$B1H-&kMU$EWwZefG OGkCiCxvX4k|U@g7l@az5sza^JJkya8^LNPwae~e<(qaM z3^Vh)IW;2-Yk!V@EsWHZFtin!+8QcgCWM2zo_j4lKMq1_R@9z&Q2_u|?-C)a=u4e- z<H|RCLHGnf2mpKt2Xz3(!cMhNsM-o+09L}3LV>BU0$?L_N`NfDZ5m*dlN!2SOsO09>LyIG?v3?TyBL3i1u*X6!okpTs#uWzABGVp8axolhKtr)Sj`Vwy>lltY&mr#Zlr|OVT+gaTSiW+_mhBD^EU+j+Cd-5u8S_nnx4D z<0~}jFW?~pkxs-cjP`TeqL+CW(G3f|JE>T1-X@sbhk#m?%xsA}ENJDW7 zU4&eEoRlLIHLj<~q#99)BqY3hdjEs>*Vl*7jP>lj)_1S{-D|DAo^!6d9OYzGWB>qi zh!evN04)591uF>f<6C^s5BMP!{Tt%30{$ed2;#v1rCCnSyS2Uv%Ujvfmsn6Oa+Kw}7DZ1adO{5*7`M`zE8FH>K- zhXl`+9~@HnlOSDUzlNH8R)jBfx_HA%%JrHP3){FatkT_SA}w!?W8sRke-(57uqw`C ztaKolo;53%l{uU&yGhGTe(_xf{oSVLk4|-#(;F%}XYcmy8+x$o^N)2$zKov!Ug@WW zaB2T%R}<9`xjP_}PBjm~6kth&hmkT7W^-Mtn2)&domPFx=+UHMS=-IBXBI74 zimX_Mjmevx++Y-IAI1~NiZ-C%(Y?uCxu|>69?}lg$J@~w)R<7DpVYx-YL7H?Tlk?N z7%jzIUIC^7Yi%&a^Re)l){rtH8mT0l*||qyz%Wy{6>FbgEGU$VAli@yj!gbnefY-r zcrZtqY-$-j%c;8_q*dN!0Qf0~#MpqMmJs8B%XiMv-)5Z^EucKKo-*b7pOagn7jUxd z5ah5!aK_mEPBmAmriL&-ttSmduP9Ru|D53Ox(hPHGI`;ElNPhF^g6d)uT&j5iUH}#iI8Np-`qUz*Otm1w7m0kCwZ0r?6F+@7IF#_ewiuft?6 zG|^w=;&E$f-=Wo^vJXKzv3Vt>_99ukCJE2}wuZMwIbLUSQ$_b|kET5EC)%!|*lkqb zv9TR%ukx?pI!JWs9WVOE`;p&xm!09d)$Ic*%_7zSSj?D3JBrj%=KS)}C*9xbxD_ks z5*UO2^a~anlt8{WZ(dF0hrTYodqYtP>xgOFx8ksh7dSi~B(6k<-?mnjHsI$>STT%2 zANqwJ1rl&c6AbdgMiL{(495RZEvIAK7_G^rkDOxf0@p8x#JcD*hTLj5t@3&XU0|=? z%CtW{;-4~pOHk-oQ~zZ}K+Zo=J*VxkuQe#67s=G#7OZ`fdA#bdPtxoN^MuF4Zgw1_ zwaS^8_2dAicz?TKEBklglh;|Yzd?Bpusj%po)EVT3C#Q>y18_Xb=g0AhAFy5nfff; z#y>ExTuMK}+P_@Dp2dYLre7P2Rbeq01_tVk{0$@!yEiy;+e}&QR+3{Dn7B&!8b4g5 z5(v9kK7luGh z&X>)edO_lw0vl}I8ok7tVP8>Ek~u-uqqOffuDGv5ke0NXy}XZp-r}rJ(!&d!qd8=% zVLO@aZD_tMJGWui(s*TTQn&eZAaCr7Voi!VsBsJK#Jt`fn_u*2#g7pgQ4S^Q#F!Qr z&-T_cR|eNX*ZRv2VW^+S@6v^CbWT};-M;cY1jqTMm3oOY{rPB~`E(I6u zNW2c!OWIO;*J<@J^5j=eU46_-7fljS%;SZFHe3}{wNXSD=}@M!h1)Fr41kA6uq|bd zP`!Q3z-`+sw(1d!;NQ-b=&(Y(+B8oS!D=M&>XELWxio$vI!DCwEn;~I(iXYD-0mt90cPnUGX{Bf0 zZ>-K(e7}mV3u6kd2#^ggJB(+BCpBr0c$OyLNQ7>$6Zo#Rk{);%qg0-JM()#Uif0bF z!ijGJVU3cBV1@(QwfCa46Y<3D8}!Hi;y2AG@4M2;I1zQV2XDH ziO13Yq$Xxa*2#CfD&Dg-p{fbU)z1XG`DNAW5C}_ii2UjOix!o<3{1t|WXolDxaRyj zWELchKb`a78P>jLt<8Acs_LUqeb+7A&r`v+O))ED^O&IGalltQO8g#35QTZe>Vi?J z!wScNN1puKcg8L+Q_#ftgT*@_m2V#spGtb#Oz$RFldu~u?e15lz9ZWNiyB4-sDp1C#=5$g+X~Z3^%FI;P1{gQ$&7#SyP1Qco=#KBifHqDYJWDmH zr@%dGVrurop4jl+M`V0&No06-?gsN&@OmFTmG2#aOSl)aq9&CIyekC0%5xhk7nrA~ zo#mxhL8TiAA?J$7pL`&OmqUbz)s)S-#hqX2;g+&Z(|2aQ`^U+j4%qx@fC&NLEkXDfVaWkqY59j}&N2_zHTW?D04%&D+ut zvohxZ(H;be%h30z%AePq@^3z(j=U}1e)y{akg^Y6B3A!CrpDEskVm_z2M>&CCExejL{SVzPGJ3H?FpqYDVO1lSTDuHRRSx-TI}8l+qIbs9wpqMrX6o~I zUSQ{K!Ju^f5vb4m%{g?z84By9OiBsY*?!=YFI;I~JbV07Ko{W>_Bbf(4~Lxmc39ut zkQ62}fVuQra%}V8>>0@F9_Lezt@|Js_Q7G@)^IuZA{;}+g~D|`1|;@|Y@RCC@nYw? z^4=?i#>$u#mL1rDA8O~?7M;CsYnw;cz+q}|7?Z{TSfLF5Rm#$6fM-D5_g}&NrgbS8UWq(Fwk^i$X9=yODw%}juyI} z$Y{-j+>kueGT%&i&4^u*5i-dfuPDM=GZ>IuQ~Q9$dh;S_HFBd=QC((_eDj8Cy#)Tv zVBQvebN?*fvUDM_QB5xqS)3{pVm#!nfn~d3AIdDhk~37nQbWR?Zb#2@yu9d~CxOI( zoxFOt;AV22%$&@wCGL+SFvpCoR4Gztn+A!CFq+u=Fe-C(Kh$~jM2i8r%JH#$G$@k3*s#(+u&6PRc6{>FS!OW13u=E!{+ zU^@AZQ+G9HW!e)mDg#YmJwf96?1!|i2bCtD3fEcCtwBd7Bt}sQ{f;O1TYMg)^*Cg? zg%V(9@fL7J1Tl=Th+*v!hVNebA1R}&&{by9ivpXp@e^m@n!Ru1n8fFYKLJ?Mbg%xynW5VGs<-;*(EBLEx`@ zlmKr&OhgrBKqB+gr$fuh`46yK=A%L4-KaFGQabJACLfW*`yF!f5wAk>1Q+6Z`GX!( zk>STq=jB65?X|G^FMH_cX)~D%fF8V21ENy$p6TYULFhZrdF4!eA^6se%j8wz zmL!?%4+shl5@(@PXrY8ODxdu5?BL40)qyY{r{ng!L#-|p5}Q0Wq``(M zX>Qe|P`Hm4Dsx1M(w|Gds`#;Y6$;}&9xC1VSnn3SWHa7AxRP9$-c{o4hMdYJ7xTs* zYp`41EgSw!)9e;E*Q|FMjEOA*3mzv!-i#UAqQ?$O?zi4 z?(k!}1Ry9P?B5NMjP3R%d|-Df0hE}u`6788auOa~@`ra2S^{ZuO1OoFFk_WjD_JDg zYL+`=(0vnEt!_RaZ&ziOU;+*))RwiKa*}s^p+uNt&{@A&#wShqPyY*OP(nz*`Xq%43ue zH;Uag`sbOQq6NxSz#E&({W(~X*Wi>fQejZ?lJxzM=oDp&1?4?I;BLtiY~AUpwDO!t z!Y+I>tY8h)%b4C0ZkXTbOWX@{8@K0V-tI*--i(M5 z?oO%-5yzQFHwX_nA9f)IJpb`fp=&;m=F;~F4N011v>KK^+UcxM2fVeYDJ+1vx<@zl zhTioI7Qab;|KW9bhuyo6NvEAtGife0HAxxRkVE3!(nl7z=2qDUcM>bHVwgn{A$l6k zLWv7M?%Au-!7>}tpxkdSJ{@}|llS`u^)hJ($;rY^7TvqHGK&@(Iw3{Q;B@7Y-L4Oh|~BP?{_oUZM^eO!Pfp^&^Ki(_x85YmdAXZVL_*s)Ik>SlP50=2CqqE zPDCwp&k}FV6JKc)dWhSBIGphXPP8*|UoH|0$Y{Gi*J5iQXwKqU=aKIiWRHWNYX+iL z&2nkw4in&tIwdcM{M9Qh4xT(^iR$jqBccm}!Fw^8ZTYRM&;mV{Ji>h$rd^Si^f8Bg zhrc5aj`S04MmM^=cI5KObznfBI$@|TmH!1alA%kv;5a_LLqI(`a{6W&iH%d$@aOW4 z=1HQEINsGZ5jP?h*a<*~2m12S}T0vk}3-D$PpM-zC8@-x4c3_+{fF|h5{%n0OrwgqQVDH;H4*eQ&+EG?%-3l9pe+lN&bx$8SX$Al$Mc46i=WJ-Ocv$X;1a+@T%_yEDb!Wy@`4 zgsQ)B)mtY2$bSM`HaSgZceCWa$0`Erev5P(a`N8nCey=ycyyO#nl{4KIPyi15ImIJ zw+BRcc3W|2V^go)XW2D{FyPmYXaIzpf6tHM>((C-DB1nfl2wMi^!ccEGhK&$z278- zhIlvQ%%htg5MU30R<61iL#_<3rpvHzg{|4nrQI4(HIF{7j_chGBD^21;nG4MZz6oJ z3@nBt^nOjx=k@oQ{yUI?2^N1zs<#Zvq9OXnHLG_s25mdF0%M_3b&1h@{+=YybnAcX zn4sW-sy&w`_xb%>vPmSS`b`~%h6tY@*PPwSjv|5I!9`l=*I?X2nmJmBM;lG6@@UAl z6Ju7&89v~qxp)R*u;^+RWNjXS0)giANrY>WpTpkj*fgL6*ZUy6Xt%LiE%1sKeAGdN zAO2DV`e`ScXo$*CE^mvFXY*DG=x6*AbWL3I4Z>(4ZndB3PaTv1mlo@jg+;f*FF7?X z!Ih?;9N^&dZH@c6w6qZNq~4?b7Lk}q^in^Q%bz-8{t5qU&Tw~XQ@|irwDb$=+k$vZ z0MVYy|9W5JJJwpFbePVoZ!uB6-~$FNCHTuV3dqy%qe$%GU%)EbT)#-Ifru^s_n}0o z%hhvAzz73bM^=@eJ|s~y^QiGavrQz1HT}!}Nr9%g1H$XaZ4pcMB_UfMP)BSKu zX-LhbJf3N{amFhQsJowH3LP9T27f>M))A`!`gGeFVuBYLk6RMvFRuuhfWBgKodS5d7mY9X3Am z`Xt;`8N!(_9)#RH^oyH2;+A(l^zy_^X6HYFthiPXMGSaSfX~%4AKl%gQkOd=*>(-W z-gM4m^Z8?j>TFJ(+}sJtLEvW%Zt8B=9QVNjtEwlr;W(C(-1H%m1nir*M+yI7p?}Y; z@5XRA;p{(S)ASH-%CV0o{*`xefVJb?gp@p!#MShL z0c_*VR`+R0g|8xId((;)`G#N@m^jKxK)6z)>Bn{l;?ZK2EluT9I>;)Mkyr5rp8!ZG zdjtQ?Kdv1MN+uMTPho*=iJsN{qKybIK9C+*Pfa=VwekfS6?v3&Z8`x1c-g?(Yde>o z|6B#osJgYTT)u9h>G+>%%77BJn&s9#3)ALP8`;7w^+C*%%q13o?W5Tz<3Fslj$l z83hPScd`i?_T=)fSXHKP(`}XlnY;#!-TJ7b2$vo~sg7a{Zl18$$psuvbW*qTpbtz% z*0NprMwQWDQ~$xEsPg1(dxV>N{rl{gf*&@X5gdu} z#opWtD^SER+H&$9C;mp~wpY(FTGUFyQgaL$;d+1g_kmvGVT1@^few_h))!$ppOYFjR4Y#>MN5;lNPmE#D_(O=K_r^-d*-x zwxmNxE59i9ox^$Zx}duPIWK^3v;!&~vEEMZHT5g@+e%>0ul4m1J!&YTIl27);zUrNMI{ZHL2oVHK+M!Ig zgQUjoseX*MWMBGu^*tV%ll!_+^hRPX?D=)=kwKUddzk<-@F6}O_DHyV!cW2gojOt2 zBiG;o1lV1e{l6S!=`H(7kuqHH&c)}U)Fh{69uP63lDm{!nytxLyC%H{10C8)Gy=b_ ze0)1i3EDA^SC9W>(ThJaED&212a+CzvfUCl&L=9y4=!! zJfy6x^qg+>5!gxMmq*$3K1d8N+Vv6M#M=Ji41=zcaVZG*ULbq>OPl#e3KF7}tr*`9fHjHDLb5(Zs&OwZWz-64n%$G#Yc_ zE72NcWJ_+_QhmSfBpYY3sj!dcOe{~vqi#Xs)v$}-wBL)%*JnTEaF|SD`FTh|3gzK> zPBL2=^ed)oBO5<50!D3JmI5%KADPW6|TmC_e352XVw`TI5#QQZ3l=x^y0$gTUy_5-`W0gIg)l!SA zl^CjMOZF&uSX-FVuK5XE&@u0qdM*1?uKM^JD=z;U&YNW3o#5Dl$CCW7W5xW)> zO=`=cJrnezuqV_b+~!;jZtM=OB&lK4WCZ%KOK~rTl#~c;RVnfp$?kvQ?Qv$&av~r~ z#P}#l>1mu(&Z(g!5?i`ma2;EP z-!E@-H9`e4+phHGe~HH!HeT@l2per)!Id(NvhlcT>m}<f_uFqJuV~Yb}8=ykxY>1q#HBMDoP6wH(SFOto6WYmJ*-=fs1-^Mfwr;{J$(|Wfh&MA?Hq~5K zbEj6ZK{5T(VM9@(YZLY4p@6g&Pcimx2S2;# z*esaENe`ONeOUqqe{mchyZyg?VfueuxXOz}jCZwLVj*s)3w#?65Qkli%HIOc{tqf} B-o^j` literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-nodpi/place_holder_peertube.png b/app/src/main/res/drawable-nodpi/place_holder_peertube.png new file mode 100644 index 0000000000000000000000000000000000000000..68850054d27d6d7518d6c62e9a28388e3051b929 GIT binary patch literal 15722 zcmbWec|4T;`#yZlJ{Ut3V<&@V_e|jkLPWGvo&0kCFS=!otKHm?z?4v2VRIa4$ zV-!~gRjRaY#Hzh6_|KP%DX2rY@9%Vf_UH)TUF=o4yVUrY!w1^^yS}LZC$#6E3(S`I zeT&{cPd>KnZe3sfDz|N0^R5r)Z!b;0teyXlb^R~@veol-OG_K3vNSgJ|K^Y5;T>b{ z<|di?MVT@EMtwRucfys~)ag{}Ec&OL&E)MZ-#<=&Pnn5~?AcCh57LGbtq@%!!=h|^ z1lHM4o|ySmvz^8cyY38qZ9Ag!O0t@=oCXObn8y`AbyB-!eMWjPD?E}-HBmN#zNWF) z!Jy2)jU~HkQWxI|9Xqzn@`JixtHn z#o_bJ>&yU_^OhYgnFAlS$HlNzT4kdc^Xw0mTglJ{o>;$RrUJF%lNEMJxa1a|WrR74 zW)}!$>IZ+S386WrJ;i9gSR{PHb;k?ZK|b-sp*oQi>bDk1Q#ToX^lh^W@*rP9Lg*${ zS;GUB*m&3tWt=L4I3Np7clXm{oPGTQoYS4r?EGS1h>~DJf{bo4oiQ3HX|a^%nunb(@Fn4}GTEl<~m>J83~i3sT^tlg|;=i^Tj@)jkbEc>zV#5f6Jhw3erR^vCe7uosOya;qj#cB*^ zHHR#TG3NVWc6!EiNz_8(5#d(vO569^%^&Q1L+*TNWC(f$pMWP5^n-)>Sqp0S&twiA z`TZV@&8-&wKwt=d=QWlFYsI(i3Vv(}zWS9f^gssp1ZUCnR!1G5Bek8oCo*Q^eUUwi zv=VJRbEN~9MJ~P0I?PN?mAWtaW&DZ)?E(xr)aMX(q8{TncE+cGTLJ}BsM8_|(7O}7 zSD^5M17McD6fY&#f>x5{)0&q*cK%6~qqT#rYom~{8#&Dxf(@Cn2L-mgbXVR! zo3=1TF%P{GW8f{c%~#w(jBIxJ{T+lB*hWi!?lvAp7|6m+!++qFfw=j}z^64nHAd#_ z`T8kH=W!%lkJ1-vnB;l(H>#vS0GxnMhwMXiVK9hS*qkF`56>M4e?Es|caD*uk0UIge4gcX zHAK)wfD{h4E*SZnVj+YBsavJR>odDgnEXV#dhEFX*E`bO)r;#{pf5aK~c4qOg^tMJW?qa z|E2cDP)O_D%y4&hKF@bS#GrFR5P3|6wOL-*B%#4n>ifb^$wD1Pt@IDKM{6h3sJ4(1 z6t!y=@ZLOdu5cH`=kJc_-j>$8* z1;ydrZpD{V8|IE%SUjJMS15kmi=rebPC>@QI>-a4%=Q{ISz}!wRq)A9Pk{X0J(KDu zIt87s&EelkW~gy~>NGWgDS{K6FYXJ1JfjxFiNGPp;0hXXb#L{3mjim?VIl3uRJ2Z8 znX~?|=scpxy9^JU z1*|0(e@T9@tJ^WU`qitnV+CPX@Cm;k+p{y0ouLeCg*+wrWrg*EQ(mXV>i`t(Ulz(d zGJ?!FOLW83?y`6d0wCi!h}=s6OC4ohw}%`gp3q}hF<(D+ymnXHN2~TNxGCdc7b{}gQCMXn}P1uqpasnd#OP}6HwW=0#YBw5nr zX+gm@myshXf#<;}OmvMP|E&PEFoQ%%;d^hvsz#l|L%M7mlaD`kvhG}Fm z_Sv!c%`-pk-Y203vny0T$6i#*uBR+*W~dfJsLeFy_`^)Bg6>WXbG{}=U?=V|)+3`y z=dS+q%opcLXtp>vqa)kQ8&%M)xMUhT-=ty1Lg2C){w-LIj-ZKqF$&7_ zkNZL?icLBQKb&Df+&r)t-xkEo&-g}y4Gb`ON^PiG_S6KTcyh4q$j_3nzlR%~w#r5_ zQ|FfuatgjB7jMHFZ@n{Y&5az%_U!OLHkJbAzwbn4VM_E9r5PNgKg8Xh!Ye5Ae_KNkTR5ZY_Lq@& z%`7>dkk*M$00(SgK#Ul8bK!hU#1Ufc)`+bqkiu@9mNe}ZJY3Vktv zipl@#(sI0`|9qea%~nkO8!1f1W2gv`z8PlB_Q693*dMc{6s+A9^n!=80KfZrM+z!l zoF-%m(*qNK1;Q`|0PX%odwX{TTCe*S2=Hy`AKgA-+qhlo$;?op;F4Crr1Xfm}o4?c3<{ z+Zs?tjdC9vGM)AxxOA2 zOIb4j9&XC}f-@`zb=?)>{Dd^{(s=w!>jMAuk!>j%Y!qFR1eI9+1!c57VTRgd_0rn? zS*5M-WVagFGQWSomgyD=F~rN01x282?V{&e;mNrYw=*t-u>`f^)KQOC%u zLmM3B%Q3PO#UU6Of+^41w?|9QD}!eIe3x$?11s&9^-rXzZ1MMPOy{j=f#}scRs!m< z{(e_JGl}DEcDl45$4&2m2wJ=d22}Gq0BQMjdp@Rh@V?BA-^xGul zbCooA?PBkm?^}E)P*a7D!_{6DEQ40qcgUp0XNMo;x4b+PFdT=c)c~Yv|2l+OI~=c% z-n66V$K~ZFg$o$>)o((GG4h?#;Ci*?!B*h}Y=I#F0BpHbHBUt2fy$LKIQS}SgS9cV z-dZo{vX6d>Derqm`V?c2(TyBEqgk``e5Dgm%Dfp|to1txF;b%mtrAxH%Fj0DY{dAW zgK^zKcZb5%%j4vvycl~f49A($>|rseIMRgV#_uL>QLWn4d}V0!?WWUrw2T5nr_E;U z`X!U;`&xKwRk!WbE?x|?GrEacmcsuhMh&ZCq~bLg-fANJr~W=v%WH5ke)8H*Wonma zphugXO~Jn{T?4;2X3hPhJWvNdej_87w%9>+TZy;$G3&xH%Vr)t21#(gjv^mAB(ZWU z<=g>>f3rb&ksAR#t=-ON@Njl$O?@=k9FCG^@UQ~VwOKd4!9*?jNN7ioT6BuID{pi{ z>Com2!Co!LN1Ekl>9uhquRi0~%#gPg6m_Ncbj8t`d${R*0~uLckjEXrz7$!IdQcZ)*MeMPpZDv9cc^iIW$1Y!|%bN%8`P_kt8ktO(CSN!vFF{(t2di zLIf&eOL+#&yIrGq(Ol^gU%yz4t1Ey&+t=WZ+XJkQlVk31T!Q(aW?Sn}_(*oRyS+?5&On!#I$B(8z3LsjGO|s+Xx}oAml{|< z->*$ckv)3H`kD2pp#KPN@PmnLR$O>0C`msG|ALI(mtQdx^V4YA9*ty%@IP*39VR+arj$j=%@D> zW!kHMbI)`N2cwu?QRWO&B9%BFsqxR^^5RsMmU>E#!A8W=%nR>0eQ@19hlS(j{gwcT z7may$qP{E9T%_m^#J%Lnf0BBPk&BdSR7A|xXC(F0zHM@Q{GJnjP8m$**zKTOTTGYYb$71^)O1+;oK>j@+^#vRnpv ztz*`uH)?|Q7#ggC2AMQxSp=8hpa!VRUcij#s^7f9y}6Q>P)j_ z*?1w=NCEAZH<+Nnt92NFwtq6n!&cZjzFMRGQA`=LYA;ptAKR;ba5VDW+Gbgh;V@bH zhy;lBIvK=Jakv>ntbBVs!#=TM*mUr)1{rO-6;mev_J6tQ`Ft-ajkQN|ZuG4UqM&HJ|cc!`Z!bX;{ zaCHj2u-8K(&!D&CQ%V`GGd>HIqG+|PdfN|MuQf-ZhSR$I^A|Wh7e$az_>f;im)tqO z;|FIX0YE46WUCV99@}RC2AY4ZM_|o8UZ2n~j!q1s{8-|YnE-C~7Vb}J3P(S+V85g} z*fv74wpowhO8D2=2VWZ?fuw>XEAFGNlVr^8x9~Tu-)sl|696-Qwa%&AWVitw`YOtj zfv%q%>%dlBUObVz*p)s3OYp4u?JQKiRw^E4_b&KK&aL)}hF1QET4GTCuSlrcxZyW; zCYj~4?i74K!jN+UT_)eO4;}~jZ^hPWuWOghYdQ&~EPmR-;A?YD`c*}Z*D2SzF|T2|@fRivsgcUpLl& z4uO7ba_&J#{dFSt)2wO1;~4vY|W^dnADbwF?j62O-IN zKks+-C;=bU=+(K$HkG5qJdG>m;t^**xagA9PLGe~+ymWeW7u{4A1l2g-IG9gt} zpUv84H)7Z8Cg4n#u6*FzdJOr@3ByZ#Q}_p_;0&&JUxK#*icPt?CH>FoaAVAJI>kWz zWH576{HLGHwY`R`gHn0@r=XN4`s<$9sB5DV@h(!uPA_`w7yOqL;M~+{@-me{%lkHd z$>iy2G26Rm|5SmZVuR@n`G?h`KcR>5(fkkohEdP(PploPd!NcI&e|&wXQp48;SD)F z6~`Wh-YM{WV1DdN$z58@5NySZSXL)DKg3jMaszn|RU!w9r9LNCaO$gxdIjwMk}jku zN>}VS%ZnviKs&eZ>Q4K#HnV4R5x=W9SHN(i`%n(rFC>-)IZfuvI+{;Tj0RmL#Ihi)(rU(Lcs-=HQ*42~78wLrt@QaL{pfiEam!=l|1d2XNv*}ovjP$*9{ zH(M_B)ICM!Y#~}DP%f}sxlx0m9X_4rSU63F9((lr~NGuB=q&Grl5Z?LX=5qtJVC}<5x(FNPQL4sP?iQC9Lb9r{0=WKk5 z=hR{M0Ne&|gqGY3$sd=z=?{EGXz|BXvjp1SRDfDORDg2=)LQkHl6{(Ov!iAEE6fXPxlmJB=2DoPw(``l#RsPG7B9t53Jpft0NPR2lTclSEaPyl>JJ{XtQ- z)rJ1wk8Sv2C@<=_S6)JM4QqBQTkxspdeHB~mYF=Y+gyxv0#{zZ8@T=~{(+S1LYmpCY9p9i=sh&@>I@j_D_Kl#~5KBsK!S`$|{1WFz(b=n|9B5@0cW_NsFO>D4tJ%j*)T7;`ki=CWCE`JnuT&us}^2nHd^X^Bxp<0SuUQi;aNvW|3Yx&0OGIT$x z+qThD##Ki2cbUfls2y&B^4<%wf}~)Q3a@84rqspMLxP!^SKphVS}Avj zzNgUB&VuTA+7#LKBz%2ID7sszs~qSx@a7B|(FR*&#HRRpj?OUm1AR>R6rr3P;U)gZ z7x8F+)96_egNoo`L{jHb2_qe!Upv?vWeyw3&-UwF1HvKE3}>hGn~b>1rEs5{4M+~N zlE5#J%RlA%K!h@XJnw`Uq$7O;1jknkx-M&!a<wm`@#_wB97>Y5Sn#?a|Gg9d%pGrVK<+9FlAB-;)!1HSe)&EfLM6c~d{?}06P$jNf+#oi}#tmyc#p_w@C&}w3&f|>M zu)X>oOhORptHwHZ#zylYpJPqYXu= zETz4g@Z5r4f=nmVLIgSYh-H_mcfkMUF@h6rGvEtP63p$r|FW1jV~wzhS664FXS>~B z5<}g2o#8?W^eQjP4lJrR%JZN=^Q*D=S^U1Y(npxCERa_n ziHQY%7WIVse3K4~MO)T=$A&JHx~=exg96fi4i}pKYraxg(QcB!U(w(0bY6+`iY4Vd1N$zZRWucPvQC8bPH&Dwgce56v9 z{lFPhu&w+rVHw1_8MiyGO|)GME4h4AJpo&k2k@8H;1utoJRh398)GCO({~?;%mFSCl1&3li8b1x8@GRx8+swU+ z(IZgQwdhp%^g+zJ1eZv#a}Mw^bQn%dRn{rLyf{v0*wxfersEjJG%0427PMx<@j;$q z_dnHN=K?#e=7|hv5At0wQdJXk(NjFj8(2lp`KzT3Vh4V-;RI!;#-N)hWL-pwY z*^P0?mBy+QRxIdHbcaUqXbWoNR`X}?#*3a5P^R&t3~vjJySrLyLY?V{5I%3q*ymdw zBs=YYv6Qn4vJ6|%lI!0Yq)IRqx~!MKA;r@vg&~g_;c!F*&cQ5}CyS?6YS}RUC)iAZ zINHZNY!+{+EpG+k2bKDGtF#ivd|HC)I$~rN@4frY$2W*ceZJ4U8**}c4^$@mrU85% zp7m;MoB4DJdITHgmFP*7OU$pZ-ek&5r*?MsGdKBa?|?eEjc`VC%1?=!ntW=8-0L0& z`FHF`WLWyRWfChD)Z)?`?0HuLG%kD$v!=$H(Bda$`{>idftC0{5801Il)MLQ&uOho zgJ%EjGJI+;Fa$(=ohZhY+4s_lzPUSp)P_2fWl(&~C1^zH+Sx{PGYS1+Q2er;CV}LD z=}rz*&cRH{@Tq~P<9wmtOM(nsx#W+8W_;W)oA%*5e9=?SYcO9#3HE_9@)o|iHJI33 zl&sl(IKBDGS`fdS6HVBxOO^KZFyz56xrfw)v5{Si0w+CR(67Ez?sYcsN#B*Rp1wbR z)Du?%Qjf`+g5Q@A)GV%hkMU)5tKkN62N)_9!#;$gcNHze7akb068yPbJ4XPwl z28v^XC`F2DA0t6u8NH(kQc%_wuc`jeH^cpa&0cYjUP{;h7$e&$=nb|3$gd#t~oPiOud1g5o*ZpiVW z+p?+S7{aq>&%njpt$7xZS~?Ub1T12iK#8n9R+lsgzj^5=rAX0O1H-0X39s4$H}xBD zD936e&H}lHz0rn&{qt@#55<4)<2?=CiQwU!L&^mkl3@19isiX;FA{IoyB}^+Wxns=*$H(Yec07;GO7o zQC|9P&2nw*@)-xV;*ncZ>9HDev8j~(d^GEKzkw=yOVtR3a2(=B=VzcbG364nv@~C6 z`2zBBuPNV&XHSExCj{a&p|Ni}=v`i$2E4>?=k98^8fyccxcJZ+%wxiL=i^^+@TIr6 zRkCgSXX3WwnQJYwlju@~N!}^Xog!R=YeV>a3TD6;3Ma~A4B3_v(kS(27I+SFY={WRb77ahnx1&*(8HLx?@Z~<`nWWcu(CZQcWGJhitl~>n-9mba`i(DUv zR#{w!s$YPG!wzGj>kW@y&5mgdQJdq)Xkg4^e&S8>(Lx3yrz<67i(eaK-H4Jh$Z>iA zdvC&;Vv07j@DQpMH@J9a=$jJL^VWw`Clhyi)c!Tsf)ml6=qR2+>--G8U!tqr47k#R zt4%)>4lA2m5nqFeM$HH09Yg|QXSoX;x)Y%$TWm*4jgbtD zt<2b|@I)C89+`>Ct9XWr;~(5=vFwY8fnqH`;}-fkN=fL;Dl>Q6^&@LtX1ZCZ(+3Ic zn9Y??csru~?Odc0@T?s_Rwqo^Zu)Ou622I`QgjC-eZ(SmEox0M&FqWu);AT z{K^$igYQh(gpIj79|j!|HzMJ8@2mWz_@5b2cliBdd@xLEx0%nSe_ij}ubvwxnV(li zm!Uk&t6aR4%VT|s3{&%V?7*MGxU&bo^mvJ@&=_~^AC+U;vV1VJN&`~Bn=f&1uw;f+z zdtL%e94|>2Bt=sqqFh}wf!lvU1y_g4qM&C9u^wtb6ZKVFcw8?6D#h#^{F*%HCFSdO z#O%Yj-(-fKdAs_M?PBroE_i)QbcZKl3i|O2%~1kM2LPOGd;B!&&C_8VuD0JCA-xXwZebsXQS&W&Q=%$oWFgRQDm3a4I-Z1+WxA1&aK2QH@W) zT|VrUIAt5B!E(Lk)Slva?qz)tD5Pfk2?oM0m|DKm0&>0lDH)AXAkZGc&a&J<^Be(m z`EUa2Lo*(?No}Ikilm5l3o^tQZUsVSd%O>4}B_ML8FZwLZ&}V;_mF(eeofj6ed&QNq(Vm|R2H6M75cb@zA{*InCaYlKhIV-tUsK&~gR+AZnx zltDUT$C*7CE0i-P{iw>V8W0^cb~$6T0q98?LzKSrbslI>c}E)i%_&pcCNfj8qt1H- z;|bautcWL$W)wyv7@6i8_2RojsAxe0)|m?Xf~Uc~jrBaBT-!z_+n z^VK1!>nShi6gY(h=E7y_y~d)L3@osdw@e9S@vVWzh_l4e5r8;)3D_gPdQdvr86|KD zNSJP6Q0D<|QzFF~WmQ>Kb>)9?+P(d7AKHz-Wj5mToG41*xg8kcm^Sp_a|>h#btfh! zX7NqsQm^%(OZIJ9R%bXerIB}+MqXuqsyk8WIW}7M-WPJXpTW1L+Bbe>llQb*`UgC! z>z~#4-;WLp%IBi1-ceqS_uM>(H%}^sYK06Wt667CR?-3VP-!mJj1$8P#eWi^P2Bty z`1whyEgQ(FxA;J#4PN0%WpTj!?vrfx_0K}X0>30HT{7@eDb&9IaN;}ZDt-tHqp7(9rN;VR#larFGfeK z1a@XU#~JV~hh`LV=}ka!LjCV?5;LO<1xLaGtbK41cNv-cu8RP}>8Gbzxg_ZMXdYTG z*$0-g<^QDz8HL<9G+mtn+E+}!byZH(MxxmJ%dn-FmzVTL2@MbN{4IT`v~%S0Tp;*) zESqwqk*$c;z;i1Qf_>o&$smMtQh-SQC=%GF zI}r}7Y>XAugUTh!t@RI~qR{tYHFFJ)ZC*xG=PA4*+!z0L~cjJG2(e*pG3V|U{9DkuZ+n@jH3otEK$=D6)2q}Ojtv5e> zn?`1s_~)r3brW%*N-k^ScF|~~BDBiIe_)dDXnEnmz6->1E=BefUaJHZ`W=~$dBCT9 zre#dlx-@~UFno94;-U1)fvP}N%&H-^*C7_ll0>`_LcF=}i00)uplc-oqbI4=7#U$o zuy1^lMsAB7S|To$Pp= zga)pGB_g5a7<%`Hc(xSe_oYQ~b$GR3uz_Y?qk4I=p{%*Cdr1!o_%G}vf%X*JTF{Nj zddM`xvBAN4F`fh3AXnTkdCRa2)#J-ay>6#j$=}F-ig$B6ceMBo$kk%Ynw)v-f|L4r zdik(E^Law4*WC5&579m`4UL{38^s{Mb0+-DRLPbD z;@;@5`+I;LeJ{#1lXIm$l{7fBx;S0!7-1bKf1&tn^gZ|;Yt?L__=G^5t}d|YLVZx6 zgVQckZ+a(P#+ldt+W*wyHXVXSt5CM<0-LEt~Mq=K)v7K`qIe>`M{n*s-oS4PFt-Lr9bvIxuFPtGQ3-yi0 zsXExwlowvKSVb8FPrYtYZuRA0KjmwU1#udP7U>IcALvIZ``w!fG>r)X;Gy+1DCLP~ z960tC#Mg4KO?F{R(a8_5geaFQD}*-NNAUuyHtZlKnb4gNs%k_w0UNH23zA>e?BKFe zFBZU@mn&7EbJM8<_=x_kfGsRfgUGw};hR*fGa@i(S@k|xmm>j`u4h2Za}2a@?k39c z0tIWZz5U#U7jB9)wvUX^AUXQVPRSU7tH`Lvv~n33RyVm;!o{`N2`~ozKPkv2RAjn1 z@;+Q%D{S#an^nIA7?lsPD}n`-ev#F1$37MUS9|QMIDWez&+^+2W>q2YA1YK%Mp{wL%-$ z&g{<rko^Ufd!$AY-4st7&6mXm6t6>L-JIcT(0+^&Ex_W>iL%SSU*y7bwa^5e2vm8 zr&N=d=L9V(+|4kq%GYv+Lx$p z$aUD2DS!^2Sv%>u$}14(SF#%;tDCUiOdhJ2o#ZW5P56_zUV&;7Um?t|sGsYq`)@`IM1kG@<9;bU)a?;jZD zmti+wX^yS4)kDI?IcZhad|>6KI)FWER!;?!&^Dqh6B&nzqnoHePz!Cp2*=!5j~Nl9 z^OwsT08w5NfX?^ZkOqN_3Buln%rw$JP-zVfm-&{)W-Bqvl6}srb^Q6oo>OW5w?W4d zlntZlYR5j*IXybME? zxj#lWcQDk-b*@yKzA?A9b{^@j-IQd9fxeT8rAl-hPwkGT2wb_b(|)|+{)Dd%0jr~YJ6<$}w+nz!G)xe!~N3_^9{J(n+(%Eb$t9yg3UDvPH>1_H~jhzPx9Tb<*21xD7- z8L$O`E5~}w?zC+5Z%o{m3A8QTWKf6p3>;wjIiKMc4HsUMnfXJ%=5CHX6m2yaeR8uw zAUuCyVoG9>lg{nT11jMjF$Km*lC8B;;4Y0{FIX-V99{MnA#3@W!dB|jVgz6#~xxMOSVp+`sm7tYx_ zJm4NjMDI!1rkY&*XX}D5uQhZHKmZTUC`xc#r7R+3n@-nwf)047cy}Pilz%6n0R+I< zZ8P;Mq85H~N3;*@@7$j)cS z$rdn*ihTGva051-i6`C^#jta(f=(yNB}kR|fw>yEWD}(VuGt7Am|}L8IH`=Nid?Ta z{^;DVx1!_j>G|S5P|D8%;Gr^g>n76wf?P}#rJC0TKZtWE<|?y(&jHS^?ApIrSTDM4 z=Z*+F|o6k%kp_ZCz*f8E7%%&BG?e4K9{QVnG3Idx6wWEg3=(yCaKOAspDtYsZj%^&9$}{jhc)Z<41K8g*+a{2elMVpUX-nVD zO|sdbys|xLjrcu8D2DJ;^@DWua?ZEHMY1TI~?Vh)N5VU5Wex6AXD4c=;?SL^ve5) zvSzt=h9sU{eYG?kP~+*?2&#d*LjtAvqfq!e3XL6qnNRe)dpgzD5W~#)45U#|`slMd z<;-Xwo1i?OIX>>*H&F;C%L=-ne<UgL-;;+^I&P!B77oG#_7&(Q0A|qUj$z**PHc7lOEWlSzFa=$CO6 zEb8Sl>NIF4UzkdVg%CXbY4D1I-xBB>xPgC9UbktsfZm==gWT4kG`dISzAQUGaj`S(oMFUA+=-QE9-Z&lf)d9CQ}} zfO_PJt$;MpD#7Mo)4@jbf;aWV%qv{RF7l}Z?Ss}ns|B6jo)5>RJ>skzYoa%Prsp3A zj&Q_~4Vd39L>~bQxBx?WuPUXM?dgZu)DhbbohyRehZur~3@`J5gOq!ZPjF%OP zQNRiIzQW36UbnKc3Jwko#2K$C6d&UH!`nH5AZT+jVb3ZncMoaM_`zJ>)d;xo04(ub zz3#~tzlk!3jF_$H>lVb3Yg&bXInt7VGBfZ#Y-t2bS}pWsXIw(%MVg@9_;w~~4R5i4 zxGvep2Ba~e_0Hf$Jb zySl$wZ&{63ZY?}fi(d{<655Is&|80Nb7zLW0t1-JSMq^QlQOwhG}=YLVaL{LP6Hno zp`7lMpoK^1LEfF%I#MDFgdnZP2YTQs!8`>Yfi1Z^)E`3|#+|+qDCat+y@^~Bn0edi z$$kc2pz6Fqj23VqMT_^wwwf$~U}Z1G?_`g1oUsH04INGqe#wD!Hbpw1jycmYV9ah3m_oFW_O`%dqN=a{ZI{=JIOkDhASIH5sIqc5>=0QwoduxUl zbjM(zIcHuCA4}W|hV1xg7nEopF_jy4AdFKc&&NF$)Gi%hY#F274s`#P>HQ-L+)G;J zCrH!O6Kk3P5Q-KG#%*c@U-JCFdn5P%<1WSaO~N~;y!#DjKh}fyjuk+=eu(#pkW+?U z=T3nSXqTpzftuDXwOzZNG_?%1^bK|O)iiYtH8p7^W8l5!|Ko1^@s6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_kiosklocal_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_kiosklocal_black_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..e20865ab0b15605d8c51d2386cecfaf674080a89 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0DO`a}}Ar*{C3#=tr7bh?YT;kba zv?l2Xf6M9y9VrIAwq~CH3qL4Ka8Fg>%9!8NE78)(u#9KoEYTYcni5U1H+n^5F7ICW zbBksCp_e_Ur?Um9@;y4;Y|cDW!zDQ}Nx)eB&b=;`TVenJ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_kiosklocal_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..2d3474832d420c541225548569aba317a0ede07c GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0Dm7Xq+Ar*{oFE12qb`WrT*n8l? z#Vro(A&!DynilZwTEGrPAko^B0V9bB1fa&Kjb_1b)`yN=Iidrg1Qy0Yj{O;1P5Pl1`@T^te1G8C8uI~-V? aIQ&Z&u5O4^zOxMIXa-MLKbLh*2~7amL|PgE literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_kioskrecent_black_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..54e81598012322cc3c06622fe2597cde8610b028 GIT binary patch literal 597 zcmV-b0;>IqP)uZrpewH=uD1?YC;c^D@nD-&Gbfc)nx)c?8!-e)%> j{?9e`5UUwVo1l{l8^BdtbC;&P00000NkvXXu0mjfTHO)h literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_kioskrecent_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_kioskrecent_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..3141a790d7100e25ca4be716c91e15ca93092f65 GIT binary patch literal 588 zcmV-S0<-;zP)HQ)4A=4-jdiCEDW-ZSn$mg}b}EBv*c9;b(5faQNDhH9l+G zpW#1xGM^1x1LP_jnMY5Hk3Ov848@#bC4DR^5>)dVKd-4KVc}82Rf4)oN!VlGfXGXB zGm{bYCP9*(3}+6zc!kJ@z~@0{4kG|}SwPA>kYWLM5rD&VF@LmWKLT0mNLw>VQ%4g5 z`)Fr=*nq%8CRzYY<{<*>{Z^ijzy7-I`I^_7w-9{FZYnAbhctxgS=v- z=W@Yw*=SxOYt0+oXhh0P0tN_WNF&|N3pL0c5|IKD+(v55MN94@OH4up2uqNAv~(9v zL~ZDukejqL7uCq^$e-&wNR4@+JB>(rq=0gyk#0ZY zmpPJG$lB&ASJa<=ALpm87V|?p_5qTm3jgYu4O|_C zE>8LN3?a>8xBNz}?|YcV2zrwuNiRk)huyr6Sl^=$AU}PCia!LNjXnhWjjoanv^bivy@(76}`YDB`;iD)0iM}Xii0p(c#;<30ISP44;|Lnbn4h3!22o7)XgFg2J7Y!Kd^}C zW)Z&td+)z2+-Z_kCm$=YTV^!ba}`Z!mU#kXpK# My85}Sb4q9e05FAaVgLXD literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_kiosklocal_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..6b27fb23cb9c70d3538bee222ad7925ffe7b37cf GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw9(%ethEy=Vy|R$EnL)($VtnJl z#gUD?Gny1{uwJyh(Zm_S*n2!7FLd&<%-Q)B6TZylT(>K+-N5+qIoowjO@a**mCm`J zJaXAZ^7*2qXIFhhpD#>%c-Kkx`GUl!lc%bE$+-C=Z85K|RaxE5mrm{TdhA4Y7)~&K zp*F)bXa$f6Tm>Y8SE|j}8n7BjTmp)1y$BTBdikZ=jH|DKk}uRe>ZYyYoHT!FR@0O8 zm9rFnMy^5;syx8by7>vi#AgcZmKyth%mz{?9QleSG|N0uV2_mdKI;Vst0DS9xwg3PC literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_kioskrecent_black_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..92fc748ec842d6660d15baa0cd921ff7dc2beb46 GIT binary patch literal 856 zcmV-e1E>6nP)cD5BbpwFoy(YKFBH)wZKG!rHdmur^k&LGkBi=MC?x-go0U=btma z7=i=|^2ZWR5zE;@C1-iW7e4WrbCk1#6%-Lp0Lfq}ckr3C+bkmm%`_*U^Z0DEa}>}F zO%(6|pM&;*0z9V^NAS65N9cry4B`nsm+c9I@PtlmH`v8}_Ar6|wBiS?>Bl7YaL>T8 zoldxYTOfEYJi{?$ zrtAf44J3Q$GcjbFkWJGXXsYmR!;q6gHcD%t(bnGA4}>fp6`zZy5+}!P81h2MdT0&Q z-P+^RC}eH525Kd?HDE}skhKa}pjr%hC1l+LcFA60$dli4Q$N9w<3ct%V2}MV4B0BX z9$EuU6P~TsJ{{8G*B|Nc8Ah1AaIin&SrAT zXpR>&SLV^SJD;%Hg%|8X)_g+`Y%tREDZL#^#o^dwsZyYf_Bf?IrAoD>*rpj(sMEr6 zI3bQ(UQ7!^dv0i(ICn@0ckrQxTJ3UaAp{q6=W>&aFWX7QAL+)6FFKc}_KM}HjwCUN zLT0gt`v#63bixw`@f4rS_LM>Y<`So%fX9x)Jf;9mG^daYw!)mJkmhJ6on;n&B)G#e iQvTClE$k2^NRV#`WILA%PrLL00000wH)9K5Bb15QaH;#)-spDRFmYPIkUKpms`xHv6w|(25^ob&N7fZVg>`b zPZ;+ZC~>0%JBeZ^B_uv{B9*98=@i$0vxeN=V}Ej)Z5qnn)|4Fk^>P)4%9{67biSHl`|St*4O1jHmb1EpaQ5YaY8z( zK+=NJ#R<<)Ws`Quo{1Auf5}OmDo!|t>YcR9{-`)%6>5yppwXyR{vI9Mj0SD<_vq-F zw1?|6R9C;RAFWIU(bD7fLvaGN$XL)K)bZc#6?UB0^f??RJP}|h(k&J3_2`c z=!(_k*n_5cd?n_1C3%k7$hMq;)gWaq!zv&=$H>oQjP?wt##=?rCT>mWw=ZebQhJ>gE z5myV9qb*-9k?139Xd*6XW@P!|xt`jj>nV!UkWLI@JR7-3?$%Nwt_5BiQKix8?_6;z z29iP;DGW5XCLhFkf;h(@@`_nByu#-0md3EaG3Hn%CG$(k1ry__FKnSCi*tePnMYlE_L&P@G(Z_BF4Sz;%6mpm=5o(Q5@iIy$m`{LkxibZwFEj0I+~Sa@pgTd@gZl~FB{+As@9F1d8*cxL5ny0A%)s;c{V%?A yd$w>H*)lLR=rS`*VFHE-7+7$^SQAt{CoN$)b3@cq)zN+mNYvBS&t;ucLK6VShk<$k literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_kiosklocal_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..b04fd7a885c3778ceaf8438d0ea1845193f2fa53 GIT binary patch literal 345 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z^LHq;uuoF_~zh3-ev{?h6Cmj zHnT);G-%%Fw!G2A8Nt|lJYn4`&&yj%eSbW8zi3L3m*&zx-8W?-Gj$R!y4XdnULGpF z^F>Zv?yeKg`QP)dyYsQN`|~;9nSb%+?8_40(~5pRHL{(tx|q2kQKBR9+>TqW*4lTk zro|<3>a1URGw^gq-^-guc3-6c)I$ztaD0e0ssYYmtp__ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_kioskrecent_black_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..152259fabb9cf9b5b7b21c033fb6ac5f291f11d1 GIT binary patch literal 1178 zcmV;L1ZDe)P);6h-$dK9#?1+qP%lwyn0UQQPiG+nTp++qP{+L{%~}ve(KT)4ccQNd`_35fKp) z5#b}Z^B(`voWV?BKI=Kmem1ax2@Iw=|M4ET5=zp1MkiKq%tl*IXFeg1(C{E_*uyc0 zZ5Qo$2*YIaDPuY2p^fD;vN1vtHQ2y0AKE%L_c^RaE>dtwB{eN5n(hBkrw z2!Qw4%P~!DFYlq14C-5+#6d>Wo_~3phq<0O39jQI-r--`F`9#x@ePQfi8w>bEWME0 zyiBe|j+dy-!ZPy=C5{FzXHuypx6_aZaKimGVtc9iCQ*#f+`@8oF;?&=N!*a)4_2zP zvY1=&k*n2(I7%B*cp*(2wFFjj4L)(X`k7@luiyu-s4ou-xB?%DGfkPkANlx20lkzp zrV+<8EtQYXANb8*992|miAP>jp8i+p;5(&+H}Jq^Y*M8B5ncSGsJ4;Ia7_aFT=J44SqpU2Oi}e^T#&_5DbYw`*hP%BON`-C>=LqZ%x{XmP8zfefHVuF=-+Ti z7Hg$%&jS#M0C-6H9xWpaM|>-tKzj&G0CbSzzrhg;gzz0)7N~&B*&%f-9Ppx4z7_;3 z0BT7YUcffpr8%6!zy%bt%kBRl#eN~Q2?Q3#aQyH5V8%734pCamRFtkf%+O1&`0bO zIq%NaGAcm)oi)RfkwVC4Mg@E(b?7abDTF+ZnqxjUHIEBN&BT)BLdeZV1>7vIuQ`A3 zFEc9Oa^a}0SRxK3;zk9;g`3 zz6flQPd1nQfn&0nFQpkyhIRoNjFe*MlZ6YOabdpDV=j3bmvmPotAtJ}`)M_R%h(vG z+2aSLCH68r@P^WE^Bp?)QE9h%O^@N}FZ|{Yj_G;l;!M?UIm@S?vc^>6_&^B@P@Dc< z#t&X*HEPpe2|jU6skY13o^6+{tZbM1jjz9x#0^P)E7kbAoLj?fJ$}H#dVIMF^Y>!v zSem~Ryi8pdmzie>aWoOLWxpRod;U9PzfY3j`ZMt7>42h+FskXZ5P+JcgMC}8rxG?8@G(LjdRAft-Nb=C-(*?on)%=lyUII>Hb>6 zlS-vhsT9X^8|@g$TYSc^tY8m2SV@M@c#Dy=<2H_!B%+f0n8HH5Sin^7p-Pg1Ga1KL z{MyU}&XRah#Jzk>Ei$@7WcQ$JhdNK=qw?!AMkJ#unf! zR3rDTVU=To4y#nK9o5iS0t`j9g9`D6>4_X#oGxB*1-ibG2k3|H^4x~k>!J)mEgMi} za-HCq5NUu3=pGe6C}b%ri?gB%z!_MmmQg7F&;ng1vPr%yx)wn?p}(Sgk9^Ys$fptA zuM~=ZTw`|R?7E0Sbj_`5pkY5&MOyZF0=w1hC&6(8b{3C`0X&I?-F0RP zPpu?mv;ZUL9Ho41ymD5}t2p_ZQpp_+WN@;Ct0e?n%@UksXpnGlNRze8u{qZ+$GFul z%@<#v4_JIH=Q$SPYyrn6yLx@color/dark_soundcloud_accent_color + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 7b879fb4c..c03defcd3 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -28,6 +28,8 @@ + + diff --git a/app/src/main/res/values/colors_services.xml b/app/src/main/res/values/colors_services.xml index 761b721d0..2aaa2d4e6 100644 --- a/app/src/main/res/values/colors_services.xml +++ b/app/src/main/res/values/colors_services.xml @@ -22,4 +22,15 @@ #FFFFFF #ff9100 + + #f97d46 + #c04e19 + #000000 + #f97d46 + + #f97d46 + #c04e19 + #FFFFFF + #f97d46 + \ No newline at end of file diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index be0709b66..7530bd8e8 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -143,6 +143,8 @@ en GB content_language + peertube_instance_url + peertube_instance_name content_country show_age_restricted_content use_tor diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 12a5d8ca7..ff9c04737 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,6 +107,7 @@ Default content country Service Default content language + PeerTube instance Player Behavior Video & audio diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 87e19cede..1ba7699eb 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -44,6 +44,8 @@ @drawable/ic_play_arrow_black_24dp @drawable/ic_settings_black_24dp @drawable/ic_whatshot_black_24dp + @drawable/ic_kiosklocal_black_24dp + @drawable/ic_kioskrecent_black_24dp @drawable/ic_channel_black_24dp @drawable/ic_bookmark_black_24dp @drawable/ic_playlist_add_black_24dp @@ -104,6 +106,8 @@ @drawable/ic_play_arrow_white_24dp @drawable/ic_settings_white_24dp @drawable/ic_whatshot_white_24dp + @drawable/ic_kiosklocal_white_24dp + @drawable/ic_kioskrecent_white_24dp @drawable/ic_channel_white_24dp @drawable/ic_bookmark_white_24dp @drawable/ic_playlist_add_white_24dp diff --git a/app/src/main/res/values/styles_services.xml b/app/src/main/res/values/styles_services.xml index 7ca9dacde..82eac26ab 100644 --- a/app/src/main/res/values/styles_services.xml +++ b/app/src/main/res/values/styles_services.xml @@ -28,4 +28,23 @@ @color/dark_soundcloud_accent_color + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index bd623f833..e998c96ab 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -18,6 +18,11 @@ android:summary="%s" android:title="@string/content_language_title"/> + + Date: Sun, 10 Mar 2019 17:30:21 +0530 Subject: [PATCH 004/270] init services in app onCreate --- app/src/main/java/org/schabi/newpipe/App.java | 5 +++-- app/src/main/java/org/schabi/newpipe/MainActivity.java | 3 --- .../org/schabi/newpipe/settings/ContentSettingsFragment.java | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 3ac2d5014..fed38f1eb 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -6,7 +6,6 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.Build; -import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.util.Log; @@ -23,12 +22,12 @@ import org.acra.config.ConfigurationBuilder; import org.acra.sender.ReportSenderFactory; import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.utils.Localization; import org.schabi.newpipe.report.AcraReportSenderFactory; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; import java.io.IOException; @@ -100,6 +99,8 @@ public class App extends Application { StateSaver.init(this); initNotificationChannel(); + ServiceHelper.initServices(this); + // Initialize image loader ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50)); diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index cdb948436..3821af896 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -95,8 +95,6 @@ public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); - ServiceHelper.initServices(this); - ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); super.onCreate(savedInstanceState); @@ -302,7 +300,6 @@ public class MainActivity extends AppCompatActivity { drawerItems.getMenu() .add(R.id.menu_services_group, s.getServiceId(), ORDER, title) .setIcon(ServiceHelper.getIcon(s.getServiceId())); - } drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index da6d372f4..801a55fd7 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -147,7 +147,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment { if (!url.startsWith("https://")) { Toast.makeText(getActivity(), "instance url should start with https://", Toast.LENGTH_SHORT).show(); - return false; } else { pEt.setSummary("fetching instance details.."); Disposable disposable = Single.fromCallable(() -> { @@ -174,8 +173,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { Toast.LENGTH_SHORT).show(); }); disposables.add(disposable); - return false; } + return false; }); } @@ -360,5 +359,4 @@ public class ContentSettingsFragment extends BasePreferenceFragment { ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); } - } From a8b5534838328f4e5c20dab48790411e2a7fcda9 Mon Sep 17 00:00:00 2001 From: Ritvik Saraf <13ritvik@gmail.com> Date: Sun, 10 Mar 2019 19:08:08 +0530 Subject: [PATCH 005/270] darker color for peertube --- app/src/main/res/values/colors_services.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values/colors_services.xml b/app/src/main/res/values/colors_services.xml index d2d759bc0..d38df003e 100644 --- a/app/src/main/res/values/colors_services.xml +++ b/app/src/main/res/values/colors_services.xml @@ -23,15 +23,15 @@ #ff9100 - #f97d46 - #c04e19 + #e65100 + #ac1900 #000000 - #f97d46 + #ff833a - #f97d46 - #c04e19 + #e65100 + #ac1900 #FFFFFF - #f97d46 + #ff833a #9e9e9e From 039a8e0b87eec61c9c6885818529fe7aaf04adbe Mon Sep 17 00:00:00 2001 From: yausername <13ritvik@gmail.com> Date: Sat, 23 Mar 2019 19:49:37 +0530 Subject: [PATCH 006/270] reordered services --- app/build.gradle | 2 +- app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 41c397d64..d037127d9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,7 +57,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.yausername:NewPipeExtractor:c220700' + implementation 'com.github.yausername:NewPipeExtractor:f60c973' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index 582f3590b..6a4f65bcb 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -27,9 +27,9 @@ public class ServiceHelper { case 1: return R.drawable.place_holder_cloud; case 2: - return R.drawable.place_holder_peertube; - case 3: return R.drawable.place_holder_gadse; + case 3: + return R.drawable.place_holder_peertube; default: return R.drawable.place_holder_circle; } From 7e3eb5e14d616d85245a5da7cfa7079d220446d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E7=A8=8B=E5=AE=87?= Date: Fri, 11 Oct 2019 14:00:08 +0000 Subject: [PATCH 007/270] Added translation using Weblate (Chinese (Simplified)) --- app/src/main/res/values-b+zh+HANS+CN/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-b+zh+HANS+CN/strings.xml diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 7d18eb8b023b56282a50242ef978d4aed97110e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E7=A8=8B=E5=AE=87?= Date: Fri, 11 Oct 2019 13:59:06 +0000 Subject: [PATCH 008/270] Translated using Weblate (Chinese (Hong Kong)) Currently translated at 28.0% (134 of 478 strings) --- app/src/main/res/values-zh-rHK/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index bb8551137..3294094dc 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -7,7 +7,7 @@ 下載 搜尋 設定 - 您是不是要查:%1$s? + 你是不是在寻找:%1$s? 選擇瀏覽器 旋轉 使用外置影片播放器 From 6f3d5e9fb8468f910aea1726495449c9f26292e6 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Wed, 9 Oct 2019 16:51:44 +0000 Subject: [PATCH 009/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8529d6af5..6a5e2769e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -33,7 +33,7 @@ Внешний вид Другое Миниатюра видео-превью - Миниатюра видео-превью + Воспроизвести видео, длительность: Миниатюра аватара пользователя Не понравилось Понравилось @@ -510,4 +510,5 @@ Удалить все позиции воспроизведения Удалить все позиции воспроизведения\? Измените папки загрузки для вступления в силу + Переключение сервисов, сейчас выбрано: \ No newline at end of file From 84a52342ab842f8b98dbba55643f88ba489383fa Mon Sep 17 00:00:00 2001 From: AntonAkovP Date: Thu, 10 Oct 2019 16:50:16 +0000 Subject: [PATCH 010/270] Translated using Weblate (Bulgarian) Currently translated at 80.5% (385 of 478 strings) --- app/src/main/res/values-bg/strings.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 5758bfb90..a1f7a3136 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -60,7 +60,7 @@ Показвай предложения за търсене История на търсенията Съхранявай заявките за търсене локално - История и кеш-памет + История на гледане Запаметявай кои видеота са гледани Възобнови при връщане на фокус Продължавай възпроизвеждането след прекъсване (например телефонно обаждане) @@ -254,7 +254,7 @@ Липсва стрийм плейър (можете да изтеглите VLC, за да пуснете стрийма). Изключете, за да спрете зареждането на всички миниатюри, спестявайки трафик и памет. При промяна на тази настройка, текущата кеш-памет на изображенията ще бъде изтрита. Показвай подсказка, когато е избран фонов режим или режим в прозорец на страницата с детайли на съответния клип - Изтрива историята на възпроизвежданите стриймове + Изтрива историята на възпроизвежданите стриймове и позицията на възпроизвеждането Не са намерени видео стриймове Не са намерени аудио стриймове Какво:\\nЗаявка:\\nЕзик на съдържанието:\\nУслуга:\\nВреме по GMT:\\nПакет:\\nВерсия:\\nОС версия: @@ -413,6 +413,11 @@ Автоматично пускане Коментари - + + Нов раздел + Избери раздел + Промени + Продължи възпроизвеждане + Изтрии данни \ No newline at end of file From d2e065d273afa7437a12747fc899c909443602b0 Mon Sep 17 00:00:00 2001 From: ssantos Date: Wed, 9 Oct 2019 14:53:54 +0000 Subject: [PATCH 011/270] Translated using Weblate (Portuguese) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-pt/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 8c6d6aa15..8b0a19319 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -33,7 +33,7 @@ Idioma do conteúdo predefinido Vídeo e áudio Miniatura de pré-visualização de vídeo - Miniatura de pré-visualização de vídeo + Reproduzir vídeo, duração: Miniatura do avatar do canal Não gostar Gostos @@ -505,4 +505,5 @@ Elimina todas as posições de reprodução Eliminar todas as posições de reprodução\? Alterar as pastas de descarregamento para que tenham efeito + Alternar serviço, agora selecionado: \ No newline at end of file From 4688b1fe23181429ccef62f0ab1f79f2537572f8 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Sat, 12 Oct 2019 04:54:55 +0000 Subject: [PATCH 012/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6a5e2769e..fb6c3f877 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -375,7 +375,7 @@ Титры Приложение для воспроизведения этого файла не установлено Изменить размер текста и стиль титров. Нужен перезапуск - Очистить историю просмотров + Очистить историю Удалить всю историю просмотров\? История просмотров удалена Очистить историю поиска From 884528927eca5ed96e700ac66ac06ba45e2db356 Mon Sep 17 00:00:00 2001 From: Haggai Eran Date: Sat, 12 Oct 2019 12:21:01 +0300 Subject: [PATCH 013/270] Fix Hebrew translation on some devices Hebrew on android uses two locale codes (iw and he). See details here: https://stackoverflow.com/a/8470980 For example, Nexus 7 (2013) uses iw, so it shows the English UI even when configuring the tablet to use Hebrew. Add a symbolic link from values-iw to values-he so both use the same strings. --- app/src/main/res/values-iw | 1 + 1 file changed, 1 insertion(+) create mode 120000 app/src/main/res/values-iw diff --git a/app/src/main/res/values-iw b/app/src/main/res/values-iw new file mode 120000 index 000000000..08adc17cc --- /dev/null +++ b/app/src/main/res/values-iw @@ -0,0 +1 @@ +values-he/ \ No newline at end of file From bd2b32bfbc790c29f3b93dd8f0c232975622aac7 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Mon, 14 Oct 2019 11:55:55 -0600 Subject: [PATCH 014/270] Fixed Playlists With No Uploader Crashing The App --- .../playlist/model/PlaylistRemoteEntity.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index bf446ca1f..c6a96ec13 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -16,6 +16,8 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.RE import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE; import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL; +import android.text.TextUtils; + @Entity(tableName = REMOTE_PLAYLIST_TABLE, indices = { @Index(value = {REMOTE_PLAYLIST_NAME}), @@ -72,10 +74,18 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { - return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && + boolean returnMe; + if (!TextUtils.isEmpty(getUploader())) { // If the playlist has an uploader + returnMe = getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && getThumbnailUrl().equals(info.getThumbnailUrl()) && getUploader().equals(info.getUploaderName()); + } else { // Else ignore uploader + returnMe = getServiceId() == info.getServiceId() && getName().equals(info.getName()) && + getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && + getThumbnailUrl().equals(info.getThumbnailUrl()); + } + return returnMe; } public long getUid() { From 3794002c7b77bd17ba4ece5d9b6545c88a2cc92a Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Mon, 14 Oct 2019 14:56:04 -0600 Subject: [PATCH 015/270] much simpler, "uploader" will never change on a playlist --- .../playlist/model/PlaylistRemoteEntity.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index c6a96ec13..e23b1cf20 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -74,18 +74,9 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { - boolean returnMe; - if (!TextUtils.isEmpty(getUploader())) { // If the playlist has an uploader - returnMe = getServiceId() == info.getServiceId() && getName().equals(info.getName()) && + return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && - getThumbnailUrl().equals(info.getThumbnailUrl()) && - getUploader().equals(info.getUploaderName()); - } else { // Else ignore uploader - returnMe = getServiceId() == info.getServiceId() && getName().equals(info.getName()) && - getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && - getThumbnailUrl().equals(info.getThumbnailUrl()); - } - return returnMe; + getThumbnailUrl().equals(info.getThumbnailUrl()); } public long getUid() { From cb5c219ffe0dc67b8868d30ac7c97e7d38abbb4f Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Mon, 14 Oct 2019 16:37:58 -0600 Subject: [PATCH 016/270] Added a restart song button to signle track expanded notification ... and some comments to the code --- .../schabi/newpipe/player/BackgroundPlayer.java | 14 ++++++++++++-- .../res/layout/player_notification_expanded.xml | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index ab07ded22..42ed546f1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -219,20 +219,30 @@ public final class BackgroundPlayer extends Service { remoteViews.setOnClickPendingIntent(R.id.notificationContent, PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)); - if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { + // Lets check if we are playing more than one song in the background + if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { // If we have more than one song + // Use track skiping for forward and back remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous); remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); - } else { + // We dont need a restart track button (skip track backwards) + remoteViews.setViewVisibility(R.id.notificationRestartTrack, View.INVISIBLE); + } else { // But if we only have one song + // Use time skipping for fastforward/rewind remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind); remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT)); + // Add a restart track button (skip track backwards) + remoteViews.setViewVisibility(R.id.notificationRestartTrack, View.VISIBLE); + remoteViews.setOnClickPendingIntent(R.id.notificationRestartTrack, + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); + } setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode()); diff --git a/app/src/main/res/layout/player_notification_expanded.xml b/app/src/main/res/layout/player_notification_expanded.xml index 090642303..d8ed96bf0 100644 --- a/app/src/main/res/layout/player_notification_expanded.xml +++ b/app/src/main/res/layout/player_notification_expanded.xml @@ -116,6 +116,22 @@ android:src="@drawable/ic_repeat_white" tools:ignore="ContentDescription"/> + + Date: Mon, 14 Oct 2019 16:41:38 -0600 Subject: [PATCH 017/270] Revert "Added a restart song button to signle track expanded notification" This reverts commit cb5c219ffe0dc67b8868d30ac7c97e7d38abbb4f. --- .../schabi/newpipe/player/BackgroundPlayer.java | 14 ++------------ .../res/layout/player_notification_expanded.xml | 16 ---------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 42ed546f1..ab07ded22 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -219,30 +219,20 @@ public final class BackgroundPlayer extends Service { remoteViews.setOnClickPendingIntent(R.id.notificationContent, PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)); - // Lets check if we are playing more than one song in the background - if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { // If we have more than one song - // Use track skiping for forward and back + if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous); remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); - // We dont need a restart track button (skip track backwards) - remoteViews.setViewVisibility(R.id.notificationRestartTrack, View.INVISIBLE); - } else { // But if we only have one song - // Use time skipping for fastforward/rewind + } else { remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind); remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT)); - // Add a restart track button (skip track backwards) - remoteViews.setViewVisibility(R.id.notificationRestartTrack, View.VISIBLE); - remoteViews.setOnClickPendingIntent(R.id.notificationRestartTrack, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); - } setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode()); diff --git a/app/src/main/res/layout/player_notification_expanded.xml b/app/src/main/res/layout/player_notification_expanded.xml index d8ed96bf0..090642303 100644 --- a/app/src/main/res/layout/player_notification_expanded.xml +++ b/app/src/main/res/layout/player_notification_expanded.xml @@ -116,22 +116,6 @@ android:src="@drawable/ic_repeat_white" tools:ignore="ContentDescription"/> - - Date: Mon, 14 Oct 2019 16:48:44 -0600 Subject: [PATCH 018/270] Explain What we did in the code itself --- .../database/playlist/model/PlaylistRemoteEntity.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index e23b1cf20..f57f8c5d9 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -74,6 +74,15 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { + // Returns boolean comparing the online playlist and the local copy. + // (False if info changed such as playlist name or track count) + // [Note that we don't check if the playlist creator changed: + // getUploader().equals(info.getUploaderName()) + // because this would crash the app on playlists that are auto-generated with no creator, + // and the creator can not change to my knowledge. + // if you need this functionality back please use + // getUploader() == info.getUploaderName() + // instead as it will work with blank names (Null value if I remember correctly). return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && getThumbnailUrl().equals(info.getThumbnailUrl()); From c93c52a58ce62b132f353eec8da5db7b0c009116 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Mon, 14 Oct 2019 16:55:16 -0600 Subject: [PATCH 019/270] Wrap in multiline comment so it colapses --- .../newpipe/database/playlist/model/PlaylistRemoteEntity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index f57f8c5d9..2fc6cfde7 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -74,6 +74,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { + /* // Returns boolean comparing the online playlist and the local copy. // (False if info changed such as playlist name or track count) // [Note that we don't check if the playlist creator changed: @@ -83,6 +84,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { // if you need this functionality back please use // getUploader() == info.getUploaderName() // instead as it will work with blank names (Null value if I remember correctly). + */ return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && getThumbnailUrl().equals(info.getThumbnailUrl()); From bfc987f81b482ecab1669c3c35f3bd5b5107f33f Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Mon, 14 Oct 2019 18:01:15 -0600 Subject: [PATCH 020/270] Revert "Revert "Added a restart song button to signle track expanded notification"" This reverts commit 646e327ed2972811c595cac6d0543baaad61e44b. --- .../schabi/newpipe/player/BackgroundPlayer.java | 14 ++++++++++++-- .../res/layout/player_notification_expanded.xml | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index ab07ded22..42ed546f1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -219,20 +219,30 @@ public final class BackgroundPlayer extends Service { remoteViews.setOnClickPendingIntent(R.id.notificationContent, PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)); - if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { + // Lets check if we are playing more than one song in the background + if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { // If we have more than one song + // Use track skiping for forward and back remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous); remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); - } else { + // We dont need a restart track button (skip track backwards) + remoteViews.setViewVisibility(R.id.notificationRestartTrack, View.INVISIBLE); + } else { // But if we only have one song + // Use time skipping for fastforward/rewind remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind); remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT)); + // Add a restart track button (skip track backwards) + remoteViews.setViewVisibility(R.id.notificationRestartTrack, View.VISIBLE); + remoteViews.setOnClickPendingIntent(R.id.notificationRestartTrack, + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); + } setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode()); diff --git a/app/src/main/res/layout/player_notification_expanded.xml b/app/src/main/res/layout/player_notification_expanded.xml index 090642303..d8ed96bf0 100644 --- a/app/src/main/res/layout/player_notification_expanded.xml +++ b/app/src/main/res/layout/player_notification_expanded.xml @@ -116,6 +116,22 @@ android:src="@drawable/ic_repeat_white" tools:ignore="ContentDescription"/> + + Date: Tue, 15 Oct 2019 07:18:06 -0600 Subject: [PATCH 021/270] Fixes Improved --- .../playlist/model/PlaylistRemoteEntity.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index 2fc6cfde7..d51267220 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -16,8 +16,6 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.RE import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE; import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL; -import android.text.TextUtils; - @Entity(tableName = REMOTE_PLAYLIST_TABLE, indices = { @Index(value = {REMOTE_PLAYLIST_NAME}), @@ -75,19 +73,18 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { /* - // Returns boolean comparing the online playlist and the local copy. - // (False if info changed such as playlist name or track count) - // [Note that we don't check if the playlist creator changed: - // getUploader().equals(info.getUploaderName()) - // because this would crash the app on playlists that are auto-generated with no creator, - // and the creator can not change to my knowledge. - // if you need this functionality back please use - // getUploader() == info.getUploaderName() - // instead as it will work with blank names (Null value if I remember correctly). + * Returns boolean comparing the online playlist and the local copy. + * (False if info changed such as playlist name or track count) + * [Note that + * getUploader().equals(info.getUploaderName()) + * crashes the app on playlists that are auto-generated with no creator, + * please use + * getUploader() == info.getUploaderName() + * instead as it will work with blank names (Null value if I remember correctly). */ return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && - getThumbnailUrl().equals(info.getThumbnailUrl()); + getThumbnailUrl().equals(info.getThumbnailUrl()) && getUploader() == info.getUploaderName(); } public long getUid() { From c68c35e0846cbecbbd1b35280d8647b2a4e74a23 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 15 Oct 2019 07:19:11 -0600 Subject: [PATCH 022/270] Better FIx, Improved Comment Style --- .../playlist/model/PlaylistRemoteEntity.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index 2fc6cfde7..a3efa20d5 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -16,8 +16,6 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.RE import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE; import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL; -import android.text.TextUtils; - @Entity(tableName = REMOTE_PLAYLIST_TABLE, indices = { @Index(value = {REMOTE_PLAYLIST_NAME}), @@ -75,19 +73,18 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { /* - // Returns boolean comparing the online playlist and the local copy. - // (False if info changed such as playlist name or track count) - // [Note that we don't check if the playlist creator changed: - // getUploader().equals(info.getUploaderName()) - // because this would crash the app on playlists that are auto-generated with no creator, - // and the creator can not change to my knowledge. - // if you need this functionality back please use - // getUploader() == info.getUploaderName() - // instead as it will work with blank names (Null value if I remember correctly). - */ + * Returns boolean comparing the online playlist and the local copy. + * (False if info changed such as playlist name or track count) + * [Note that + * getUploader().equals(info.getUploaderName()) + * crashes the app on playlists that are auto-generated with no creator, + * please use + * getUploader() == info.getUploaderName() + * instead as it will work with blank names (Null value if I remember correctly). + */ return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && - getThumbnailUrl().equals(info.getThumbnailUrl()); + getThumbnailUrl().equals(info.getThumbnailUrl()) && getUploader() == info.getUploaderName(); } public long getUid() { From 4106645d6e89a2fbff494e3db4c482cfa1ded8f1 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 15 Oct 2019 08:40:51 -0600 Subject: [PATCH 023/270] Polished Results --- .../playlist/model/PlaylistRemoteEntity.java | 27 +++++++++++++------ .../list/playlist/PlaylistFragment.java | 6 ++++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index a3efa20d5..e3b38851b 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -1,14 +1,19 @@ package org.schabi.newpipe.database.playlist.model; +import android.text.TextUtils; +import android.util.Log; + import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Ignore; import androidx.room.Index; import androidx.room.PrimaryKey; +import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.util.Constants; +import org.w3c.dom.Text; import static org.schabi.newpipe.database.LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM; import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_NAME; @@ -72,19 +77,25 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { + String TAG = "isIdenticalTo"; + boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); /* * Returns boolean comparing the online playlist and the local copy. * (False if info changed such as playlist name or track count) - * [Note that - * getUploader().equals(info.getUploaderName()) - * crashes the app on playlists that are auto-generated with no creator, - * please use - * getUploader() == info.getUploaderName() - * instead as it will work with blank names (Null value if I remember correctly). */ - return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && + boolean returnMe = true; + String uploaderAction = ""; + if (!TextUtils.isEmpty(getUploader()) || !TextUtils.isEmpty(info.getUploaderName())) { // We have an uploader, add it to the comparison + returnMe &= getUploader().equals(info.getUploaderName()); // Use .equals for uploader names + uploaderAction = "We compared uploaders: "+returnMe; + } else { + uploaderAction = "No Uploader"; + } + returnMe &= getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && - getThumbnailUrl().equals(info.getThumbnailUrl()) && getUploader() == info.getUploaderName(); + getThumbnailUrl().equals(info.getThumbnailUrl()); + if (DEBUG) Log.d(TAG, TAG+" Called With Result: "+returnMe+". And Uploader Action: "+uploaderAction); + return returnMe; } public long getUid() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 32b83bb22..c2defb0de 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -259,7 +259,8 @@ public class PlaylistFragment extends BaseListInfoFragment { animateView(headerRootLayout, true, 100); animateView(headerUploaderLayout, true, 300); headerUploaderLayout.setOnClickListener(null); - if (!TextUtils.isEmpty(result.getUploaderName())) { + if (!TextUtils.isEmpty(result.getUploaderName())) { // If we have an uploader : Put them into the ui + //headerUploaderLayout.setVisibility(View.VISIBLE); headerUploaderName.setText(result.getUploaderName()); if (!TextUtils.isEmpty(result.getUploaderUrl())) { headerUploaderLayout.setOnClickListener(v -> { @@ -273,6 +274,9 @@ public class PlaylistFragment extends BaseListInfoFragment { } }); } + } else { // Else : hide the uploader section + //headerUploaderLayout.setVisibility(View.INVISIBLE); + headerUploaderName.setText("Auto-Generated"); } playlistCtrl.setVisibility(View.VISIBLE); From 8c73253a52c3765ea54ffcaaa9406dfb84186d23 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 15 Oct 2019 08:48:36 -0600 Subject: [PATCH 024/270] follow stye on debug --- .../database/playlist/model/PlaylistRemoteEntity.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index e3b38851b..3c9085069 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -13,7 +13,6 @@ import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.util.Constants; -import org.w3c.dom.Text; import static org.schabi.newpipe.database.LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM; import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_NAME; @@ -87,14 +86,14 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { String uploaderAction = ""; if (!TextUtils.isEmpty(getUploader()) || !TextUtils.isEmpty(info.getUploaderName())) { // We have an uploader, add it to the comparison returnMe &= getUploader().equals(info.getUploaderName()); // Use .equals for uploader names - uploaderAction = "We compared uploaders: "+returnMe; + uploaderAction = "compared uploaders: "+returnMe; } else { - uploaderAction = "No Uploader"; + uploaderAction = "no uploader"; } returnMe &= getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && getThumbnailUrl().equals(info.getThumbnailUrl()); - if (DEBUG) Log.d(TAG, TAG+" Called With Result: "+returnMe+". And Uploader Action: "+uploaderAction); + if (DEBUG) Log.d(TAG, TAG+"() called with result: returnMe = "+returnMe+". and uploaderAction: "+uploaderAction); return returnMe; } From cc1e5edaec8784e56f69ba8a110a39909a8803b5 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 15 Oct 2019 08:52:51 -0600 Subject: [PATCH 025/270] Revert "Merge branch 'dev-all-changes' into dev" This reverts commit f6060261a194d8744e73ee4c0c9d272d7198ec0c, reversing changes made to 8c73253a52c3765ea54ffcaaa9406dfb84186d23. --- .../schabi/newpipe/player/BackgroundPlayer.java | 14 ++------------ .../res/layout/player_notification_expanded.xml | 16 ---------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 42ed546f1..ab07ded22 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -219,30 +219,20 @@ public final class BackgroundPlayer extends Service { remoteViews.setOnClickPendingIntent(R.id.notificationContent, PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)); - // Lets check if we are playing more than one song in the background - if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { // If we have more than one song - // Use track skiping for forward and back + if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous); remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); - // We dont need a restart track button (skip track backwards) - remoteViews.setViewVisibility(R.id.notificationRestartTrack, View.INVISIBLE); - } else { // But if we only have one song - // Use time skipping for fastforward/rewind + } else { remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind); remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT)); - // Add a restart track button (skip track backwards) - remoteViews.setViewVisibility(R.id.notificationRestartTrack, View.VISIBLE); - remoteViews.setOnClickPendingIntent(R.id.notificationRestartTrack, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); - } setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode()); diff --git a/app/src/main/res/layout/player_notification_expanded.xml b/app/src/main/res/layout/player_notification_expanded.xml index d8ed96bf0..090642303 100644 --- a/app/src/main/res/layout/player_notification_expanded.xml +++ b/app/src/main/res/layout/player_notification_expanded.xml @@ -116,22 +116,6 @@ android:src="@drawable/ic_repeat_white" tools:ignore="ContentDescription"/> - - Date: Tue, 15 Oct 2019 09:01:30 -0600 Subject: [PATCH 026/270] describe what "Auto-Generated" Means --- .../newpipe/fragments/list/playlist/PlaylistFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index c2defb0de..3bd03508b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -276,7 +276,7 @@ public class PlaylistFragment extends BaseListInfoFragment { } } else { // Else : hide the uploader section //headerUploaderLayout.setVisibility(View.INVISIBLE); - headerUploaderName.setText("Auto-Generated"); + headerUploaderName.setText("Auto-Generated (no uploader found)"); } playlistCtrl.setVisibility(View.VISIBLE); From df2bb228c5ed6a7d5d39c8f342865c3578c81614 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 15 Oct 2019 09:11:04 -0600 Subject: [PATCH 027/270] Much Simpler Fix --- .../database/playlist/model/PlaylistRemoteEntity.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index 3c9085069..4a7ff06d2 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -83,17 +83,10 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { * (False if info changed such as playlist name or track count) */ boolean returnMe = true; - String uploaderAction = ""; - if (!TextUtils.isEmpty(getUploader()) || !TextUtils.isEmpty(info.getUploaderName())) { // We have an uploader, add it to the comparison - returnMe &= getUploader().equals(info.getUploaderName()); // Use .equals for uploader names - uploaderAction = "compared uploaders: "+returnMe; - } else { - uploaderAction = "no uploader"; - } returnMe &= getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && - getThumbnailUrl().equals(info.getThumbnailUrl()); - if (DEBUG) Log.d(TAG, TAG+"() called with result: returnMe = "+returnMe+". and uploaderAction: "+uploaderAction); + getThumbnailUrl().equals(info.getThumbnailUrl()) && TextUtils.equals(getUploader(), info.getUploaderName()); + if (DEBUG) Log.d(TAG, TAG+"() called with result: returnMe = "+returnMe); return returnMe; } From 26a1b1377dc3ea878e2862ceeac8a009e016f3f2 Mon Sep 17 00:00:00 2001 From: Tushar Pandey Date: Tue, 15 Oct 2019 15:13:04 +0000 Subject: [PATCH 028/270] Added translation using Weblate (Pirate) --- app/src/main/res/values-pr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-pr/strings.xml diff --git a/app/src/main/res/values-pr/strings.xml b/app/src/main/res/values-pr/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-pr/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 9c00e7f45cd00330599bbec191cc5f5288dd4032 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 15 Oct 2019 12:49:24 -0600 Subject: [PATCH 029/270] Using Strings still need to find out why its null on the library --- .../newpipe/fragments/list/playlist/PlaylistFragment.java | 2 +- app/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 3bd03508b..38ae88efa 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -276,7 +276,7 @@ public class PlaylistFragment extends BaseListInfoFragment { } } else { // Else : hide the uploader section //headerUploaderLayout.setVisibility(View.INVISIBLE); - headerUploaderName.setText("Auto-Generated (no uploader found)"); + headerUploaderName.setText(R.string.playlist_no_uploader); } playlistCtrl.setVisibility(View.VISIBLE); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb7adfe75..70bd9e0a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -428,6 +428,7 @@ Playlisted Playlist thumbnail changed. Could not delete playlist. + Auto-Generated (no uploader found) No Captions Fit From 797e1a105d7f40d5b9a9c613df049c670a16c741 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 15 Oct 2019 14:02:37 -0600 Subject: [PATCH 030/270] Comment out debuging code --- .../playlist/model/PlaylistRemoteEntity.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index 4a7ff06d2..1a1474f80 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -76,18 +76,18 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { - String TAG = "isIdenticalTo"; - boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); + //String TAG = "isIdenticalTo"; + //boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); /* * Returns boolean comparing the online playlist and the local copy. * (False if info changed such as playlist name or track count) */ - boolean returnMe = true; - returnMe &= getServiceId() == info.getServiceId() && getName().equals(info.getName()) && + //boolean returnMe = true; + return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && getThumbnailUrl().equals(info.getThumbnailUrl()) && TextUtils.equals(getUploader(), info.getUploaderName()); - if (DEBUG) Log.d(TAG, TAG+"() called with result: returnMe = "+returnMe); - return returnMe; + //if (DEBUG) Log.d(TAG, TAG+"() called with result: returnMe = "+returnMe); + //return returnMe; } public long getUid() { From ad2de3a828707ef36ddbb355569bc491ce2f2869 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 15 Oct 2019 17:22:17 -0600 Subject: [PATCH 031/270] only use TextUtils.equals (fixes more crashes) --- .../database/playlist/model/PlaylistRemoteEntity.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index 1a1474f80..75515b33a 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -83,9 +83,12 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { * (False if info changed such as playlist name or track count) */ //boolean returnMe = true; - return getServiceId() == info.getServiceId() && getName().equals(info.getName()) && - getStreamCount() == info.getStreamCount() && getUrl().equals(info.getUrl()) && - getThumbnailUrl().equals(info.getThumbnailUrl()) && TextUtils.equals(getUploader(), info.getUploaderName()); + return getServiceId() == info.getServiceId() + && getStreamCount() == info.getStreamCount() + && TextUtils.equals(getName(), info.getName()) + && TextUtils.equals(getUrl(), info.getUrl()) + && TextUtils.equals(getThumbnailUrl(), info.getThumbnailUrl()) + && TextUtils.equals(getUploader(), info.getUploaderName()); //if (DEBUG) Log.d(TAG, TAG+"() called with result: returnMe = "+returnMe); //return returnMe; } From 284228ef16a1ec8d26c8333f6f83f997694f5d6d Mon Sep 17 00:00:00 2001 From: Oguz Ersen Date: Fri, 11 Oct 2019 18:24:37 +0000 Subject: [PATCH 032/270] Translated using Weblate (Turkish) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-tr/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1d5a3fba6..bb52d95d2 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -503,4 +503,8 @@ Tüm oynatım konumları silinsin mi\? Etkili olması için indirme dizinlerini değiştirin Hizmeti değiştir, şu anda seçili olan: + + Videolar + + \ No newline at end of file From 19ba37dfad177f2416ce0fabc2dca075038f9c92 Mon Sep 17 00:00:00 2001 From: Daniele Lira Mereb Date: Mon, 14 Oct 2019 18:06:17 +0000 Subject: [PATCH 033/270] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-pt-rBR/strings.xml | 32 ++++++++++++---------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 109d173b3..d5ecfe56f 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -7,7 +7,7 @@ Informações: %1$s visualizações Reproduzir - Mostra vídeo com restrições de idade. Permissão para vídeos com essa restrição pode ser dada no menu Configurações. + Contém vídeo com restrição de idade. É possível permitir vídeos com essa restrição em Configurações > Conteúdo. Vídeo Reproduz um vídeo quando o NewPipe for aberto a partir de outro aplicativo Reprodução automática @@ -80,13 +80,13 @@ A interface do aplicativo parou Reproduzindo em segundo plano Não foi possível configurar o menu de download - Capa do vídeo + Reproduzir vídeo, duração: Miniatura do usuário que enviou o vídeo - Informe a pasta de download de áudios - Pasta para salvar áudios baixados + Escolha a pasta de download para áudios + Os áudios baixados serão salvos aqui Pasta para áudios baixados - Informe a pasta de download de vídeos - Pasta para salvar vídeos baixados + Escolha a pasta de download para vídeos + Os vídeos baixados serão salvos aqui Pasta para vídeos baixados Aplicativo Kore não instalado. Instalar? Não foi possível interpretar completamente o site @@ -122,9 +122,9 @@ Sim Depois Desativado - k + M M - G + B Essa permissão é necessária para abrir em modo popup Modo de popup NewPipe @@ -177,7 +177,7 @@ abrir em modo popup Ative para continuar reproduzindo depois de interrupções (por exemplo: ligações) Histórico de pesquisas Armazena histórico de pesquisas feitas - Histórico de visualizações + Histórico de assistidos Armazena histórico de vídeos assistidos Histórico Pesquisado @@ -238,7 +238,7 @@ abrir em modo popup Remover Detalhes Configurações de áudio - Mantenha pressionado para colocar na fila + Segure para adicionar à fila [Desconhecido] Adicionar à fila em segundo plano Adicionar à fila em novo popup @@ -287,7 +287,7 @@ abrir em modo popup Excluir todos Dispensar Renomear - Deseja apagar este item do seu histórico de visualizações? + Deseja apagar este item do seu histórico de assistidos\? Tem certeza que deseja apagar todos itens do histórico? Reproduzido anteriormente Mais reproduzido @@ -318,7 +318,7 @@ abrir em modo popup Usar pesquisa rápida A pesquisa rápida permite que o player procure resultados mais rapidamente porém com precisão reduzida Adicionar o próximo vídeo à fila automaticamente - Adicionar automaticamente um vídeo relacionado ao último vídeo reproduzido quando a repetição estiver desativada + Adicionar automaticamente um vídeo relacionado ao último da lista quando a repetição estiver desativada Arquivo Pasta não encontrada Origem do arquivo/conteúdo não encontrada @@ -376,10 +376,10 @@ abrir em modo popup Legendas Altere o tamanho da legenda e o estilo da tela de fundo. É necessário reiniciar o aplicativo para ter efeito. Nenhum player instalado para reproduzir este arquivo - Limpar histórico de reproduções + Limpar histórico de assistidos Apaga o histórico de vídeos assistidos e a lista de reprodução - Apagar todo o histórico de reproduções\? - Histórico de reproduções limpo. + Apagar todo o histórico de assistidos\? + Histórico de assistidos limpo. Limpar histórico de pesquisas Apaga o histórico de pesquisas feitas Apagar todo o histórico de pesquisas\? @@ -513,4 +513,6 @@ abrir em modo popup Apagar lista de reprodução Deletar todo o histórico de reprodução Deletar todo o histórico de reprodução\? + Mude as pastas de download para surtir efeito + Alterar serviço, selecionados: \ No newline at end of file From 701207dcc55f57f1433c667ef645f8db5d43092a Mon Sep 17 00:00:00 2001 From: Enol P Date: Sun, 13 Oct 2019 01:04:46 +0000 Subject: [PATCH 034/270] Translated using Weblate (Asturian) Currently translated at 38.9% (186 of 478 strings) --- app/src/main/res/values-b+ast/strings.xml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml index d38f5f9e0..b301e949f 100644 --- a/app/src/main/res/values-b+ast/strings.xml +++ b/app/src/main/res/values-b+ast/strings.xml @@ -195,7 +195,7 @@ Ver en GitHub Llicencia de NewPipe - + Si sabes traducir, quies encuriosar el códigu, amestar carauteríques o proponer cambeos nel diseñu, vamos agradecételo siempres. ¡Cuánto más, meyor! Lleer la llicencia Collaboración Historial @@ -254,10 +254,10 @@ - - + Importar una base de datos + Esportar la base de datos Anula l\'historial y les soscripciones actuales - + Esporta l\'historial, les soscripciones y les llistes de reproducción. URL nun ye válida @@ -354,8 +354,14 @@ Sotítulos Aceutar ¿Quies reafitar los valores\? - + El sirvidor nun aceuta descargues multifilu, volvi probar con @string/msg_threads = 1 Nun hai comentarios Llimpieza de datos + + Vídeos + + + Amosar comentarios + Toca p\'alternar la so des/activación \ No newline at end of file From 834f599fb1c0b8242b9cc69bb47131bc0bd1f9c4 Mon Sep 17 00:00:00 2001 From: naofum Date: Mon, 14 Oct 2019 04:01:49 +0000 Subject: [PATCH 035/270] Translated using Weblate (Japanese) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ja/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 8057e9f48..481fa30be 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -499,4 +499,7 @@ すべての再生位置を削除しますか\? ダウンロードフォルダーを変更して有効にします サービスの切り替え、現在の選択: + + 動画 + \ No newline at end of file From fab692c38631edbb9ddec1ff2aa37a0970dcb3c4 Mon Sep 17 00:00:00 2001 From: zeritti Date: Sat, 12 Oct 2019 11:05:14 +0000 Subject: [PATCH 036/270] Translated using Weblate (Czech) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-cs/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 0a760af40..d539923fe 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -49,7 +49,7 @@ Nebylo možné analyzovat stránku Obsah není k dispozici Náhled videa - Náhled videa + Přehrát video, délka: Náhled avataru uploadera To se mi líbí To se mi nelíbí @@ -514,4 +514,5 @@ otevření ve vyskakovacím okně Smazat všechny pozice playbacku Smazat všechny pozice playbacku\? Změnit adresář pro stažené soubory + Přepnout službu, právě vybráno: \ No newline at end of file From c3433baf0cbc060d4c1fae620bddbea9feece896 Mon Sep 17 00:00:00 2001 From: WaldiS Date: Fri, 11 Oct 2019 20:05:50 +0000 Subject: [PATCH 037/270] Translated using Weblate (Polish) Currently translated at 99.8% (477 of 478 strings) --- app/src/main/res/values-pl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a899b342e..ea966390d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -72,7 +72,7 @@ Twój komentarz (po angielsku): Szczegóły: Miniatura filmu - Miniatura filmu + Odtwarzane wideo, czas trwania: Miniatura zdjęcia wysyłającego Polubienia Łapka w dół From b1396b98a3b8b08b3fa528e1c2162011f8855105 Mon Sep 17 00:00:00 2001 From: zmni Date: Tue, 15 Oct 2019 10:32:22 +0000 Subject: [PATCH 038/270] Translated using Weblate (Indonesian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-id/strings.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 0bbbbbe9e..e17fcd575 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -68,7 +68,7 @@ Komentar anda (dalam bahasa Inggris): Detail: Thumbnail pratinjau video - Thumbnail pratinjau video + Putar video, durasi: Suka Thumbnail avatar pengunggah Tidak suka @@ -111,7 +111,7 @@ Hitam Semua Channel - r + R J T Ya @@ -499,4 +499,8 @@ Hapus semua posisi pemutaran Hapus semua posisi pemutaran\? Ubah folder unduhan + + Video + + Aktif/Nonaktifkan layanan, saat ini aktif: \ No newline at end of file From 9a47714645fb4a7fa41e1fec89a29efccc24326b Mon Sep 17 00:00:00 2001 From: Nathan Date: Sat, 12 Oct 2019 15:57:59 +0000 Subject: [PATCH 039/270] Translated using Weblate (French) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-fr/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8506b6511..266d53eae 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -31,7 +31,7 @@ Vidéo & audio Autre Miniature d’aperçu vidéo - Miniature d’aperçu vidéo + Lecture vidéo, durée : Je n’aime pas J’aime Langue du contenu par défaut @@ -506,4 +506,5 @@ Supprimer toutes les positions de reprise Voulez-vous supprimer toutes les positions de reprise \? Changez les dossier de téléchargement pour activer + Activer/Désactiver le service, actuellement sélectionné : \ No newline at end of file From 8849ddab81792761b0814175f7647c785bee54ac Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 12 Oct 2019 03:45:25 +0000 Subject: [PATCH 040/270] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 7e7eaf1c4..d49054f5e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -499,4 +499,7 @@ 刪除所有播放位置? 變更下載資料夾以使其生效 切換服務,目前已選取: + + 影片 + \ No newline at end of file From 95f44cbe9a82dcfc7f28ffc9326f158a599a7df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E7=A8=8B=E5=AE=87?= Date: Fri, 11 Oct 2019 14:01:39 +0000 Subject: [PATCH 041/270] Translated using Weblate (Chinese (Simplified)) Currently translated at 1.7% (8 of 478 strings) --- app/src/main/res/values-b+zh+HANS+CN/strings.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index a6b3daec9..70c0fbdec 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -1,2 +1,11 @@ - \ No newline at end of file + + 点击搜索按钮即可开始使用 + %1$s 次观看 + 发布于 %1$s + 在浏览器中打开 + 在悬浮窗模式下打开 + 您是不是要找:%1$s? + 找不到串流播放器 (您可以安裝並使用VLC播放)。 + 下载媒体文件 + \ No newline at end of file From 87378fc79c0f89f06a4cb335389f3ce2dde6fb71 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Wed, 16 Oct 2019 19:56:31 -0600 Subject: [PATCH 042/270] Fixed library showing null --- .../database/playlist/model/PlaylistRemoteEntity.java | 7 ------- .../newpipe/local/holder/RemotePlaylistItemHolder.java | 5 +++++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index 75515b33a..fa257cfed 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.database.playlist.model; import android.text.TextUtils; -import android.util.Log; import androidx.room.ColumnInfo; import androidx.room.Entity; @@ -9,7 +8,6 @@ import androidx.room.Ignore; import androidx.room.Index; import androidx.room.PrimaryKey; -import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.util.Constants; @@ -76,21 +74,16 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @Ignore public boolean isIdenticalTo(final PlaylistInfo info) { - //String TAG = "isIdenticalTo"; - //boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); /* * Returns boolean comparing the online playlist and the local copy. * (False if info changed such as playlist name or track count) */ - //boolean returnMe = true; return getServiceId() == info.getServiceId() && getStreamCount() == info.getStreamCount() && TextUtils.equals(getName(), info.getName()) && TextUtils.equals(getUrl(), info.getUrl()) && TextUtils.equals(getThumbnailUrl(), info.getThumbnailUrl()) && TextUtils.equals(getUploader(), info.getUploaderName()); - //if (DEBUG) Log.d(TAG, TAG+"() called with result: returnMe = "+returnMe); - //return returnMe; } public long getUid() { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index f9542850e..b30a6230d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -28,8 +28,13 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { itemTitleView.setText(item.getName()); itemStreamCountView.setText(String.valueOf(item.getStreamCount())); + // Here is where the uploader name is set in the bookmarked playlists library itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), NewPipe.getNameOfService(item.getServiceId()))); + if (item.getUploader() == null) { + itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId())); + } + itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView, ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); From 8f46757c0dda80ca32ac15381dbf65bc367ef171 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Wed, 16 Oct 2019 20:32:12 -0600 Subject: [PATCH 043/270] New no uploader, still needs a way to programaticly switch preferably they can both be the same file and modified at runtime --- app/src/main/res/layout/playlist_header.xml | 34 +++---- .../layout/playlist_header_no_uploader.xml | 94 +++++++++++++++++++ 2 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 app/src/main/res/layout/playlist_header_no_uploader.xml diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml index f49ca295d..93fde3b8f 100644 --- a/app/src/main/res/layout/playlist_header.xml +++ b/app/src/main/res/layout/playlist_header.xml @@ -33,8 +33,6 @@ android:layout_marginLeft="4dp" android:layout_marginRight="6dp" android:layout_marginTop="6dp" - android:layout_toLeftOf="@+id/playlist_stream_count" - android:layout_toStartOf="@+id/playlist_stream_count" android:background="?attr/selectableItemBackground" android:gravity="left|center_vertical" android:padding="2dp" @@ -65,23 +63,25 @@ tools:ignore="RtlHardcoded" tools:text="Typical uploader name"/> - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From d5b1dceb66c432cf13c501e7df4d9440881924bd Mon Sep 17 00:00:00 2001 From: Goudarz Jafari Date: Thu, 17 Oct 2019 18:22:31 +0000 Subject: [PATCH 044/270] Translated using Weblate (Persian) Currently translated at 98.5% (471 of 478 strings) --- app/src/main/res/values-fa/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 55fa53c61..836c55e63 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -71,7 +71,7 @@ توضیح شما (به انگلیسی): جزییات: بندانگشتی پیش‌نمایش ویدیو - بندانگشتی پیش‌نمایش ویدیو + پخش ویدیو، مدت زمان: بندانگشتی کاربر بارگذاری کننده پسندها نپسندیدن‌ها @@ -496,4 +496,7 @@ گزارش کردن خطاهای \"خارج از چرخه حیات\" حذف حافظه کش شده همه صفحات وب کوچک کردن پخش کننده به پس زمینه + آی‌دی شما، soundcloud.com/yourid + عملکرد هنگام تغییر به برنامه دیگر از پخش‌کننده اصلی فیلم — %s + آهنگ‌ها \ No newline at end of file From b4449e4998407c0616724019b0828b85ecf875bb Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Fri, 18 Oct 2019 22:02:18 +0000 Subject: [PATCH 045/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fb6c3f877..3f46894de 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -132,7 +132,7 @@ Изменение размера Убирает звук в НЕКОТОРЫХ разрешениях млн - млрд. + млрд тыс. Разрешение очереди всплывающего окна Помнить последние размер и позицию всплывающего окна @@ -378,8 +378,8 @@ Очистить историю Удалить всю историю просмотров\? История просмотров удалена - Очистить историю поиска - Удалить историю запросов поиска + Очистить запросы поиска + Удалить все запросы поиска Удалить историю воспроизведённых потоков и позиции воспроизведения Удалить всю историю поиска\? История поиска удалена @@ -387,7 +387,7 @@ NewPipe — свободное программное обеспечение: вы можете использовать, изучать и улучшать его по своему усмотрению. В частности, вы можете распространять и/или изменять его в соответствии с условиями GNU General Public License, опубликованной Free Software Foundation, либо версии 3, либо (по вашему выбору) любой более поздней версии. При открытии ссылки Хотите импортировать настройки? - Политика конфиденциальности NewPipe + Политика конфиденциальности Проект NewPipe очень серьёзно относится к вашей конфиденциальности. Поэтому приложение не собирает никаких данных без вашего согласия. \nПолитика конфиденциальности NewPipe подробно объясняет, какие данные отправляются и хранятся при отправке отчёта о сбоях. Прочитать политику From 4579fa52acd794cb35c46063c459d1b9929b7c51 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Fri, 18 Oct 2019 22:11:36 +0000 Subject: [PATCH 046/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3f46894de..a3926b7a8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -132,7 +132,7 @@ Изменение размера Убирает звук в НЕКОТОРЫХ разрешениях млн - млрд + \\u0020млрд тыс. Разрешение очереди всплывающего окна Помнить последние размер и позицию всплывающего окна From b358231b20c04c4ba39e80dc4126280172e59f6b Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Fri, 18 Oct 2019 22:16:28 +0000 Subject: [PATCH 047/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a3926b7a8..7d89cf1a3 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -131,9 +131,9 @@ Отключено Изменение размера Убирает звук в НЕКОТОРЫХ разрешениях - млн + \\u0020млн \\u0020млрд - тыс. + \\u0020тыс. Разрешение очереди всплывающего окна Помнить последние размер и позицию всплывающего окна Поисковые предложения From 55a82b631e8f64b31c808627d69b6f817736257b Mon Sep 17 00:00:00 2001 From: ozyc Date: Sat, 19 Oct 2019 10:29:52 +0000 Subject: [PATCH 048/270] Translated using Weblate (Esperanto) Currently translated at 58.4% (279 of 478 strings) --- app/src/main/res/values-eo/strings.xml | 272 +++++++++++++++++++++---- 1 file changed, 236 insertions(+), 36 deletions(-) diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 42d46cb95..5f84236fb 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -4,59 +4,59 @@ Eldonita je %1$s Instali Nuligi - Malfermi per retumilo + Malfermi per krozilo Konigi Elŝuti Serĉi Agordoj - Ĉu vi intencis: %1$s? + Ĉu vi signifis: %1$s\? Konigi kun - Elekti retumilon + Elekti krozilon turno - Uzi eksteran videoludilon + Uzi eksteran filmetoludilon Uzi eksteran sonludilon Defaŭlta distingivo - Ludi per Kodi - Montri \"Ludi per Kodi\"-opcion + Legi per Kodi + Montri \"Legi per Kodi\"-opcion Sono Defaŭlta sondosierformo Etoso Malluma Luma Elŝuti - Sekva video + Vica filmeto Ligilo ne subtenita Preferata enhavlingvo - Video kaj sono + Filmeto kaj sono Apero Alia Ludado fone - Ludi + Legi Eraro Reteraro - Enhavo ne estas disponebla + Enhavo malhavebla Ŝatoj Malŝatoj Uzi la programon Tor - Neniu elsendlflua ludilo trovita. Ĉu instali la aplikaĵon VLC? + Neniu elsendlflua ludilo trovita. Ĉu vi volas instali la aplikaĵon VLC\? La aplikaĵo Kore ne estas trovita. Ĉu instali ĝin? - Montri la sekvan videon kaj similajn videojn + Montri la sekvan filmeton kaj similajn filmetojn Ĉiuj miniaturoj ne ŝargeblas - La subskribo de la ligilo de la video ne malĉifreblas + La subskribo de la ligilo de la filmeto ne malĉifreblas La retejo ne analizeblas - Miniaturo de la antaŭrigardo de la video - Miniaturo de la antaŭrigardo de la video + Miniaturo de la antaŭrigardo de la filmeto + Legi filmeton, daŭro: Miniaturo de la bildo de la alŝutinto La elŝutujo \'%1$s\' ne kreeblas Elŝutujo \'%1$s\' kreita - Elŝutujo por videoj + Elŝutujo por filmetoj Elŝutujo por muziko - (Eksperimenta) Devigi elŝuttrafikon tra Tor por pli bona privateco (elsendfluaj videoj estas ankoraŭ ne subtenitaj). - Montri opcion por ludi videon per la aplikaĵo Kodi - Dosierujo por konservi elŝutitajn videojn - Dosierujo por konservi elŝutitan muzikon - Elektu lokon por konservi elŝutitajn videojn - Elektu lokon por konservi elŝutitan muzikon + (Eksperimenta) Devigi elŝuttrafikon tra Tor por pli bona privateco (elsendfluaj filmetoj estas ankoraŭ ne subtenitaj). + Montri opcion por ludi filmeton per la aplikaĵo Kodi + Elŝutitaj filmetoj estas konservitaj tie + Dosierujo por konservi elŝutitajn muzikojn + Elektu lokon por konservi elŝutitajn filmetojn + Elektu lokon por konservi elŝutitajn muzikojn Enhavo Raporti eraron per retpoŝto RAPORTI @@ -64,37 +64,237 @@ Via komento (en la angla): Detaloj: Raporti eraron - Video + Filmeto Reprovi - Tapu serĉo por komenci + Premu serĉo por komenci Neniu elsendlflua ludilo trovita (instalu VLC por ludi ĝin). Malfermi en ŝprucfenestron modon - Iuj rezolucioj NE havos aŭdion kiam ĉi tiu eblo estas ebligita + Forigas aŭdion ĉe KELKAJ distingivoj NewPipe ŝprucfenestron modon - Aboni + Abonu Abonita Kanalo malabonita - Neebla ŝanĝi abonon + Ne povis ŝanĝi abonon Elŝutu dosieron - Ne eblas ĝisdatigi abonon - Montri informon + Ne povis ĝisdatigi abonon + Montri informojn Ĉefa Abonoj Legosigno Kio novas Fono - ŝprucfenestron + Ŝprucfenestro Aldonu al Aŭtomata play - Ludas video kiam NewPipe vokas de alia programo - Defaŭlta popup rezolucio - Montri pli altajn rezoluciojn - Nur kelkaj aparatoj subtenas ludante 2K / 4K filmetojn - Defaŭlta video-formato + Legas filmeton kiam NewPipe vokas de alia programo + Defaŭlta distingivo de la ŝprucfenestro + Montri pli altajn distingivojn + Nur kelkaj aparatoj subtenas legante 2K / 4K filmetojn + Defaŭlta fomato de filmeto Nigra Memoru ŝprucfenestron kaj pozicion Memoru lastan grandecon kaj pozicion de ŝprucfenestro Uzu rapide, ne preciza serĉon Ne preciza serĉo permesas al la ludanto serĉi poziciojn pli rapide kun malalta precizeco Ŝarĝi bildetojn - + Ne povis konstrui la dosierujon de elŝuto + Nunaj filmetoj ne estas ankoraŭ subtenataj + Enhavo limigita al aĝo + Montri limigitan al aĝo filmeto. Permesanta tian materialon eblas el Parametroj. + Ne povis tute analizi la retejon + Ne povis akiri ajnan torenton + NUNA + Elŝutoj + Elŝutoj + Raporto de eraro + Ne povis ŝarĝi bildon + Apo kraŝis + reCAPTCHA + reCAPTCHA defio + reCAPTCHA defio petita + Ĉiuj + Kanalo + + Filmetoj + + + Jes + Poste + Tiu permeso estas necesa por +\nmalfermi en ŝprucfenestro modo + Leganta en ŝprucfenestro modo + Malaktiva + Filtri + Aktualigi + Forviŝi + Ŝprucfenestro + Regrandiganta + Kontrolo de gesto de ludilo + Uzu gestojn por kontroli la brilon kaj volumenon de la ludilo + Serĉi sugestojn + Montri sugestojn kiam serĉanto + Plej bona distingivo + Libera malpeza torentado ĉe Android. + Elŝuti + Leteroj kaj ciferoj + Plej specialaj karakteroj + Rekomencu en fokusa gajno + Daŭrigi la legon post la interrompaĵoj (ekzemple telefonadoj) + Serĉa historio + Konservi la historio de serĉo lokale + Rigardu historion + Spuri la viditajn filmetojn + Newpipe Sciifo + Sciigoj por NewPipe fono kaj ŝprucfenestroj ludiloj + Ludilo + Konduto + Historio kaj kaŝmemoro + Ludlisto + Malfari + Kiosko + Tendencoj + Supro 50 + Nova & varma + Montri la indiko « Tenu por aldoni » + Montri indikon kiam la fono aŭ ŝprucfenestro butono estas premita en la retpaĝo de dalatadoj de la filmeto + Viciĝita en la ludilo en fono + Viciĝita en ŝprucfenestro ludilo + Ludi ĉiuj + Ne povis ludi tion torenton + Neatendebla eraro de ludilo okazis + Reakiri el eraro de la ludilo + Fona ludilo + Ŝprucfenestro ludilo + Retiri + Detalado + Sonaj parametroj + Teni por viciĝi + [Nekonata] + Viciĝi en la fono + Viciĝi en nova ŝprucfenestro + Komencu ludi en nova ŝprucfenestro + Defaŭlta enhava lando + Ŝangi Orientiĝon + Ŝanĝi al Fono + Ŝanĝi al ŝprucfenestro + Ŝangi al Ĉefa + Servo + Ĉiam + Nur unfoje + Nevalida ligilo + Neniuj filmeta torentoj trovitaj + Neniuj sonaj torentoj trovis + Ŝprucfenestro ludilo + Importi la datumbazon + Eksporti la datumbazon + Anstataŭigas vian aktualan historion kaj abonojn + Eksporti historion, abonojn kaj ludlistoj + Ĉiam peti + Nova ludlisto + Forigi + Alinomi + Nomo + Aldoni al la ludlisto + Meti kiel bildeto de ludlisto + Legosigno Ludlisto + Forigi Legosignon + Ĉu forigi ĉi tiun ludliston \? + Ludlisto kreita + Ludlistita + Bildeto de ludlisto ŝanĝiĝita. + Ne povis forigi ludlisto. + Sencimigi + Auto-vico sekva fluo + Aŭto-aldoni rilatan enhavon kiam leganta la lasta enhavo en malrepetita atendovico + Dosiero + Tia dosierujo ne ekzistas + Tia dosiero/enhavo ne ekzistas + Dosiernomo ne povas esti malplena + Eraro okazis : %1$s + Importu Jutubajn abonaĵojn per elŝuti la dosieron de eksporto : +\n +\n1. Iru ĉe tie retpaĝo : %1$s +\n2. Ensalutu kiam oni petas vin +\n3. Elŝuto devus komenci (ĝi estas la dosiero de eksporto) + Importu Soundcloud-n profilon per elŝuti la dosieron de eksporto : +\n +\n1. Ebligu komputilon modon en krozilo (la retejo ne estas havebla por poŝtelefonoj) +\n2. Iru al tie retpaĝo : %1$s +\n3. Ensalutu kiam oni petas vin +\n4. Kopiu la ligilon de profilo ke oni kondikis vin. + Malŝaltu por malebligi ŝarĝajn bildetojn, konservi datumojn kaj uzadon de memoro. Ŝanĝoj forviŝas ambaŭ en-memoro kaj sur-disko bildo kaŝmemoro. + Bildoj en kaŝmemoro forviŝitaj + Forviŝi la metadatumojn kaŝitajn + Forviŝi ĉiojn retpaĝojn kaŝitajn + Metadatumoj kaŝitaj forviŝitaj + Neniuj torentoj haveblaj por elŝuti + Neniu apo instalita por ludi ĉi tiun dosieron + Forviŝi vidohistorion + Forviŝi la historion de viditaj filmetojn kaj ludajn poziciojn + Ĉu vi volas forviŝi la tutan historion \? + Forviŝi la historion de serĉoj + Forviŝi la serĉajn ŝlosilvortojn + Ĉu vi volas forviŝi la totalon de la historio de serĉo \? + Historio de serĉo forviŝita. + Limigi distingivo kiam uzanta moveblan datumon + Minimumigu al ŝprucfenestro ludilo + Kanaloj + Ludlistoj + Spuroj + Uzantoj + Malabonu + Nova ongleto + Elektu ongleton + Kontrolo de volumena gesto + Uzu gestojn por kontroli la volumon de la ludilo + Kontrolo de gesto de brilo + Uzu gestojn por kontroli la brilon de la ludilo + Ĝisdatigoj + Dosiero forigita + Sciigo por ĝisdatigi apon + Sciigo por nova versio de Newpipe + Ekstera konservejo malhavebla + Elŝuti al ekstera SD-karto ne eblas. Ĉu vi volas rekomencigi la elŝutan dosierujon \? + viciĝita + Atendovico + Halti + Maksimuma nombro de provoj + Maksimuma nombro de provoj antaŭ fordecidi la elŝuton + interrompi ĉe limigitaj komputilaj retoj + Eventoj + Konferencoj + Montri komentojn + Malebligu por malvidigi komentojn + Aŭtolego + + Komentoj + + + Ne povis ŝarĝi komentojn + Fermi + Repreni la legon + Restaŭri la lastan legan pozicion + Pozicioj en listoj + Montri la indikilojn de pozicion lega en listoj + Forviŝi datumojn + Historio forviŝita. + Ludaj pozicioj forviŝitaj. + Dosiero movita aŭ foviŝita + ne povas dispremi la dosieron + Ĉu vi certas\? + Limigi la elŝutan atendovicon + Unu elŝuto ruliĝos en la sama tempo + Komenci elŝutojn + Paŭzigi elŝutojn + Peti kie elŝuti + Oni petos vin kie konservi ĉion elŝuton + Oni petos vin kie konservi ĉion elŝutaĵon. +\nElektu AFM se vi volas elŝuti al ekstera SD-karto + Uzu AFM + La Atinga Framo al la Memoro ebligas elŝuti al ekstera SD-karto. +\nKomento : kelkaj aparatoj ne kongruas + Forviŝi ludajn poziciojn + Forviŝi la totalon de ludaj pozicioj + Ĉu vi volas forviŝi ĉiujn ludajn poziciojn \? + Ŝanĝu la elŝutojn dosierujojn por efekti + \ No newline at end of file From 34cb44f2be3df7af1caf39de9cf7a8ffb40c6a1c Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Mon, 21 Oct 2019 00:31:28 +0000 Subject: [PATCH 049/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 7d89cf1a3..92c548532 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -126,7 +126,7 @@ \nвоспроизведения в окне reCAPTCHA Открыть во всплывающем окне - Показать предложения при поиске + Предлагать варианты при поиске Позже Отключено Изменение размера @@ -235,7 +235,7 @@ Не удалось воспроизвести этот поток Сведения Настройки звука - Пока нет подписок на каналы + Подписок на каналы пока нет Удалить Вы подписаны Подписка отменена @@ -249,8 +249,8 @@ Добавить в очередь \"В фоне\" Добавить в очередь \"В окне\" Начать отсюда в плеере - Начать отсюда в фоне - Начать отсюда в окне + Играть отсюда в фоне + Играть отсюда в окне Потоковый плеер не найден (можно установить VLC) Страна контента по умолчанию Сервис @@ -504,8 +504,7 @@ Для каждой загрузки запрашивать папку для сохранения. \nИспользуйте SAF для загрузки на внешний накопитель Использовать SAF - Storage Access Framework позволяет сохранять файлы на внешнем накопителе. -\nПоддерживается не всеми устройствами + Storage Access Framework позволяет сохранять файлы на внешнем накопителе. Поддерживается не всеми устройствами Очистить позиции воспроизведения Удалить все позиции воспроизведения Удалить все позиции воспроизведения\? From 83dc62239f8502ca6d159462429296e87423ef76 Mon Sep 17 00:00:00 2001 From: ozyc Date: Sat, 19 Oct 2019 10:25:39 +0000 Subject: [PATCH 050/270] Translated using Weblate (French) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-fr/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 266d53eae..20df29139 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -54,7 +54,7 @@ Répertoire de téléchargement « %1$s » créé Erreur Impossible d’analyser le site web - Contenu non disponible + Contenu indisponible Désolé, des erreurs sont survenues. Contenu Contenu avec limite d’âge @@ -217,7 +217,7 @@ Fil de syndication Chaîne Sélectionner une chaîne - Populaires + Tendances Top 50 Nouveau & populaire En file d’attente sur le lecteur en arrière-plan @@ -493,14 +493,14 @@ Mettre en pause les téléchargements Demander où télécharger Vous allez être interrogé sur l’emplacement d’enregistrement de chaque téléchargement - Vous allez être interrogé sur l’emplacement de téléchargement de chaque téléchargement. -\nVeuillez choisir SAF si vous voulez télécharger sur une carte mémoire externe + Vous allez être interrogé sur l’emplacement de téléchargement de chaque téléchargement. +\nVeuillez choisir ILAF si vous voulez télécharger sur une carte mémoire externe Rétablir la précédente position de lecture Positions dans les listes Afficher les indicateurs de position de lecture dans les listes Positions de reprise supprimées. - Utiliser SAF - Le « Storage Access Framework » permet de télécharger sur une carte mémoire externe. + Utiliser ILAF + L\'Infrastructure Logicielle d\'Accès au Stockage permet de télécharger sur une carte mémoire externe. \nNote : certains appareils ne sont pas compatibles Supprimer les positions de lecture Supprimer toutes les positions de reprise From 0b593b343eda2ccc0806329680a31bb6fe9c6ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20M=C3=BCnster?= Date: Mon, 21 Oct 2019 17:36:04 +0000 Subject: [PATCH 051/270] Translated using Weblate (Swedish) Currently translated at 79.7% (381 of 478 strings) --- app/src/main/res/values-sv/strings.xml | 132 +++++++------------------ 1 file changed, 35 insertions(+), 97 deletions(-) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 0d583a41b..5364678da 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1,5 +1,6 @@ - -Tryck på sök för att komma igång + + + Tryck på sök för att komma igång Publicerad den %1$s Ingen strömspelare hittades. Vill du installera VLC? Installera @@ -20,15 +21,12 @@ NewPipe popup-läge Bakgrund Extrafönster - - Sökväg för nerladdning av video - Sökväg för lagring av nerladdade videor - Ange sökväg för nedladdning av videor - + Mapp för nerladdning av video + Nedladdade videofiler lagras här + Välj nedladdningsmappen för videofiler Sökväg för nerladdning av ljud - Nedladdat ljud lagras här - Ange sökväg för nedladdning av ljudfiler - + Nedladdade ljudfiler lagras här + Välj nedladdningsmapp för ljudfiler Spelar automatiskt upp videon när NewPipe öppnas av en annan app Standardupplösning Standardupplösning för popup @@ -51,9 +49,7 @@ Använd gester för att kontrollera spelarens ljusstyrka och volym Sökförslag Visa förslag i samband med sökning - Ladda ned - Nästa Visa \'Nästa\' och \'Liknande\' videor Webbadressen stöds inte @@ -67,7 +63,7 @@ Spela upp Innehåll Åldersbegränsat innehåll - Videon är åldersbegränsad. Du kan aktivera åldersbegränsade videor i \"inställningar\". + Videon är åldersbegränsad. Du kan aktivera åldersbegränsade videor i inställningar. LIVE Nedladdningar Nedladdningar @@ -81,7 +77,6 @@ Uppdatera Rensa Bästa upplösningen - Fel Nätverksfel Kunde inte ladda alla miniatyrbilder @@ -99,8 +94,6 @@ Vad som skedde: Din kommentar (på Engelska): Detaljer: - - Gillar Ogillar Använd Tor @@ -111,16 +104,13 @@ Prenumeration avslutad Kunde inte ändra prenumeration Kunde inte uppdatera prenumeration - Hem Prenumerationer - Vad är nytt - Spela upp automatiskt Sökhistorik Spara sökfrågor lokalt - Historik & Cacheminne + Visningshistorik Håll koll på videor som du tittat på Återuppta när fokus återfås Fortsätta spela efter avbrott (t.ex. telefonsamtal) @@ -135,67 +125,54 @@ Storleksändring Ångra Spela Alla - NewPipe Avisering Aviseringar för NewPipe bakgrunds och popup-spelare - [Okänd] - Kunde inte dekryptera video URL signatur Kunde inte hitta någon ström Misslyckades med att spela denna ström Allvarligt spelarfel inträffade Återhämtar sig från spelarfel - Rapportera fel via e-post Vad:\\nBegäran:\\nInnehållsspråk:\\nTjänst:\\nGMT Tid:\\nPaket:\\nVersion:\\nOS-version: Videons miniatyrbild - Videons miniatyrbild + Spela video, längd: Uppladdarens avatar miniatyrbild (Experimentellt) Tvinga nedladdningstrafiken via Tor för ökad integritet (videostreaming stöds inte än). Användarrapport Inga resultat Här va\' det tomt - Kunde inte skapa nedladdnings katalog \'%1$s\' Skapa nedladdnings katalog \'%1$s\' - Video Ljud Försök igen Tillgång till lagringsområde nekades - - k + K mn - md - + B Inga prenumeranter - %s prenumerant - %s prenumeranter - - + %s prenumerant + %s prenumeranter + Inga visningar - %s visning - %s visningar - - + %s visning + %s visningar + Inga videor - %s video - %s videor - - + Videor + Videor + Start Pausa Spela Ta bort Kontrollsumma - Nytt uppdrag Ok - Filnamn Trådar Fel @@ -206,22 +183,18 @@ Tryck för detaljer Vänta… Kopierat till urklipp - Välj en tillgänglig nedladdningsmapp + Ange en hämtningsmapp senare i inställningar Denna tillåtelse behövs för att \nöppna i popup-läge - reCAPTCHA reCAPTCHA utmaning reCAPTCHA utmaning begärd - Nedladdning Tillåtna tecken i filnamn Ogiltiga tecken ersätts med detta värde Ersättningstecknet - Bokstäver och siffror De flesta specialtecken - Om NewPipe Inställningar Om @@ -238,7 +211,6 @@ Vad du än har för idéer; översättningar, designändringar, kodstädning eller riktiga stora kodändringar—hjälp är alltid välkommen. Ju mer som görs desto bättre blir det! Läs hela licensen Bidra - Historik Sökt Tittade @@ -248,7 +220,6 @@ Historiken har rensats Objektet bortaget Vill du ta bort det här objektet från sökhistoriken? - Huvudsidans innehåll Tom sida Kiosk sida @@ -258,7 +229,6 @@ Välj en kanal Inga kanal prenumerationer ännu Välj en kiosk - Kiosk Trend Topp 50 @@ -272,29 +242,25 @@ Placera i bakgrunds-kön Placera i popup-kön Börja spela här - Börja här i bakgrunden - Börja här i popup -Donera + Börja spela i bakgrunden + Börja spela i en ny popup + Donera NewPipe utvecklas av frivilliga som spenderar sin fritid på att ge dig den bästa användarupplevelsen. Nu är det tid att ge tillbaka för att säkerställa att utvecklarna kan göra NewPipe ännu bättre medan de njuter av en kopp kaffe. Ge tillbaka Webbplats För att få mer information och de senaste nyheterna om NewPipe, besök vår webbplats. Visa info - Bokmärkta Spellistor - Lägg till - Använd snabb inexakt sökning Ladda miniatyrbilder - Inaktivera för att stänga av laddning av miniatyrbilder, sparar data och minnesanvändning. Förändringar kommer att rensa cache-minnet. + Stäng av för att hindra laddning av miniatyrbilder, spara data och minnesanvändning. Förändringar kommer att rensa cache-minnet. Bild cacheminnet rensad Tjänst Debug Alltid Bara en gång Fil - Växla orienteringen Rensa metadatan i cacheminnet Ingen strömspelare hittades (du kan installera VLC för att spela upp). @@ -303,7 +269,7 @@ Ta bort alla cachade webbsidor Metadata cache rensad "Köa nästa ström automatiskt " - Lägg automatiskt till en relaterad ström när du spelar den sista strömmen i en ej upprepad kö. + Lägg automatiskt till en relaterad ström när du spelar den sista strömmen i en ej upprepad kö Standard innehållsland Kanaler Spellistor @@ -312,13 +278,12 @@ Växla till Bakgrunden Växla till popup Växla till main - Importera databas Exportera databas Skriver över din nuvarande historik och prenumerationer Exportera historik, prenumerationer och spellistor Rensa visningshistorik - Tar bort historiken för spelade videoklipp + Tar bort historiken för spelade videoklipp och uppspelningspositioner Ta bort hela visningshistoriken\? Visningshistorik borttagen. Rensa sökhistorik @@ -329,25 +294,20 @@ Ogiltig URL Inga videoströmmar hittades Inga ljudspår hittades - Ogiltig katalog - Ogiltig fil/innehålls källa + Ingen sådan mapp + Ingen sådan fil/innehållskälla Filen finns inte eller behörighet att läsa eller skriva till den saknas Filnamnet får inte vara tomt Ett fel uppstod: %1$s Inga strömmar är tillgängliga för nedladdning - Dra för att ändra ordning - Skapa Ta bort en Ta bort alla Avfärda Byt namn - 1 objekt borttaget. - Ingen app installerad för att spela upp filen - NewPipes Sekretesspolicy NewPipe projektet tar din integritet på största allvar. Appen samlar därför inte in några uppgifter utan ditt medgivande. NewPipes Sekretesspolicy förklarar i detalj vad för data som skickas och lagras när du skickar en kraschrapport. Läs sekretesspolicy @@ -356,77 +316,57 @@ Är du säker du vill ta bort alla föremål från historiken? Senast spelade Mest spelade - Exporterad Importerad Ogiltig ZIP-fil Varning: det gick inte att importera alla filer. Det här kommer skriva över dina nuvarande inställningar. Vill du också importera inställningar? - Öppna navigationspanelen Stäng navigationspanelen Något kommer att visas här snart ;D - - Föredragen \'öppna\' åtgärd Standardåtgärden när du öppnar innehåll — %s - Videospelare Bakgrundsspelare Popup-spelare Fråga alltid - Hämtar information… Laddar begärt innehåll - Ny spellista Radera Byt namn Namn Lägg till i spellistan Använd som spellistans miniatyrbild - Bokmärk spellistan Ta bort bokmärke - Ta bort spellista\? Spellistan skapades Tillagad i spellistan Spellistans miniatyrbild förändrades. Kunde inte ta bort spellistan. - Ingen textning - Passa Fyll Zoom - Autogenererade - Textning Ändra spelarens textskala och bakgrundsstil. Kräver att appen startar om för att träder i kraft. - Aktivera LeakCanary Minnesläcka övervakning kan orsaka att appen inte svarar under heap dumpning - Rapportera out-of-lifecycle fel Tvinga rapportering av otillåtna Rx-undantag utanför fragment eller aktivitetslivscykel efter uppstädning - Importera/exportera Importera Importera från Exportera till - Importera… Exporterar… - Importera fil Föregående export - Kunde inte importera prenumerationer kunde inte exportera prenumerationer - Importera YouTube-prenumerationer genom att hämta export-filen: \n \n1. Gå till följande webbadress: %1$s @@ -439,11 +379,9 @@ \n3. Logga in när du blir tillfrågad \n4. Kopiera profilens webbadress som du blev omdirigerad till. dittID, soundcloud.com/dittid - Tänk på att operationer kan vara nätverks intensiv. \n \nVill du fortsätta? - Uppspelningshastighet Kontroller Tempo Tonhöjd @@ -451,11 +389,9 @@ Snabbspola vid frånvaro av ljud Steg Återställ - För att uppfylla den Europeiska dataskyddsförordningen (GDPR), uppmärksammar vi NewPipes sekretesspolicy. Läs den noggrant. Du måste acceptera det om du vill skicka felrapporten. Acceptera Avböj - Ingen gräns Begränsa upplösningen när du använder mobil data "Minimera vid växling av applikationen " @@ -463,5 +399,7 @@ Ingen Minimera till Bakgrunds-spelare Minimera till popup-spelare - - + Avsluta prenumeration + Ny flik + Välj flik + \ No newline at end of file From 40a2322e09ed5b26ca49abcb1ba83c684f5f3fb9 Mon Sep 17 00:00:00 2001 From: 01QueN10 <01quen10@gmail.com> Date: Mon, 21 Oct 2019 15:37:55 +0000 Subject: [PATCH 052/270] Translated using Weblate (Korean) Currently translated at 99.0% (473 of 478 strings) --- app/src/main/res/values-ko/strings.xml | 84 +++++++++++++------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 6cd24123a..b1b3b3827 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1,6 +1,6 @@ - 시청 횟수 %1$s + 조회수 %1$s %1$s에 업로드됨 스트리밍 플레이어가 발견되지 않았습니다. VLC를 설치할까요? 설치 @@ -15,19 +15,19 @@ 브라우저 선택 회전 비디오 다운로드 폴더 - 다운로드된 비디오 파일이 이 곳에 저장 됩니다 - 비디오 파일을 다운로드 받을 폴더를 선택하세요 + 다운로드된 비디오 파일이 이 곳에 저장됩니다 + 비디오 파일이 다운로드 될 폴더를 선택하세요 기본 해상도 Kodi로 재생 Kore 앱이 발견되지 않았습니다. Kore를 설치할까요? \"Kodi로 재생\" 옵션 표시 - 비디오를 Kodi media center를 사용해 재생하는 옵션을 표시합니다 + 비디오를 Kodi 미디어 센터를 사용해 재생하는 옵션을 표시합니다 오디오 기본 오디오 형식 다운로드 다음 다음 및 유사한 비디오 표시 - 지원하지 않는 URL 입니다 + 지원하지 않는 URL입니다 기본 컨텐츠 언어 비디오 & 오디오 비디오 미리보기 썸네일 @@ -38,8 +38,8 @@ 외부 비디오 플레이어 사용 외부 오디오 플레이어 사용 오디오 다운로드 폴더 - 다운로드된 오디오 파일은 여기에 저장됩니다 - 오디오 파일을 다운로드할 폴더를 선택하세요 + 다운로드된 오디오 파일이 이 곳에 저장됩니다 + 오디오 파일이 다운로드 될 폴더를 선택하세요 테마 어두운 테마 밝은 테마 @@ -89,11 +89,11 @@ 삭제 체크섬 팝업 모드에서 열기 - 일부 해상도에서 소리가 나지 않습니다 - 뉴파이프 팝업 모드 + 일부 해상도에서 소리가 나지 않을 수 있습니다 + NewPipe 팝업 모드 구독 구독됨 - 채널 구독 해제됨 + 구독 해제됨 구독 여부를 변경할 수 없음 구독을 업데이트할 수 없음 메인 화면 @@ -115,11 +115,11 @@ 검색 기록 검색 기록을 기기에 저장합니다 기록 보기 - 시청했던 비디오 기록을 저장 - 초점 복원시 재생 재개 + 시청했던 비디오 기록을 저장합니다 + 자동으로 다시 재생 전화 통화 등으로 인해 재생이 중단된 이후에 다시 재생을 시작합니다 - 눌러서 팁 표시 - 비디오 상세 정보 페이지에서 백그라운드 재생 또는 팝업 버튼을 누를 경우 팁 표시 + \"길게 눌러 대기열에 추가하기\" 팁 표시 + 비디오 상세 정보 페이지에서 백그라운드 재생 또는 팝업 버튼을 누를 경우 팁을 표시합니다 플레이어 동작 기록과 캐시 @@ -141,8 +141,8 @@ 최대 해상도 되돌리기 전부 재생 - 뉴파이프 알림 - 뉴파이프 백그라운드 및 팝업 플레이어 알림 + NewPipe 알림 + NewPipe 백그라운드 및 팝업 플레이어 알림 [알 수 없음] 이미지를 불러올 수 없습니다 앱/UI 충돌 @@ -154,7 +154,7 @@ 구독할 항목을 추가하세요 백만 - 10억 + 십억 구독자 없음 %s 구독자 @@ -173,7 +173,7 @@ 지원하지 않는 서버 파일이 이미 존재합니다 올바르지 않은 URL이거나 인터넷에 접속할 수 없음 - 뉴파이프 다운로드 중 + NewPipe 다운로드 중 터치해서 상세 정보 확인 잠시만 기다려주십시오… 클립보드에 복사됨 @@ -188,14 +188,14 @@ 대체 문자 문자 및 숫자 가장 특수한 문자 - 뉴파이프에 대해서 + NewPipe에 대해서 설정 - 뉴파이프 + NewPipe 정보 제3자 라이센스 © %3$s 하에서 %2$s 에 의한 %1$s 라이센스 불러올 수 없음 웹사이트 열기 - 뉴파이프 + NewPipe 정보 기여자 라이센스 가볍고 빠른 자유 안드로이드 스트리밍 앱입니다. @@ -203,11 +203,11 @@ 번역, 디자인, 코딩 등 다양한 기여를 언제나 환영합니다. 향상에 참여해주세요! GitHub에서 보기 기부 - 여러분의 사용자 경험을 위해 자원봉사자들이 시간을 들여 뉴파이프를 개발하고 있습니다. 뉴파이프를 개선하는데 참여하는 개발자들을 위하여 기부해 주세요. + 여러분들의 더 나은 경험을 위해 많은 사람들이 NewPipe를 개발하는데 노력을 기울이고 있습니다. NewPipe에 참여하는 개발자들이 커피 한 잔을 즐길 수 있도록 기부해주세요. 보답하기 웹사이트 - 뉴파이프에 관한 최신 및 상세 정보를 얻으려면 웹사이트를 방문하세요. - 뉴파이프가 채택한 라이센스 + NewPipe에 관한 더 많은 정보를 얻으려면 웹사이트를 방문하세요. + NewPipe가 채택한 라이센스 라이센스 읽기 기록 검색함 @@ -242,13 +242,13 @@ 여기서부터 재생 백그라운드에서 재생 새 팝업에서 재생 - 스트리밍 플레이어를 찾을 수 없습니다. VLC를 설치하면 플레이하실 수 있습니다. + 스트리밍 플레이어를 찾을 수 없습니다. VLC를 설치하여 동영상을 재생할 수 있습니다. 스트리밍 파일 다운로드하기 정보 보기 플레이리스트 북마크 이곳에 추가 정확하지는 않지만 빠른 탐색 - 정확하지 않은 탐색은 빠르게 위치로 탐색할 수 있지만 정확도는 떨어집니다 + 정확하지 않은 탐색은 더 빠르게 위치를 탐색할 수 있지만 정확도는 떨어집니다 다음 스트림을 자동으로 재생열에 추가하기 이전 스트림이 무한 반복 재생 큐가 아닐 경우, 관련 스트림을 자동 재생합니다 기본 콘텐츠 국가 @@ -348,9 +348,8 @@ 경고: 데이터 소모량이 늘어날 수 있습니다. \n \n계속하시겠습니까? - 썸내일 로드하기 - 동영상 썸네일을 로드하지 않으며, 데이터와 메모리 사용을 최대한 줄입니다. 이 옵션을 -\n선택 시 모든 메모리 캐시와 저장소 캐시를 삭제합니다. + 썸네일 로드하기 + 동영상 썸네일을 로드하지 않으며, 데이터와 메모리 사용을 최대한 줄입니다. 이 옵션을 선택 시 모든 메모리 캐시와 저장소 캐시를 삭제합니다. 이미지 캐시 지워짐 캐시된 메타데이터 지우기 캐시된 모든 웹페이지 데이터 지우기 @@ -376,14 +375,14 @@ 검색 기록을 모두 삭제합니다 모든 검색 기록을 삭제하시겠습니까\? 검색 기록이 삭제되었습니다. - 뉴파이프 개인정보 보호 정책 - 뉴파이프 프로젝트는 사용자의 개인 정보 보호를 최우선으로 생각하며, 동의 없이 어떠한 정보도 수집하지 않습니다. -\n뉴파이프 개인정보 보호 정책에서는 오류 보고 시 어떠한 정보가 수집되고 저장되는지 자세히 명시되어 있습니다. + NewPipe 개인정보 보호 정책 + NewPipe 프로젝트는 사용자의 개인 정보 보호를 최우선으로 생각하며, 동의 없이 어떠한 정보도 수집하지 않습니다. +\nNewPipe 개인정보 보호 정책에서는 오류 보고 시 어떠한 정보가 수집되고 저장되는지 자세히 명시되어 있습니다. 개인정보 보호 정책 읽기 - 뉴파이프는 카피레프트 자유 소프트웨어입니다. 사용자는 이 앱을 사용, 공유, 또는 수정할 수 있고, 수정 후 재배포 시 자유 소프트웨어 재단의 GNU 라이센스 버전 3 또는 그 이상의 버전을 포함해야 합니다. + NewPipe는 카피레프트 자유 소프트웨어입니다. 사용자는 이 앱을 사용, 공유, 또는 수정할 수 있고, 수정 후 재배포 시 자유 소프트웨어 재단의 GNU 라이센스 버전 3 또는 그 이상의 버전을 포함해야 합니다. 앱 설정을 가져오시겠습니까? 무음 구간 스킵 - 유럽 연합 일반 데이터 보호 규정 (GDPR) 에 따라, 사용자는 뉴파이프 개인정보 보호 정책을 읽고 꼼꼼히 확인해야 합니다. 버그 리포트를 보내시려면 개인정보 보호 정책에 동의해주세요. + 유럽 연합 일반 데이터 보호 규정 (GDPR) 에 따라, 사용자는 NewPipe 개인정보 보호 정책을 읽고 꼼꼼히 확인해야 합니다. 버그 리포트를 보내시려면 개인정보 보호 정책에 동의해주세요. 동의 동의하지 않음 데이터 제한 없음 @@ -391,17 +390,17 @@ 구독 해제 새 탭 탭 선택 - 제스쳐로 음량 조작 - 제스쳐를 사용해 플레이어의 음량을 조작 합니다 - 제스쳐로 밝기 조작 - 제스쳐를 사용해 화면 밝기를 조작합니다 + 제스처로 음량 조작 + 제스처를 사용해 플레이어의 음량을 조작 합니다 + 제스처로 밝기 조작 + 제스처를 사용해 화면 밝기를 조작합니다 업데이트 트랙 사용자 이벤트 파일 삭제됨 앱 업데이트 알림 - 새 뉴파이프 버전 알림 + 새 NewPipe 버전 알림 외부 저장소 없음 SD 카드로 다운로드 할 수 없습니다. 다운로드 폴더 경로를 초기화 하시겠습니까\? 1개의 항목이 삭제되었습니다. @@ -474,8 +473,8 @@ 닫기 재생 재개 마지막 재생 위치부터 재생 - 리스트내 위치 - 리스트에서 재생위치 인디케이터를 표시합니다 + 리스트 내 위치 + 리스트에서 재생 위치를 표시합니다 데이터 삭제 재생위치 삭제완료. 파일이 이동되거나 삭제되었습니다 @@ -499,4 +498,7 @@ 모든 재생 위치를 삭제하시겠습니까\? 매 다운로드 마다 저장경로를 묻습니다. \n외장 SD카드에 다운로드 하고자 한다면 SAF를 선택하십시오 + + 동영상만 + \ No newline at end of file From d3ca1845704509618d0f4e3bff7c471d4491b9d0 Mon Sep 17 00:00:00 2001 From: Muhammad Sharjeel Date: Mon, 21 Oct 2019 03:33:53 +0000 Subject: [PATCH 053/270] Translated using Weblate (Urdu) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ur/strings.xml | 485 ++++++++++++++++++++++++- 1 file changed, 483 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 07495895b..028986866 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -1,6 +1,6 @@ - شروع کرنے کیلئے تلاش کو ٹپ کریں + شروع کرنے کے لیے تلاش پر ٹیپ کریں ملاحظات کوشائع ہوا انسٹال @@ -20,9 +20,490 @@ \n rotation کرو external video player استعمال کریں - SOME قرارداد پر آواز ہٹا دے گا + کچھ ریذولوشن پر آڈیو ہٹاتا ہے بیرونی سوتی عامل ایپ استعمال کریں NewPipe ابھاری اسلوب سبسکرائیب کریں سبسکرائیب کر لیا + چینل کی رکنیت ختم کردی گئی + رکنیت کو تبدیل نہیں کیا جاسکا + سبسکرپشن کو اپ ڈیٹ نہیں کیا جاسکا + معلومات دکھائیں + بنیادی + سبسکرپشنز + بُک مارک کردہ پلے لسٹس + نئ آمد + پس منظر + پوپ اپ + شامل کریں + ویڈیو ڈاؤن لوڈ فولڈر + ڈاؤن لوڈ کی ویڈیو فائلوں کو یہاں ذخیرہ کیا جاتا ہے + ویڈیو فائلوں کے لئے ڈاؤن لوڈ فولڈر کا انتخاب کریں + آڈیو ڈاؤن لوڈ فولڈر + ڈاؤن لوڈ کی گئی آڈیو فائلیں یہاں محفوظ ہیں + آڈیو فائلوں کے لئے ڈاؤن لوڈ فولڈر کا انتخاب کریں + آٹوپلے + ایک ویڈیو ادا کرتا ہے جب نیو پائپ کسی دوسرے ایپ سے بلایا جاتا ہے + طے شدہ جزیات + طے شدہ پوپ جزیات + اعلی قراردادیں دکھائیں + صرف کچھ آلات 2K / 4K ویڈیوز کھیلنے میں معاون ہیں + کوڈی کے ساتھ استعمال کریں + کور ایپ نہیں ملی۔ اسے انسٹال کریں؟ + \"کودی کے ساتھ کھیلیں\" کا آپشن دکھائیں + کوڈی میڈیا سنٹر کے ذریعے ویڈیو چلانے کا آپشن دکھائیں + آڈیو + پہلے سے طے شدہ آڈیو فارمیٹ + طے شدہ وڈیو وضع + خیالیہ + روشنی + تاریک + سیاہ + پاپ اپ سائز اور پوزیشن کو یاد رکھیں + آخری سائز اور پوپ اپ کی پوزیشن یاد رکھیں + تیز رفتار تلاش کریں + غیر معقول تلاش سے کھلاڑی کم ہونے والی صحت سے متعلق تیزی سے پوزیشن حاصل کرنے کی اجازت دیتا ہے + Thumbnailلوڈکریں + تھمب نیلز کو لوڈ کرنے ، اعداد و شمار کی بچت اور میموری کے استعمال کو روکنے کیلئے آف کریں۔ تبدیلیاں دونوں میموری میں اور آن ڈسک امیج کیشے کو صاف کرتی ہیں۔ + تصویری کیشے کا صفایا کردیا + کیشڈ میٹا ڈیٹا کو صاف کریں + ویب میں موجود سبھی ڈیٹا کو ہٹا دیں + میٹا ڈیٹا کیشے کا صفایا کردیا + آٹو قطار اگلا اسٹریم + جب دوبارہ نہ چلنے والی قطار میں آخری سلسلہ جاری ہو تو متعلقہ سلسلے کو خود سے شامل کریں + پلیر اشارہ کنٹرول + اشارے کی چمک اور آواذکو کنٹرول کرنے کے لئے اشاروں کا استعمال کریں + تلاش کی تجاویز + تلاش کرتے وقت تجاویز دکھائیں + تاریخ تلاش کریں + تلاش کے استفسارات کو مقامی طور پر ذخیرہ کریں + واچ ہسٹری + دیکھے ہوئے ویڈیوز کا ریکارڈرکھیں + فوکس حاصل پر دوبارہ شروع کریں + مداخلت کے بعد چلاناجاری رکھیں (جیسے فون کالز) + ڈاؤن لوڈ + اگلا + \'اگلا\' اور \'ملتے جلتے\' ویڈیوز دکھائیں + \"الحاق کرنے کے لئے منعقد\" ٹپ دکھائیں + ویڈیو تفصیلات کے صفحے پر جب بیک گراؤنڈ یا پاپ اپ بٹن دبائے جاتے ہیں تو ٹپ دکھائیں + غیر معاون URL + طے شدہ مواد والا ملک + خدمت + پلیئر + رویہ + ویڈیو & آڈیو + تاریخ اور کیشے + پوپ اپ + ظہور + دیگر + ڈیبگ + پس منظر میں چلائیں + پوپ اپ موڈ میں چلائیں + بیک گراؤنڈ پلیئر پر قطار ہے + پاپ اپ پلیئر پر قطارہے + چلائیں + مواد + عمر محدود مواد + عمر پر پابندی والا ویڈیو دکھائیں۔ ترتیبات سے اس طرح کے مواد کی اجازت ممکن ہے۔ + براہ راست + ڈاؤن لوڈ + ڈاؤن لوڈ + غلطی کی اطلاع + تمام + چینل(ذریعہ) + پلے لسٹ + ہاں + بعد میں + غیر فعال + فلٹر + تازہ + صاف + سائز تبدیل کریں + بہترین ریزولوشن + کالعدم کریں + تمام چلائیں + ہمیشہ + صرف ایک بار + فائل + نیو پائپ نوٹیفیکیشن + نیو پائپ کے پس منظر اور پاپ اپ پلیئرز کیلئے اطلاعات + [نامعلوم] + ترتیب وضع کریں + پس منظر پر جائیں + پاپ اپ پر جائیں + مین پر سوئچ کریں + کوائفیہ درآمد کریں + کوائفیہ برآمد کریں + اپنی موجودہ تاریخ اور رکنیت ضد + تاریخ ، خریداری اور پلے لسٹ برآمد کریں + خرابی + نیٹ ورک کی خرابی + تمام تمبنیلذلوڈ نہیں کر سکے + ویڈیو یو آر ایل کے دستخط ڈکرپٹ نہیں ہو سکے + ویب سائٹ کو تجزیہ نہیں کیا جاسکا + ویب سائٹ کو مکمل طور پر تجزیہ نہیں کرسکے + مواد دستیاب نہیں ہے + ڈاؤن لوڈ مینو ترتیب نہیں دے سکتے + براہ راست سلسلے ابھی تک تعاون یافتہ نہیں ہیں + کوئی اسٹریم نہیں مل سکی + تصویر کو لوڈ نہیں کر سکتا + ایپ/UI کریش + یہ اسٹریم نہیں چل سکا + انریکوورابلی پلیئر نقص واقع ہوا + پلیر نقص سے بازیافت کر رہا ہے + بیرونی پلیراس قسم کے لنکس کی حمایت نہیں کرتے + غلط URL + کوئی ویڈیو اسٹریمز نہیں ملے + کوئی آڈیو اسٹریمز نہیں ملے + ایسا کوئی فولڈر نہیں + ایسی کوئی فائل / مواد کا ذریعہ نہیں + فائل موجود نہیں ہے اور نہ ہی اسے پڑھنے یا لکھنے کی اجازت ہے + فائل کا نام ضروری ہے + ایک خامی پیش آگئی:٪ 1 $ s + کوئی اسٹریمز ڈاؤن لوڈ کرنے کے لئے دستیاب نہیں + معذرت ، ایسا نہیں ہونا چاہئے تھا۔ + ای میل کے ذریعے غلطی کی اطلاع دیں + معذرت ، کچھ خرابی آگئی + رپورٹ کریں + معلومات: + کوئی مسئلہ ہے + What:\\nRequest:\\nContent Lang:\\nService:\\nGMT Time:\\nPackage:\\nVersion:\\nOS version: + آپ کا تبصرہ (انگریزی میں): + تفصیلات: + ویڈیو پیش نظارہ تھمب نیل + وڈیو کادورانیہ: + اپ لوڈر کا اوتار تھمب نیل + پسند + ناپسند + Tor کا استعمال + (تجرباتی) بڑھتی ہوئی رازداری کے لئے ٹور کے ذریعہ ٹریفک کو ڈاؤن لوڈ کرنے پر مجبور کریں (ویڈیوز کو ابھی تک سپورٹ نہیں کیا گیا ہے)۔ + غلطی کی اطلاع دیں + صارف رپورٹ + کوئی نتیجہ نہیں + یہاں کچھ نہیں مگر اداسی کے + دوبارہ ترتیب دینے کیلئے کھینچں + ڈاؤن لوڈ ڈائریکٹری \'٪ 1 $ s\' تشکیل نہیں دے سکتے + ڈاؤن لوڈ ڈائریکٹری \'٪ 1 $ s\' بن گئی + ویڈیو + آڈیو + دوبارہ کوشش کریں + ذخیرہ رسائی اجازت مسترد + K + ایم + بی + کوئی صارفین نہیں + + % s صارف + % s صارفین + + کوئی مناظر نہیں + + % s منظر + % s مناظر + + ویڈیوز دستیاب نہیں + + ویڈیوز + + + شروع کریں + توقف + چلائیں + بنانا + حذف کریں + ایک کو حذف کریں + تمام حذف کریں + تشخیص کریں + برخاست کریں + نام تبدیل کریں + نیا حدف + ٹھيک ہے + فائل کا نام + موضوعات + خرابی + غیر معاون سرور + فائل پہلے سے موجود ہے + ناقص URL یا انٹرنیٹ دستیاب نہیں + نیو پائپ ڈاؤن لوڈ ہو رہا ہے + تفصیلات کے لیے ٹیپ کریں + براۓ مہربانی انتظار کريں… + کلپ بورڈ میں کاپی کریں + براہ کرم بعد میں ترتیبات میں ڈاؤن لوڈ فولڈر کی وضاحت رکھیں + پوپ اپ موڈ میں کھولنے کیلئے اس اجازت کی ضرورت ہے + reCAPTCHA + reCAPTCHA چیلنج + reCAPTCHA چیلینج کی درخواست کی گئی + ڈاؤن لوڈ + فائل ناموں میں حروف کی اجازت ہے + ناجائز کریکٹر اس قدر کے ساتھ تبدیل کیےگئے ہیں + متبادل کریکٹر + خطوط اور ہندسے + سب سے خاص کردار + نیو پائپ کے بارے میں + ترتیبات + کے بارے میں + تیسری پارٹی کے لائسنس + 3٪ 1 $ s بذریعہ٪ 2 $ s٪ 3 $ s کے تحت + لائسنس لوڈ نہیں کر سکا + ویب سائٹ کھولیں + کے بارے میں + معاونین + لائسنس + لوڈ ، اتارنا Android پر ہلکا پھلکا اسٹریمنگ۔ + تعاون کریں + چاہے آپ کے خیالات ہوں؛ ترجمہ ، ڈیزائن میں تبدیلیاں ، کوڈ صاف کرنا ، یا حقیقی ہیوی کوڈ میں تبدیلی — مدد کا ہمیشہ خیرمقدم کیا جاتا ہے۔ جتنا زیادہ ہوتا ہے اتنا ہی بہتر ہوتا ہے! + گٹ ہب پر دیکھیں + عطیہ + نیا پائپ رضاکاروں کے ذریعہ تیار کیا گیا ہے جو آپ کو بہترین تجربہ فراہم کرنے میں وقت گزارتے ہیں۔ ایک کپ کافی سے لطف اندوز ہوتے ہوئے ڈویلپرز کو نیو پائپ کو اور بہتر بنانے میں مدد دینے کے لئے واپس دیں۔ + واپس دو + ویب سائٹ + مزید معلومات اور خبروں کے لئے نیو پائپ ویب سائٹ ملاحظہ کریں۔ + نیو پائپ کا لائسنس + لائسنس پڑھیں + تاریخ + تلاش کیا گیا + دیکھاھوا + تاریخ آف کردی گئی ہے + تاریخ + تاریخ خالی ہے + تاریخ صاف ہوگئی + آئٹم حذف ہوگیا + کیا آپ اس آئٹم کو تلاش کی تاریخ سے حذف کرنا چاہتے ہیں؟ + کیا آپ اس آئٹم کو دیکھنے کی تاریخ سے حذف کرنا چاہتے ہیں؟ + کیا آپ واقعی تاریخ سے سبھی اشیاء کو حذف کرنا چاہتے ہیں؟ + آخری پلے کیا گیا + سب سے زیادہ دیکھاگیا + مرکزی صفحہ کا مواد + خالی صفحہ + رجحان صفحہ + سکریپشن پیج + فیڈ صفحہ + چینل کا صفحہ + ایک چینل منتخب کریں + کسی چینل کی رکنیت نہیں + ایک کیوسک منتخب کریں + برآمدشدہ + درآمدشدہ + کوئی درست زپ فائل نہیں ہے + انتباہ: تمام فائلوں کو درآمد نہیں کیا جاسکا۔ + یہ آپ کے موجودہ سیٹ اپ کو اوور رائڈ کرے گا۔ + کیوسک + رجحان سازی + Top 50 + نیا اور تاذہ + پس منظر پلیئر + پوپ اپ پلیئر + ہٹائیں + تفصیلات + آڈیو کی ترتیبات + انقطار کو پکڑو + پس منظر میں قطار + ایک نئی پوپ اپ میں انقطار + یہاں سےچلاناشروع کریں + پس منظر میں چلاناشروع کریں + نئے پاپ اپ میں چلاناشروع کریں + دراز کھولیں + دراز بند کریں + یہاں جلد ہی کچھ نظر آئے گا D D + ترجیح \' کھلی \' عمل + مواد کھولنے پر ڈیفالٹ کارروائی -٪ s + ویڈیو پلیئر + پس منظر پلیئر + پوپ اپ پلیئر + ہمیشہ اجازت لیں + معلومات حاصل کر رہا ہے… + درخواست کردہ مواد کو لوڈ کیا جارہا ہے + نئی پلے لسٹ + حذف کریں + نام تبدیل کریں + نام + پلے لسٹ میں شامل + پلے لسٹ تھمب نیل کے بطور سیٹ کریں + پلے لسٹ کو بُک مارک کریں + بُک مارک کو حدف کریں + اس پلے لسٹ کو حذف کریں؟ + پلے لسٹ بنائی گئی + فہرست میں شامل کر دیا + پلے لسٹ تھمب نیل تبدیل کر دیا گیا۔ + پلے لسٹ کو حذف نہیں کیا جاسکا۔ + کوئی کیپشن نہیں + فٹ + بھریں + زوم + خود بخود تیار + لیک کینری کو قابل بنائیں + میموری لیک ہونے کی نگرانی ہیپ ڈمپنگ کے وقت ایپ کو غیرذمہ دار بننے کا باعث بن سکتی ہے + زندگی سے دور کی غلطیوں کی اطلاع دیں + ضائع ہونے کے بعد ٹکڑے یا سرگرمی زندگی سے دور کے ناقابل تسخیر Rx مستثنیات کی اطلاع دہندگی + درآمد برآمد + درآمد + سے درآمد کریں + کو برآمد کریں + درآمد کیا جا رہا ہے… + برآمد کر رہا ہے… + فائل درآمد کریں + پچھلی برآمد + سبسکرپشنز کو درآمد نہیں کیا جاسکا + رکنیت برآمد نہیں کر سکا + برآمد فائل کو ڈاؤن لوڈ کرکے YouTube کی رکنیت کو درآمد کریں: +\n +\n1. اس یو آر ایل پر جائیں:٪ 1 $ s +\n2. جب پوچھا جائے تو لاگ ان کریں +\nA. ڈاؤن لوڈ شروع ہونا چاہئے (یہ برآمد فائل ہے) + URL یا آپ کی ID ٹائپ کرکے ایک SoundCloud پروفائل درآمد کریں: +\n +\n1. ویب براؤزر میں \"ڈیسک ٹاپ موڈ\" کو فعال کریں (سائٹ موبائل آلات کے لئے دستیاب نہیں ہے) +\n2. اس URL پر جائیں: %1 $ s +\n3. پوچھا گیا میں لاگ ان کریں +\n4. پروفائل یو آر ایل کاپی کریں جو آپ کو ہدایت کی گئی تھی. + yourID، soundcloud.com/yourid + یاد رکھیں کہ یہ آپریشن نیٹ ورک مہنگا ہوسکتا ہے۔ +\n +\nکیا آپ جاری رکھنا چاہتے ہیں؟ + پلے بیک رفتار کنٹرول + Tempo + "آواز کو موٹا کرنا" + ختم (مسخ کا سبب بن سکتا ہے) + کیپشن + پلیئر کیپشن ٹیکسٹ اسکیل اور بیک گراونڈ اسٹائل میں ترمیم کریں۔ اثر لینے کیلئے ایپ کو دوبارہ شروع کرنا ضروری ہے۔ + اس فائل کو چلانے کے لئے کوئی ایپ انسٹال نہیں ہے + دیکھنے کی تاریخ صاف کریں + ادا شدہ اسٹریمز اور پلے بیک پوزیشنوں کی تاریخ کو خارج کریں + دیکھنے کی پوری تاریخ کو حذف کریں؟ + تلاش کی ہسٹری کو مٹا دیں + تلاش کے مطلوبہ الفاظ کی تاریخ کو حذف کریں + پوری تلاش کی تاریخ کو حذف کریں؟ + تلاش کی تاریخ حذف ہوگئی۔ + 1 شے حذف کر دی گئی ۔ + نیو پائپ کاپلیفٹ فری سافٹ ویئر ہے: آپ استعمال کرسکتے ہیں ، مطالعہ شیئر کرسکتے ہیں اور اپنی مرضی سے اسے بہتر کرسکتے ہیں۔ خاص طور پر آپ اسے مفت سافٹ ویئر فاؤنڈیشن کے ذریعہ شائع کردہ GNU جنرل پبلک لائسنس کی شرائط کے تحت دوبارہ تقسیم اور / یا ترمیم کرسکتے ہیں ، جو لائسنس کا ورژن 3 ، یا (آپ کے اختیار پر) کسی بھی بعد کا ورژن ہے۔ + کیا آپ سیٹنگیں بھی درآمد کرنا چاہتے ہیں؟ + نیو پائپ کی رازداری کی پالیسی + نیوپی پائپ منصوبہ آپ کی رازداری کو بہت سنجیدگی سے لیتا ہے. لہذا ، اپلی کیشن آپ کی رضامندی کے بغیر کسی بھی ڈیٹا کو جمع نہیں کرتا. +\nنیو پائپ کی رازداری کی پالیسی تفصیل سے وضاحت کرتی ہے کہ آپ کو کریش رپورٹ بھیجنے پر کیا ڈیٹا بھیجا جاتا ہے اور ذخیرہ کیا جاتا ہے ۔ + رازداری کی پالیسی پڑھیں + یورپی جنرل ڈیٹا پروٹیکشن ریگولیشن (جی ڈی پی آر) کی تعمیل کرنے کیلئے ، ہم آپ کی توجہ نیو پائپ کی رازداری کی پالیسی کی طرف مبذول کراتے ہیں۔ برائے مہربانی اسے غور سے پڑھیں۔ +\nہمیں بگ رپورٹ بھیجنے کے ل it آپ کو اسے قبول کرنا چاہئے۔ + قبول کریں + رد + کوئی حد نہیں + موبائل ڈیٹا کا استعمال کرتے وقت ریذولوشن کو محدود کریں + ایپ سوئچ کو کم سے کم کریں + اہم ویڈیو پلیئر سے دوسرے ایپ میں سوئچنگ کرتے وقت کارروائی-% s + کوئی نہیں + پس منظری پلیر میں کم کریں + پاپ اپ پلیر میں کم کریں + خاموشی کے دوران فاسٹ فارورڈ + قدم + ری سیٹ کریں + چینلز + پلے لسٹس + ویڈیو ذ + صارفین + اندراج خارج کریں + نیا ٹیب + ٹیب کا انتخاب کریں + صوت اشارہ کنٹرول + پلیر کی آواذکنٹرول کرنے کے لیے اشاروں کا استعمال کریں + چمک اشارہ کنٹرول + پلیرکی چمک کو کنٹرول کرنے کیلئے اشاروں کا استعمال کریں + ڈیفالٹ مواد کی زبان + تازہ کاریاں + فائل حذف ہوگئی + ایپ اپ ڈیٹ نوٹیفکیشن + نئے نیو پائپ ورژن کیلئے نوٹیفیکیشن + بیرونی اسٹوریج دستیاب نہیں + بیرونی ایسڈی کارڈ پر ڈاؤن لوڈ ممکن نہیں۔ ڈاؤن لوڈ فولڈر کے مقام کو دوبارہ ترتیب دیں؟ + پہلے سے طے شدہ ٹیبز کا استعمال ، محفوظ ٹیبز کو پڑھنے کے دوران خرابی + ڈیفالٹس بحال کریں + کیا آپ ڈیفالٹس کو بحال کرنا چاہتے ہیں؟ + صارفین کا شمار دستیاب نہیں + مرکزی صفحہ پر کون کون سے ٹیبزدکھائے جاہیں + انتخاب + تازہ ترین + جب نیا ورژن دستیاب ہوتا ہے تو فوری طور پر ایپ اپڈیٹ کے لئے ایک اطلاع دکھائیں + فہرست منظردیکھیں + فہرست + گرڈ + خودکار + منظر سوئچ کریں + نیو پائپ اپ ڈیٹ دستیاب! + ڈاؤن لوڈ کرنے کے لئے کلک کریں + ختم + ملتوی + موقوف + قطار میں کھڑا ہے + پوسٹ پروسیسنگ + قطار + سسٹم نےکارروائی سے انکار کیا گیا + ڈاؤن لوڈ ناکام + ڈاؤن لوڈ تکمیل + ٪ s ڈاؤن لوڈ مکمل ہوگئے + منفرد نام بنائیں + برتحریر + اس نام کے ساتھ ایک ڈاؤن لوڈ جاری ہے + غلطی دکھائیں + کوڈ + مقصود پوشہ نہیں بنایا جا سکتا + فائل نہیں بنائی جاسکتی ہے + نظام نے اجازت نہیں دی + محفوظ کنکشن ناکام + سرور نہیں مل سکا + سرور سے متصل نہیں ہوسکتا + سرور ڈیٹا نہیں بھیجتا ہے + سرور ملٹی تھریڈڈ ڈاؤن لوڈز کو قبول نہیں کرتا ہے ، @ سٹرنگ / msg_threads = 1 کے ساتھ دوبارہ کوشش کریں + درخواست کی گئی حد قابل اطمینان نہیں ہے + نہیں ملا + پوسٹ پروسیسنگ ناکام + ختم شدہ ڈاؤن لوڈ صاف کریں + اپنے% s زیر التوا منتقلیوں کو ڈاؤن لوڈز سے جاری رکھیں + رکو + زیادہ سے زیادہ دوبارہ کوشش کریں + ڈاؤن لوڈ منسوخ کرنے سے پہلے کوششوں کی زیادہ سے زیادہ تعداد + میٹرڈ نیٹ ورک پر مداخلت + موبائل ڈیٹا پر سوئچنگ کرتے وقت مفید ہے ، اگرچہ کچھ ڈاؤن لوڈ معطل نہیں ہوسکتی ہیں + تقریبات + کانفرنسیں + تبصرے دکھائیں + تبصرے دکھانا بند کرو + آٹوپلے + + تبصرے + + + کوئی تبصرہ نہیں + تبصرے لوڈ نہیں ہوسکے + بند کریں + پلے بیک دوبارہ شروع کریں + آخری پلے بیک پوزیشن بحال کریں + فہرست میں پوزیشن + فہرستوں میں پلے بیک پوزیشن کے اشارے دکھائیں + کوائف صاف کریں + دیکھنے کی تاریخ حذف ہوگئی۔ + پلے بیک پوزیشنیں حذف ہوگئیں۔ + فائل منتقل یا حذف ہوگئی + اس نام والی فائل پہلے سے موجود ہے + اس نام والی ڈاؤن لوڈ کی فائل پہلے سے موجود ہے + فائل برتحریر نہیں کر سکتا + اس نام کے ساتھ ڈاؤن لوڈ زیر التوا ہے + فائل پر کام کرنے کے دوران نیو پائپ بند کردی گئی تھی + آلے میں کوئی جگہ نہیں بچی + پیشرفت ختم ہوگئی ، کیونکہ فائل کو حذف کردیا گیا تھا + رابطے کا وقت ختم + کیا تمہیں یقین ہے؟ + ڈاؤن لوڈ کی قطار کو محدود کریں + ایک ڈاؤن لوڈ ایک ہی وقت میں چلے گا + ڈاؤن لوڈ شروع کریں + ڈاؤن لوڈ روکیں + کہاں پرڈاؤن لوڈ کریں + آپ سے پوچھا جائے گا کہ ہر ڈاؤن لوڈ کو کہاں محفوظ کرنا ہے + آپ سے پوچھا جائے گا کہ ہر ڈاؤن لوڈ کو کہاں محفوظ کرنا ہے۔ +\nاگر آپ کسی بیرونی SD کارڈ پر ڈاؤن لوڈ کرنا چاہتے ہیں تو SAF کا انتخاب کریں + SAF استعمال کریں + اسٹوریج ایکسیس فریم ورک ایک بیرونی SD کارڈ میں ڈاؤن لوڈ کی اجازت دیتا ہے۔ +\nنوٹ: کچھ آلات مطابقت نہیں رکھتے ہیں + پلے بیک پوزیشن حذف کریں + تمام پلے بیک پوزیشن حذف کریں + تمام پلے بیک پوزیشنوں کو حذف کریں ؟ + اثر لینے کے لئے ڈاؤن لوڈ فولڈرز کو تبدیل کریں + ٹوگل خدمت ، حالیہ منتخب: \ No newline at end of file From 65279b3364a7bd49dd380dafcc25ebdecb82e460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sun, 20 Oct 2019 00:14:23 +0000 Subject: [PATCH 054/270] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 95.0% (454 of 478 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 456da27fc..223b17c39 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -505,4 +505,5 @@ Sletter alle avspillingsposisjoner Slett alle avspillingsposisjoner\? Endre nedlastingsmapper for å benytte dette + Skru tjeneste av/på, for tiden er dette status: \ No newline at end of file From 3e7ff96445be973d4687f9e6e1f8ca2c9e4639a9 Mon Sep 17 00:00:00 2001 From: Enol P Date: Fri, 25 Oct 2019 19:10:18 +0000 Subject: [PATCH 055/270] Translated using Weblate (Asturian) Currently translated at 40.6% (194 of 478 strings) --- app/src/main/res/values-b+ast/strings.xml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml index b301e949f..6a5f6739f 100644 --- a/app/src/main/res/values-b+ast/strings.xml +++ b/app/src/main/res/values-b+ast/strings.xml @@ -87,7 +87,7 @@ Desaniciar Suma de comprobación Misión nueva - + Aceutar Filos Fallu @@ -286,7 +286,7 @@ Escartar - + ¿De xuru que quies desaniciar tolos elementos del historial\? Equí va apaecer dalgo ceo ;D @@ -360,8 +360,15 @@ Llimpieza de datos Vídeos - + Amosar comentarios Toca p\'alternar la so des/activación + ¿Desaniciar tol historial de gueta\? + Pa cumplir cola GDPR (Regulación Xeneral de Proteición de Datos) europea, pidímoste que revises la política de privacidá de NewPipe. Lléila con procuru +\n.Has aceutala unviándonos un informe de fallos. + Aición al cambiar a otra aplicación dende\'l reproductor de vídeos principal — %s + El númberu máximu d\'intentos enantes d\'encaboxar la descarga + Posiciones nes llistes + ¿De xuru\? \ No newline at end of file From 3787c6ec709a3da157e1783c505ed074c8599d3d Mon Sep 17 00:00:00 2001 From: Deleted User Date: Sat, 26 Oct 2019 20:47:51 +0000 Subject: [PATCH 056/270] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 97.7% (467 of 478 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 223b17c39..f1afc06e5 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -16,12 +16,12 @@ sideoppsett Bruk ekstern videoavspiller Bruk ekstern lydavspiller - Videoens nedlastingssti - Mappe lagrede nedlastinger skal puttes i - Skriv inn nedlastingssti for videoer + Video nedlasting mappe + Nedlastede videofiler lagres her + Velg nedlastingsmappe for videofiler Nedlastingsmappe for lyd - Nedlastet lyd lagres her - Skriv inn nedlastingssti for lydfiler + Nedlastede lydfiler lagres her + Velg nedlastingsmappe for lydfiler Forvalgt oppløsning Spill av med Kodi Kore-programmet ble ikke funnet. Installer det? @@ -44,7 +44,7 @@ Spill av Nettverksfeil Video-forhåndsvisning i miniatyrbilde - Miniatyrbilde for video-forhåndsvisning + Spill av video, varighet: Opplasterens avatar Nikk Nesetrekk @@ -105,7 +105,7 @@ Start Nytt mål Feilaktig nettadresse eller manglende internettilknytning - Velg nedlastingsmappe senere i innstillingene + Definer en nedlastingsmappe senere i innstillingene reCAPTCHA reCAPTCHA-oppgave Åpne i oppsprettsmodus @@ -121,9 +121,9 @@ Ja Senere Avskrudd - k + K M - Mrd. + B Denne tilgangen trengs for åpning i oppsprettsmodus reCAPTCHA-oppgave forespurt @@ -415,7 +415,7 @@ Merknader for nye NewPipe-versjoner Ekstern lagring utilgjengelig Nedlasting til eksternt SD-kort er ikke mulig. Tilbakestill plassering av nedlastingsmappe\? - Bruker forvalgte faner, feil under lagring av lagrede faner. + Bruker standard faner, feil under lesing av lagrede faner Gjenopprett forvalg Ønsker du å gjenopprette alt til forvalgene\? Abonnementsantall ikke tilgjengelig @@ -423,13 +423,13 @@ Utvalg Konferanser Oppdatering - Vis en merknad for spørring om programoppdatering når en ny versjon er tilgjengelig + Vis et varsel for å be om appoppdatering når en ny versjon er tilgjengelig Listevisningmodus Liste Rutenett Auto Veksle visning - Ny NewPipe-versjon tilgjengelig. + NewPipe-oppdatering tilgjengelig! Trykk for å laste ned Fullført pauset @@ -504,6 +504,6 @@ Slett avspillingsposisjoner Sletter alle avspillingsposisjoner Slett alle avspillingsposisjoner\? - Endre nedlastingsmapper for å benytte dette + Endre nedlastingsmappene for å tre i kraft Skru tjeneste av/på, for tiden er dette status: \ No newline at end of file From 1af8481fff598202d9a3be6696298afffe8dcf66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sun, 27 Oct 2019 22:19:03 +0000 Subject: [PATCH 057/270] Translated using Weblate (English) Currently translated at 99.8% (477 of 478 strings) --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb7adfe75..6297a8d0f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -247,7 +247,7 @@ Audio Retry Storage access permission denied - K + k M B Toggle service, currently selected: @@ -556,4 +556,4 @@ You will be asked where to save each download.\nChoose SAF if you want to download to an external SD card Use SAF The Storage Access Framework allows downloads to an external SD card.\nNote: some devices are not compatible - + \ No newline at end of file From 0f656aa7a9a26876b8b099f557a0a618f99a5872 Mon Sep 17 00:00:00 2001 From: naofum Date: Mon, 28 Oct 2019 13:48:34 +0000 Subject: [PATCH 058/270] Translated using Weblate (Japanese) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 481fa30be..1b80ff3ce 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -112,7 +112,7 @@ ブラック すべて チャンネル - K + k M B はい From c7699a5a3b6f23d3dd11421cd32e92c8d619ac46 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 28 Oct 2019 02:46:08 +0000 Subject: [PATCH 059/270] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index d49054f5e..c3823c2cf 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -114,7 +114,7 @@ 音訊 重試 無法存取儲存空間 - K + k 百萬 B 開始 From dc933a5e66b1c23b29efeccd181a9df833e073ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sun, 27 Oct 2019 05:12:50 +0000 Subject: [PATCH 060/270] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 97.5% (466 of 478 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index f1afc06e5..91f34d2fa 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -121,9 +121,9 @@ Ja Senere Avskrudd - K + k M - B + Mrd. Denne tilgangen trengs for åpning i oppsprettsmodus reCAPTCHA-oppgave forespurt @@ -415,7 +415,7 @@ Merknader for nye NewPipe-versjoner Ekstern lagring utilgjengelig Nedlasting til eksternt SD-kort er ikke mulig. Tilbakestill plassering av nedlastingsmappe\? - Bruker standard faner, feil under lesing av lagrede faner + Bruker forvalgte faner, feil under lesing av lagrede faner. Gjenopprett forvalg Ønsker du å gjenopprette alt til forvalgene\? Abonnementsantall ikke tilgjengelig @@ -423,13 +423,13 @@ Utvalg Konferanser Oppdatering - Vis et varsel for å be om appoppdatering når en ny versjon er tilgjengelig + Varsle om programoppdatering når en ny versjon er tilgjengelig Listevisningmodus Liste Rutenett Auto Veksle visning - NewPipe-oppdatering tilgjengelig! + Ny NewPipe-versjon tilgjengelig. Trykk for å laste ned Fullført pauset From 36c1e19ef0814fb8aaec1fa3a51db549a340e72c Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Tue, 29 Oct 2019 21:04:35 +0000 Subject: [PATCH 061/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 92c548532..75ed29932 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -131,9 +131,9 @@ Отключено Изменение размера Убирает звук в НЕКОТОРЫХ разрешениях - \\u0020млн - \\u0020млрд - \\u0020тыс. + " млн" + " млрд" + " тыс." Разрешение очереди всплывающего окна Помнить последние размер и позицию всплывающего окна Поисковые предложения From ae8f47500f0aa64bd22523d716d4932970e0363e Mon Sep 17 00:00:00 2001 From: Oguz Ersen Date: Tue, 29 Oct 2019 18:41:56 +0000 Subject: [PATCH 062/270] Translated using Weblate (Turkish) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-tr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index bb52d95d2..597189c7a 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -119,7 +119,7 @@ Yorumunuz (İngilizce): Ayrıntılar: Video ön izleme küçük resmi - K + b M B Bu izin, açılır pencere modunda @@ -505,6 +505,6 @@ Hizmeti değiştir, şu anda seçili olan: Videolar - + \ No newline at end of file From 5e2f373a57231f636ece1c05fd6355214894cea0 Mon Sep 17 00:00:00 2001 From: ssantos Date: Mon, 28 Oct 2019 19:36:01 +0000 Subject: [PATCH 063/270] Translated using Weblate (Portuguese) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-pt/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 027cd4a02..ecba237da 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -112,7 +112,7 @@ Canais Sim Depois - K + k M B Esta permissão é necessária From 0a66f5448710b80c01e87b71db1b782422ddc1e6 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Tue, 29 Oct 2019 21:13:51 +0000 Subject: [PATCH 064/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 75ed29932..7e4b693ba 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -73,7 +73,7 @@ Автовоспроизведение Воспроизводить видео при вызове NewPipe из другого приложения Контент - Видео с возрастными ограничениями. Разрешить подобный контент можно в \"Настройках\". + Видео с возрастными ограничениями. Разрешить подобный контент можно в \"Настройках\" Трансляция Загрузки Загрузки @@ -92,7 +92,7 @@ Информация: Что произошло: Подробности: - (Экспериментально) Загружать через Tor для повышения конфиденциальности (прямые трансляции пока не поддерживаются). + Загружать через Tor для повышения конфиденциальности (прямые трансляции пока не поддерживаются) Сообщить об ошибке Сообщить о нарушении Создана папка для загрузок \"%1$s\" @@ -480,9 +480,9 @@ Отключите, чтобы скрыть комментарии Автовоспроизведение - Комментарии - - + комментарий + комментария + комментариев Нет комментариев Не удалось загрузить комментарии From 98ae78eb06a0db6bbd83e20dc72cd59b4d263c85 Mon Sep 17 00:00:00 2001 From: Mitesh Sanjay Mutha Date: Thu, 31 Oct 2019 14:41:55 +0000 Subject: [PATCH 065/270] Translated using Weblate (Hindi) Currently translated at 92.3% (441 of 478 strings) --- app/src/main/res/values-hi/strings.xml | 123 +++++++++++++++++++++---- 1 file changed, 105 insertions(+), 18 deletions(-) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 9c3278d7c..4c04e929a 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -155,7 +155,7 @@ आपकी टिप्पणी: विवरण: विडियो के thumbnail के पूर्व दर्शन - विडियो के thumbnail के पूर्व दर्शन + वीडियो चलाये, समय : अपलोडर के thumbnail वाले फोटो पसंद नापसंद @@ -204,7 +204,7 @@ विवरण देखने के लिए दबाये कृपया इंतज़ार करे… क्लिपबोर्ड पर कॉपी हो गया है - कृपया उपलब्ध डाउनलोड फोल्डर को चुने + कृपया बाद में सेटिंग्स में डाउनलोड स्थान चुने पॉपअप के तरीके में खोलने के लिए अनुमति की जरुरत है reCAPTCHA reCAPTCHA चुनौती @@ -237,11 +237,11 @@ पॉपअप प्लेयर निकाले विवरण - कतार में खड़ा करे - कतार को बैकग्राउंड में लगाये - कतार को पॉपअप में लगाये + जोड़ने के लिए पकड़ें रहे + पृष्टभूमि की कतार में लगाए + नए पॉपअप कि कतार में लगाए यंहा से चलाना शुरू करे - बैकग्राउंड में चलाना शुरू करे + पृष्टभूमि में चलाना शुरू करे पॉपअप में चलाना शुरू करे स्ट्रीम करने के लिए प्लेयर उपलब्ध नहीं है (आप इसे चलाने के लिए VLC प्लेयर इंस्टॉल कर सकते हैं)। स्ट्रीम डाउनलोड करें @@ -293,19 +293,19 @@ हमेशा पूछें जानकारी प्राप्त की जा रही है… अनुरोधित सामग्री लोड कर रहे है - नई प्लेलिस्ट बनाएं - प्लेलिस्ट हटाएं - प्लेलिस्ट का नाम बदलें + नई प्लेलिस्ट + हटाएं + नाम बदलें नाम प्लेलिस्ट में जोड़ें प्लेलिस्ट थंबनेल के रूप में सेट करें प्लेलिस्ट बुकमार्क करें बुकमार्क हटायें - क्या आप इस प्लेलिस्ट को हटाना चाहते हैं? + प्लेलिस्ट को हटाना चाहते हैं\? सूची बना दी गई प्लेलिस्ट में जोड़ा गया - प्लेलिस्ट का थंबनेल बदल दिया गया है - सूची हटाने में असफल + प्लेलिस्ट का थंबनेल बदल दिया गया है। + सूची हटाने में असफल। कोई कैप्शन नहीं है फिट भरें @@ -331,7 +331,7 @@ क्स उपभोगता देखे हुए वीडियो की सूची साफ करें - चलाये गए स्ट्रीम का इतिहास साफ करता है + चलाये गए स्ट्रीम के इतिहास और प्लेबैक स्थानों को साफ करता है देखे गए सभी का इतिहास साफ करें\? देखे हुए का इतिहास साफ कर दिया गया। ढूंढने के इतिहास को साफ करें @@ -351,7 +351,7 @@ \nन्यूपाइप की गोपनीयता नीति विस्तार से समज़ाती है कि कोनसा डेटा भेजा या संग्रह किया जाता है जब आप क्रेश विवरण भेजते है। गोपनीयता नीति पढ़े क्या आप सेटिंग्स भी आयात करना चाहते है? - पसंदीदा \'खोलने पर\' करवाई + पसंदीदा \'खोलने\' कि प्रक्रिया सामग्री खोलते समय डिफ़ॉल्ट कारवाही — %s केप्सन प्लेयर केप्शन के शब्दों का परिमाण और पृष्ठभूमि शैलियो को बदले। लागू करने के लिए ऐप को पुनः प्रारम्भ करना जरूरी है। @@ -384,16 +384,103 @@ स्वीकारे अस्वीकार करे असीमित - मोबाइल डेटा उपयोग करते समय रेसॉल्युसेन मर्यादित करे + मोबाइल डेटा उपयोग करते समय रेसॉल्युसेन को सिमित करे ऐप बदलते समय मिनिमाइज करे - "कार्यवाई, मुख्य वीडियो चालक से दूसरी ऐप पर जाने पर — %s" + "मुख्य वीडियो चालक से दूसरी ऐप पर जाने पर — %s" कोई नही - पृष्ठभूमि प्लेयर जैसे मिनिमाइज करे - पॉप अप प्लेयर जैसे मिनिमाइज करे + पृष्ठभूमि प्लेयर में बदले + पॉप अप प्लेयर में बदले न्यूपाइप एक काॅपीलेफ़्ट फ़्री साॅफ़्टवेर है: इसे आप अपनी इच्छा के अनुसार इस्तेमाल, जाँच, बाँट तथा और बेहतर बना सकते है। खास तौर पर आप इसे फ़्री साॅफ़्टवेर फ़ाउंडेशन के द्वारा जारी जीएनयू जनरल पब्लिक लाइसेंस के तीसरे या उसके बाद आने वाले कोई भी वर्णन के शर्तों के मुताबिक फिर से बाँट या बदल सकते हैं। सदस्यता वापस ले ली नया टॅब टॅब चुने वॉल्यूम नियंत्रण कतार + प्लेयर का वॉल्यूम बढ़ने के लिए इशारो का इस्तेमाल करे + चमक बदलने का इशारा + प्लेयर की चमक बदलने के लिए इशारो का इस्तेमाल करे + अपडेट + फाइल मिटा दी गयी + अप्प अपडेट अधिसूचना + नए NewPipe अपडेट की अधिसूचना + एक्सटर्नल स्टोरेज अनुपलब्ध + SD कार्ड पर डाउनलोड करना संभव नहीं।डाउनलोड स्थान पुनः चुने\? + सामान्य चुनाव पर लौटे + क्या आप सामान्य चुनाव पर लौटना चाहते है\? + अभिदाता संख्या अनुपलब्ध + मुख्य पृष्ठ पर कौन से टैब दिखाए जाते हैं + चयन + अपडेट + सूची + दृश्य बदले + NewPipe अपडेट उपलब्ध! + डाउनलोड करने के लिए टैप करें + समाप्त + अपूर्ण + रोका हुआ + कतार में + प्रक्रिया के बाद + कार्य सिस्टम द्वारा अस्वीकार + डाउनलोड विफल रहा + डाउनलोड समाप्त + %s डाउनलोड समाप्त + अनोखा नाम बनाये + ऊपर लिखना + इस नाम का एक डाउनलोड चालू है + कोड + गंतव्य फ़ोल्डर नहीं बनाया जा सकता + फ़ाइल नहीं बनाई जा सकती + सिस्टम द्वारा अनुमति से इनकार किया गया + सुूरक्षित कनेक्शन विफल + सर्वर नहीं ढूँढ सका + सर्वर से जुड़ नहीं सकता + सर्वर डेटा नहीं भेजता है + अनुरोधित सीमा संतोषजनक नहीं है + नहीं मिला + प्रक्रिया के बाद का कार्य विफल रहा + समाप्त डाउनलोड साफ़ करें + रुको + अधिकतम पुनर्प्रयास + डाउनलोड रद्द करने से पहले प्रयासों की अधिकतम संख्या + मीटर्ड नेटवर्क पर रोके + मोबाइल डाटा का इस्तेमाल करते समय उपयोगी है, परन्तु कुछ डौन्लोडस रोके नहीं जा सकते है + घटनायें + सम्मेलनों + टिप्पणियाँ दिखाए + टिप्पणियाँ न देखने के लिए बंद करे + ऑटोप्ले + + टिप्पणियाँ + + + कोई टिपण्णी नहीं + टिप्पणियाँ लोड नहीं कर सका + बंद करे + फिर से शुरू करे + आखरी प्लेबैक स्थान पर लौटे + सूचियों में स्थान + प्लेबैक स्थान निशान सूचियों में दिखाए + डाटा मिटायें + प्लेबैक स्थान मिटा दिए गए + फाइल की जगह बदली गयी या फिर फाइल मिटा दी गयी + इस नाम की कोई फ़ाइल पहले से मौजूद है + इस नाम की एक डाउनलोड की गई फ़ाइल पहले से मौजूद है + फाइल के ऊपर नहीं लिख सकते + इस नाम का एक डाउनलोड बाकी है + फ़ाइल पर कार्य करते समय NewPipe बंद किया गया + डिवाइस पर जगह समाप्त + प्रगति खो गई, क्योंकि फ़ाइल मिटा दी गई थी + कनेक्शन का समय समाप्त + क्या आपको यकीन है\? + डाउनलोड कतार सीमित करें + एक ही समय में एक डाउनलोड चलेगा + डाउनलोड प्रारंभ करें + डाउनलोड रोकें + डोलोड कहाँ करने के लिए पूछे + आपको हर डाउनलोड का स्थान पूछा जाएगा + SAF का उपयोग करें + प्लेबैक स्थानों को मिटाये + सारे प्लेबैक स्थानों को मिटाये + सारे प्लेबैक स्थानों को मिटाये\? + प्रभावी होने के लिए डाउनलोड फ़ोल्डर बदलें \ No newline at end of file From 6011dec272abc16bf05635bb1c81a36c2fa1e0c1 Mon Sep 17 00:00:00 2001 From: Adolfo Jayme Barrientos Date: Wed, 30 Oct 2019 18:09:07 +0000 Subject: [PATCH 066/270] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d5ecfe56f..887cbe7ff 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -122,7 +122,7 @@ Sim Depois Desativado - M + k M B Essa permissão é necessária para From fd4a4d979a92297a524ef5aa8cda080d7ed348a4 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Tue, 29 Oct 2019 21:37:51 +0000 Subject: [PATCH 067/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 7e4b693ba..5da31c0b9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -309,8 +309,8 @@ Экспорт завершён Импорт завершён Нет верного Zip-файла - Внимание: не все файлы были импортированы. - Текущие данные будут заменены. + Внимание: не все файлы были импортированы + Текущие данные будут заменены Скоро здесь кое-что появится ;D Всегда спрашивать Создать плейлист From 0790a43aa226bcc6bcfd33d4546bec8dfed8da60 Mon Sep 17 00:00:00 2001 From: Enol P Date: Fri, 1 Nov 2019 19:37:57 +0000 Subject: [PATCH 068/270] Translated using Weblate (Asturian) Currently translated at 47.1% (225 of 478 strings) --- app/src/main/res/values-b+ast/strings.xml | 66 +++++++++++++++-------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml index 6a5f6739f..820ede1bd 100644 --- a/app/src/main/res/values-b+ast/strings.xml +++ b/app/src/main/res/values-b+ast/strings.xml @@ -43,7 +43,7 @@ - + Conteníu Conteníu torgáu pola edá EN DIREUTO @@ -124,7 +124,7 @@ reCAPTCHA Retu de reCAPTCHA Solicitóse\'l retu de reCAPTCHA - + En segundu planu Ventanu Resolución predeterminada del ventanu Amosar resoluciones más altes @@ -137,11 +137,11 @@ Usa xestos pa controlar el brilléu y volume del reproductor - - + Guetar suxerencies + Amuesa suxerencies al guetar - + Soscribise @@ -149,7 +149,7 @@ Soscripciones Qué hai nuevo - + Historial de gueta @@ -177,7 +177,7 @@ Descarga - + Caráuteres almitíos nos nomes de ficheros Lletres y díxitos @@ -206,8 +206,8 @@ L\'historial ta baleru - - + ¿Quies desaniciar esti elementu del historial de gueta\? + Reproducir too Nun pudo reproducise esti fluxu Asocedió un fallu irrecuperable del reproductor @@ -219,20 +219,20 @@ - + Esbilla d\'un quioscu Quioscu Tendencies - + Destácase Detalles - + Novedaes - + [Desconozse] @@ -276,11 +276,11 @@ Amosar la información - Llistes de reproducción en marcadores + Llistes de reproducción - + Crear Escartar @@ -290,11 +290,11 @@ Equí va apaecer dalgo ceo ;D - + Llista nueva de repoducción Nome - + Amestar a una llista de repoducción @@ -324,7 +324,7 @@ Llingüeta nueva Usa xestos pa controlar el volume del reproductor Usa xestos pa controlar el brilléu del reproductor - Valores predeterminaos + Reafitar El númberu de soscriptores nun ta disponible Esbilla Anovamientos @@ -360,15 +360,39 @@ Llimpieza de datos Vídeos - + Amosar comentarios Toca p\'alternar la so des/activación ¿Desaniciar tol historial de gueta\? - Pa cumplir cola GDPR (Regulación Xeneral de Proteición de Datos) europea, pidímoste que revises la política de privacidá de NewPipe. Lléila con procuru -\n.Has aceutala unviándonos un informe de fallos. + Pa cumplir cola GDPR (Regulación Xeneral de Proteición de Datos) europea, pidímoste que revises la política de privacidá de NewPipe. Lléila con procuru. +\nHas aceutala unviándonos un informe de fallos. Aición al cambiar a otra aplicación dende\'l reproductor de vídeos principal — %s El númberu máximu d\'intentos enantes d\'encaboxar la descarga Posiciones nes llistes ¿De xuru\? + Importar el ficheru + Esportación anterior + Importa les soscripciones de YouTube baxando\'l ficheru d\'esportación: +\n +\n1.- Vete a esta URL: %1$s +\n2.- Anicia sesión cuando se te pida +\n3.- Debería aniciar una descarga (que ye\'l ficheru d\'esportación) + Importa un perfil de SoundCloud teclexando la URL o la ID de to: +\n +\n1.- Activa\'l mou escritoriu nun restolador web (el sitiu nun ta disponible pa móviles) +\n2.- Vete a esta URL: %1$s +\n3.- Anicia sesión cuando se te pida +\n4.- Copia la URL del perfil al que se te redirixa. + LaToID, soundcloud.com/latoid + Cargar miniatures + Desactiva esta opción pa evitar la carga de miniatures, aforrar datos y usu de la memoria. Los cambeos van llimpiar la memoria y la caché d\'imáxenes. + Minimizar al cambiar a otra aplicación + Minimizar al reproductor en segundu planu + Minimizar al reproductor en ventanu + Desoscribise + Escoyeta d\'una llingüeta + Les llingüetes que s\'amuesen na páxina principal + Siguir cola reproducción + Entrugar ánde baxar \ No newline at end of file From 19738c0c1bd1be25d429e2404f92c1b41a9f8224 Mon Sep 17 00:00:00 2001 From: Mitosagi Date: Sat, 2 Nov 2019 06:35:33 +0000 Subject: [PATCH 069/270] Translated using Weblate (Japanese) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ja/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 1b80ff3ce..ac275ba5c 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -85,7 +85,7 @@ 開始 一時停止 再生 - 削除する + 削除 チェックサム 新しいミッション OK @@ -244,7 +244,7 @@ これにより、現在の設定が上書きされます。 バックグラウンド再生 ここから再生を開始 - バックグランドで再生を開始 + バックグランドで連続再生を開始 ドロワーを開く ドロワーを閉じる 動画プレイヤー @@ -284,7 +284,7 @@ 変更 プレイリスト名 プレイリストに追加 - プレイリストのサムネイルとして設定 + プレイリストのサムネイルに設定 プレイリストをブックマーク ブックマークを削除 このプレイリストを削除しますか? @@ -348,10 +348,10 @@ トラック NewPipe バックグラウンドおよびポップアップのプレイヤーの通知 新着 & 人気 - 長押ししてキューに入れる - バックグラウンドでキューに入れる - ポップアップでキューに入れる - 新しいポップアップで再生を開始 + 長押ししてキューに追加 + バックグラウンドのキューに追加 + ポップアップのキューに追加 + ポップアップで連続再生を開始 すぐにここに表示されます;D お好みの \'開く\' アクション コンテンツを開くときのデフォルト動作 — %s From e05def491ffe97908af8a1c7e7417feb8a2b2d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 2 Nov 2019 14:17:33 +0000 Subject: [PATCH 070/270] Translated using Weblate (Hindi) Currently translated at 92.3% (441 of 478 strings) --- app/src/main/res/values-hi/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 4c04e929a..24506df84 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -386,7 +386,7 @@ असीमित मोबाइल डेटा उपयोग करते समय रेसॉल्युसेन को सिमित करे ऐप बदलते समय मिनिमाइज करे - "मुख्य वीडियो चालक से दूसरी ऐप पर जाने पर — %s" + मुख्य वीडियो चालक से दूसरी ऐप पर जाने पर — %s कोई नही पृष्ठभूमि प्लेयर में बदले पॉप अप प्लेयर में बदले @@ -451,7 +451,7 @@ ऑटोप्ले टिप्पणियाँ - + कोई टिपण्णी नहीं टिप्पणियाँ लोड नहीं कर सका From 59f90fcdfe3bb1218b93b24c582e0d514c80c418 Mon Sep 17 00:00:00 2001 From: WaldiS Date: Thu, 31 Oct 2019 19:06:09 +0000 Subject: [PATCH 071/270] Translated using Weblate (Polish) Currently translated at 99.8% (477 of 478 strings) --- app/src/main/res/values-pl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ea966390d..8b4073d99 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -133,7 +133,7 @@ Odśwież Wyczyść Zmiana rozmiaru - K + k M B To pozwolenie jest wymagane, aby From 7c70033f190272de97a702ce4cb67044665b3fd2 Mon Sep 17 00:00:00 2001 From: Adolfo Jayme Barrientos Date: Wed, 30 Oct 2019 03:45:17 +0000 Subject: [PATCH 072/270] Translated using Weblate (Spanish) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-es/strings.xml | 92 +++++++++++++------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f6771d26c..c9c182e6e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -39,8 +39,8 @@ Contenido no disponible Usar Tor (Experimental) Forzar la descarga a través de Tor para una mayor privacidad (transmisión de vídeos aún no compatible). - No se puede crear la carpeta de descarga \'%1$s\' - Carpeta de descarga creada \'%1$s\' + No se puede crear la carpeta de descargas «%1$s» + Se creó la carpeta de descargas «%1$s» Los archivos de audio descargados se guardarán aquí Elegir carpeta de descarga para archivos de audio Carpeta de descarga de audio @@ -53,15 +53,15 @@ No se pudo analizar el sitio web Mostrar videos \'siguientes\' y \'similares\' Idioma del contenido por defecto - Vista previa del vídeo - Reproducir vídeo, duración: + Miniatura de previsualización del vídeo + Reproducir vídeo; duración: Me gusta No me gusta Miniatura del avatar del usuario - Las transmisiones en vivo aún no están soportadas + Aún no se admiten las transmisiones en vivo Contenido Contenido restringido por edad - Mostrar vídeo restringido por edad. Permitir este contenido es posible desde Ajustes. + "Mostrar vídeo restringido por edad. Se puede permitir este tipo de material desde Ajustes." Toque en buscar para empezar Reproducción automática Reproducir un vídeo cuando NewPipe es llamado desde otra app @@ -90,20 +90,20 @@ Pausar Reproducir Eliminar - Checksum - Nueva misión - OK + Suma de comprobación + Misión nueva + Aceptar Nombre del archivo - Hilos + Subprocesos Error - Servidor no soportado + Servidor incompatible El archivo ya existe - URL mal formada o Internet no disponible + URL mal escrito o Internet no disponible NewPipe está descargando Toque para ver detalles - Por favor espere… - Copiado al portapapeles - Por favor, defina un directorio de descarga más tarde en los ajustes + Espere… + Copiado en el portapapeles + Defina una carpeta de descargas más tarde en la configuración No se pudo cargar la imagen La interfaz de la app dejó de funcionar Lo sucedido:\\nPetición:\\nIdioma del contenido:\\nServicio:\\nHora GMT:\\nPaquete:\\nVersión:\\nVersión del SO: @@ -112,16 +112,16 @@ Canal Después - K + k M MM reCAPTCHA - Abrir en modo popup - Este permiso es necesario para -abrir en modo popup + Abrir en modo emergente + Se necesita este permiso +\npara abrir en modo emergente Reto reCAPTCHA Reto reCAPTCHA requerido - Modo popup de NewPipe + Modo emergente de NewPipe Reproduciendo en modo popup Formato de vídeo por defecto Desactivado @@ -153,10 +153,10 @@ abrir en modo popup Acerca de Colaboradores Licencias - Streaming gratuito y ligero en Android. + Reproducción de transmisiones ligera y libre para Android. Ver en GitHub Licencia de NewPipe - Si tienes ideas de; traducción, cambios de diseño, limpieza de código o grandes cambios de código—la ayuda siempre es bienvenida. Cuanto más se hace, mejor se pone! + Si puede traducir, quiere cambiar el diseño, tiene ideas para mejorar el código o proponer mejoras grandes, siempre agradeceremos su ayuda. ¡Mientras más manos participen, mejor! Leer licencia Contribuir Suscribirse @@ -174,14 +174,14 @@ abrir en modo popup Los caracteres no válidos se reemplazan por este valor Carácter de reemplazo Letras y dígitos - La mayoría de caracteres especiales + La mayoría de los caracteres especiales Historial de búsqueda Almacenar búsquedas localmente Historial de vistas Almacenar historial de vídeos vistos Historial - Buscado - Visto + Búsquedas + Reproducciones Historial desactivado Historial El historial está vacío @@ -246,10 +246,10 @@ abrir en modo popup Nuevo y popular Mantener presionado para agregar a la cola Donar - NewPipe es desarrollado por voluntarios que emplean su tiempo para brindarte la mejor experiencia. Devuelve el favor para ayudar a los desarrolladores a crear un NewPipe aún mejor mientras disfrutan de una taza de café. + NewPipe es desarrollado por voluntarios que emplean su tiempo libre para brindarle la mejor experiencia. Haga una aportación para ayudarlos a crear un NewPipe aún mejor mientras disfrutan de una taza de café. Donar - Página web - Visita el sitio web de NewPipe para más información y noticias. + Sitio web + Visite el sitio web de NewPipe para más información y noticias. País del contenido por defecto Alternar orientación Cambiar a segundo plano @@ -266,13 +266,13 @@ abrir en modo popup No se encontraron transmisiones de vídeo No se encontraron transmisiones de audio Reproductor de vídeo - Reproductor de fondo - Reproductor de popup + Reproductor en segundo plano + Reproductor emergente Obteniendo información… Cargando contenido solicitado Importar base de datos Exportar base de datos - Reemplaza tu historial actual y suscripciones + Anula su historial actual y suscripciones Exportar historial, suscripciones y listas de reproducción Exportado Importado @@ -280,7 +280,7 @@ abrir en modo popup ADVERTENCIA: no se pudieron importar todos los archivos. Esto reemplazará su configuración actual. Descargar archivo de stream - Mostrar info + Mostrar información "Listas de reproducción en marcadores " Añadir a Arrastrar para reordenar @@ -288,15 +288,15 @@ abrir en modo popup Eliminar uno Eliminar todos Descartar - Renombrar + Cambiar nombre ¿Desea eliminar este elemento del historial de reproducciones? ¿Seguro que desea eliminar todos los elementos del historial? Última reproducción Más reproducido Preguntar siempre - Nueva lista de reproducción + Lista de reproducción nueva Eliminar - Renombrar + Cambiar nombre Nombre Añadir a la lista de reproducción Definir como miniatura de lista de reproducción @@ -324,7 +324,7 @@ abrir en modo popup Añadir de forma automática un vídeo relacionado con el último vídeo reproducido en una cola no repetitiva Archivo Archivo movido o eliminado - No existe el directorio + La carpeta no existe No existe la fuente del archivo/contenido El archivo no existe o carece de los permisos para leer o escribir en él El nombre del archivo no puede estar vacío @@ -365,9 +365,9 @@ abrir en modo popup Tono Desenganchar (puede causar distorsión) No hay streams disponibles para descargar - Acción \'abrir\' preferida - Acción por defecto al abrir contenido — %s - No hay ninguna app instalada para reproducir este archivo + Acción de apertura preferida + Acción predeterminada al abrir contenido: %s + No se encontró ninguna aplicación que reproduzca este archivo Subtítulos Modificar la escala de texto de los subtítulos y los estilos de fondo. Requiere reiniciar la app para que surta efecto. Borrar historial de reproducciones @@ -378,12 +378,12 @@ abrir en modo popup Elimina el historial de palabras clave de búsqueda ¿Eliminar todo el historial de búsqueda\? Historial de búsquedas eliminado. - 1 elemento eliminado. + Se eliminó 1 elemento. NewPipe es un software copyleft libre: puedes usarlo, estudiarlo, compartirlo y mejorarlo a voluntad. Específicamente, puedes redistribuirlo y/o modificarlo bajo los términos de la Licencia Pública General GNU publicada por la Free Software Foundation, ya sea la versión 3 de la Licencia, o (a tu elección) cualquier versión posterior. ¿Desea importar también los ajustes? - Política de Privacidad de NewPipe - El proyecto NewPipe toma su privacidad muy en serio. Por lo tanto, la aplicación no recopila ningún dato sin su consentimiento. La política de privacidad de NewPipe explica en detalle qué datos se envían y almacenan cuando envía un informe de fallas. - Leer la Política de Privacidad + Normativa de privacidad de NewPipe + El proyecto NewPipe toma su privacidad muy en serio. Por ello, la aplicación no recopila ningún dato sin su consentimiento. La normativa de privacidad de NewPipe explica en detalle qué datos se envían y almacenan cuando envía un informe de fallo. + Leer la normativa de privacidad Para cumplir con el Reglamento general europeo de protección de datos (GDPR), podemos llamar su atención sobre la política de privacidad de NewPipe. Por favor léelo cuidadosamente. Debe aceptarlo para enviarnos el informe de error. Aceptar Declinar @@ -446,7 +446,7 @@ abrir en modo popup No se puede conectar con el servidor El servidor no devolvio datos El servidor no acepta descargas multi-hilos, intente de nuevo con @string/msg_threads = 1 - No se logro obtener el rango solicitado + No se puede satisfacer el intervalo seleccionado No encontrado Fallo el post-procesado NewPipe se cerro mientras se trabajaba en el archivo @@ -460,8 +460,8 @@ abrir en modo popup Usar SAF El Framework de Acceso al Almacenamiento permite descargar en la tarjeta SD externa.\nNota: Algunos los dispositivos no son compatibles Cancelar suscripción - Nueva pestaña - Elige la pestaña + Pestaña nueva + Elija la pestaña Control de volumen por gestos Usar gestos para controlar el volumen del reproductor Control de brillo por gestos From e25c5544baeb01f06c208f2537bc95be6a856aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89frit?= Date: Thu, 31 Oct 2019 22:43:43 +0000 Subject: [PATCH 073/270] Translated using Weblate (French) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-fr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 20df29139..3b2b15a38 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -118,7 +118,7 @@ Plus tard Désactivé Quoi :\\nRequête :\\nLangue du contenu :\\nService :\\nHeure UTC :\\nPaquet :\\nVersion :\\nVersion du système d’exploitation : - K + k M Cette autorisation est nécessaire pour \nutiliser le mode flottant From 2839154b6817e659992f39821e437b7de024c4c7 Mon Sep 17 00:00:00 2001 From: Joseph Kim Date: Sat, 2 Nov 2019 00:16:44 +0000 Subject: [PATCH 074/270] Translated using Weblate (Korean) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ko/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b1b3b3827..8e3d28a00 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -31,7 +31,7 @@ 기본 컨텐츠 언어 비디오 & 오디오 비디오 미리보기 썸네일 - 비디오 미리보기 썸네일 + 비디오 재생, 구간: 업로더 썸네일 싫어요 좋아요 @@ -501,4 +501,7 @@ 동영상만 + 하나의 다운로드가 동시에 진행됩니다 + 적용하려면 다운로드 폴더를 변경하세요 + 서비스 토글, 현재 선택된 서비스: \ No newline at end of file From 2ce0caa943dff5ff4430aa8a7d27cd693da0bd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 2 Nov 2019 14:24:48 +0000 Subject: [PATCH 075/270] Translated using Weblate (Spanish) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-es/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c9c182e6e..966dce5a2 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -61,7 +61,7 @@ Aún no se admiten las transmisiones en vivo Contenido Contenido restringido por edad - "Mostrar vídeo restringido por edad. Se puede permitir este tipo de material desde Ajustes." + Mostrar vídeo restringido por edad. Se puede permitir este tipo de material desde Ajustes. Toque en buscar para empezar Reproducción automática Reproducir un vídeo cuando NewPipe es llamado desde otra app From 137fabee0e72e40ed6a8a7cc79ecd97f40ec3d4e Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sun, 28 Apr 2019 17:43:50 -0300 Subject: [PATCH 076/270] Update Gradle and introduce Kotlin --- app/build.gradle | 9 +++++++-- build.gradle | 4 +++- gradle/wrapper/gradle-wrapper.properties | 3 +-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 792893b1b..5477a11a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,7 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' android { compileSdkVersion 28 @@ -53,6 +56,8 @@ ext { } dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude module: 'support-annotations' }) @@ -92,10 +97,10 @@ dependencies { implementation "androidx.room:room-runtime:${roomDbLibVersion}" implementation "androidx.room:room-rxjava2:${roomDbLibVersion}" - annotationProcessor "androidx.room:room-compiler:${roomDbLibVersion}" + kapt "androidx.room:room-compiler:${roomDbLibVersion}" implementation "frankiesardo:icepick:${icepickLibVersion}" - annotationProcessor "frankiesardo:icepick-processor:${icepickLibVersion}" + kapt "frankiesardo:icepick-processor:${icepickLibVersion}" debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}" diff --git a/build.gradle b/build.gradle index 60a85021a..5ceffb6e7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.50' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.0' + classpath 'com.android.tools.build:gradle:3.5.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e98d29bd8..d757f3d33 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Oct 07 06:29:33 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip From 114a7ccdd420db7098fe996af28f00eefbb3ee12 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Mon, 4 Nov 2019 11:21:22 +0100 Subject: [PATCH 077/270] Make "Default Kiosk" string translatable Closes #2778 --- .../org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java | 4 ++-- app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 67295d8a5..4297fb13e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -231,7 +231,7 @@ public class ChooseTabsFragment extends Fragment { break; case DEFAULT_KIOSK: if (!tabList.contains(tab)) { - returnList.add(new ChooseTabListItem(tab.getTabId(), "Default Kiosk", + returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.default_kiosk_page_sumatry), ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot))); } break; @@ -317,7 +317,7 @@ public class ChooseTabsFragment extends Fragment { tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab).getChannelServiceId()) + "/" + tabName; break; case DEFAULT_KIOSK: - tabName = "Default Kiosk"; + tabName = requireContext().getString(R.string.default_kiosk_page_sumatry); break; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6297a8d0f..07ffdb292 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -366,6 +366,7 @@ Selection Blank Page Kiosk Page + Default Kiosk Subscription Page Feed Page Channel Page From 504f45efa67c37c490b01aa78c3133ff37ba4228 Mon Sep 17 00:00:00 2001 From: Adolfo Jayme Barrientos Date: Mon, 4 Nov 2019 00:37:26 +0000 Subject: [PATCH 078/270] Translated using Weblate (Spanish) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-es/strings.xml | 132 ++++++++++++------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 966dce5a2..bc95663d1 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2,7 +2,7 @@ %1$s vistas Publicado el %1$s - No se encontró ningún reproductor de vídeo. ¿Desea instalar VLC? + No se encontró ningún reproductor de retransmisiones. ¿Quiere instalar VLC\? Instalar Cancelar Abrir en el navegador @@ -13,21 +13,21 @@ ¿Quiso decir: %1$s? Compartir con Elegir navegador - rotación + giro Carpeta de descarga de vídeo Aquí se almacenan los vídeos descargados Elegir carpeta de descarga para vídeos Cambie las carpetas de descarga para que tenga efecto - Resolución por defecto de vídeo + Resolución predeterminada Reproducir con Kodi - Aplicación Kore no encontrada. ¿Instalarla? - Mostrar opción \"Reproducir con Kodi\" - Mostrar una opción para reproducir vídeo en Kodi + No se encontró la aplicación Kore. ¿Quiere instalarla\? + Mostrar opción «Reproducir con Kodi» + Mostrar una opción para reproducir vídeo en el centro multimedia Kodi Audio - Formato de audio por defecto + Formato de audio predeterminado Descargar Siguiente - URL no soportada + No se admite el URL Usar reproductor de vídeo externo Usar reproductor de audio externo Tema @@ -41,7 +41,7 @@ (Experimental) Forzar la descarga a través de Tor para una mayor privacidad (transmisión de vídeos aún no compatible). No se puede crear la carpeta de descargas «%1$s» Se creó la carpeta de descargas «%1$s» - Los archivos de audio descargados se guardarán aquí + Aquí se almacenan los audios descargados Elegir carpeta de descarga para archivos de audio Carpeta de descarga de audio Vídeo y audio @@ -51,8 +51,8 @@ No se pudo cargar las miniaturas No se pudo descifrar la URL del vídeo No se pudo analizar el sitio web - Mostrar videos \'siguientes\' y \'similares\' - Idioma del contenido por defecto + Mostrar videos «siguientes» y «similares» + Idioma predeterminado del contenido Miniatura de previsualización del vídeo Reproducir vídeo; duración: Me gusta @@ -68,7 +68,7 @@ EN DIRECTO Descargas Descargas - Reportar error + Informe de error No se pudo analizar el sitio web completamente No se pudo configurar el menú de descarga No se pudo obtener ninguna transmisión @@ -122,23 +122,23 @@ Reto reCAPTCHA Reto reCAPTCHA requerido Modo emergente de NewPipe - Reproduciendo en modo popup - Formato de vídeo por defecto + Reproduciendo en modo emergente + Formato de vídeo predeterminado Desactivado Mostrar resoluciones más altas Solo algunos dispositivos soportan reproducción de vídeos en 2K/4K - Resolución por defecto del popup + Resolución predeterminada de emergente Segundo plano Popup Filtro Actualizar Limpiar - Recordar tamaño y posición del popup - Recordar el último tamaño y posición del popup - Popup + Recordar tamaño y posición del repr. emerg. + Recordar el último tamaño y posición del repr. emerg. + Emergente Redimensionando Elimina el audio en ALGUNAS resoluciones - Controles de gestos del reproductor + Controles del reproductor por gestos Usar gestos para controlar el brillo y volumen del reproductor Sugerencias de búsqueda Mostrar sugerencias cuando esté buscando @@ -175,22 +175,22 @@ Carácter de reemplazo Letras y dígitos La mayoría de los caracteres especiales - Historial de búsqueda + Historial de búsquedas Almacenar búsquedas localmente Historial de vistas Almacenar historial de vídeos vistos Historial Búsquedas Reproducciones - Historial desactivado + El historial está desactivado Historial El historial está vacío - Historial borrado + Se vació el historial Notificación de NewPipe - Notificaciones para NewPipe en segundo plano y reproductores popup + Notificaciones de reproductores en segundo plano o emergentes de NewPipe Reproductor Funcionamiento - Historial y caché + Historial y antememoria Lista de reproducción Deshacer No hay resultados @@ -210,13 +210,13 @@ %s vídeo %s vídeos - Elemento eliminado - ¿Desea eliminar este elemento del historial de búsqueda? + Se eliminó el elemento + ¿Quiere eliminar este elemento del historial de búsquedas\? Contenido de la página principal Página en blanco Página del kiosco Página de suscripción - Página de feed + Página de novedades Página del canal Seleccione un canal No hay suscripciones a canales todavía @@ -224,9 +224,9 @@ Kiosco Tendencias Top 50 - Mostrar sugerencia cuando se presiona el botón de segundo plano o popup en la página de detalles del vídeo - En cola en el reproductor de fondo - En cola en el reproductor popup + Mostrar sugerencia cuando se presiona el botón de 2.º plano o emergente en la página de detalles del vídeo + En cola en el reproductor de 2.º plano + En cola en el reproductor emergente Reproducir todo No se pudo reproducir este stream Se produjo un error irrecuperable del reproductor @@ -242,7 +242,7 @@ Comenzar a reproducir aquí Comenzar a reproducir en segundo plano Comenzar a reproducir en un popup nuevo - Muestra el consejo \"Manten para poner en la cola\" + Mostrar consejo «Mantener pulsado para añadir» Nuevo y popular Mantener presionado para agregar a la cola Donar @@ -250,17 +250,17 @@ Donar Sitio web Visite el sitio web de NewPipe para más información y noticias. - País del contenido por defecto + País predeterminado del contenido Alternar orientación Cambiar a segundo plano - Cambiar a popup + Cambiar a emergente Cambiar a principal Servicio Abrir cajón Cerrar cajón - No se ha encontrado ningún reproductor de vídeo (puede instalar VLC para reproducirlo). + No se encontró ningún reproductor de retransmisiones (puede instalar VLC para reproducirlo). Siempre - Sólo una vez + Solo una vez Los reproductores externos no soportan este tipo de enlaces URL no válida No se encontraron transmisiones de vídeo @@ -277,9 +277,9 @@ Exportado Importado Archivo ZIP no válido - ADVERTENCIA: no se pudieron importar todos los archivos. + Atención: no se pudieron importar todos los archivos. Esto reemplazará su configuración actual. - Descargar archivo de stream + Descargar archivo de retransmisión Mostrar información "Listas de reproducción en marcadores " Añadir a @@ -289,8 +289,8 @@ Eliminar todos Descartar Cambiar nombre - ¿Desea eliminar este elemento del historial de reproducciones? - ¿Seguro que desea eliminar todos los elementos del historial? + ¿Quiere eliminar este elemento del historial de reproducciones\? + ¿Confirma que quiere eliminar todos los elementos del historial\? Última reproducción Más reproducido Preguntar siempre @@ -320,8 +320,8 @@ Forzar reporte de excepciones no entregables de RX fuera del fragmento o del ciclo de actividad después del descarte Usar búsqueda rápida inexacta La búsqueda inexacta permite al reproductor buscar posiciones más rápido con menor precisión - Auto-encolar la siguiente transmisión - Añadir de forma automática un vídeo relacionado con el último vídeo reproducido en una cola no repetitiva + Poner en cola vídeo relacionado siguiente + Añadir de forma automática un vídeo relacionado con el último reproducido en una cola sin repetición Archivo Archivo movido o eliminado La carpeta no existe @@ -351,15 +351,15 @@ \n3. Inicie sesión cuando se le pida \n4. Copie la URL del perfil a la que fue redireccionado. suID, soundcloud.com/suID - Tenga en cuenta que esta operación puede ser costosa para la red. -\n -\n¿Desea continuar? + Observe que esta operación puede causar un uso intensivo de la red. +\n +\n¿Quiere continuar\? Cargar miniaturas - Desactívalo para evitar la carga de miniaturas, ahorrando datos y uso de memoria. Los cambios borrarán tanto la caché de imágenes en la memoria como en el disco. - Caché de imagen limpiado - Eliminar metadatos de la caché - Eliminar todos los datos de la página web en caché - Metadatos del caché limpiados + Desactívela para evitar la carga de miniaturas y ahorrar datos y memoria. Se vaciará la antememoria de imágenes en la memoria volátil y en el disco. + Se vació la antememoria de imágenes + Eliminar metadatos en antememoria + Eliminar todos los datos de páginas web en antememoria + Se vació la antememoria de metadatos Controles de velocidad de reproducción Tiempo Tono @@ -370,17 +370,17 @@ No se encontró ninguna aplicación que reproduzca este archivo Subtítulos Modificar la escala de texto de los subtítulos y los estilos de fondo. Requiere reiniciar la app para que surta efecto. - Borrar historial de reproducciones + Vaciar historial de reproducciones Elimina el historial de contenido visto y posiciones de reproducción ¿Eliminar todo el historial de reproducciones\? - Historial de reproducciones eliminado. - Borrar historial de búsqueda + Se eliminó el historial de reproducciones. + Vaciar historial de búsquedas Elimina el historial de palabras clave de búsqueda ¿Eliminar todo el historial de búsqueda\? Historial de búsquedas eliminado. Se eliminó 1 elemento. NewPipe es un software copyleft libre: puedes usarlo, estudiarlo, compartirlo y mejorarlo a voluntad. Específicamente, puedes redistribuirlo y/o modificarlo bajo los términos de la Licencia Pública General GNU publicada por la Free Software Foundation, ya sea la versión 3 de la Licencia, o (a tu elección) cualquier versión posterior. - ¿Desea importar también los ajustes? + ¿Quiere importar también la configuración\? Normativa de privacidad de NewPipe El proyecto NewPipe toma su privacidad muy en serio. Por ello, la aplicación no recopila ningún dato sin su consentimiento. La normativa de privacidad de NewPipe explica en detalle qué datos se envían y almacenan cuando envía un informe de fallo. Leer la normativa de privacidad @@ -405,10 +405,10 @@ Pendientes pausado en cola - post-procesado + posprocesamiento Añadir a cola Acción denegada por el sistema - Archivo borrado + Se eliminó el archivo Descarga fallida Descarga finalizada @@ -425,7 +425,7 @@ Mostrar como lista Limpiar descargas finalizadas Tienes %s descargas pendientes, ve a Descargas para continuarlas - ¿Estas seguro? + ¿Lo confirma\? Detener Intentos máximos Cantidad máxima de intentos antes de cancelar la descarga @@ -444,12 +444,12 @@ Fallo la conexión segura No se pudo encontrar el servidor No se puede conectar con el servidor - El servidor no devolvio datos - El servidor no acepta descargas multi-hilos, intente de nuevo con @string/msg_threads = 1 + El servidor no está enviando datos + El servidor no acepta descargas multiproceso; intente de nuevo con @string/msg_threads = 1 No se puede satisfacer el intervalo seleccionado No encontrado - Fallo el post-procesado - NewPipe se cerro mientras se trabajaba en el archivo + Falló el posprocesamiento + NewPipe se cerró mientras se trabajaba en el archivo No hay suficiente espacio disponible en el dispositivo Se perdió el progreso porque el archivo fue eliminado Tiempo de espera excedido @@ -469,7 +469,7 @@ Actualizaciones Eventos Notificación de actualización de la aplicación - Notificaciones para nueva versión de NewPipe + Notificaciones de versiones nuevas de NewPipe Almacenamiento externo no disponible No es posible descargar a una tarjeta SD externa. \¿Restablecer la ubicación de la carpeta de descarga\? Usando las pestañas por defecto, error al leer las pestañas guardadas @@ -487,7 +487,7 @@ ¡Actualización de NewPipe disponible! Pulsa para descargar Mostrar comentarios - Deshabilitar para dejar de mostrar comentarios + Desactívela para ocultar los comentarios Reproducción automática Comentarios @@ -500,10 +500,10 @@ Restaurar última posición de la reproducción Posiciones en listas Mostrar indicador de posición en listas de reproducción - Borrar datos - Posiciones de reproducción borradas. - Borrar posiciones de reproducción + Vaciar datos + Se eliminaron las posiciones de reproducción. + Eliminar posiciones de reproducción Elimina todas las posiciones de reproducción - ¿Borrar todas las posiciones de reproducción\? + ¿Quiere eliminar todas las posiciones de reproducción\? Activar/desactivar servicio, seleccionados actualmente: \ No newline at end of file From 4db4b3af864f6aa870174a39ffd647c8bcda02f2 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Mon, 4 Nov 2019 00:27:05 +0000 Subject: [PATCH 079/270] Translated using Weblate (Russian) Currently translated at 100.0% (478 of 478 strings) --- app/src/main/res/values-ru/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5da31c0b9..46203b6f2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -134,7 +134,7 @@ " млн" " млрд" " тыс." - Разрешение очереди всплывающего окна + Разрешение очереди \"В окне\" Помнить последние размер и позицию всплывающего окна Поисковые предложения Лучшее разрешение @@ -246,9 +246,9 @@ В фоне В окне Зажмите, чтобы добавить в очередь - Добавить в очередь \"В фоне\" - Добавить в очередь \"В окне\" - Начать отсюда в плеере + В очередь \"В фоне\" + В очередь \"В окне\" + Играть отсюда в плеере Играть отсюда в фоне Играть отсюда в окне Потоковый плеер не найден (можно установить VLC) @@ -317,7 +317,7 @@ Удалить Переименовать Имя - Добавить в плейлист + В плейлист На миниатюру плейлиста Сохранить плейлист Удалить закладку From 58181ee37fd1cce9119dd3d26fe21dc4cba8462b Mon Sep 17 00:00:00 2001 From: "gold.ris90" Date: Mon, 4 Nov 2019 12:36:29 +0000 Subject: [PATCH 080/270] Translated using Weblate (Macedonian) Currently translated at 65.9% (315 of 478 strings) --- app/src/main/res/values-mk/strings.xml | 96 ++++++-------------------- 1 file changed, 21 insertions(+), 75 deletions(-) diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index ca1721115..3fbdf83ce 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -1,16 +1,17 @@ - -Стисни на пребарување за да почнеш + + + Стисни на пребарување за да почнеш %1$s прегледи Објавено на %1$s Не е најден плеер за стримови. Инсталирај VLC? - Не е најден плеер за стримови (можеш да инсталираш VLC) + Не е пронајден плеер за стримови (можеш да инсталираш VLC) Инсталирај Откажи Отвори во пребарувач Отвори во прозорче Сподели Превземи - Превземи стрим датотека. + Превземи датотека за стрим Пребарај Поставки Мислеше на: %1$s\? @@ -18,7 +19,7 @@ Одбери пребарувач ротација Користи надворешен видео плеер - Некои резолуции НЕМААТ звук со оваа опција + Отстранува звук на НЕКОИ резолуции Користи надворешен аудио плеер NewPipe мало прозорче Зачлени се @@ -27,25 +28,19 @@ Не може да се промени членството Не може да се освежи членството Покажи информации - Почетна Членства - Обележани - + Обележани плејлисти Новости - Позадина Подпрозорче Додај на - Локација за зачувување на видеата Локација за зачувување на видеата Внеси локација за зачувување на видеата - Локација за зачувување аудио Локација за зачувување аудио Внеси локација за зачувување аудио - Автоматско пуштање Пушта видео кога NewPipe е повикана од друга апликација Стандардна резолуција @@ -130,17 +125,13 @@ Секогаш Само еднаш Датотека - Известување од NewPipe NewPipe Известувања за подпрозорчето и заднинското пуштање - [Непознато] - Смени ориентација Пушти во позадина Пушти во подпрозорче Пушти во главната апликација - Внеси датабаза Изнеси база на податоци Ќе ги избрише моменталните членства и историја @@ -169,7 +160,6 @@ Датотеката не постои или се потребни привилегии за достапност Датотеката не може да биде празна Настана проблем: %1$s - Извинете, не требаше да се случи тоа. Извести за грешката по е-пошта Извинете, настана проблем. @@ -179,8 +169,6 @@ Што:\\nБарање:\\nЈазик на Содрж.:\\nУслуга:\\nGMT Час:\\nПакет:\\nВерзија:\\nВерз. на ОС: Ваш коментар (на Англиски): Детали: - - Сликичка за преглед на видеото Сликичка за преглед на видеото Икона од аватарот на објавителот @@ -193,37 +181,30 @@ Нема резултати Тука Е Празно :( Влечи за да разместиш - Не може да се создаде локација \'%1$s\' Создадена локација \'%1$s\' - Видео Звук Пробај повторно Нема привилегии за пристап - K M B - Нема зачленети - %s зачленет - %s зачленети - - + %s зачленет + %s зачленети + Нула прегледи - %s преглед - %s прегледи - - + %s преглед + %s прегледи + Нема видеа - %s видео - %s видеа - - + %s видео + %s видеа + Започни Пауза Пушти @@ -234,10 +215,8 @@ Чексума Отфрли Прекрсти - Нова мисија Готово - Име на датотека Нишки Грешка @@ -251,19 +230,15 @@ Одберете достапна локација за превземања Оваа привилегија е потребна за \nотворање во подпрозорче - „reCAPTCHA“ reCAPTCHA Предизвик Потребен е reCAPTCHA предизвик - Превземања Дозволени знаци во имињата на датотеките Невалидните знаци се менуваат со оваа вредност Знак за замена - Букви и броеви Повеќето посебни знаци - За NewPipe Поставки За апликацијата @@ -285,8 +260,6 @@ За повеќе информации и новости за NewPipe посети ја вебстраната. "Лиценцата на NewPipe " Прочитај лиценца - - Историја Пребарано Гледано @@ -300,7 +273,6 @@ Сигурно сакаш да ја избришеш целата историја? Последно пуштено Најгледани - Содржина Празна страна Киоск @@ -315,7 +287,6 @@ Нема валиден ZIP фајл Предупредување: Не се внесени сите датотеки. Ова ќе ги избрише моменталните поставки. - Киоск Популарни Топ 50 @@ -331,64 +302,47 @@ Пушти тука Пушти тука во позадина Пушти тука во прозорче - Отвори картон Затвори картон Нешто спремаме за тука ;D - - Видео плеер Позадински плеер Пушти во прозорче Секогаш прашај - Чекање информации… Одбраната содржина се чита - Создај нова плејлиста Избриши плејлиста Прекрсти плејлиста Име Додај во плејлиста Постави како икона на плејлистата - Обележи ја плејлистата Избриши ја белешката - Сакаш да ја избришеш плејлистата? Листата е создадена Додадено во плејлиста Се смени иконата на плејлистата Неуспешно бришење на листата - Без превод - Вклопи Исполни Зумирај - Автоматски создадено - Вклучи LeakCanary Следењето на мемориските грешки може да ја прикочува апликацијата - Пријави Out-of-lifecycle грешки Секогаш пријавувај неуспешни Rx исклучоци кои настануваат надвор од fragment или activity циклусот по отстранување - Внеси/Изнеси Внеси Внеси од Изнеси до - Внесување… Изнесување… - Внеси датотека Претходно изнесување - Неуспешно внесување членства Неуспешно изнесување членства - Внеси YouTube членства, преку симнување на извозен фајл: \n \n1. Оди на оваа врска: %1$s @@ -401,17 +355,15 @@ \n3. Пријавете се на вашиот профил \n4. Копирајте го URL-то на кое бевте пренасочени (тоа е URL-то на вашиот профил). korisnickoime, soundcloud.com/korisnickoime - Треба да знаеш дека оваа операција троши многу интернет. \n \nСакаш да продолжиш? - Контрола на брзината на траката Темпо Тон Откачи (може да создаде мутации) Стандардно -Избриши историја на гледаност + Избриши историја на гледаност Ја брише историјата на пуштени видеа Избриши ја целата историја на гледаност. Избришана е историјата на гледаност. @@ -420,20 +372,14 @@ Избриши ја целата историја на пребарувања. Избришана е историјата на пребарувања. Нема стримови за симнување - 1 ставка избришана. - Нема апликација за пуштање на овој фајл - NewPipe е „copyleft“ слободен софтвер: Можеш да ја користиш, истражуваш и подобруваш по твоја желба. Можеш да ја редистрибуираш и/или да ја промениш под условите на GNU GPL лиценцата, публикувана од фондацијата FSF - или верзија 3 од лиценцата, или (по можност) понова верзија. Дали сакаш да се внесат и подесувањата? - Претпочитана акција за „отворање“ Стандардна акција при отворање видеа — %s - Преводи Смени ја големината и стилот на преводот. Потребен е рестарт за промена - NewPipe - политика за приватност Проектот NewPipe сериозно ја сфаќа вашата приватност. Затоа апликацијата не собира ваши податоци без ваша дозвола. \nПолитиката за приватност на NewPipe детално објаснува кои податоци се зачувани и пратени кога праќате извештај за грешка во апликацијата. @@ -442,7 +388,6 @@ \nВе молиме прочитајте ја внимателно. Мора да ја прифатите за да ни го испратите извештајот за грешка во апликацијата. Прифати Отфрли - Неограничено Ограничи резолуција при користење мобилен интернет Канали @@ -452,11 +397,12 @@ Прескокни ја тишината Скокни Почни одново - Скриј при промена на апликацијата Пушти во заднина при промена на апликацијата од видео плеерот — %s Никое Смали го во позадина Смали го во прозорче - - + Откажи членство + Ново јазиче + Одбери јазиче + \ No newline at end of file From ab3b8d0a14fab505d9a1b43567f7cb351c7c31e3 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Mon, 4 Nov 2019 21:38:52 +0000 Subject: [PATCH 081/270] Translated using Weblate (Russian) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ru/strings.xml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 46203b6f2..b5b498ad7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -11,7 +11,7 @@ Поиск Настройки Возможно, вы имели в виду: %1$s? - Поделиться в + Поделиться Выбор браузера поворот Папка для скачанного видео @@ -122,7 +122,7 @@ Фильтр Новая цель Что:\\nЗапрос:\\nЯзык контента:\\nСервис:\\nВремя по Гринвичу:\\nПакет:\\nВерсия:\\nВерсия ОС: - Это разрешение нужно для + Это разрешение нужно для \nвоспроизведения в окне reCAPTCHA Открыть во всплывающем окне @@ -260,7 +260,7 @@ Перейти в фон Перейти в окно Перейти в плеер - Ошибка плеера без возможности восстановления + Неустранимая ошибка плеера Внешние плееры не поддерживают ссылки этих типов Неверная ссылка Видеопотоки не найдены @@ -320,7 +320,7 @@ В плейлист На миниатюру плейлиста Сохранить плейлист - Удалить закладку + Удалить плейлист Удалить плейлист\? Плейлист создан Добавлено в плейлист @@ -383,7 +383,7 @@ Удалить историю воспроизведённых потоков и позиции воспроизведения Удалить всю историю поиска\? История поиска удалена - 1 элемент удалён. + 1 элемент удалён NewPipe — свободное программное обеспечение: вы можете использовать, изучать и улучшать его по своему усмотрению. В частности, вы можете распространять и/или изменять его в соответствии с условиями GNU General Public License, опубликованной Free Software Foundation, либо версии 3, либо (по вашему выбору) любой более поздней версии. При открытии ссылки Хотите импортировать настройки? @@ -431,7 +431,7 @@ Менять громкость плеера жестом Жест громкости Обновления - Файл удален + Файл удалён Уведомление об обновлении Уведомления о новой версии NewPipe Обновления @@ -451,9 +451,9 @@ Код Папка назначения не может быть создана Файл не может быть создан - Доступ запрещен системой + Доступ запрещён системой Сервер не найден - "Сервер не поддерживает многопотоковую загрузку, попробуйте с @string/msg_threads = 1" + Сервер не принимает многопоточные загрузки, повторная попытка с @string/msg_threads = 1 Запрашиваемый диапазон недопустим Не найдено Очистить завершённые @@ -475,7 +475,7 @@ Пост-обработка не удалась Прервать в мобильной сети Закрыть - Время соединения вышло + Время соединения истекло Показать комментарии Отключите, чтобы скрыть комментарии Автовоспроизведение @@ -510,4 +510,5 @@ Удалить все позиции воспроизведения\? Измените папки загрузки для вступления в силу Переключение сервисов, сейчас выбрано: + Киоск по умолчанию \ No newline at end of file From f84907e2c978e239a1af243d8178e6fbbbc77292 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Tue, 5 Nov 2019 12:14:58 +0000 Subject: [PATCH 082/270] Translated using Weblate (Russian) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b5b498ad7..d5b04fb76 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -387,7 +387,7 @@ NewPipe — свободное программное обеспечение: вы можете использовать, изучать и улучшать его по своему усмотрению. В частности, вы можете распространять и/или изменять его в соответствии с условиями GNU General Public License, опубликованной Free Software Foundation, либо версии 3, либо (по вашему выбору) любой более поздней версии. При открытии ссылки Хотите импортировать настройки? - Политика конфиденциальности + Конфиденциальность Проект NewPipe очень серьёзно относится к вашей конфиденциальности. Поэтому приложение не собирает никаких данных без вашего согласия. \nПолитика конфиденциальности NewPipe подробно объясняет, какие данные отправляются и хранятся при отправке отчёта о сбоях. Прочитать политику From 72adb7a53b2834430bfbe6918d328bfa8de31f36 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Tue, 5 Nov 2019 20:43:35 +0000 Subject: [PATCH 083/270] Translated using Weblate (Russian) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ru/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d5b04fb76..d955f00f5 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -134,7 +134,7 @@ " млн" " млрд" " тыс." - Разрешение очереди \"В окне\" + Разрешение очереди всплывающего окна Помнить последние размер и позицию всплывающего окна Поисковые предложения Лучшее разрешение @@ -246,8 +246,8 @@ В фоне В окне Зажмите, чтобы добавить в очередь - В очередь \"В фоне\" - В очередь \"В окне\" + В очередь в фоне + В очередь в окне Играть отсюда в плеере Играть отсюда в фоне Играть отсюда в окне From fdab92c92ba20d902ceb4c88a95db01cec480a24 Mon Sep 17 00:00:00 2001 From: Swyter Date: Mon, 4 Nov 2019 17:16:01 +0000 Subject: [PATCH 084/270] Translated using Weblate (Spanish) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-es/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index bc95663d1..3060f6b64 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -27,7 +27,7 @@ Formato de audio predeterminado Descargar Siguiente - No se admite el URL + No se admite la dirección URL Usar reproductor de vídeo externo Usar reproductor de audio externo Tema @@ -98,7 +98,7 @@ Error Servidor incompatible El archivo ya existe - URL mal escrito o Internet no disponible + Dirección URL mal escrita o Internet no disponible NewPipe está descargando Toque para ver detalles Espere… From 225647cf650ddd7e55bebbc66817ea9ba7cc3519 Mon Sep 17 00:00:00 2001 From: Laura Arjona Reina Date: Tue, 5 Nov 2019 13:49:08 +0000 Subject: [PATCH 085/270] Translated using Weblate (Spanish) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-es/strings.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3060f6b64..1a4fa9be2 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -51,7 +51,7 @@ No se pudo cargar las miniaturas No se pudo descifrar la URL del vídeo No se pudo analizar el sitio web - Mostrar videos «siguientes» y «similares» + Mostrar vídeos «siguientes» y «similares» Idioma predeterminado del contenido Miniatura de previsualización del vídeo Reproducir vídeo; duración: @@ -242,7 +242,7 @@ Comenzar a reproducir aquí Comenzar a reproducir en segundo plano Comenzar a reproducir en un popup nuevo - Mostrar consejo «Mantener pulsado para añadir» + Mostrar consejo «Mantener presionado para agregar» Nuevo y popular Mantener presionado para agregar a la cola Donar @@ -459,7 +459,7 @@ \nHabilite esta opción junto con SAF si quiere descargar en la tarjeta SD Usar SAF El Framework de Acceso al Almacenamiento permite descargar en la tarjeta SD externa.\nNota: Algunos los dispositivos no son compatibles - Cancelar suscripción + Cancelar la suscripción Pestaña nueva Elija la pestaña Control de volumen por gestos @@ -506,4 +506,5 @@ Elimina todas las posiciones de reproducción ¿Quiere eliminar todas las posiciones de reproducción\? Activar/desactivar servicio, seleccionados actualmente: + kiosco predeterminado \ No newline at end of file From a43c434376059c63ea8f120bf6a69a7f9fb5c1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 4 Nov 2019 17:08:55 +0000 Subject: [PATCH 086/270] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 97.6% (489 of 501 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 91f34d2fa..37e39ea72 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -506,4 +506,5 @@ Slett alle avspillingsposisjoner\? Endre nedlastingsmappene for å tre i kraft Skru tjeneste av/på, for tiden er dette status: + Forvalgt kiosk \ No newline at end of file From e5afffdcedac34ba2479c0e1dee49dbc2e1606d4 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 6 Nov 2019 22:23:39 +0000 Subject: [PATCH 087/270] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 97.6% (489 of 501 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 37e39ea72..cb8079e2e 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -65,7 +65,7 @@ Kunne ikke tolke nettside fullstendig Innholdet er utilgjengelig Kunne ikke sette opp nedlastingsmeny - Direktesendinger støttes ikke enda. + Direktesendinger støttes ikke ennå Kunne ikke finne noen strømmer Beklager, dette skal ikke skje. Rapporter feil via e-post @@ -415,7 +415,7 @@ Merknader for nye NewPipe-versjoner Ekstern lagring utilgjengelig Nedlasting til eksternt SD-kort er ikke mulig. Tilbakestill plassering av nedlastingsmappe\? - Bruker forvalgte faner, feil under lesing av lagrede faner. + Bruker forvalgte faner, feil under lesing av lagrede faner Gjenopprett forvalg Ønsker du å gjenopprette alt til forvalgene\? Abonnementsantall ikke tilgjengelig @@ -429,7 +429,7 @@ Rutenett Auto Veksle visning - Ny NewPipe-versjon tilgjengelig. + NewPipe-oppdatering tilgjengelig! Trykk for å laste ned Fullført pauset @@ -480,7 +480,7 @@ Posisjon i lister Vis avspillingsposisjonsindikator i lister Tøm data - Avspillingsposisjon slettet + Avspillingsposisjon slettet. Fil flyttet eller slettet En fil ved dette navnet finnes allerede Kan ikke overskrive filen From 3e079a685871ad89431cb57efc6b43d9f9e6f8fc Mon Sep 17 00:00:00 2001 From: Redirion Date: Thu, 7 Nov 2019 16:36:45 +0100 Subject: [PATCH 088/270] Bump ExoPlayer to 2.10.7 Little dependency update. ExoPlayer Changelog: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5477a11a8..3eff64138 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,7 +47,7 @@ android { ext { androidxLibVersion = '1.0.0' - exoPlayerLibVersion = '2.10.6' + exoPlayerLibVersion = '2.10.7' roomDbLibVersion = '2.1.0' leakCanaryLibVersion = '1.5.4' //1.6.1 okHttpLibVersion = '3.12.6' From 655993d8f264ae7ea63e5e02e1c99d91b78c7ede Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Fri, 8 Nov 2019 22:18:44 +0000 Subject: [PATCH 089/270] Translated using Weblate (Russian) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d955f00f5..6f079a221 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -508,7 +508,7 @@ Очистить позиции воспроизведения Удалить все позиции воспроизведения Удалить все позиции воспроизведения\? - Измените папки загрузки для вступления в силу + Измените папки загрузки для вступления настроек в силу Переключение сервисов, сейчас выбрано: Киоск по умолчанию \ No newline at end of file From 4daae95979e3da79c618bed981fa43124dcb9b8b Mon Sep 17 00:00:00 2001 From: Oguz Ersen Date: Mon, 4 Nov 2019 19:26:23 +0000 Subject: [PATCH 090/270] Translated using Weblate (Turkish) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-tr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 597189c7a..124d84306 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -507,4 +507,5 @@ Videolar + Varsayılan Kiosk \ No newline at end of file From 28f87dcb2b7a24fdc831034c1d5debe247a74cc7 Mon Sep 17 00:00:00 2001 From: ssantos Date: Tue, 5 Nov 2019 21:52:23 +0000 Subject: [PATCH 091/270] Translated using Weblate (German) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 49fcc4506..a25b6e202 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -507,4 +507,5 @@ Alle Wiedergabepositionen löschen\? Ändere die Downloadordner, damit sie wirksam werden Dienst umschalten, aktuell ausgewählt: + Quiosque Predefinido \ No newline at end of file From a3dff2c6083ec2b690aa19c3f2e87cfd31924542 Mon Sep 17 00:00:00 2001 From: naofum Date: Tue, 5 Nov 2019 14:16:41 +0000 Subject: [PATCH 092/270] Translated using Weblate (Japanese) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ja/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ac275ba5c..3ff912cc4 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -502,4 +502,5 @@ 動画 + デフォルトのキオスク \ No newline at end of file From f82dd3e1521db2b369d7a41652943862880b206d Mon Sep 17 00:00:00 2001 From: ssantos Date: Mon, 4 Nov 2019 18:23:05 +0000 Subject: [PATCH 093/270] Translated using Weblate (Portuguese) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-pt/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index ecba237da..5d7cd8146 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -506,4 +506,5 @@ Eliminar todas as posições de reprodução\? Alterar as pastas de descarregamento para que tenham efeito Alternar serviço, agora selecionado: + Quiosque Predefinição \ No newline at end of file From 720bff02aa8d741a7713dd9c0e883bff413bb0dc Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Tue, 5 Nov 2019 10:50:02 +0000 Subject: [PATCH 094/270] Translated using Weblate (Hebrew) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-he/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 6f826fdf0..b5a0778d4 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -515,4 +515,5 @@ למחוק את מיקומי הנגינה\? יש להחליף את תיקיות ההורדה כדי שייכנס לתוקף הפעלה/כיבוי שירות, בחירה נוכחית: + קיוסק בררת מחדל \ No newline at end of file From ceb1d70551e037d43f44bac7655490ef65713e91 Mon Sep 17 00:00:00 2001 From: "gold.ris90" Date: Tue, 5 Nov 2019 14:54:05 +0000 Subject: [PATCH 095/270] Translated using Weblate (Macedonian) Currently translated at 75.6% (379 of 501 strings) --- app/src/main/res/values-mk/strings.xml | 81 ++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 3fbdf83ce..94119119a 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -405,4 +405,85 @@ Откажи членство Ново јазиче Одбери јазиче + Гест за контрола на јачина на звук + Користи гест за контрола на јачината на звукот + Гест за контрола на осветленост + Користи гест за контрола на осветленот + Ажурирања + Дадотеката е избришана + Известување за ажурирање на апликација + Известување за нова NewPipe верзија + Надворешната меморија е недостапна + Превземањето на надворешната SD картичка не е можно. Ресетирајте ја локацијата на папката за преземање \? + Бројот на зачленети не е достапен + Кои јазичиња се прикажани на главната страница + Избор + Ажурирања + Покажи известување за навремено ажурирање на апликацијата кога е достапна нова верзија + Режим на прегледување список + Список + Решетка + Автоматски + NewPipe ажурирање е достапно! + Допри за преземање + Завршено + Во очекување + Паузирано + Во ред за чекање + Редица + Акција одбиена од системот + преземањето не успеа + Преземањето заврши + %s преземањата завршија + Генерирај уникатно име + Презапиши + Во тек е преземање со ова име + Покажи грешка + Код + Папката не може да се создаде + Датотеката не може да се создаде + Дозволата е одбиена од системот + Безбедна врска не успеа + Не може да се пронајде серверот + Не може да се поврзе со серверот + Серверот не испраќа податоци + Серверот не прифаќа преземања со повеќе навои, обидете се со @string/msg_threads = 1 + Побараниот опсег не е задоволителен + Не е најдено + Избриши завршени преземања + Стоп + Максимални обиди + Максимален број обиди пред откажување на преземањето + Корисно при префрлување на мобилни податоци, иако некои преземања не можат да бидат прекинати + Настани + Конференции + Прикажи коментари + Автоматски старт + + Коментари + + + Нема коментари + Не може да се вчитаат коментарите + Затвори + Продолжи со репродукција + Врати ја последната позиција за репродукција + Избриши податоци + Дадотеката е преместена или избришана + Ддотека со ова име веќе постои + Преземената дадотека со ова име веќе постои + NewPipe беше затворен додека работеше на датотеката + Не останува простор на уредот + Истечено време за поврзување + Дали си сигурен\? + Ограничи ја редицата за преземање + Едно преземање ќе работи истовремено + Започи со преземања + Паузирај преземања + Вие ќе добиете прашање каде да го зачувате секое преземање + Вие ќе добиете прашање каде да го зачувате секое преземање +\nИзберете SAF ако сакате да преземете во надворешна SD картичка + Користи SAF + Рамката за пристап до складирање овозможува преземања во надворешна SD-картичка. +\nЗабелешка: некои уреди не се поддржани. \ No newline at end of file From 727fb27ad0a4779f4cbfc531de4de281e2d1ea7e Mon Sep 17 00:00:00 2001 From: thami simo Date: Fri, 8 Nov 2019 10:09:07 +0000 Subject: [PATCH 096/270] Translated using Weblate (Arabic) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ar/strings.xml | 27 +++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index bf4867855..43ef5d024 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -42,7 +42,7 @@ مشاركة مشاركة بواسطة عرض مقاطع الفيديو \"التالية\" و \"المشابهة\" - عرض خيار تشغيل الفيديو عبر وسائط Kodi + عرض خيار تشغيل الفيديو عبر مركز وسائط كودي عرض خيار التشغيل بواسطة كودي السمة تم النشر يوم %1$s @@ -80,7 +80,7 @@ التاريخ التاريخ فتح في وضع منبثق - يزيل الصوت في بعض مستوى الدقة + إزالة الصوت في بعض مستوى الدقة وضع النوافذ المنبثقة NewPipe تم إلغاء الاشتراك في القناة تعذر تغيير حالة الاشتراك @@ -153,17 +153,17 @@ الصوت إعادة المحاولة تم رفض إذن الوصول إلى التخزين - K + ألف مليون G ليس هناك مشترِكون %s لا يوجد مشاركين %s مشترك - "%s مشتركتين" + %s مشاريكان %s اشتراكات - %s مشاركين - %s مشتركون + %s مشاركون + %s اشتراك دون مشاهدات لاتوجد فيديوهات @@ -200,7 +200,7 @@ فتح الموقع المساهمون التراخيص - تطبيق مجاني خفيف الوزن وبث حي على نظام أندرويد. + مجاني خفيف الوزن بث حي على أندرويد. ساهم إذا كانت لديك أفكار؛ أو ترجمة، أو تغييرات تخص التصميم، أو تنظيف و تحسين الشفرة البرمجية ، أو تعديلات عميقة عليها، فتذكر أنّ مساعدتك دائما موضع ترحيب. وكلما أتممنا شيئا كلما كان ذلك أفضل ! عرض على GitHub @@ -238,12 +238,12 @@ تحدي الكابتشا ضغط مطول للإدراج الى قائمة الانتظار - بدون مشاهدات - %s مشاهدة - %s مشاهدتين - %s مشاهدون - %s مشاهدات - %s مشاهدين + %s بدون مشهد + %s شاهد + %s مشاهدتان + %s مشاهدات + %s مشاهدون + %s شاهدو %s لا يوجد فيديو @@ -522,4 +522,5 @@ حذف كل مواقف التشغيل؟ تغيير مجلدات التنزيل إلى حيز التنفيذ‮‮‮ تبديل الخدمة ، المحدد حاليًا: + الكشك الافتراضي \ No newline at end of file From 1561c210fbfd998c3f206a9a5bb472431b3db1f0 Mon Sep 17 00:00:00 2001 From: zmni Date: Tue, 5 Nov 2019 08:09:56 +0000 Subject: [PATCH 097/270] Translated using Weblate (Indonesian) Currently translated at 99.8% (500 of 501 strings) --- app/src/main/res/values-id/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index e17fcd575..db738d749 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -111,7 +111,7 @@ Hitam Semua Channel - R + r J T Ya From f0c7cb9f94f5153da1b60f3d67de4dd193ec4e88 Mon Sep 17 00:00:00 2001 From: Adolfo Jayme Barrientos Date: Wed, 6 Nov 2019 10:30:47 +0000 Subject: [PATCH 098/270] Translated using Weblate (Spanish) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-es/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 1a4fa9be2..6083b6952 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -27,7 +27,7 @@ Formato de audio predeterminado Descargar Siguiente - No se admite la dirección URL + No se admite el URL Usar reproductor de vídeo externo Usar reproductor de audio externo Tema @@ -98,7 +98,7 @@ Error Servidor incompatible El archivo ya existe - Dirección URL mal escrita o Internet no disponible + URL mal escrito o Internet no disponible NewPipe está descargando Toque para ver detalles Espere… @@ -223,7 +223,7 @@ Seleccione un kiosco Kiosco Tendencias - Top 50 + 50 mejores Mostrar sugerencia cuando se presiona el botón de 2.º plano o emergente en la página de detalles del vídeo En cola en el reproductor de 2.º plano En cola en el reproductor emergente @@ -238,12 +238,12 @@ Ajustes de audio [Desconocido] Añadir a cola en segundo plano - Añadir a cola en un popup nuevo + Añadir a cola en reproductor emergente Comenzar a reproducir aquí Comenzar a reproducir en segundo plano Comenzar a reproducir en un popup nuevo Mostrar consejo «Mantener presionado para agregar» - Nuevo y popular + Novedades Mantener presionado para agregar a la cola Donar NewPipe es desarrollado por voluntarios que emplean su tiempo libre para brindarle la mejor experiencia. Haga una aportación para ayudarlos a crear un NewPipe aún mejor mientras disfrutan de una taza de café. @@ -302,7 +302,7 @@ Definir como miniatura de lista de reproducción Marcar lista de reproducción Eliminar marcador - ¿Borrar esta lista de reproducción\? + ¿Quiere eliminar esta lista\? Lista de reproducción creada Añadido a la lista de reproducción Miniatura de lista de reproducción cambiada. @@ -459,7 +459,7 @@ \nHabilite esta opción junto con SAF si quiere descargar en la tarjeta SD Usar SAF El Framework de Acceso al Almacenamiento permite descargar en la tarjeta SD externa.\nNota: Algunos los dispositivos no son compatibles - Cancelar la suscripción + Cancelar suscripción Pestaña nueva Elija la pestaña Control de volumen por gestos From b772afeeabdd50730803c603ea94d4a487b9888b Mon Sep 17 00:00:00 2001 From: Deleted User Date: Thu, 7 Nov 2019 19:23:07 +0000 Subject: [PATCH 099/270] Translated using Weblate (Slovak) Currently translated at 88.2% (442 of 501 strings) --- app/src/main/res/values-sk/strings.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 5682897d8..09502f60a 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -297,8 +297,8 @@ Získavajú sa informácie… Načítanie požadované obsahu Vytvoriť nový zoznam skladieb - Vymazať zoznam skladieb - Premenovať zoznam skladieb + Vymazať + Premenovať Názov Pridať do zoznamu skladieb Nastaviť ako miniatúru zoznamu skladieb @@ -342,8 +342,7 @@ Monitorovanie pretečenia pamäte môže spôsobiť, že aplikácia nebude reagovať Nahlásiť mimo-cyklické chyby Vynútiť hlásenie výnimiek nedoručiteľných Rx mimo časového cyklu fragmentov alebo aktivity po zneškodnení - Import/Export -\n + Import/Export Import \n Importovať z @@ -512,4 +511,7 @@ Odstrániť pozície prehrávania Odstráni všetky pozície prehrávania Odstrániť všetky pozície prehrávania\? + Zmeniť miesto pre stiahnuté súbory + Prepnúť službu, práve vybraté: + Predvolený kiosk \ No newline at end of file From f7be69347055f83921048cbe54358768bae87443 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 5 Nov 2019 05:01:54 +0000 Subject: [PATCH 100/270] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c3823c2cf..e893c336d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -502,4 +502,5 @@ 影片 + 預設 Kiosk \ No newline at end of file From b5058f99ce0594ba6f8d185361872b9fe283bebf Mon Sep 17 00:00:00 2001 From: Deleted User Date: Wed, 6 Nov 2019 22:26:56 +0000 Subject: [PATCH 101/270] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 99.2% (497 of 501 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index cb8079e2e..d26886844 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -376,7 +376,7 @@ Skru av for å stoppe innlasting av miniatyrbilder, noe som sparer data- og minnebruk. Endring av dette vil tømme både disk- og minne-hurtiglager. Legg til en relatert strøm ved avspilling av forrige strøm i ikke-repeterende kø Minnelekkasjeoppsyn kan forårsake programmet å opptre uresponsivt under haugdumping - Rapporter feil som opptrer utenfor sin levetid + Rapporter feil utenfor livssyklusen Tving rapportering av uleverbare Rx-unntak utenom fragment eller aktivitetslevetid etter forkastelse Avhekt (kan forårsake forvrenging) NewPipes personvernspraksis @@ -433,7 +433,7 @@ Trykk for å laste ned Fullført pauset - i kø + I kø etterbehandling Handling avvist av systemet @@ -454,7 +454,7 @@ Kan ikke koble til tjeneren Tjeneren sender ikke data "Tjeneren godtar ikke flertrådede nedlastinger, prøv igjen med @string/msg_threads = 1 " - Forespurt nummerområde kan ikke innvilges + Forespurt rekkevidde er ikke tilfredsstillende Ikke funnet Etterbehandling mislyktes Tøm fullførte nedlastinger @@ -496,15 +496,15 @@ Sett nedlastinger på pause Spør om hvor ting skal lastes ned til Du vil bli spurt om hvor hver nedlasting skal plasseres - Du vil bli spurt om hvor hver nedlasting skal plasseres. -\nSkru på dette valget hvis du vil laste ned til eksternt SD-kort. + Du vil bli spurt om hvor hver nedlasting skal plasseres. +\nSkru på SAF hvis du vil laste ned til eksternt SD-kort Bruk SAF Lagringstilgangsrammeverk (SAF) tillater nedlastinger til eksternt SD-kort. -\nMerk: Noen enheter er ikke kompatible. +\nMerk: Noen enheter er ikke kompatible Slett avspillingsposisjoner Sletter alle avspillingsposisjoner Slett alle avspillingsposisjoner\? Endre nedlastingsmappene for å tre i kraft Skru tjeneste av/på, for tiden er dette status: - Forvalgt kiosk + Standard kiosk \ No newline at end of file From 0886c6b216b9cb05c2bf7498074deadb643d9e08 Mon Sep 17 00:00:00 2001 From: Ozyc Date: Mon, 11 Nov 2019 15:24:06 +0000 Subject: [PATCH 102/270] Make reCAPTCHA string untranslatable I updated reCaptchaActivity string and made it untranslatable, because it's causing "Failed check: Unchanged translation" on Weblate. --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 07ffdb292..d9419b7d6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -304,7 +304,7 @@ MD5 SHA-1 - reCAPTCHA + reCAPTCHA reCAPTCHA challenge reCAPTCHA challenge requested @@ -557,4 +557,4 @@ You will be asked where to save each download.\nChoose SAF if you want to download to an external SD card Use SAF The Storage Access Framework allows downloads to an external SD card.\nNote: some devices are not compatible - \ No newline at end of file + From 0ee6c1e47e904435e941fd11d9c8ca85c0977cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 10 Nov 2019 18:41:00 +0000 Subject: [PATCH 103/270] Translated using Weblate (Turkish) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-tr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 124d84306..c17b58f50 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -504,8 +504,8 @@ Etkili olması için indirme dizinlerini değiştirin Hizmeti değiştir, şu anda seçili olan: - Videolar - + %s video + %s video Varsayılan Kiosk \ No newline at end of file From af70fdd7a661e086993e9e88da5a9bdf778d8e3e Mon Sep 17 00:00:00 2001 From: ozyc Date: Sun, 10 Nov 2019 12:22:39 +0000 Subject: [PATCH 104/270] Translated using Weblate (Esperanto) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-eo/strings.xml | 250 +++++++++++++++++++++++-- 1 file changed, 230 insertions(+), 20 deletions(-) diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 5f84236fb..d5cc38a1a 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -1,6 +1,6 @@ - %1$s vidoj + %1$s spektaĵoj Eldonita je %1$s Instali Nuligi @@ -58,12 +58,12 @@ Elektu lokon por konservi elŝutitajn filmetojn Elektu lokon por konservi elŝutitajn muzikojn Enhavo - Raporti eraron per retpoŝto - RAPORTI + Signali eraron per retpoŝto + SIGNALI Informoj: Via komento (en la angla): Detaloj: - Raporti eraron + Signali eraron Filmeto Reprovi Premu serĉo por komenci @@ -106,7 +106,7 @@ NUNA Elŝutoj Elŝutoj - Raporto de eraro + Erarosignalo Ne povis ŝarĝi bildon Apo kraŝis reCAPTCHA @@ -115,8 +115,8 @@ Ĉiuj Kanalo - Filmetoj - + %s filmeto + %s filmetoj Jes Poste @@ -203,7 +203,7 @@ Ludlistita Bildeto de ludlisto ŝanĝiĝita. Ne povis forigi ludlisto. - Sencimigi + Malcimigi Auto-vico sekva fluo Aŭto-aldoni rilatan enhavon kiam leganta la lasta enhavo en malrepetita atendovico Dosiero @@ -216,10 +216,10 @@ \n1. Iru ĉe tie retpaĝo : %1$s \n2. Ensalutu kiam oni petas vin \n3. Elŝuto devus komenci (ĝi estas la dosiero de eksporto) - Importu Soundcloud-n profilon per elŝuti la dosieron de eksporto : -\n -\n1. Ebligu komputilon modon en krozilo (la retejo ne estas havebla por poŝtelefonoj) -\n2. Iru al tie retpaĝo : %1$s + Importu Soundcloud-n profilon per elŝuti la dosieron de eksporto : +\n +\n1. Ebligu komputilon modon en krozilo (la retejo malhaveblas por poŝtelefonoj) +\n2. Iru al tie retpaĝo : %1$s \n3. Ensalutu kiam oni petas vin \n4. Kopiu la ligilon de profilo ke oni kondikis vin. Malŝaltu por malebligi ŝarĝajn bildetojn, konservi datumojn kaj uzadon de memoro. Ŝanĝoj forviŝas ambaŭ en-memoro kaj sur-disko bildo kaŝmemoro. @@ -237,7 +237,7 @@ Ĉu vi volas forviŝi la totalon de la historio de serĉo \? Historio de serĉo forviŝita. Limigi distingivo kiam uzanta moveblan datumon - Minimumigu al ŝprucfenestro ludilo + Minimumigi al ŝprucfenestro ludilo Kanaloj Ludlistoj Spuroj @@ -250,11 +250,11 @@ Kontrolo de gesto de brilo Uzu gestojn por kontroli la brilon de la ludilo Ĝisdatigoj - Dosiero forigita + Dosiero forviŝita Sciigo por ĝisdatigi apon Sciigo por nova versio de Newpipe Ekstera konservejo malhavebla - Elŝuti al ekstera SD-karto ne eblas. Ĉu vi volas rekomencigi la elŝutan dosierujon \? + Elŝuti al ekstera SD-karto ne eblas. Ĉu vi volas restarigi la elŝutan dosierujon \? viciĝita Atendovico Halti @@ -268,18 +268,18 @@ Aŭtolego Komentoj - + Ne povis ŝarĝi komentojn Fermi - Repreni la legon - Restaŭri la lastan legan pozicion + Repreni la ludon + Restaŭri la lastan ludan pozicion Pozicioj en listoj - Montri la indikilojn de pozicion lega en listoj + Montri la indikilojn de pozicioj de ludoj en listoj Forviŝi datumojn Historio forviŝita. Ludaj pozicioj forviŝitaj. - Dosiero movita aŭ foviŝita + Dosiero movita aŭ forviŝita ne povas dispremi la dosieron Ĉu vi certas\? Limigi la elŝutan atendovicon @@ -297,4 +297,214 @@ Forviŝi la totalon de ludaj pozicioj Ĉu vi volas forviŝi ĉiujn ludajn poziciojn \? Ŝanĝu la elŝutojn dosierujojn por efekti + Pardonu, eraro okazis. + Pardonu, kelkaj eraroj okazis. + Kio okazis: + Kio:\\nPeto:\\nEnhavlingvo:\\nServo:\\nGMT Horo:\\nPako:\\nVersio:\\nOperaciumo versio: + Aŭdio + Permeso por atingi la konservon rifuzita + Uzantosignalo + Komenci + Paŭzigi + Ludi + Forigi + Kontrolsumo + Nova misio + Bone + Dosiernomo + Fadenoj + Eraro + Nesubtenata servilo + Dosiero jam ekzistas + Malformita ligilo aŭ interreto malhavebla + NewPipe estas elŝutanta + Premu por detaladoj + Bonvolu atendi… + Enpoŝigita + Bonvolu difini elŝutan dosierujon poste en agordoj + + %s spektaĵo + %s spektaĵoj + + k + M + B + Pri NewPipe + Agordoj + Pri + Eksteraj permesiloj + © %1$s de %2$s sub %3$s + Ne povis ŝargi permisilon + Malfermi la retejon + Pri + Kontribuintoj + Permesiloj + Rigardu ĉe GitHub + Permesilo de NewPipe + Ĉu vi havas ideojn pri; traduko, desegnaĵoj ŝanĝoj, purigado de kodo, aŭ realaj masivaj ŝanĝoj—helpo estas ĉiam bonvena. Ju pli oni faras, des pli bonas! + Legu permesilon + Kontribui + Permesitaj karakteroj en dosiernomoj + Nevalidaj karakteroj estas anstataŭigita kun ĉi tiu valoro + Anstataŭa karaktero + Historio + Serĉita + Spektita + La historio estas malŝatita + Historio + La historio estas malplena + Historio vakigita + Neniuj rezultoj + Neniu enhavo + Neniuj abonantoj + + %s abonanto + %s abonantoj + + Neniuj spektaĵoj + Neniu filmeto + Ero forviŝita + Ĉu vi volas forviŝi tion eron el la historio de serĉo \? + Enhavo de la ĉefpaĝo + Malplena Paĝo + Kioska Paĝo + Abonpaĝo + Paĝo de la fluo + Kanala Paĝo + Elekti kanalon + Neniuj kanalaj abonoj ankoraŭ + Elekti kioskon + Komenci ludi ĉi tie + Komenci ludi en la fono + Donaci + NewPipe estas programada par volontuoj, elspezante tempo por alporti vin la plej bona sperto. Redonu por helpi programistojn plibonigi NewPipe dum ĝuante tason da kafo. + Redoni + Retejo + Vizitu la retejon de NewPipe por pli da informoj kaj novaĵoj. + Malfermi la tirkeston + Fermi la tirtekston + Ekstaraj ludantoj ne suportas tiajn ligilojn + Filmetoludilo + Fona ludilo + Akiranta informoj… + Ŝarĝante petita enhavo + Eksportita + Importita + Neniu valida ZIP-dosiero + Averto: Ne povis importi ĉiujn dosierojn. + Ĉi tio nuligos vian nunan aranĝon. + Trenu por reorgidi + Krei + Forigi Unu + Forigi ĉiujn + Rezigni + Alinomi + Ĉu vi volas forviŝi tion eron el la spekta historio \? + Ĉu vi certas, ke vi volas forviŝi ĉiujn erojn el la historio\? + Lasta Ludado + Plej ludataj filmetoj + Neniuj Subtitoloj + Alĝustigi + Plenigi + Zomi + Io aperos ĉi tie baldaŭ ;D + Aŭtomate generita + Ebligi LeakCanary + La monitorado de la memorlikadoj povas frostigi la apon dum la hejta dumpingo + Signali ekster-vivciklajn erarojn + Perforti signalante neenretigaj Rx esceptoj eksere la fragmento aŭ aktiveco vivciklo post dispono + La dosiero ne ekzistas aŭ la legopermeso mankas + Importi/eksporti + Importi + Importi el + Eksporti al + Importante … + Eksportante… + Importi la dosieron + Antaŭa eksportaĵo + Ne povis importi abonojn + Ne povis eksporti abonojn + via salutnomo, soundcloud.com/salutnomo + "Memoru, ke ĉi tiu operacio povas esti multekosta en la reto. +\n +\nĈu vi volas daŭrigi\?" + Kontroloj de rapideco de ludo + Tempoindiko + Ludkampo + Malligi (povas kaŭzi distordon) + Preferita \'malfermi\' ago + Defaŭlta ago malfermante enhavo — %s + Subtitoloj + Modifi la dimension de la teksto kaj la fonajn stilojn de la subtitoloj de la ludilo. Ĝi bezonas restarto de la apo por efektiviĝi. + 1 ero forviŝita. + NewPipe estas programaro sub rajtoceda permesilo: Vi povas uzi, studi, komuniki kaj plibonigi ĝin kiel vi volas. Precize, vi povas redistribui kaj/aŭ modifi ĝin sub la kondiĉojn de la Ĝenerala Publika Permesilo de GNU, kiel publikigita per la Free Software Foundation, ĉu en la versio 3, ĉu (se vi volas) ajna posta versio. + Ĉu vi volas ankaŭ importi agordojn\? + Privateca politiko de NewPipe + La NewPipe projekto respektas vian privatecon serioze. Konsekvence, la apo ne kolektas ajnan datumo sen via konsento. +\nLa privateco politiko de Newpipe detale eksplikas kion datumon estas sendita kaj stokita kiam vi sendas falegosignalon. + Legi la privatecan politikon + Por konformiĝi al la Ĝenerala Datum-Protekta Regularon (GDPR), ni allogas vian atento al la privateca politiko de NewPipe. Bonvolu legi ĝin atentive. +\nVi devas akcepti ĝin por sendi nin la cimsignalo. + Akcepti + Rifuzi + Neniu limo + Minimumigi dum la apo ŝanĝo + Ago dum ŝanĝante al alia apo el la ĉefa filmetludilo + Neniu + Minimumigi por ludi fone + Plirapidigi dum silentoj + Paŝo + Restarigi + Uzante defaŭltajn ongletojn, eraro dum leganta savajn ongletojn + Restaŭri la defaŭltojn + Ĉu vi volas restaŭri la defaŭltojn \? + Kalkulo de abonantoj malhavebla + Kioj ongletoj estas montritaj en la ĉefpaĝo + Elektaĵo + Ĝisdatigoj + Montri sciigon por proponi ĝisdatigon de la apo kiam nova versio estas havebla + Lista vido maniero + Listo + Krado + Aŭto + Ŝanĝi vidon + Ĝisdatigo de NewPipe havebla ! + Premu por elŝuti + Finita + Pritraktata + Paŭzigita + postprocesado + Ago rifuzita kaŭze de la sistemon + Elŝuto fiaskis! + Elŝuto finita + %s elĝutoj finitaj + Generu unikan nomon + Anstataŭigi + Estas elŝuto en progreso kun ĉi tiu nomo + Montri la eraron + Kodo + La celloko-dosierujo ne povas esti kreita + La dosiero ne povas esti kreita + Permeso rifuzita kaŭze de la sistemo + Sekura konekto malsukcesis + Ne povis trovi la servilon + Ne povas konektiĝi al la servilo + La servilo ne sendas datumojn + La servilo ne akceptas plurfadenajn elŝutojn, reprovu kun @string/msg_threads = 1 + Petita kampo ne estas havebla + Ne trovita + Postprocesado malsukcesis + Forviŝi la finitajn elŝutojn + Daŭrigi viajn %s pritraktajn delokigojn el Elŝutoj + Utila dum la ŝanĝo al poŝdatumoj, kvankam kelkaj elŝutoj ne povas esti interrompitaj + Neniu komento + Dosiero kun ĉi tiu nomo jam ekzistas + Elŝutita dosieron kun ĉi tiu nomo jam ekzistas + Estas pritraktata elŝuto kun ĉi tiu nomo + NewPipe estis fermita dum laborante sur la dosiero + Neniu spaco havebla sur la aparato + Progreso perdita, ĉar la dosiero estis forviŝita + Eltempiĝo de Konekto + Ŝangi la servon, nuntempe elektita: + Defaŭlta Kiosko \ No newline at end of file From ed4196f732acf6e8cbeca7d4f71e9850eb4b1824 Mon Sep 17 00:00:00 2001 From: naofum Date: Mon, 11 Nov 2019 15:30:09 +0000 Subject: [PATCH 105/270] Translated using Weblate (Japanese) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 3ff912cc4..b67da798c 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -500,7 +500,7 @@ ダウンロードフォルダーを変更して有効にします サービスの切り替え、現在の選択: - 動画 + %s 動画 デフォルトのキオスク \ No newline at end of file From 86d4cf3dfcfaf48f8dbf9a7e2a82bd5e706e321b Mon Sep 17 00:00:00 2001 From: Mitesh Sanjay Mutha Date: Tue, 12 Nov 2019 09:17:08 +0000 Subject: [PATCH 106/270] Translated using Weblate (Hindi) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-hi/strings.xml | 42 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 24506df84..364ab8f02 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -128,7 +128,7 @@ वापस जाए सारे प्ले करे NewPipe की अधिसूचना - NewPipe के बैकग्राउंड में चल रहे विडियो और पॉपअप विडियो के लिए अधिसूचना + न्यूपाइप के बैकग्राउंड में चल रहे विडियो और पॉपअप विडियो के लिए अधिसूचना [नहीं जानते] त्रुटी नेटवर्क में त्रुटी @@ -184,8 +184,8 @@ %s दर्शके - वीडियो - वीडियोस + %s वीडियो + %s वीडियो शुरू रोके @@ -306,7 +306,7 @@ प्लेलिस्ट में जोड़ा गया प्लेलिस्ट का थंबनेल बदल दिया गया है। सूची हटाने में असफल। - कोई कैप्शन नहीं है + कोई अनुशीर्षक नहीं है फिट भरें ज़ूम करें @@ -318,7 +318,7 @@ छायाप्रारुप लोड करें तेजी से अचूक तलाश का प्रयोग करें अचूक खोज प्लेयर को कम परिशुद्धता के साथ तेजी से पदों की तलाश करने की अनुमति देता है - लोडिंग थंबनेल, डेटा और मेमोरी उपयोग को रोकने के लिए बंद करें। इन-मेमोरी और ऑन-डिस्क छवि कैश दोनों को बदलता है + थंबनेल लोड करने, डेटा और मेमोरी उपयोग को रोकने के लिए बंद करें। इन-मेमोरी और ऑन-डिस्क छवि कैश दोनों को बदलता है| छवि कैश मिटा दिया कैश मेटाडेटा वाइप करें सभी कैश किए गए वेबपृष्ठ डेटा हटाएं @@ -353,8 +353,8 @@ क्या आप सेटिंग्स भी आयात करना चाहते है? पसंदीदा \'खोलने\' कि प्रक्रिया सामग्री खोलते समय डिफ़ॉल्ट कारवाही — %s - केप्सन - प्लेयर केप्शन के शब्दों का परिमाण और पृष्ठभूमि शैलियो को बदले। लागू करने के लिए ऐप को पुनः प्रारम्भ करना जरूरी है। + अनुशीर्षक + प्लेयर अनुशीर्षक के शब्दों का परिमाण और पृष्ठभूमि शैलियो को बदले। लागू करने के लिए ऐप को पुनः प्रारम्भ करना जरूरी है। आयात/निर्यात आयात से आयात करे @@ -370,7 +370,7 @@ \n1. इस URL पर जाए: %1$s \n2. जब कहा जाए, लॉगिन करे \n3. एक डाउनलोड शुरू होना चाहिए (यही निर्यात की गई फाइल है) - आपका आई डी, soundcloud.com/yourid + आपका आई डी, soundcloud.com/(आपका आई डी) ध्यान रखे, यह तरीका नेटवर्क साधनो के लिए मंहगा हो सकता है। \n \nक्या आप आगे बढ़ना चाहते है? @@ -385,7 +385,7 @@ अस्वीकार करे असीमित मोबाइल डेटा उपयोग करते समय रेसॉल्युसेन को सिमित करे - ऐप बदलते समय मिनिमाइज करे + ऐप बदलते समय उसे मिनिमाइज करे मुख्य वीडियो चालक से दूसरी ऐप पर जाने पर — %s कोई नही पृष्ठभूमि प्लेयर में बदले @@ -461,7 +461,7 @@ सूचियों में स्थान प्लेबैक स्थान निशान सूचियों में दिखाए डाटा मिटायें - प्लेबैक स्थान मिटा दिए गए + प्लेबैक स्थान मिटा दिए गए| फाइल की जगह बदली गयी या फिर फाइल मिटा दी गयी इस नाम की कोई फ़ाइल पहले से मौजूद है इस नाम की एक डाउनलोड की गई फ़ाइल पहले से मौजूद है @@ -483,4 +483,26 @@ सारे प्लेबैक स्थानों को मिटाये सारे प्लेबैक स्थानों को मिटाये\? प्रभावी होने के लिए डाउनलोड फ़ोल्डर बदलें + फ्रेगमेंट या एक्टिविटी लाइफसाइकिल के बाद Rx सन्देश ना पहुँचाया जा सके तोह ज़रूर कोशिश करे + SoundCloud प्रोफाइल निर्यात करने के लिए आईडी या युआरएल दीजिये: +\n१ अपने वेब ब्राउज़र मैं \"डेस्कटॉप मोड\" चालू करे (वेबसाइट मोबाइल उपकरणों के लिए उपलब्ध नहीं है) +\n२ इस युआरएल को खोले:% 1 $ s +\n३ लोग इन करे +\n४ आप जिस प्रोफाइल युआरएल पे भेजे जाते है उसे कॉपी करे| + यूरोप के जनरल डाटा प्रोटेक्शन रेगुलेशन (जी डी पी आर) का पालन करने के लिए, हम आपका ध्यान न्यूपाइप की नयी प्राइवेसी पालिसी पी डालना चाहते है।इसे बारीकी से पढ़िए। +\nआपको अगर हमें किसी मुसीबत का सन्देश भेजना हो तो इसे स्वीकार करे। + सामान्य टैब्स का इस्तेमाल, सहेजे टैब्स को पढ़ने में रूकावट + जब इस ऐप के लिए अपडेट उपलब्ध हो, अधिसूचना दिखाई जाये + सूचि देखने वाला ढंग + ग्रिड + ऑटो + मुसीबत दिखाए + सर्वर मल्टी थ्रेडेड डाउनलोड स्वीकार नहीं करता, पुनः कोशिश करे @string/msg_threads = 1 के साथ + आपके %s रुके हुए कामों को डौन्लोडस में पुनः से शुरू करे + आपको डाउनलोड कहाँ सहेजना है हर डाउनलोड पर पूछा जायेगा +\nअगर आपको एक्सटर्नल एस डी कार्ड पर डाउनलोड करना हो तो एस ऐ ऍफ़ चुनिए + स्टोरेज एक्सेस फ्रेमवर्क (एस ऐ ऍफ़) आपको एस डी कार्ड पर डाउनलोड करने देता है +\nसुचना - कुछ उपकरणों पर ये नहीं चलता + सेवा चुने, वर्तमान चुनाव : + सामान्य कीओस्क \ No newline at end of file From e3829303f94e19ee66ab3d3f66697aae235d684d Mon Sep 17 00:00:00 2001 From: Adolfo Jayme Barrientos Date: Wed, 13 Nov 2019 09:17:45 +0000 Subject: [PATCH 107/270] Translated using Weblate (Spanish) Currently translated at 99.8% (500 of 501 strings) --- app/src/main/res/values-es/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6083b6952..3aa0bac66 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -235,13 +235,13 @@ Reproductor emergente Quitar Detalles - Ajustes de audio + Configuración de audio [Desconocido] Añadir a cola en segundo plano Añadir a cola en reproductor emergente Comenzar a reproducir aquí Comenzar a reproducir en segundo plano - Comenzar a reproducir en un popup nuevo + Comenzar a reproducir en modo emergente Mostrar consejo «Mantener presionado para agregar» Novedades Mantener presionado para agregar a la cola From 4d4a86f88975acd8e4d0d834e6c1f3f9371b88e0 Mon Sep 17 00:00:00 2001 From: ozyc Date: Sun, 10 Nov 2019 12:20:32 +0000 Subject: [PATCH 108/270] Translated using Weblate (French) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-fr/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 3b2b15a38..a6c3a9ef7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -86,7 +86,7 @@ Téléchargements Rapport d’erreur Démarrer - Pause + Mettre en pause Lire Supprimer Somme de contrôle @@ -490,7 +490,7 @@ Limiter la file d’attente de téléchargement Un téléchargement s’exécutera en même temps Démarrer les téléchargements - Mettre en pause les téléchargements + Mettre les téléchargements en pause Demander où télécharger Vous allez être interrogé sur l’emplacement d’enregistrement de chaque téléchargement Vous allez être interrogé sur l’emplacement de téléchargement de chaque téléchargement. @@ -507,4 +507,5 @@ Voulez-vous supprimer toutes les positions de reprise \? Changez les dossier de téléchargement pour activer Activer/Désactiver le service, actuellement sélectionné : + Kiosque par défaut \ No newline at end of file From cb6d7e6dd7a3e7c97c1651af114fdae4963c0baa Mon Sep 17 00:00:00 2001 From: Pekka Ristola Date: Mon, 11 Nov 2019 19:24:50 +0000 Subject: [PATCH 109/270] Translated using Weblate (Finnish) Currently translated at 67.3% (337 of 501 strings) --- app/src/main/res/values-fi/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 6749a90f2..11c3f9111 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -17,7 +17,7 @@ Valitse selain kierto Käytä ulkoista videosoitinta - Joillain resoluutioilla EI ole ääntä, kun tämä on valittuna + Poistaa äänen JOILLAIN resoluutioilla Käytä ulkoista äänisoitinta NewPipe ponnahdusikkuna Tilaa @@ -397,4 +397,5 @@ Ei koskaan Pienennä taustasoittimeksi Muuta ponnahdusikkunaksi + Peru tilaus \ No newline at end of file From f4dbd5c9bdb41ee412dd53d25fd5541f1a16ef2e Mon Sep 17 00:00:00 2001 From: Koo Hyomin Date: Sun, 10 Nov 2019 19:40:55 +0000 Subject: [PATCH 110/270] Translated using Weblate (Korean) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ko/strings.xml | 109 +++++++++++++------------ 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 8e3d28a00..333891910 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1,16 +1,16 @@ - 조회수 %1$s + 조회수 %1$s회 %1$s에 업로드됨 - 스트리밍 플레이어가 발견되지 않았습니다. VLC를 설치할까요? + 스트림 플레이어가 발견되지 않았습니다. VLC를 설치할까요\? 설치 취소 - 브라우저에서 열기 + 브라우저로 열기 공유 다운로드 검색 설정 - 혹시 이것을 검색하셨습니까\?: %1$s\? + %1$s를 찾으셨나요\? 다음으로 공유 브라우저 선택 회전 @@ -26,7 +26,7 @@ 기본 오디오 형식 다운로드 다음 - 다음 및 유사한 비디오 표시 + 다음/유사한 비디오 표시 지원하지 않는 URL입니다 기본 컨텐츠 언어 비디오 & 오디오 @@ -53,8 +53,8 @@ 다운로드 디렉토리를 만들 수 없습니다 \'%1$s\' 다운로드 디렉토리를 만들었습니다 \'%1$s\' 검색 버튼을 눌러서 시작하세요 - 자동으로 재생 - NewPipe가 다른 앱으로부터 호출되었을 때 동영상을 재생합니다 + 자동재생 + NewPipe가 다른 앱에서 호출되었을 때 동영상을 재생합니다 컨텐츠 연령 제한 컨텐츠 연령 제한 비디오입니다. 설정 메뉴에서 시청 허용 여부를 변경하실 수 있습니다. @@ -88,18 +88,18 @@ 일시정지 삭제 체크섬 - 팝업 모드에서 열기 + 팝업 모드로 열기 일부 해상도에서 소리가 나지 않을 수 있습니다 NewPipe 팝업 모드 구독 - 구독됨 - 구독 해제됨 + 구독중 + 구독 취소됨 구독 여부를 변경할 수 없음 구독을 업데이트할 수 없음 메인 화면 구독 새로운 영상 - 배경 + 백그라운드 팝업 기본 팝업 해상도 높은 해상도 표시 @@ -108,8 +108,8 @@ 검은 테마 팝업 크기 및 위치 기억 마지막으로 사용한 팝업 위치 및 크기를 기억합니다 - 제스쳐로 재생 조작 - 제스쳐를 사용해 화면 밝기와 음량을 조절합니다 + 제스처 재생 조작 + 제스처를 사용해 화면 밝기와 음량을 조절합니다 검색 제안 검색 중에 제안을 표시합니다 검색 기록 @@ -119,10 +119,10 @@ 자동으로 다시 재생 전화 통화 등으로 인해 재생이 중단된 이후에 다시 재생을 시작합니다 \"길게 눌러 대기열에 추가하기\" 팁 표시 - 비디오 상세 정보 페이지에서 백그라운드 재생 또는 팝업 버튼을 누를 경우 팁을 표시합니다 + 비디오 상세 정보 페이지에서 백그라운드/팝업 재생 버튼을 누를 경우 팁을 표시합니다 플레이어 동작 - 기록과 캐시 + 기록 & 캐시 팝업 팝업 모드에서 재생 중 백그라운드 플레이어에 대기됨 @@ -157,11 +157,11 @@ 십억 구독자 없음 - %s 구독자 + 구독자 %s명 - 시청 횟수 없음 + 조회수 없음 - %s 시청 횟수 + 조회수 %s회 비디오 없음 재생 @@ -175,13 +175,13 @@ 올바르지 않은 URL이거나 인터넷에 접속할 수 없음 NewPipe 다운로드 중 터치해서 상세 정보 확인 - 잠시만 기다려주십시오… + 잠시만 기다리세요… 클립보드에 복사됨 - 다운로드 할 폴더를 설정에서 지정하십시오 + 다운로드 할 폴더를 설정에서 지정하세요 이 권한은 팝업 모드에서 \n열기 위해 필요합니다 - 로봇인지 확인 (reCAPTCHA) - reCAPTCHA challenge 요청됨 + reCAPTCHA + reCAPTCHA 확인 요청됨 다운로드 파일명에 허용되는 문자 올바르지 않은 문자는 다음 문자로 대체됩니다 @@ -200,7 +200,7 @@ 라이센스 가볍고 빠른 자유 안드로이드 스트리밍 앱입니다. 기여 - 번역, 디자인, 코딩 등 다양한 기여를 언제나 환영합니다. 향상에 참여해주세요! + 번역, 디자인, 코딩 등 다양한 기여를 언제나 환영합니다. 더 나아지도록 도와주세요! GitHub에서 보기 기부 여러분들의 더 나은 경험을 위해 많은 사람들이 NewPipe를 개발하는데 노력을 기울이고 있습니다. NewPipe에 참여하는 개발자들이 커피 한 잔을 즐길 수 있도록 기부해주세요. @@ -212,7 +212,7 @@ 기록 검색함 시청함 - 기록을 사용하지 않습니다 + 기록 기능이 꺼져 있습니다 기록 기록이 없습니다 기록이 삭제되었습니다 @@ -237,20 +237,20 @@ 상세 정보 오디오 설정 눌러서 대기열에 추가 - 백그라운드로 갈 경우 재생목록에서 제거 - 새 팝업으로 갈 경우 재생목록에서 제거 + 백그라운드 대기열에 추가 + 새 팝업으로 대기열에 추가 여기서부터 재생 백그라운드에서 재생 새 팝업에서 재생 - 스트리밍 플레이어를 찾을 수 없습니다. VLC를 설치하여 동영상을 재생할 수 있습니다. - 스트리밍 파일 다운로드하기 + 스트림 플레이어를 찾을 수 없습니다 (VLC를 설치하여 동영상을 재생할 수 있습니다). + 스트림 파일 다운로드하기 정보 보기 - 플레이리스트 북마크 + 재생목록 북마크 이곳에 추가 정확하지는 않지만 빠른 탐색 정확하지 않은 탐색은 더 빠르게 위치를 탐색할 수 있지만 정확도는 떨어집니다 - 다음 스트림을 자동으로 재생열에 추가하기 - 이전 스트림이 무한 반복 재생 큐가 아닐 경우, 관련 스트림을 자동 재생합니다 + 다음 스트림을 자동으로 대기열에 추가하기 + 이전 스트림이 반복 재생 대기열이 아닐 경우, 관련 스트림을 자동 재생합니다 기본 콘텐츠 국가 서비스 디버그 @@ -270,11 +270,11 @@ 발견된 오디오 스트림 없음 드래그하여 재배열 만들기 - 1개 삭제하기 + 하나 삭제하기 모두 삭제하기 취소 이름 바꾸기 - 로봇인지 확인합니다 + reCAPTCHA 확인 이 항목을 시청 기록에서 삭제하시겠습니까? 모든 항목을 시청 기록에서 삭제하시겠습니까? 마지막으로 재생 @@ -282,7 +282,7 @@ 내보내기 완료 가져오기 완료 유효한 ZIP 파일 없음 - 경고: 모든 파일 가져오기를 실패했습니다. + 경고: 파일을 전부 가져오지 못했습니다. 이것은 현재 설정을 덮어쓸 것입니다. 드로어 열기 드로어 닫기 @@ -292,7 +292,7 @@ 팝업 플레이어 항상 묻기 정보 가져오는 중… - 요청한 콘텐츠를 로딩 중입니다 + 요청한 콘텐츠를 로드 중입니다 새로운 재생목록 삭제 이름 바꾸기 @@ -304,15 +304,15 @@ 이 재생목록을 삭제하시겠습니까? 재생목록 생성 완료 재생목록에 추가됨 - 재생목록 썸내일이 바뀜. + 재생목록 썸네일 변경 완료. 재생목록을 삭제할 수 없습니다. 자막 없음 꼭 맞게 하기 채우기 확대 자동 생성됨 - LeakCanary 할성화 - 힙 덤프 중 메모리 누수 점검으로 앱이 불안정해질 수 있습니다 + LeakCanary 활성화 + 메모리 누수 감시를 사용할 시 힙 덤프로 인해 앱이 반응하지 않을 수 있습니다 out-of-lifecycle 오류 보고 프래그먼트 또는 버려진 액티비티 주기 밖에서 일어나는 전달할 수 없는 Rx 예외를 강제적으로 보고하기 파일 @@ -345,9 +345,9 @@ \n3. 로그인이 필요하면 하세요. \n4. 리디렉트된 프로필 URL을 복사하세요. 프로필ID, soundcloud.com/프로필ID - 경고: 데이터 소모량이 늘어날 수 있습니다. + 경고: 데이터가 많이 소모될 수 있습니다. \n -\n계속하시겠습니까? +\n계속하시겠습니까\? 썸네일 로드하기 동영상 썸네일을 로드하지 않으며, 데이터와 메모리 사용을 최대한 줄입니다. 이 옵션을 선택 시 모든 메모리 캐시와 저장소 캐시를 삭제합니다. 이미지 캐시 지워짐 @@ -364,9 +364,9 @@ 선호하는 열기 동작 컨텐츠를 열 때 사용할 기본 동작 — %s 자막 - 플레이어 자막 텍스트 크기와 배경 스타일을 변경합니다. 효과를 적용하려면 앱을 재시작 해야합니다. - 채널만 - 재생 목록만 + 플레이어 자막 글자 크기와 배경 스타일을 변경합니다. 이를 적용하려면 앱을 재시작 해야 합니다. + 채널 + 재생목록 시청 기록 삭제하기 동영상 시청 기록과 저장된 재생 위치를 삭제합니다 모든 시청 기록을 삭제하시겠습니까\? @@ -380,19 +380,19 @@ \nNewPipe 개인정보 보호 정책에서는 오류 보고 시 어떠한 정보가 수집되고 저장되는지 자세히 명시되어 있습니다. 개인정보 보호 정책 읽기 NewPipe는 카피레프트 자유 소프트웨어입니다. 사용자는 이 앱을 사용, 공유, 또는 수정할 수 있고, 수정 후 재배포 시 자유 소프트웨어 재단의 GNU 라이센스 버전 3 또는 그 이상의 버전을 포함해야 합니다. - 앱 설정을 가져오시겠습니까? - 무음 구간 스킵 + 설정도 가져오시겠습니까\? + 무음 구간 빨리 감기 유럽 연합 일반 데이터 보호 규정 (GDPR) 에 따라, 사용자는 NewPipe 개인정보 보호 정책을 읽고 꼼꼼히 확인해야 합니다. 버그 리포트를 보내시려면 개인정보 보호 정책에 동의해주세요. 동의 동의하지 않음 데이터 제한 없음 모바일 데이터 사용 시 화질 제한 - 구독 해제 + 구독 취소 새 탭 탭 선택 - 제스처로 음량 조작 - 제스처를 사용해 플레이어의 음량을 조작 합니다 - 제스처로 밝기 조작 + 제스처 음량 조작 + 제스처를 사용해 플레이어의 음량을 조작합니다 + 제스처 밝기 조작 제스처를 사용해 화면 밝기를 조작합니다 업데이트 트랙 @@ -414,8 +414,8 @@ 저장된 탭을 읽는 중 오류가 발생하여 기본 탭을 사용합니다 초기화 초기 설정으로 복원하시겠습니까\? - 구독자 숫자가 없습니다 - 메인 화면에 표시할 탭 + 구독자 수를 가져올 수 없습니다 + 메인 화면에 표시할 탭을 설정합니다 선택 업데이트 새 버전이 있을 경우 앱을 업데이트하도록 알림 표시 @@ -473,7 +473,7 @@ 닫기 재생 재개 마지막 재생 위치부터 재생 - 리스트 내 위치 + 리스트 내 위치 표시 리스트에서 재생 위치를 표시합니다 데이터 삭제 재생위치 삭제완료. @@ -488,7 +488,7 @@ 다운로드 대기 제한 다운로드 시작 다운로드 일시정지 - 다운로드 위치를 물음 + 다운로드 위치를 묻기 다운로드 할때 마다 저장위치를 물을 것 입니다 SAF 사용 스토리지 액세스 프레임워크(SAF)는 외장 SD카드에 다운로드 할 수 있도록 해줍니다. @@ -499,9 +499,10 @@ 매 다운로드 마다 저장경로를 묻습니다. \n외장 SD카드에 다운로드 하고자 한다면 SAF를 선택하십시오 - 동영상만 + %s개의 동영상 하나의 다운로드가 동시에 진행됩니다 적용하려면 다운로드 폴더를 변경하세요 서비스 토글, 현재 선택된 서비스: + 기본 키오스크 \ No newline at end of file From b665122c3c0df40a0b8cd5ed4f884d5e9bfc9cde Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 11 Nov 2019 03:30:53 +0000 Subject: [PATCH 111/270] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index e893c336d..fe4c1b00a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -500,7 +500,7 @@ 變更下載資料夾以使其生效 切換服務,目前已選取: - 影片 + %s 影片 預設 Kiosk \ No newline at end of file From f449aee90105db1f7b694f4fe54d723cb4926cd6 Mon Sep 17 00:00:00 2001 From: winqooq Date: Thu, 14 Nov 2019 15:23:30 +0000 Subject: [PATCH 112/270] Translated using Weblate (Ukrainian) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-uk/strings.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index d86ce9a50..375557b04 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -233,9 +233,9 @@ %s підписників - %s Відео - %s Відео - %s Відео + %s відео + %s відео + %s відео Створити Видалити одне @@ -510,4 +510,5 @@ Видалити усі запам\'ятовані позиції\? Змініть папки завантаження для ефективності Перемкнути службу, наразі обрано: + Кіоск за замовчуванням \ No newline at end of file From cb19f792da84d0300fbf362c75622289cc00ab0e Mon Sep 17 00:00:00 2001 From: WaldiS Date: Thu, 14 Nov 2019 18:31:51 +0000 Subject: [PATCH 113/270] Translated using Weblate (Polish) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-pl/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8b4073d99..d3c84aa22 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -510,4 +510,6 @@ Usuwa wszystkie pozycje odtwarzania Usunąć wszystkie pozycje odtwarzania\? Zmień foldey pobierania, aby zostały uwzględnione + Przełączanie usługi, aktualnie wybranej: + Domyślny Kiosk \ No newline at end of file From 9915287a5a15fd3fd74af31997c19cb7928ae0cf Mon Sep 17 00:00:00 2001 From: thami simo Date: Thu, 14 Nov 2019 12:48:29 +0000 Subject: [PATCH 114/270] Translated using Weblate (Arabic) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-ar/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 43ef5d024..7156d08ba 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -246,12 +246,12 @@ %s شاهدو - %s لا يوجد فيديو - %s فيديو - %s فيديوان - %s فيديوات - %s فيديوهات - %s مزيد من الفيديوات + فيديو%s video + %s videosفيديوهات + %s videosفيديوهات + %s videosفيديوهات + %s videosفيديوهات + %s videosفيديوهات طلب اختبار الكابتشا مطلوب © %1$sبواسطة%2$sتحت%3$s From d694e6100617c3b28a29b2215261466c7759a420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89frit?= Date: Sat, 16 Nov 2019 19:23:37 +0000 Subject: [PATCH 115/270] Translated using Weblate (French) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-fr/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a6c3a9ef7..e6160c0bd 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -18,10 +18,10 @@ Paramètres Partager Partager avec - Afficher une option pour lire une vidéo avec Kodi + Affiche une option pour lire une vidéo avec Kodi Afficher l’option « Lire avec Kodi » Publiée le %1$s - %1$s vues + %1$s vues Audio Format audio par défaut Télécharger @@ -129,7 +129,7 @@ Seuls certains appareils prennent en charge les vidéos 2K et 4K Format vidéo par défaut Mémoriser les taille et position de la fenêtre flottante - Mémoriser les dernières taille et position de la fenêtre flottante + Mémorise les dernières taille et position de la fenêtre flottante Fenêtre flottante Filtre Rafraîchir @@ -257,7 +257,7 @@ Service Ouvrir le menu Fermer le menu - Aucun lecteur de flux trouvé (vous pouvez installer VLC pour le lire). + Aucun lecteur de flux multimédias trouvé (vous pouvez installer VLC pour le lire). Toujours Une seule fois Les lecteurs externes ne prennent pas en charge ces types de liens @@ -308,7 +308,7 @@ Ajuster Zoomer Utiliser la recherche rapide approximative - Permettre au lecteur d’accéder plus rapidement à une position au détriment de la précision + Permet au lecteur d’accéder plus rapidement à une position au détriment de la précision Charger les miniatures Désactiver pour empêcher le chargement des miniatures, afin de réduire l’utilisation de bande passante et de mémoire. Modifier cette option vide les caches d’image en mémoire vive et sur le disque. Images en cache effacées @@ -505,7 +505,7 @@ Supprimer les positions de lecture Supprimer toutes les positions de reprise Voulez-vous supprimer toutes les positions de reprise \? - Changez les dossier de téléchargement pour activer + Changez les dossiers de téléchargement pour que cela prenne effet Activer/Désactiver le service, actuellement sélectionné : Kiosque par défaut \ No newline at end of file From 0338ff8d514c90d0672b2138d9d075d2ad838b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20M=C3=BCnster?= Date: Sat, 16 Nov 2019 17:00:23 +0000 Subject: [PATCH 116/270] Translated using Weblate (Swedish) Currently translated at 91.2% (457 of 501 strings) --- app/src/main/res/values-sv/strings.xml | 61 +++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 5364678da..92785a950 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -163,8 +163,8 @@ Inga videor - Videor - Videor + %s video + %s videor Start Pausa @@ -402,4 +402,61 @@ Avsluta prenumeration Ny flik Välj flik + Gestkontroll för volym + Använd gester för att justera spelarens volym + Gestkontroll för ljusstyrka + Använd gester för att justera ljusstyrkan + Uppdateringar + Fil raderad + Appuppdateringsnotifikation + Notifikationer för nya NewPipe versioner + Extern lagring otillgänglig + Fel vid läsning av sparade flikar, använder standard flikar istället + Återställ default + Vill du återställa default\? + Antalet prenumeranter är otillgängligt + Vilka flikar visas på Huvudsidan + Markering + Uppdateringar + Visa notifikation för att uppdatera appen när en ny version finns tillgänglig + Listvy + Lista + Rutnät + Auto + Växla vy + NewPipe uppdatering tillgänglig! + Tryck för att ladda ned + Färdig + pausad + köad + efterbehandling + + Åtgärd som nekas av systemet + Nedladdningen misslyckades + Nedladdningen är färdig + %s nedladdningar är färdiga + Generera unikt namn + Skriv över + Det finns en pågående nedladdning med det här namnet + Visa fel + Kod + Säker anslutning misslyckades + Kunde inte hitta servern + Kunde inte ansluta till servern + Servern skickar ingen data + Servern accepterar inte flertrådade nedladdningar, försök igen med @string/msg_threads = 1 + Hittades inte + Efterbehandling misslyckades + Töm färdiga nedladdningar + Fortsätt med %s väntande överföringar från Nedladdningar + Stanna + Max försök + Max antal försök tills nedladdning avbryts + Händelser + Konferenser + Visa kommentarer + + Kommentarer + + \ No newline at end of file From 8ac82584004f391e6605da3e14b2b135bed23000 Mon Sep 17 00:00:00 2001 From: Pekka Ristola Date: Fri, 15 Nov 2019 20:09:11 +0000 Subject: [PATCH 117/270] Translated using Weblate (Finnish) Currently translated at 67.7% (339 of 501 strings) --- app/src/main/res/values-fi/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 11c3f9111..1e91fa55a 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -398,4 +398,6 @@ Pienennä taustasoittimeksi Muuta ponnahdusikkunaksi Peru tilaus + Uusi välilehti + Valitse välilehti \ No newline at end of file From bdb86a7fdec2388b890fb609a14c7591e952abac Mon Sep 17 00:00:00 2001 From: chr56 Date: Sat, 16 Nov 2019 17:21:44 +0000 Subject: [PATCH 118/270] Translated using Weblate (Chinese (Simplified)) Currently translated at 19.2% (96 of 501 strings) --- .../main/res/values-b+zh+HANS+CN/strings.xml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index 70c0fbdec..ce0df6d37 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -8,4 +8,73 @@ 您是不是要找:%1$s? 找不到串流播放器 (您可以安裝並使用VLC播放)。 下载媒体文件 + 安装 + 取消 + 分享 + 下载 + 搜索 + 设置 + 分享给... + 选择浏览器 + 视频下载文件夹 + 已下载的视频存储在这里 + 请选择下载视频的保存位置 + 已下载的音频存储在这里 + 选择下载音频的储存位置 + 自动播放 + 使用Kodi播放 + 主题 + 浅色 + 暗黑 + 黑色 + 下载 + 下一个 + 不支持的 URL + 外观 + 其他 + 全部 + 频道 + + 稍后 + 网络错误 + + %s 个视频 + + + 禁用 + 背景 + 过滤器 + 刷新 + 搜索建议 + 订阅 + 已订阅 + 观看历史 + 播放器 + 历史记录与缓存 + 播放列表 + 撤销 + 全部播放 + 总是 + 仅一次 + 添加至 + 文件 + 加载缩略图 + 清除观看记录 + + 最小化后台播放器 + 最小化小窗口播放器 + 频道 + 播放列表 + 取消订阅 + 新标签 + 更新 + 文件已删除 + 无法得知订阅人数 + 每推出新版本时,弹出应用升级通知 + 格子 + 新版 NewPipe 已可升级! + 服务器不接受 接收 multi-threaded 下载, 以 @string/msg_threads = 1 重试 + 自动播放 + 清除数据 + 观看记录已删除 \ No newline at end of file From 71f1bbdcc13a0c05ea48d7acd1c1e63046f0a02c Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sun, 27 Oct 2019 23:35:51 -0300 Subject: [PATCH 119/270] Use new Localization and Downloader implementations from extractor --- app/build.gradle | 2 +- app/proguard-rules.pro | 2 + .../java/org/schabi/newpipe/DebugApp.java | 4 +- app/src/main/java/org/schabi/newpipe/App.java | 9 +- .../java/org/schabi/newpipe/Downloader.java | 296 ------------------ .../org/schabi/newpipe/DownloaderImpl.java | 156 +++++++++ .../org/schabi/newpipe/ImageDownloader.java | 2 +- .../org/schabi/newpipe/ReCaptchaActivity.java | 2 +- .../newpipe/download/DownloadDialog.java | 39 +-- .../fragments/detail/VideoDetailFragment.java | 10 +- .../holder/CommentsMiniInfoItemHolder.java | 13 +- .../holder/StreamInfoItemHolder.java | 6 +- .../org/schabi/newpipe/player/BasePlayer.java | 4 +- .../settings/ContentSettingsFragment.java | 35 ++- .../schabi/newpipe/util/ExtractorHelper.java | 2 +- .../org/schabi/newpipe/util/Localization.java | 44 +-- .../newpipe/util/StreamItemAdapter.java | 4 +- .../us/shandian/giga/get/DownloadMission.java | 4 +- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/content_settings.xml | 18 +- 20 files changed, 260 insertions(+), 394 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/Downloader.java create mode 100644 app/src/main/java/org/schabi/newpipe/DownloaderImpl.java diff --git a/app/build.gradle b/app/build.gradle index 3eff64138..bedfe9f4f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.teamnewpipe:NewPipeExtractor:v0.17.4' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:5c420340ceb39' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 20596c6eb..6b56e1c75 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -17,6 +17,8 @@ #} -dontobfuscate +-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; } + -keep class org.mozilla.javascript.** { *; } -keep class org.mozilla.classfile.ClassFileWriter diff --git a/app/src/debug/java/org/schabi/newpipe/DebugApp.java b/app/src/debug/java/org/schabi/newpipe/DebugApp.java index 154fb5a8c..66f73d1e9 100644 --- a/app/src/debug/java/org/schabi/newpipe/DebugApp.java +++ b/app/src/debug/java/org/schabi/newpipe/DebugApp.java @@ -15,7 +15,7 @@ import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.LeakDirectoryProvider; import com.squareup.leakcanary.RefWatcher; -import org.schabi.newpipe.extractor.Downloader; +import org.schabi.newpipe.extractor.downloader.Downloader; import java.io.File; import java.util.concurrent.TimeUnit; @@ -39,7 +39,7 @@ public class DebugApp extends App { @Override protected Downloader getDownloader() { - return org.schabi.newpipe.Downloader.init(new OkHttpClient.Builder() + return DownloaderImpl.init(new OkHttpClient.Builder() .addNetworkInterceptor(new StethoInterceptor())); } diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index bdf1e7837..95e56c620 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -21,13 +21,14 @@ import org.acra.config.ACRAConfiguration; import org.acra.config.ACRAConfigurationException; import org.acra.config.ConfigurationBuilder; import org.acra.sender.ReportSenderFactory; -import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.report.AcraReportSenderFactory; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.StateSaver; import java.io.IOException; @@ -95,7 +96,9 @@ public class App extends Application { SettingsActivity.initSettings(this); NewPipe.init(getDownloader(), - org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(this)); + Localization.getPreferredLocalization(this), + Localization.getPreferredContentCountry(this)); + StateSaver.init(this); initNotificationChannel(); @@ -109,7 +112,7 @@ public class App extends Application { } protected Downloader getDownloader() { - return org.schabi.newpipe.Downloader.init(null); + return DownloaderImpl.init(null); } private void configureRxJavaErrorHandler() { diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java deleted file mode 100644 index a8a581d1b..000000000 --- a/app/src/main/java/org/schabi/newpipe/Downloader.java +++ /dev/null @@ -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 - * 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 . - */ - -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 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 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 customProperties) throws IOException, ReCaptchaException { - final Request.Builder requestBuilder = new Request.Builder() - .method("GET", null).url(siteUrl); - - for (Map.Entry 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> requestHeaders = request.getRequestHeaders(); - // set custom headers in request - for (Map.Entry> 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> 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> 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()); - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java new file mode 100644 index 000000000..5738cf61a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -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> 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> pair : headers.entrySet()) { + final String headerName = pair.getKey(); + final List 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); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java index eb5e92e88..dfb7d3276 100644 --- a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java +++ b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java @@ -40,7 +40,7 @@ public class ImageDownloader extends BaseImageDownloader { } 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); } } diff --git a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java index 7f6af89c1..0a2d51b53 100644 --- a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java @@ -112,7 +112,7 @@ public class ReCaptchaActivity extends AppCompatActivity { // find cookies : s_gl & goojf and Add cookies to Downloader if (find_access_cookies(cookies)) { // Give cookies to Downloader class - Downloader.getInstance().setCookies(mCookies); + DownloaderImpl.getInstance().setCookies(mCookies); // Closing activity and return to parent setResult(RESULT_OK); diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 1d536ea1a..59bffa933 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -40,12 +40,12 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.MediaFormat; 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.Stream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.SubtitlesStream; 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.UserAction; import org.schabi.newpipe.settings.NewPipeSettings; @@ -488,35 +488,24 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck } private int getSubtitleIndexBy(List streams) { - Localization loc = NewPipe.getPreferredLocalization(); + final Localization preferredLocalization = NewPipe.getPreferredLocalization(); + int candidate = 0; for (int i = 0; i < streams.size(); i++) { - Locale streamLocale = streams.get(i).getLocale(); - String tag = streamLocale.getLanguage().concat("-").concat(streamLocale.getCountry()); - if (tag.equalsIgnoreCase(loc.getLanguage())) { - return i; + final Locale streamLocale = streams.get(i).getLocale(); + + final boolean languageEquals = streamLocale.getLanguage() != null && preferredLocalization.getLanguageCode() != null && + 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 - // 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; + return candidate; } StoredDirectoryHelper mainStorageAudio = null; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 37d8851ea..915ed59cc 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1120,9 +1120,15 @@ public class VideoDetailFragment videoTitleToggleArrow.setVisibility(View.VISIBLE); videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); 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()); updateProgressInfo(info); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index f2bf5df39..6741e4403 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -14,6 +14,7 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import java.util.regex.Matcher; @@ -101,10 +102,18 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { ellipsize(); } - if (null != item.getLikeCount()) { + if (item.getLikeCount() >= 0) { itemLikesCountView.setText(String.valueOf(item.getLikeCount())); + } else { + itemLikesCountView.setText("-"); + } + + if (item.getPublishedTime() != null) { + itemPublishedTime.setText(Localization + .formatDate(item.getPublishedTime().date().getTime())); + } else { + itemPublishedTime.setText(item.getTextualPublishedTime()); } - itemPublishedTime.setText(item.getPublishedTime()); itemView.setOnClickListener(view -> { toggleEllipsize(); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java index ea058bc0e..10c81a200 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java @@ -55,11 +55,11 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder { if (infoItem.getViewCount() >= 0) { viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); } - if (!TextUtils.isEmpty(infoItem.getUploadDate())) { + if (!TextUtils.isEmpty(infoItem.getTextualUploadDate())) { if (viewsAndDate.isEmpty()) { - viewsAndDate = infoItem.getUploadDate(); + viewsAndDate = infoItem.getTextualUploadDate(); } else { - viewsAndDate += " • " + infoItem.getUploadDate(); + viewsAndDate += " • " + infoItem.getTextualUploadDate(); } } return viewsAndDate; diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index b3c5716bc..a07afcea9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -55,7 +55,7 @@ import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import org.schabi.newpipe.BuildConfig; -import org.schabi.newpipe.Downloader; +import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -209,7 +209,7 @@ public abstract class BasePlayer implements this.progressUpdateReactor = new SerialDisposable(); this.databaseUpdateReactor = new CompositeDisposable(); - final String userAgent = Downloader.USER_AGENT; + final String userAgent = DownloaderImpl.USER_AGENT; final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build(); this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter); diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index d05c23564..39c1d890e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -17,8 +17,8 @@ import com.nononsenseapps.filepicker.Utils; import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.utils.Localization; +import org.schabi.newpipe.extractor.localization.ContentCountry; +import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.FilePickerActivityHelper; @@ -53,10 +53,16 @@ public class ContentSettingsFragment extends BasePreferenceFragment { private String thumbnailLoadToggleKey; + private Localization initialSelectedLocalization; + private ContentCountry initialSelectedContentCountry; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key); + + initialSelectedLocalization = org.schabi.newpipe.util.Localization.getPreferredLocalization(requireContext()); + initialSelectedContentCountry = org.schabi.newpipe.util.Localization.getPreferredContentCountry(requireContext()); } @Override @@ -108,20 +114,21 @@ public class ContentSettingsFragment extends BasePreferenceFragment { startActivityForResult(i, REQUEST_EXPORT_PATH); return true; }); + } - Preference setPreferredLanguage = findPreference(getString(R.string.content_language_key)); - setPreferredLanguage.setOnPreferenceChangeListener((Preference p, Object newLanguage) -> { - Localization oldLocal = org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(getActivity()); - NewPipe.setLocalization(new Localization(oldLocal.getCountry(), (String) newLanguage)); - return true; - }); + @Override + public void onDestroy() { + super.onDestroy(); - Preference setPreferredCountry = findPreference(getString(R.string.content_country_key)); - setPreferredCountry.setOnPreferenceChangeListener((Preference p, Object newCountry) -> { - Localization oldLocal = org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(getActivity()); - NewPipe.setLocalization(new Localization((String) newCountry, oldLocal.getLanguage())); - return true; - }); + final Localization selectedLocalization = org.schabi.newpipe.util.Localization + .getPreferredLocalization(requireContext()); + final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization + .getPreferredContentCountry(requireContext()); + + if (!selectedLocalization.equals(initialSelectedLocalization) + || !selectedContentCountry.equals(initialSelectedContentCountry)) { + Toast.makeText(requireContext(), R.string.localization_changes_requires_app_restart, Toast.LENGTH_LONG).show(); + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index c4471942e..0cebe5af3 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -32,7 +32,7 @@ import org.schabi.newpipe.extractor.Info; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; 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.comments.CommentsInfo; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 08c9c6d98..6835a48bd 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -10,6 +10,7 @@ import androidx.annotation.StringRes; import android.text.TextUtils; import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.localization.ContentCountry; import java.text.DateFormat; import java.text.NumberFormat; @@ -69,16 +70,18 @@ public class Localization { return stringBuilder.toString(); } - public static org.schabi.newpipe.extractor.utils.Localization getPreferredExtractorLocal(Context context) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization(final Context 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), - context.getString(R.string.default_language_value)); - - String countryCode = sp.getString(context.getString(R.string.content_country_key), - context.getString(R.string.default_country_value)); - - return new org.schabi.newpipe.extractor.utils.Localization(countryCode, languageCode); + public static ContentCountry getPreferredContentCountry(final Context context) { + final String contentCountry = PreferenceManager + .getDefaultSharedPreferences(context) + .getString(context.getString(R.string.content_country_key), context.getString(R.string.default_country_value)); + return new ContentCountry(contentCountry); } public static Locale getPreferredLocale(Context context) { @@ -106,27 +109,12 @@ public class Localization { return nf.format(number); } - private static String formatDate(Context context, String date) { - Locale locale = getPreferredLocale(context); - 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); - - return df.format(datum); + public static String formatDate(Date date) { + return DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(date); } - 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 localizeUploadDate(Context context, Date date) { + return context.getString(R.string.upload_date_text, formatDate(date)); } public static String localizeViewCount(Context context, long viewCount) { diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java index 49a7125ed..312c47263 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java @@ -10,7 +10,7 @@ import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; -import org.schabi.newpipe.Downloader; +import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.Stream; @@ -182,7 +182,7 @@ public class StreamItemAdapter extends BaseA continue; } - final long contentLength = Downloader.getInstance().getContentLength(stream.getUrl()); + final long contentLength = DownloaderImpl.getInstance().getContentLength(stream.getUrl()); streamsWrapper.setSize(stream, contentLength); hasChanged = true; } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 815ad9b65..d78f8e32b 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -5,7 +5,7 @@ import android.util.Log; import androidx.annotation.Nullable; -import org.schabi.newpipe.Downloader; +import org.schabi.newpipe.DownloaderImpl; import java.io.File; 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 conn = (HttpURLConnection) new URL(url).openConnection(); conn.setInstanceFollowRedirects(true); - conn.setRequestProperty("User-Agent", Downloader.USER_AGENT); + conn.setRequestProperty("User-Agent", DownloaderImpl.USER_AGENT); conn.setRequestProperty("Accept", "*/*"); // BUG workaround: switching between networks can freeze the download forever diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 07ffdb292..de58d14bd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -380,6 +380,8 @@ This will override your current setup. Do you want to also import settings? Could not load comments + Localization changes will not take effect until the app is restarted + Kiosk Trending diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index 1a1a39e21..596852bb6 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -3,15 +3,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:title="@string/content"> - - + + Date: Sun, 27 Oct 2019 23:37:36 -0300 Subject: [PATCH 120/270] Show proper text for live streams watching/listening count --- .../fragments/detail/VideoDetailFragment.java | 8 +++++++- .../info_list/holder/StreamInfoItemHolder.java | 9 ++++++++- .../java/org/schabi/newpipe/util/Localization.java | 8 ++++++++ app/src/main/res/values/strings.xml | 13 +++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 915ed59cc..14e989625 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1067,7 +1067,13 @@ public class VideoDetailFragment uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy)); if (info.getViewCount() >= 0) { - videoCountView.setText(Localization.localizeViewCount(activity, info.getViewCount())); + 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.setVisibility(View.VISIBLE); } else { videoCountView.setVisibility(View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java index 10c81a200..6b54d0168 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java @@ -7,6 +7,7 @@ import android.widget.TextView; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; 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.local.history.HistoryRecordManager; import org.schabi.newpipe.util.Localization; @@ -53,7 +54,13 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder { private String getStreamInfoDetailLine(final StreamInfoItem infoItem) { String viewsAndDate = ""; if (infoItem.getViewCount() >= 0) { - viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); + 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()); + } } if (!TextUtils.isEmpty(infoItem.getTextualUploadDate())) { if (viewsAndDate.isEmpty()) { diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 6835a48bd..9a6a9c96e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -141,6 +141,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) { return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, shortCount(context, viewCount)); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de58d14bd..78dcb5244 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -263,6 +263,19 @@ %s view %s views + + No one is watching + + %s watching + %s watching + + + No one is listening + + %s listener + %s listeners + + No videos %s video From b125ff702abea2d1253b6d21617e741562d1517a Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Mon, 28 Oct 2019 01:20:06 -0300 Subject: [PATCH 121/270] Show parsed relative times instead of whatever the service gives us Before, the direct value was given to the user, now it uses the parsed date so we can match the device's language. To get the relative time from the parsed dates, we use the PrettyTime library. Also introduces a debug option to check the service's original value. --- app/build.gradle | 1 + app/proguard-rules.pro | 1 + app/src/main/java/org/schabi/newpipe/App.java | 1 + .../holder/CommentsMiniInfoItemHolder.java | 3 +- .../holder/StreamInfoItemHolder.java | 28 +++++++++++-- .../org/schabi/newpipe/util/Localization.java | 42 +++++++++++++++---- app/src/main/res/values/settings_keys.xml | 2 +- app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/xml/debug_settings.xml | 7 ++++ 9 files changed, 75 insertions(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bedfe9f4f..0afc8d4c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -94,6 +94,7 @@ dependencies { implementation 'io.reactivex.rxjava2:rxjava:2.2.2' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' 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-rxjava2:${roomDbLibVersion}" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 6b56e1c75..53a9ecd5a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,6 +18,7 @@ -dontobfuscate -keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; } +-keep class org.ocpsoft.prettytime.i18n.** { *; } -keep class org.mozilla.javascript.** { *; } diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 95e56c620..8698b3c93 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -98,6 +98,7 @@ public class App extends Application { NewPipe.init(getDownloader(), Localization.getPreferredLocalization(this), Localization.getPreferredContentCountry(this)); + Localization.init(); StateSaver.init(this); initNotificationChannel(); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 6741e4403..4d94ec392 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -109,8 +109,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { } if (item.getPublishedTime() != null) { - itemPublishedTime.setText(Localization - .formatDate(item.getPublishedTime().date().getTime())); + itemPublishedTime.setText(Localization.relativeTime(item.getPublishedTime().date())); } else { itemPublishedTime.setText(item.getTextualPublishedTime()); } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java index 6b54d0168..c48934d10 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.info_list.holder; +import android.preference.PreferenceManager; import android.text.TextUtils; import android.view.ViewGroup; import android.widget.TextView; @@ -12,6 +13,8 @@ import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.Localization; +import static org.schabi.newpipe.MainActivity.DEBUG; + /* * Created by Christian Schabesberger on 01.08.16. *

@@ -62,13 +65,30 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder { viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); } } - if (!TextUtils.isEmpty(infoItem.getTextualUploadDate())) { + + final String uploadDate = getFormattedRelativeUploadDate(infoItem); + if (!TextUtils.isEmpty(uploadDate)) { if (viewsAndDate.isEmpty()) { - viewsAndDate = infoItem.getTextualUploadDate(); - } else { - viewsAndDate += " • " + infoItem.getTextualUploadDate(); + return uploadDate; } + + return Localization.concatenateStrings(viewsAndDate, uploadDate); } + 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(); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 9a6a9c96e..9274df848 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -2,25 +2,26 @@ package org.schabi.newpipe.util; import android.content.Context; import android.content.SharedPreferences; -import android.content.res.Resources; import android.preference.PreferenceManager; -import androidx.annotation.NonNull; -import androidx.annotation.PluralsRes; -import androidx.annotation.StringRes; import android.text.TextUtils; +import org.ocpsoft.prettytime.PrettyTime; +import org.ocpsoft.prettytime.units.Decade; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.localization.ContentCountry; import java.text.DateFormat; import java.text.NumberFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; +import androidx.annotation.NonNull; +import androidx.annotation.PluralsRes; +import androidx.annotation.StringRes; + /* * Created by chschtsch on 12/29/15. * @@ -43,11 +44,16 @@ import java.util.Locale; public class Localization { - public final static String DOT_SEPARATOR = " • "; + private static PrettyTime prettyTime; + private static final String DOT_SEPARATOR = " • "; private Localization() { } + public static void init() { + initPrettyTime(); + } + @NonNull public static String concatenateStrings(final String... strings) { return concatenateStrings(Arrays.asList(strings)); @@ -188,4 +194,26 @@ public class Localization { } 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); + } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index fc7abf678..80f2bb1f4 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -111,8 +111,8 @@ debug_pref_screen_key allow_heap_dumping_key - allow_disposed_exceptions_key + show_original_time_ago_text_key theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 78dcb5244..328128a62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -458,6 +458,10 @@ Memory leak monitoring may cause the app to become unresponsive when heap dumping Report out-of-lifecycle errors Force reporting of undeliverable Rx exceptions outside of fragment or activity lifecycle after disposal + + Show original time ago on items + Original texts from services will be visible in stream items + Import/export Import diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index 7059ee8ce..b51d4e232 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -18,4 +18,11 @@ android:key="@string/allow_disposed_exceptions_key" android:title="@string/enable_disposed_exceptions_title" android:summary="@string/enable_disposed_exceptions_summary"/> + + From dcd7055a9dae29fc7f1084054ec0f04cfdcb326f Mon Sep 17 00:00:00 2001 From: Redirion Date: Tue, 19 Nov 2019 14:25:40 +0100 Subject: [PATCH 122/270] Bump ExoPlayer to 2.10.8 Here we go again. Minor update. ExoPlayer Changelog: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0afc8d4c3..a1afd63a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,7 +47,7 @@ android { ext { androidxLibVersion = '1.0.0' - exoPlayerLibVersion = '2.10.7' + exoPlayerLibVersion = '2.10.8' roomDbLibVersion = '2.1.0' leakCanaryLibVersion = '1.5.4' //1.6.1 okHttpLibVersion = '3.12.6' From 3ad888399953080493c5f1025129e4673407372a Mon Sep 17 00:00:00 2001 From: Geoflly Adonias Date: Mon, 18 Nov 2019 13:44:37 +0000 Subject: [PATCH 123/270] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-pt-rBR/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 887cbe7ff..aaac4fd4c 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -212,7 +212,7 @@ abrir em modo popup Player Não há nada aqui Deseja apagar este item do seu histórico de pesquisas\? - Conteúdo + Conteúdo da página principal Página em branco Página de banca Página de inscrição @@ -515,4 +515,5 @@ abrir em modo popup Deletar todo o histórico de reprodução\? Mude as pastas de download para surtir efeito Alterar serviço, selecionados: + Quiosque Padrão \ No newline at end of file From 406d02fd2871159f0c16d3e71aea3c414910707f Mon Sep 17 00:00:00 2001 From: oskamuelller4fs Date: Sun, 17 Nov 2019 14:42:19 +0000 Subject: [PATCH 124/270] Translated using Weblate (German) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-de/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a25b6e202..2d6b5b6d2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -27,7 +27,7 @@ Bevorzugtes Audioformat Herunterladen Nächste - \'Nächste\' und \'ähnliche\' Videos anzeigen + \"Nächste\" und \"ähnliche\" Videos anzeigen Nicht unterstützte URL Video & Audio Bevorzugte Sprache des Inhalts @@ -51,8 +51,8 @@ Hell Aussehen Andere - Kann Downloadverzeichnis \'%1$s\' nicht anlegen - Downloadverzeichnis \'%1$s\' erstellt + Kann Downloadverzeichnis \"%1$s\" nicht anlegen + Downloadverzeichnis \"%1$s\" erstellt Fehler Konnte nicht alle Vorschaubilder laden Konnte Video-URL-Signatur nicht entschlüsseln @@ -364,7 +364,7 @@ \n4. Kopiere die Profil-URL, zu der Du weitergeleitet wurdest. yourID, soundcloud.com/yourid Keine Streams zum Download verfügbar - Bevorzugte \'Öffnen\' Aktion + Bevorzugte \"Öffnen\" Aktion Standardaktion beim Öffnen von Inhalten - %s Untertitel Textgröße und Hintergrund der Untertitel im Player anpassen. Wird erst nach Neustart der App wirksam. From 495fa170a9e9d467486d9c9754c067cfb25d4138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89frit?= Date: Sun, 17 Nov 2019 21:20:28 +0000 Subject: [PATCH 125/270] Translated using Weblate (French) Currently translated at 100.0% (501 of 501 strings) --- app/src/main/res/values-fr/strings.xml | 52 +++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e6160c0bd..147502088 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -18,7 +18,7 @@ Paramètres Partager Partager avec - Affiche une option pour lire une vidéo avec Kodi + Affiche une option pour lire une vidéo via Kodi Afficher l’option « Lire avec Kodi » Publiée le %1$s %1$s vues @@ -28,7 +28,7 @@ Suivant Afficher les vidéos « Suivantes » et « Similaires » URL non pris en charge - Vidéo & audio + Vidéo et audio Autre Miniature d’aperçu vidéo Lecture vidéo, durée : @@ -77,7 +77,7 @@ Autorisation d’accès au stockage refusée Appuyer sur la loupe pour commencer Lecture automatique - Afficher les vidéos soumises à une limite d’âge. Autoriser ce type de contenu est possible depuis les paramètres. + Affiche les vidéos soumises à une limite d’âge. Autoriser ce type de contenu est possible depuis les paramètres. Rapport utilisateur RAPPORTER Impossible de configurer le menu de téléchargement @@ -137,9 +137,9 @@ Redimensionner B Retire l’audio à CERTAINES définitions - Utiliser les gestes pour contrôler la luminosité et le volume du lecteur + Utiliser des gestes pour contrôler la luminosité et le volume du lecteur Suggestions de recherche - Afficher les suggestions lors d’une recherche + Affiche les suggestions lors d’une recherche Contrôles gestuels de la lecture Meilleure définition S’abonner @@ -168,12 +168,12 @@ Que ce soit pour des idées de traductions, de changements de design, de nettoyage de code ou de gros changements de code, une aide est toujours la bienvenue. Plus on contribue, meilleur il devient ! Impossible de modifier l’abonnement Impossible d’actualiser l’abonnement - Continuer la lecture après les interruptions (ex : appels téléphoniques) + Continue la lecture après les interruptions (ex : appels téléphoniques) Caractères autorisés dans les noms de fichier Les caractères invalides sont remplacés par cette valeur Caractère de remplacement Historique de recherche - Stocker l’historique de recheche sur l’appareil + Stocke l’historique de recheche sur l’appareil Voir l’historique Historique Recherché @@ -184,11 +184,11 @@ Historique supprimé Notification NewPipe Annuler - Garder un suivi des vidéos vues + Garde un suivi des vidéos vues Reprendre à l’obtention de la cible de saisie Lecteur Comportement - Historique & cache + Historique et cache Liste de lecture Notifications pour les lecteurs en arrière-plan et en mode flottant de NewPipe Aucun résultat @@ -232,7 +232,7 @@ Détails Paramètres audios Afficher l’astuce « Maintenir pour ajouter » - Afficher l’astuce lors de l’appui du bouton « Arrière-plan » ou « Mode flottant » sur la page de détails d’une vidéo + Affiche l’astuce lors de l’appui du bouton « Arrière-plan » ou « Mode flottant » sur la page de détails d’une vidéo [Inconnu] Récupération depuis l’erreur du lecteur Kiosque @@ -251,9 +251,9 @@ Donner en retour Pays du contenu par défaut Rotation - Passer en arrière-plan - Passer en flottant - Passer en principal + Basculer en arrière-plan + Basculer en mode flottant + Basculer en principal Service Ouvrir le menu Fermer le menu @@ -272,7 +272,7 @@ Importer la base de données Exporter la base de données Remplace votre historique et vos abonnements actuels - Exporter l’historique, les abonnements et les listes de lecture + Exporte l’historique, les abonnements et les listes de lecture Exporté Importé Aucun fichier ZIP valide @@ -310,10 +310,10 @@ Utiliser la recherche rapide approximative Permet au lecteur d’accéder plus rapidement à une position au détriment de la précision Charger les miniatures - Désactiver pour empêcher le chargement des miniatures, afin de réduire l’utilisation de bande passante et de mémoire. Modifier cette option vide les caches d’image en mémoire vive et sur le disque. + Désactiver pour empêcher le chargement des miniatures, afin de réduire l’utilisation de la bande passante et de la mémoire. Modifier cette option vide les caches d’image en mémoire vive et sur le disque. Images en cache effacées Effacer les métadonnées en cache - Effacer toutes les pages web mises en cache + Efface toutes les données de pages Web en cache Métadonnées en cache effacées Fichier Aucun dossier de ce type @@ -325,7 +325,7 @@ Quelque chose va bientôt apparaître ici ;D Télécharger le fichier de flux Vidéo suivante en file d’attente - Ajout automatique d’un morceau suggéré lors de la lecture du dernier morceau dans une file d’attente non bouclée + Ajoute automatiquement un flux lié lors de la lecture du dernier flux dans une file d’attente sans répétition Débogage Remplir Générés automatiquement @@ -370,11 +370,11 @@ Ton Aucune application installée pour lire ce fichier Effacer l’historique des vues - Supprimer l’historique des flux lus et des positions de reprise - Voulez-vous supprimer tout l’historique des vues \? + Supprime l’historique des flux lus et des positions de reprise de lecture + Voulez-vous supprimer entièrement l’historique des vues \? Historique des vues supprimé. Supprimer l’historique de recherche - Supprimer l’historique de recherche par mots-clés + Supprime l’historique des mots-clés de recherche Voulez-vous supprimer entièrement l’historique de recherche \? Historique de recherche supprimé. 1 élément supprimé. @@ -392,7 +392,7 @@ Limiter la définition lors de l’utilisation des données mobiles Chaînes Listes de lecture - Pistes + Morceaux Utilisateurs Accélérer pendant les silences Étape @@ -495,16 +495,16 @@ Vous allez être interrogé sur l’emplacement d’enregistrement de chaque téléchargement Vous allez être interrogé sur l’emplacement de téléchargement de chaque téléchargement. \nVeuillez choisir ILAF si vous voulez télécharger sur une carte mémoire externe - Rétablir la précédente position de lecture + Reprend la lecture à la dernière position Positions dans les listes Afficher les indicateurs de position de lecture dans les listes - Positions de reprise supprimées. + Positions de reprise de lecture supprimées. Utiliser ILAF L\'Infrastructure Logicielle d\'Accès au Stockage permet de télécharger sur une carte mémoire externe. \nNote : certains appareils ne sont pas compatibles - Supprimer les positions de lecture - Supprimer toutes les positions de reprise - Voulez-vous supprimer toutes les positions de reprise \? + Supprimer les positions de reprise de lecture + Supprime toutes les positions de reprise de lecture + Voulez-vous supprimer toutes les positions de reprise de lecture \? Changez les dossiers de téléchargement pour que cela prenne effet Activer/Désactiver le service, actuellement sélectionné : Kiosque par défaut From 8b26e5b106b572cbf1600dd83fcc48f3706af75b Mon Sep 17 00:00:00 2001 From: chr56 Date: Sun, 17 Nov 2019 01:24:33 +0000 Subject: [PATCH 126/270] Translated using Weblate (Chinese (Simplified)) Currently translated at 32.9% (165 of 501 strings) --- .../main/res/values-b+zh+HANS+CN/strings.xml | 86 ++++++++++++++++++- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index ce0df6d37..d97835b80 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -6,8 +6,8 @@ 在浏览器中打开 在悬浮窗模式下打开 您是不是要找:%1$s? - 找不到串流播放器 (您可以安裝並使用VLC播放)。 - 下载媒体文件 + 找不到串流播放器 (您可以安裝并使用VLC播放)。 + 下载串流文件 安装 取消 分享 @@ -39,7 +39,7 @@ 网络错误 %s 个视频 - + 禁用 背景 @@ -71,10 +71,88 @@ 文件已删除 无法得知订阅人数 每推出新版本时,弹出应用升级通知 - 格子 + 网格 新版 NewPipe 已可升级! 服务器不接受 接收 multi-threaded 下载, 以 @string/msg_threads = 1 重试 自动播放 清除数据 观看记录已删除 + 喜欢 + 不喜欢 + 使用Tor + (实验性)通过 Tor 强制下载流量以增强隐私(暂不支持串流视频)。 + 报告错误 + 用户报告 + 无法创建下载目录\"%1$s\" + 已成功创建下载目录「%1$s」 + 视频 + 音频 + 重试 + 存储访问权限已被拒绝 + + %1$s 次观看 + + + + 百万 + 开始 + 暂停 + 播放 + 删除 + 校验 + 新任务 + OK + 文件名 + 线程数 + 错误 + 不支持的服务器 + 文件已存在 + 点击了解详情 + 请稍候… + 复制至剪贴板 + reCAPTCHA验证码 + 弹出 + 关于NewPipe + 设置 + 关于 + 第三方许可 + © %1$s :作者 %2$s (使用 %3$s ) + 无法加载许可证 + 打开网站 + 关于 + 贡献者 + 许可证 + 下载 + 文件名中允许的字符 + 无效字符将会替换为此字符 + 字母和数字 + 最特殊字符 + 没有结果 + 没有订阅者 + + %s个订阅者 + + + 没有视频 + 拖动以重新排序 + 创建 + 仅删除一个 + 全部删除 + 解除 + 重 命名 + 未安装用于播放此文件的应用程序 + 已删除1个项目。 + 哪些标签需要在主页上展示 + 列表观看模式 + 已完成 + 等待中… + 已暂停 + 排队中 + 已加入队列 + 操作已被系统拒绝 + 下载失败 + 下载完成 + %s 次下载已完成 + 没有评论 + 切换服务,当前选择: \ No newline at end of file From 69799613aa314ce74d9fef708e35e85202b7d8cd Mon Sep 17 00:00:00 2001 From: nautilusx Date: Tue, 19 Nov 2019 14:14:02 +0000 Subject: [PATCH 127/270] Translated using Weblate (German) Currently translated at 99.6% (506 of 508 strings) --- app/src/main/res/values-de/strings.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2d6b5b6d2..db67d7f62 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -202,8 +202,8 @@ Keine Videos - %s Video - %s Videos + Video + Videos Die meisten Sonderzeichen Element gelöscht @@ -508,4 +508,15 @@ Ändere die Downloadordner, damit sie wirksam werden Dienst umschalten, aktuell ausgewählt: Quiosque Predefinido + Niemand schaut zu + + %s Zuschauer + %s Zuschauer + + Niemand hört zu + + %s Zuhörer + %s Zuhörer + + Änderungen der Lokalisierung werden erst nach einem Neustart der App wirksam \ No newline at end of file From 0491c4af9c9d7d9dfe47c94ecb86860462ec5ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 19 Nov 2019 16:53:50 +0000 Subject: [PATCH 128/270] Translated using Weblate (Turkish) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-tr/strings.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c17b58f50..ce743da6d 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -504,8 +504,19 @@ Etkili olması için indirme dizinlerini değiştirin Hizmeti değiştir, şu anda seçili olan: - %s video - %s video + Video + Videolar Varsayılan Kiosk + Kimse izlemiyor + + %s izliyor + %s izliyor + + Kimse dinlemiyor + + %s dinleyici + %s dinleyici + + Uygulama yeniden başlatıldıktan sonra dil değişecektir. \ No newline at end of file From 3798d5228c0fa57b100fd2a4e3fd0f6e3ef8d73e Mon Sep 17 00:00:00 2001 From: ozyc Date: Tue, 19 Nov 2019 20:58:29 +0000 Subject: [PATCH 129/270] Translated using Weblate (German) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-de/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index db67d7f62..88940ddc6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -202,8 +202,8 @@ Keine Videos - Video - Videos + %s video + %s videos Die meisten Sonderzeichen Element gelöscht From 4ded3adadbd2ce3e88ee753cc67079b0e5d33870 Mon Sep 17 00:00:00 2001 From: ozyc Date: Tue, 19 Nov 2019 20:44:54 +0000 Subject: [PATCH 130/270] Translated using Weblate (Esperanto) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-eo/strings.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index d5cc38a1a..97c4400b8 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -425,9 +425,9 @@ Ne povis importi abonojn Ne povis eksporti abonojn via salutnomo, soundcloud.com/salutnomo - "Memoru, ke ĉi tiu operacio povas esti multekosta en la reto. + Memoru, ke ĉi tiu operacio povas esti multekosta en la reto. \n -\nĈu vi volas daŭrigi\?" +\nĈu vi volas daŭrigi\? Kontroloj de rapideco de ludo Tempoindiko Ludkampo @@ -507,4 +507,15 @@ Eltempiĝo de Konekto Ŝangi la servon, nuntempe elektita: Defaŭlta Kiosko + Neniu spektas + + %s spektanta + %s spektanta + + Neniu aŭskultas + + %s aŭskultanto + %s aŭskultantoj + + La lingvo ŝanĝos kiam la apo restartos. \ No newline at end of file From 44192d6e49760bc16da5c44eac787e27a23c19be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 19 Nov 2019 15:49:50 +0000 Subject: [PATCH 131/270] Translated using Weblate (English) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values/strings.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 328128a62..1d3444bde 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -263,19 +263,16 @@ %s view %s views - No one is watching %s watching %s watching - No one is listening %s listener %s listeners - No videos %s video @@ -393,8 +390,7 @@ This will override your current setup. Do you want to also import settings? Could not load comments - Localization changes will not take effect until the app is restarted - + The language will change once the app is restarted. Kiosk Trending @@ -458,10 +454,8 @@ Memory leak monitoring may cause the app to become unresponsive when heap dumping Report out-of-lifecycle errors Force reporting of undeliverable Rx exceptions outside of fragment or activity lifecycle after disposal - Show original time ago on items Original texts from services will be visible in stream items - Import/export Import From 7b56244c8b7501c1560258605f9f4b650e10d220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Tue, 19 Nov 2019 19:15:57 +0000 Subject: [PATCH 132/270] Translated using Weblate (Russian) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-ru/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6f079a221..98f0e1c72 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -511,4 +511,17 @@ Измените папки загрузки для вступления настроек в силу Переключение сервисов, сейчас выбрано: Киоск по умолчанию + Никто не смотрел + + %s зритель + %s зрителя + %s зрителей + + Нет слушателей + + %s слушатель + %s слушателя + %s слушателей + + Язык будет изменён после перезапуска. \ No newline at end of file From 784b9cf207ae50118bf7d2a338f2342428f36c9e Mon Sep 17 00:00:00 2001 From: Marc Riera Date: Tue, 19 Nov 2019 14:13:26 +0000 Subject: [PATCH 133/270] Translated using Weblate (Catalan) Currently translated at 95.5% (485 of 508 strings) --- app/src/main/res/values-ca/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index ee48181c8..33e3cd73a 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -223,8 +223,8 @@ Sense vídeos - %s vídeo - %s vídeos + Vídeo + Vídeos Pausa Reprodueix From 93e99d096a74749d8a4b4b379ca3ea58bf365fa3 Mon Sep 17 00:00:00 2001 From: ssantos Date: Tue, 19 Nov 2019 16:04:40 +0000 Subject: [PATCH 134/270] Translated using Weblate (Portuguese) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-pt/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5d7cd8146..47513a600 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -507,4 +507,15 @@ Alterar as pastas de descarregamento para que tenham efeito Alternar serviço, agora selecionado: Quiosque Predefinição + Ninguém está a ver + + %s a ver + %s a ver + + Ninguém está a ouvir + + %s ouvinte + %s ouvintes + + O idioma mudará quando a app for reiniciada. \ No newline at end of file From 82738e23cef5afc7ea417c682b452ca53bc127f0 Mon Sep 17 00:00:00 2001 From: ozyc Date: Tue, 19 Nov 2019 20:50:46 +0000 Subject: [PATCH 135/270] Translated using Weblate (French) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-fr/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 147502088..5a6baed60 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -508,4 +508,15 @@ Changez les dossiers de téléchargement pour que cela prenne effet Activer/Désactiver le service, actuellement sélectionné : Kiosque par défaut + Personne ne regarde + + %s regarde + %s regardent + + Personne n\'écoute + + %s auditeur + %s auditeurs + + La langue changera lors du redémarrage de l\'application. \ No newline at end of file From 2f575c13f1749fc5d2916a3efd1fbc017398df56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 19 Nov 2019 15:48:38 +0000 Subject: [PATCH 136/270] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 99.0% (503 of 508 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index d26886844..c993c2a40 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -507,4 +507,15 @@ Endre nedlastingsmappene for å tre i kraft Skru tjeneste av/på, for tiden er dette status: Standard kiosk + Ingen ser på + + %s seer + %s seere + + Ingen lytter + + %s lytter + %s lyttere + + Språk vil ikke bli endret før programmet startes på ny. \ No newline at end of file From 7e311e55679e67b21dab98eea12e881137e6608b Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sat, 19 Oct 2019 21:31:14 -0300 Subject: [PATCH 137/270] Fix mess with tab handling and enable ignored tests again - Fix typo in a string resource - Reorder tabs so the default kiosk is on top of the others --- .../newpipe/fragments/MainFragment.java | 22 ++---- .../list/kiosk/DefaultKioskFragment.java | 51 +++++++++++++ .../settings/tabs/ChooseTabsFragment.java | 24 +++--- .../org/schabi/newpipe/settings/tabs/Tab.java | 75 ++++++++----------- .../newpipe/settings/tabs/TabsJsonHelper.java | 27 ++++--- app/src/main/res/values/strings.xml | 2 +- .../schabi/newpipe/settings/tabs/TabTest.java | 2 - .../settings/tabs/TabsJsonHelperTest.java | 24 +++--- 8 files changed, 127 insertions(+), 100 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index e0661a49f..085056302 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -159,27 +159,17 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte pagerAdapter.notifyDataSetChanged(); viewPager.setOffscreenPageLimit(pagerAdapter.getCount()); - updateTabsIcon(); - updateTabsContentDescription(); + updateTabsIconAndDescription(); updateCurrentTitle(); } - private void updateTabsIcon() { + private void updateTabsIconAndDescription() { for (int i = 0; i < tabsList.size(); i++) { final TabLayout.Tab tabToSet = tabLayout.getTabAt(i); if (tabToSet != null) { - tabToSet.setIcon(tabsList.get(i).getTabIconRes(activity)); - } - } - } - - private void updateTabsContentDescription() { - for (int i = 0; i < tabsList.size(); i++) { - final TabLayout.Tab tabToSet = tabLayout.getTabAt(i); - if (tabToSet != null) { - final Tab t = tabsList.get(i); - tabToSet.setIcon(t.getTabIconRes(activity)); - tabToSet.setContentDescription(t.getTabName(activity)); + final Tab tab = tabsList.get(i); + tabToSet.setIcon(tab.getTabIconRes(requireContext())); + tabToSet.setContentDescription(tab.getTabName(requireContext())); } } } @@ -217,7 +207,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte Throwable throwable = null; Fragment fragment = null; try { - fragment = tab.getFragment(); + fragment = tab.getFragment(requireContext()); } catch (ExtractionException e) { throwable = e; } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java new file mode 100644 index 000000000..35b68b094 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java @@ -0,0 +1,51 @@ +package org.schabi.newpipe.fragments.list.kiosk; + +import android.os.Bundle; + +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.kiosk.KioskList; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.KioskTranslator; +import org.schabi.newpipe.util.ServiceHelper; + +public class DefaultKioskFragment extends KioskFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (serviceId < 0) { + updateSelectedDefaultKiosk(); + } + } + + @Override + public void onResume() { + super.onResume(); + + if (serviceId != ServiceHelper.getSelectedServiceId(requireContext())) { + if (currentWorker != null) currentWorker.dispose(); + updateSelectedDefaultKiosk(); + reloadContent(); + } + } + + private void updateSelectedDefaultKiosk() { + try { + serviceId = ServiceHelper.getSelectedServiceId(requireContext()); + + final KioskList kioskList = NewPipe.getService(serviceId).getKioskList(); + kioskId = kioskList.getDefaultKioskId(); + url = kioskList.getListLinkHandlerFactoryByType(kioskId).fromId(kioskId).getUrl(); + + kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, requireContext()); + name = kioskTranslatedName; + + currentInfo = null; + currentNextPageUrl = null; + } catch (ExtractionException e) { + onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none", "Loading default kiosk from selected service", 0); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 4297fb13e..6aba2783f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -231,7 +231,7 @@ public class ChooseTabsFragment extends Fragment { break; case DEFAULT_KIOSK: if (!tabList.contains(tab)) { - returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.default_kiosk_page_sumatry), + returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.default_kiosk_page_summary), ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot))); } break; @@ -305,23 +305,25 @@ public class ChooseTabsFragment extends Fragment { return; } - String tabName = tab.getTabName(requireContext()); + final String tabName; switch (type) { case BLANK: - tabName = requireContext().getString(R.string.blank_page_summary); - break; - case KIOSK: - tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab).getKioskServiceId()) + "/" + tabName; - break; - case CHANNEL: - tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab).getChannelServiceId()) + "/" + tabName; + tabName = getString(R.string.blank_page_summary); break; case DEFAULT_KIOSK: - tabName = requireContext().getString(R.string.default_kiosk_page_sumatry); + tabName = getString(R.string.default_kiosk_page_summary); + break; + case KIOSK: + tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab).getKioskServiceId()) + "/" + tab.getTabName(requireContext()); + break; + case CHANNEL: + tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab).getChannelServiceId()) + "/" + tab.getTabName(requireContext()); + break; + default: + tabName = tab.getTabName(requireContext()); break; } - tabNameView.setText(tabName); tabIconView.setImageResource(tab.getTabIconRes(requireContext())); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index 64ba3683b..f80a8bb7f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.settings.tabs; import android.content.Context; + import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -9,18 +10,20 @@ import androidx.fragment.app.Fragment; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonSink; -import org.jsoup.helper.StringUtil; -import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.fragments.BlankFragment; import org.schabi.newpipe.fragments.list.channel.ChannelFragment; +import org.schabi.newpipe.fragments.list.kiosk.DefaultKioskFragment; import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.local.bookmark.BookmarkFragment; import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -40,7 +43,7 @@ public abstract class Tab { /** * Return a instance of the fragment that this tab represent. */ - public abstract Fragment getFragment() throws ExtractionException; + public abstract Fragment getFragment(Context context) throws ExtractionException; @Override public boolean equals(Object obj) { @@ -115,12 +118,6 @@ public abstract class Tab { return new KioskTab(jsonObject); case CHANNEL: return new ChannelTab(jsonObject); - case DEFAULT_KIOSK: - DefaultKioskTab tab = new DefaultKioskTab(); - if(!StringUtil.isBlank(tab.getKioskId())){ - return tab; - } - return null; } } @@ -133,13 +130,13 @@ public abstract class Tab { public enum Type { BLANK(new BlankTab()), + DEFAULT_KIOSK(new DefaultKioskTab()), SUBSCRIPTIONS(new SubscriptionsTab()), FEED(new FeedTab()), BOOKMARKS(new BookmarksTab()), HISTORY(new HistoryTab()), KIOSK(new KioskTab()), - CHANNEL(new ChannelTab()), - DEFAULT_KIOSK(new DefaultKioskTab()); + CHANNEL(new ChannelTab()); private Tab tab; @@ -176,7 +173,7 @@ public abstract class Tab { } @Override - public BlankFragment getFragment() { + public BlankFragment getFragment(Context context) { return new BlankFragment(); } } @@ -201,7 +198,7 @@ public abstract class Tab { } @Override - public SubscriptionFragment getFragment() { + public SubscriptionFragment getFragment(Context context) { return new SubscriptionFragment(); } @@ -227,7 +224,7 @@ public abstract class Tab { } @Override - public FeedFragment getFragment() { + public FeedFragment getFragment(Context context) { return new FeedFragment(); } } @@ -252,7 +249,7 @@ public abstract class Tab { } @Override - public BookmarkFragment getFragment() { + public BookmarkFragment getFragment(Context context) { return new BookmarkFragment(); } } @@ -277,7 +274,7 @@ public abstract class Tab { } @Override - public StatisticsPlaylistFragment getFragment() { + public StatisticsPlaylistFragment getFragment(Context context) { return new StatisticsPlaylistFragment(); } } @@ -327,7 +324,7 @@ public abstract class Tab { } @Override - public KioskFragment getFragment() throws ExtractionException { + public KioskFragment getFragment(Context context) throws ExtractionException { return KioskFragment.getInstance(kioskServiceId, kioskId); } @@ -394,7 +391,7 @@ public abstract class Tab { } @Override - public ChannelFragment getFragment() { + public ChannelFragment getFragment(Context context) { return ChannelFragment.getInstance(channelServiceId, channelUrl, channelName); } @@ -428,22 +425,6 @@ public abstract class Tab { public static class DefaultKioskTab extends Tab { public static final int ID = 7; - private int kioskServiceId; - private String kioskId; - - protected DefaultKioskTab() { - initKiosk(); - } - - public void initKiosk() { - this.kioskServiceId = ServiceHelper.getSelectedServiceId(App.getApp()); - try { - this.kioskId = NewPipe.getService(this.kioskServiceId).getKioskList().getDefaultKioskId(); - } catch (ExtractionException e) { - this.kioskId = ""; - } - } - @Override public int getTabId() { return ID; @@ -451,27 +432,31 @@ public abstract class Tab { @Override public String getTabName(Context context) { - return KioskTranslator.getTranslatedKioskName(kioskId, context); + return KioskTranslator.getTranslatedKioskName(getDefaultKioskId(context), context); } @DrawableRes @Override public int getTabIconRes(Context context) { - final int kioskIcon = KioskTranslator.getKioskIcons(kioskId, context); - - if (kioskIcon <= 0) { - throw new IllegalStateException("Kiosk ID is not valid: \"" + kioskId + "\""); - } - - return kioskIcon; + return KioskTranslator.getKioskIcons(getDefaultKioskId(context), context); } @Override - public KioskFragment getFragment() throws ExtractionException { - return KioskFragment.getInstance(kioskServiceId, kioskId); + public DefaultKioskFragment getFragment(Context context) throws ExtractionException { + return new DefaultKioskFragment(); } - public String getKioskId() { + private String getDefaultKioskId(Context context) { + final int kioskServiceId = ServiceHelper.getSelectedServiceId(context); + + String kioskId = ""; + try { + final StreamingService service = NewPipe.getService(kioskServiceId); + kioskId = service.getKioskList().getDefaultKioskId(); + } catch (ExtractionException e) { + ErrorActivity.reportError(context, e, null, null, + ErrorActivity.ErrorInfo.make(UserAction.REQUESTED_KIOSK, "none", "Loading default kiosk from selected service", 0)); + } return kioskId; } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java index 9553e47e1..9f54d59f6 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -1,7 +1,5 @@ package org.schabi.newpipe.settings.tabs; -import androidx.annotation.Nullable; - import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; @@ -9,18 +7,25 @@ import com.grack.nanojson.JsonParserException; import com.grack.nanojson.JsonStringWriter; import com.grack.nanojson.JsonWriter; -import org.jsoup.helper.StringUtil; - import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import androidx.annotation.Nullable; + /** * Class to get a JSON representation of a list of tabs, and the other way around. */ public class TabsJsonHelper { private static final String JSON_TABS_ARRAY_KEY = "tabs"; + private static final List FALLBACK_INITIAL_TABS_LIST = Collections.unmodifiableList(Arrays.asList( + Tab.Type.DEFAULT_KIOSK.getTab(), + Tab.Type.SUBSCRIPTIONS.getTab(), + Tab.Type.BOOKMARKS.getTab() + )); + public static class InvalidJsonException extends Exception { private InvalidJsonException() { super(); @@ -83,16 +88,6 @@ public class TabsJsonHelper { return returnTabs; } - public static List getDefaultTabs(){ - List tabs = new ArrayList<>(); - Tab.DefaultKioskTab tab = new Tab.DefaultKioskTab(); - if(!StringUtil.isBlank(tab.getKioskId())){ - tabs.add(tab); - } - tabs.add(Tab.Type.SUBSCRIPTIONS.getTab()); - tabs.add(Tab.Type.BOOKMARKS.getTab()); - return Collections.unmodifiableList(tabs); - } /** * Get a JSON representation from a list of tabs. * @@ -112,4 +107,8 @@ public class TabsJsonHelper { jsonWriter.end(); return jsonWriter.done(); } + + public static List getDefaultTabs(){ + return FALLBACK_INITIAL_TABS_LIST; + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6449833ea..a34b00ea9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -379,7 +379,7 @@ Selection Blank Page Kiosk Page - Default Kiosk + Default Kiosk Subscription Page Feed Page Channel Page diff --git a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabTest.java b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabTest.java index 4b3fbc2e0..45c7c0fff 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabTest.java +++ b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabTest.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.settings.tabs; -import org.junit.Ignore; import org.junit.Test; import java.util.HashSet; @@ -9,7 +8,6 @@ import java.util.Set; import static org.junit.Assert.assertTrue; public class TabTest { - @Ignore @Test public void checkIdDuplication() { final Set usedIds = new HashSet<>(); diff --git a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java index 20785e548..1f951159f 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java @@ -5,7 +5,6 @@ import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParserException; -import org.junit.Ignore; import org.junit.Test; import java.util.Arrays; @@ -21,19 +20,19 @@ public class TabsJsonHelperTest { private static final String JSON_TABS_ARRAY_KEY = "tabs"; private static final String JSON_TAB_ID_KEY = "tab_id"; - @Ignore @Test public void testEmptyAndNullRead() throws TabsJsonHelper.InvalidJsonException { + final List defaultTabs = TabsJsonHelper.getDefaultTabs(); + final String emptyTabsJson = "{\"" + JSON_TABS_ARRAY_KEY + "\":[]}"; List items = TabsJsonHelper.getTabsFromJson(emptyTabsJson); - assertTrue(!items.isEmpty()); + assertEquals(items, defaultTabs); final String nullSource = null; items = TabsJsonHelper.getTabsFromJson(nullSource); - assertTrue(!items.isEmpty()); + assertEquals(items, defaultTabs); } - @Ignore @Test public void testInvalidIdRead() throws TabsJsonHelper.InvalidJsonException { final int blankTabId = Tab.Type.BLANK.getTabId(); @@ -84,17 +83,17 @@ public class TabsJsonHelperTest { return jsonObject.getArray(JSON_TABS_ARRAY_KEY).size() == 0; } - @Ignore @Test public void testSaveAndReading() throws JsonParserException { // Saving final Tab.BlankTab blankTab = new Tab.BlankTab(); + final Tab.DefaultKioskTab defaultKioskTab = new Tab.DefaultKioskTab(); final Tab.SubscriptionsTab subscriptionsTab = new Tab.SubscriptionsTab(); final Tab.ChannelTab channelTab = new Tab.ChannelTab(666, "https://example.org", "testName"); final Tab.KioskTab kioskTab = new Tab.KioskTab(123, "trending_key"); - final List tabs = Arrays.asList(blankTab, subscriptionsTab, channelTab, kioskTab); - String returnedJson = TabsJsonHelper.getJsonToSave(tabs); + final List tabs = Arrays.asList(blankTab, defaultKioskTab, subscriptionsTab, channelTab, kioskTab); + final String returnedJson = TabsJsonHelper.getJsonToSave(tabs); // Reading final JsonObject jsonObject = JsonParser.object().from(returnedJson); @@ -106,16 +105,19 @@ public class TabsJsonHelperTest { final Tab.BlankTab blankTabFromReturnedJson = requireNonNull((Tab.BlankTab) Tab.from(((JsonObject) tabsFromArray.get(0)))); assertEquals(blankTab.getTabId(), blankTabFromReturnedJson.getTabId()); - final Tab.SubscriptionsTab subscriptionsTabFromReturnedJson = requireNonNull((Tab.SubscriptionsTab) Tab.from(((JsonObject) tabsFromArray.get(1)))); + final Tab.DefaultKioskTab defaultKioskTabFromReturnedJson = requireNonNull((Tab.DefaultKioskTab) Tab.from(((JsonObject) tabsFromArray.get(1)))); + assertEquals(defaultKioskTab.getTabId(), defaultKioskTabFromReturnedJson.getTabId()); + + final Tab.SubscriptionsTab subscriptionsTabFromReturnedJson = requireNonNull((Tab.SubscriptionsTab) Tab.from(((JsonObject) tabsFromArray.get(2)))); assertEquals(subscriptionsTab.getTabId(), subscriptionsTabFromReturnedJson.getTabId()); - final Tab.ChannelTab channelTabFromReturnedJson = requireNonNull((Tab.ChannelTab) Tab.from(((JsonObject) tabsFromArray.get(2)))); + final Tab.ChannelTab channelTabFromReturnedJson = requireNonNull((Tab.ChannelTab) Tab.from(((JsonObject) tabsFromArray.get(3)))); assertEquals(channelTab.getTabId(), channelTabFromReturnedJson.getTabId()); assertEquals(channelTab.getChannelServiceId(), channelTabFromReturnedJson.getChannelServiceId()); assertEquals(channelTab.getChannelUrl(), channelTabFromReturnedJson.getChannelUrl()); assertEquals(channelTab.getChannelName(), channelTabFromReturnedJson.getChannelName()); - final Tab.KioskTab kioskTabFromReturnedJson = requireNonNull((Tab.KioskTab) Tab.from(((JsonObject) tabsFromArray.get(3)))); + final Tab.KioskTab kioskTabFromReturnedJson = requireNonNull((Tab.KioskTab) Tab.from(((JsonObject) tabsFromArray.get(4)))); assertEquals(kioskTab.getTabId(), kioskTabFromReturnedJson.getTabId()); assertEquals(kioskTab.getKioskServiceId(), kioskTabFromReturnedJson.getKioskServiceId()); assertEquals(kioskTab.getKioskId(), kioskTabFromReturnedJson.getKioskId()); From 58a626dedb16df61c431710058e5e95798bcfbf9 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sat, 19 Oct 2019 21:31:15 -0300 Subject: [PATCH 138/270] Fix broken view pager tabs implementation - Fragments were being recreated from scratch (losing their state) every time some configuration change occurred (e.g. screen rotation). - Use `FragmentStatePagerAdapter` instead, as it is built to work with them and manage their states. --- .../newpipe/fragments/MainFragment.java | 69 ++++++++----------- .../org/schabi/newpipe/settings/tabs/Tab.java | 19 +++++ 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 085056302..79199a14d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.fragments; +import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -15,7 +16,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; +import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.viewpager.widget.ViewPager; import com.google.android.material.tabs.TabLayout; @@ -52,32 +53,19 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte super.onCreate(savedInstanceState); setHasOptionsMenu(true); - destroyOldFragments(); - tabsManager = TabsManager.getManager(activity); tabsManager.setSavedTabsListener(() -> { if (DEBUG) { Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed()); } if (isResumed()) { - updateTabs(); + setupTabs(); } else { hasTabsChanged = true; } }); } - private void destroyOldFragments() { - for (Fragment fragment : getChildFragmentManager().getFragments()) { - if (fragment != null) { - getChildFragmentManager() - .beginTransaction() - .remove(fragment) - .commitNowAllowingStateLoss(); - } - } - } - @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_main, container, false); @@ -90,23 +78,17 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte tabLayout = rootView.findViewById(R.id.main_tab_layout); viewPager = rootView.findViewById(R.id.pager); - /* Nested fragment, use child fragment here to maintain backstack in view pager. */ - pagerAdapter = new SelectedTabsPagerAdapter(getChildFragmentManager()); - viewPager.setAdapter(pagerAdapter); - tabLayout.setupWithViewPager(viewPager); tabLayout.addOnTabSelectedListener(this); - updateTabs(); + + setupTabs(); } @Override public void onResume() { super.onResume(); - if (hasTabsChanged) { - hasTabsChanged = false; - updateTabs(); - } + if (hasTabsChanged) setupTabs(); } @Override @@ -153,14 +135,21 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte // Tabs //////////////////////////////////////////////////////////////////////////*/ - public void updateTabs() { + public void setupTabs() { tabsList.clear(); tabsList.addAll(tabsManager.getTabs()); - pagerAdapter.notifyDataSetChanged(); - viewPager.setOffscreenPageLimit(pagerAdapter.getCount()); + if (pagerAdapter == null || !pagerAdapter.sameTabs(tabsList)) { + pagerAdapter = new SelectedTabsPagerAdapter(requireContext(), getChildFragmentManager(), tabsList); + } + // Clear previous tabs/fragments and set new adapter + viewPager.setAdapter(pagerAdapter); + viewPager.setOffscreenPageLimit(tabsList.size()); + updateTabsIconAndDescription(); updateCurrentTitle(); + + hasTabsChanged = false; } private void updateTabsIconAndDescription() { @@ -194,26 +183,30 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte updateCurrentTitle(); } - private class SelectedTabsPagerAdapter extends FragmentPagerAdapter { + private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapter { + private final Context context; + private final List internalTabsList; - private SelectedTabsPagerAdapter(FragmentManager fragmentManager) { - super(fragmentManager); + private SelectedTabsPagerAdapter(Context context, FragmentManager fragmentManager, List tabsList) { + super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + this.context = context; + this.internalTabsList = new ArrayList<>(tabsList); } @Override public Fragment getItem(int position) { - final Tab tab = tabsList.get(position); + final Tab tab = internalTabsList.get(position); Throwable throwable = null; Fragment fragment = null; try { - fragment = tab.getFragment(requireContext()); + fragment = tab.getFragment(context); } catch (ExtractionException e) { throwable = e; } if (throwable != null) { - ErrorActivity.reportError(activity, throwable, activity.getClass(), null, + ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); return new BlankFragment(); } @@ -234,15 +227,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public int getCount() { - return tabsList.size(); + return internalTabsList.size(); } - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - getChildFragmentManager() - .beginTransaction() - .remove((Fragment) object) - .commitNowAllowingStateLoss(); + public boolean sameTabs(List tabsToCompare) { + return internalTabsList.equals(tabsToCompare); } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index f80a8bb7f..cba3c4534 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -28,6 +28,8 @@ import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; +import java.util.Objects; + public abstract class Tab { Tab() { } @@ -47,6 +49,8 @@ public abstract class Tab { @Override public boolean equals(Object obj) { + if (obj == this) return true; + return obj instanceof Tab && obj.getClass().equals(this.getClass()) && ((Tab) obj).getTabId() == this.getTabId(); } @@ -340,6 +344,13 @@ public abstract class Tab { kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, ""); } + @Override + public boolean equals(Object obj) { + return super.equals(obj) && + kioskServiceId == ((KioskTab) obj).kioskServiceId + && Objects.equals(kioskId, ((KioskTab) obj).kioskId); + } + public int getKioskServiceId() { return kioskServiceId; } @@ -409,6 +420,14 @@ public abstract class Tab { channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, ""); } + @Override + public boolean equals(Object obj) { + return super.equals(obj) && + channelServiceId == ((ChannelTab) obj).channelServiceId + && Objects.equals(channelUrl, ((ChannelTab) obj).channelUrl) + && Objects.equals(channelName, ((ChannelTab) obj).channelName); + } + public int getChannelServiceId() { return channelServiceId; } From a5b7666188d0e05b9d22890a3e1199e89f3fbb6b Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sat, 19 Oct 2019 21:31:16 -0300 Subject: [PATCH 139/270] Clear the item list when starting loading --- .../org/schabi/newpipe/fragments/list/BaseListInfoFragment.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 764af271a..9a8e1fd17 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -111,6 +111,8 @@ public abstract class BaseListInfoFragment super.startLoading(forceLoad); showListFooter(false); + infoListAdapter.clearStreamItemList(); + currentInfo = null; if (currentWorker != null) currentWorker.dispose(); currentWorker = loadResult(forceLoad) From 38a0395d45e5c7e365d6ca42bf3120e509e5c706 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sat, 19 Oct 2019 21:31:17 -0300 Subject: [PATCH 140/270] Enable toolbar title visibility when setting a new one --- app/src/main/java/org/schabi/newpipe/BaseFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/schabi/newpipe/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/BaseFragment.java index ccdb806ef..d4795cde2 100644 --- a/app/src/main/java/org/schabi/newpipe/BaseFragment.java +++ b/app/src/main/java/org/schabi/newpipe/BaseFragment.java @@ -107,6 +107,7 @@ public abstract class BaseFragment extends Fragment { if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]"); if((!useAsFrontPage || mIsVisibleToUser) && (activity != null && activity.getSupportActionBar() != null)) { + activity.getSupportActionBar().setDisplayShowTitleEnabled(true); activity.getSupportActionBar().setTitle(title); } } From 544cae4fb410d382e06180cbcd1d4504e1266d29 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sat, 19 Oct 2019 21:31:19 -0300 Subject: [PATCH 141/270] Use tab position from parameters instead of relying on the view pager --- .../org/schabi/newpipe/fragments/MainFragment.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 79199a14d..720e0f216 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -147,7 +147,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte viewPager.setOffscreenPageLimit(tabsList.size()); updateTabsIconAndDescription(); - updateCurrentTitle(); + updateTitleForTab(viewPager.getCurrentItem()); hasTabsChanged = false; } @@ -163,14 +163,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte } } - private void updateCurrentTitle() { - setTitle(tabsList.get(viewPager.getCurrentItem()).getTabName(requireContext())); + private void updateTitleForTab(int tabPosition) { + setTitle(tabsList.get(tabPosition).getTabName(requireContext())); } @Override public void onTabSelected(TabLayout.Tab selectedTab) { if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]"); - updateCurrentTitle(); + updateTitleForTab(selectedTab.getPosition()); } @Override @@ -180,7 +180,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public void onTabReselected(TabLayout.Tab tab) { if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]"); - updateCurrentTitle(); + updateTitleForTab(tab.getPosition()); } private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapter { From bc283bce4e8c99a4d6e6a60d5071bbaec453a2c2 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sun, 20 Oct 2019 12:31:46 -0300 Subject: [PATCH 142/270] Make the KioskFragment aware of changes in the preferred content country --- .../fragments/list/kiosk/KioskFragment.java | 17 +++++++++++++++++ .../settings/ContentSettingsFragment.java | 3 +++ 2 files changed, 20 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java index 6eaa2ea70..d082b8078 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java @@ -4,6 +4,8 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; + +import android.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -17,10 +19,12 @@ import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.kiosk.KioskInfo; import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory; +import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.KioskTranslator; +import org.schabi.newpipe.util.Localization; import icepick.State; import io.reactivex.Single; @@ -52,6 +56,8 @@ public class KioskFragment extends BaseListInfoFragment { @State protected String kioskId = ""; protected String kioskTranslatedName; + @State + protected ContentCountry contentCountry; /*////////////////////////////////////////////////////////////////////////// @@ -87,6 +93,7 @@ public class KioskFragment extends BaseListInfoFragment { kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, activity); name = kioskTranslatedName; + contentCountry = Localization.getPreferredContentCountry(requireContext()); } @Override @@ -108,6 +115,15 @@ public class KioskFragment extends BaseListInfoFragment { return inflater.inflate(R.layout.fragment_kiosk, container, false); } + @Override + public void onResume() { + super.onResume(); + + if (!Localization.getPreferredContentCountry(requireContext()).equals(contentCountry)) { + reloadContent(); + } + } + /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -127,6 +143,7 @@ public class KioskFragment extends BaseListInfoFragment { @Override public Single loadResult(boolean forceReload) { + contentCountry = Localization.getPreferredContentCountry(requireContext()); return ExtractorHelper.getKioskInfo(serviceId, url, forceReload); diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 39c1d890e..0c7a4b46e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -17,6 +17,7 @@ import com.nononsenseapps.filepicker.Utils; import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.report.ErrorActivity; @@ -128,6 +129,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { if (!selectedLocalization.equals(initialSelectedLocalization) || !selectedContentCountry.equals(initialSelectedContentCountry)) { Toast.makeText(requireContext(), R.string.localization_changes_requires_app_restart, Toast.LENGTH_LONG).show(); + + NewPipe.setupLocalization(selectedLocalization, selectedContentCountry); } } From ceabfd1a8b5d526e2af98e87668c0a05e2dfbf87 Mon Sep 17 00:00:00 2001 From: yausername <13ritvik@gmail.com> Date: Thu, 21 Nov 2019 05:41:14 +0530 Subject: [PATCH 143/270] updated extractor --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7d51a392a..fbd9a7c8c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.yausername:NewPipeExtractor:f3a59a6' + implementation 'com.github.yausername:NewPipeExtractor:4e0adbe' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' From 9cdec5de5011ab525c91c4fb44b33d3e9eef4371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Wed, 20 Nov 2019 10:01:21 +0000 Subject: [PATCH 144/270] Translated using Weblate (Turkish) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-tr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index ce743da6d..f39be2b15 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -504,7 +504,7 @@ Etkili olması için indirme dizinlerini değiştirin Hizmeti değiştir, şu anda seçili olan: - Video + Videolar Videolar Varsayılan Kiosk From e900a69a264aa22b486c4020e9a33f78ad4dbc49 Mon Sep 17 00:00:00 2001 From: nautilusx Date: Wed, 20 Nov 2019 08:16:34 +0000 Subject: [PATCH 145/270] Translated using Weblate (German) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-de/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 88940ddc6..9bc04ccad 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -202,8 +202,8 @@ Keine Videos - %s video - %s videos + Video + Videos Die meisten Sonderzeichen Element gelöscht @@ -518,5 +518,5 @@ %s Zuhörer %s Zuhörer - Änderungen der Lokalisierung werden erst nach einem Neustart der App wirksam + Die Sprache ändert sich, sobald die App neu gestartet wird. \ No newline at end of file From 406114593319b5c0992df799d51e5961e0be823a Mon Sep 17 00:00:00 2001 From: ozyc Date: Tue, 19 Nov 2019 21:23:08 +0000 Subject: [PATCH 146/270] Translated using Weblate (Esperanto) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-eo/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 97c4400b8..25fa34e06 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -328,7 +328,7 @@ k M - B + Mrd Pri NewPipe Agordoj Pri From 59f76ef3047bd33d3ea75eb1d023067edb424092 Mon Sep 17 00:00:00 2001 From: naofum Date: Wed, 20 Nov 2019 15:27:42 +0000 Subject: [PATCH 147/270] Translated using Weblate (Japanese) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-ja/strings.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b67da798c..cb83d7253 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -500,7 +500,16 @@ ダウンロードフォルダーを変更して有効にします サービスの切り替え、現在の選択: - %s 動画 + 動画 デフォルトのキオスク + 誰も見ていません + + %s 見ています + + 誰も聞いていません + + %s リスナー + + アプリを再起動すると、言語が変更されます。 \ No newline at end of file From 84089453e7c47990e048ac390be440c64d4f51dd Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Tue, 19 Nov 2019 21:41:07 +0000 Subject: [PATCH 148/270] Translated using Weblate (Hebrew) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-he/strings.xml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index b5a0778d4..80c47b001 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -150,7 +150,7 @@ הגישה לאחסון נדחתה ק׳ מ׳ - ב׳ + מיליארד אין מנויים מנוי אחד @@ -167,7 +167,7 @@ אין סרטונים - %s סרטון + סרטון %s %s סרטונים %s סרטונים %s סרטונים @@ -516,4 +516,19 @@ יש להחליף את תיקיות ההורדה כדי שייכנס לתוקף הפעלה/כיבוי שירות, בחירה נוכחית: קיוסק בררת מחדל + אין צופים + + צופה %s + %s צופים + %s צופים + %s צופים + + אין מאזינים + + מאזין %s + %s מאזינים + %s מאזינים + %s מאזינים + + השפה תוחלף עם הפעלת היישומון מחדש. \ No newline at end of file From 27330951aafc9d00156d48862e6de77593ba6a67 Mon Sep 17 00:00:00 2001 From: WaldiS Date: Wed, 20 Nov 2019 08:20:36 +0000 Subject: [PATCH 149/270] Translated using Weblate (Polish) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-pl/strings.xml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d3c84aa22..e7766e199 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -216,9 +216,9 @@ Brak filmów - %s film - %s filmy - %s filmów + %s Film + %s Filmy + %s Filmów Większość znaków specjalnych Dotacja @@ -512,4 +512,17 @@ Zmień foldey pobierania, aby zostały uwzględnione Przełączanie usługi, aktualnie wybranej: Domyślny Kiosk + Nikt nie ogląda + + %s obserwujący + %s obserwujących + %s obserwujących + + Nikt nie słucha + + %s słuchacz + %s słuchaczy + %s słuchaczy + + Język zmieni się po ponownym uruchomieniu aplikacji. \ No newline at end of file From 912f09c83e192e5c72006475bb87bf5dd48f9cc2 Mon Sep 17 00:00:00 2001 From: zmni Date: Wed, 20 Nov 2019 10:23:00 +0000 Subject: [PATCH 150/270] Translated using Weblate (Indonesian) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-id/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index db738d749..8c13af2b4 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -503,4 +503,13 @@ Video Aktif/Nonaktifkan layanan, saat ini aktif: + Tidak ada yang menonton + + %s menonton + + Tidak ada yang mendengarkan + + %s mendengarkan + + Bahasa akan diterapkan setelah aplikasi dimulai ulang. \ No newline at end of file From f3988c37b672f4f70f87b89e95fa380c5a5281f3 Mon Sep 17 00:00:00 2001 From: ozyc Date: Tue, 19 Nov 2019 21:11:47 +0000 Subject: [PATCH 151/270] Translated using Weblate (French) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-fr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5a6baed60..f10e57510 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -135,7 +135,7 @@ Rafraîchir Effacer Redimensionner - B + Mrd Retire l’audio à CERTAINES définitions Utiliser des gestes pour contrôler la luminosité et le volume du lecteur Suggestions de recherche From 719d8651b3ee82a7c9c4d98bde07475d71fa1b29 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 20 Nov 2019 03:25:17 +0000 Subject: [PATCH 152/270] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index fe4c1b00a..125eb1828 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -500,7 +500,19 @@ 變更下載資料夾以使其生效 切換服務,目前已選取: - %s 影片 + 影片 + 預設 Kiosk + 沒有人在看 + + %s 個觀眾 + + + 沒有人正在聽 + + %s 個聽眾 + + + 語言將會在重新啟動應用程式後變更。 \ No newline at end of file From 2ad0d47f6115241c73b38201e89c5584ecadc813 Mon Sep 17 00:00:00 2001 From: mitosagi <54105954+mitosagi@users.noreply.github.com> Date: Sat, 2 Nov 2019 12:12:25 +0900 Subject: [PATCH 153/270] Fix popup player gestures --- .../main/java/org/schabi/newpipe/player/PopupVideoPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 68446ed9f..969c47990 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -1036,7 +1036,7 @@ public final class PopupVideoPlayer extends Service { public boolean onTouch(View v, MotionEvent event) { popupGestureDetector.onTouchEvent(event); if (playerImpl == null) return false; - if (event.getPointerCount() == 2 && !isResizing) { + if (event.getPointerCount() == 2 && !isMoving && !isResizing) { if (DEBUG) Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing."); playerImpl.showAndAnimateControl(-1, true); playerImpl.getLoadingPanel().setVisibility(View.GONE); From ac2543d0a1e5b18acc8372cefbbe827e4dcf6c87 Mon Sep 17 00:00:00 2001 From: yausername <13ritvik@gmail.com> Date: Sat, 23 Nov 2019 00:31:35 +0530 Subject: [PATCH 154/270] validate peertube instance. changed peertube color --- app/build.gradle | 2 +- .../settings/ContentSettingsFragment.java | 34 +++++++----------- .../schabi/newpipe/util/ServiceHelper.java | 4 ++- ...24dp.png => ic_kiosk_local_black_24dp.png} | Bin ...24dp.png => ic_kiosk_local_white_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_black_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_white_24dp.png} | Bin ...24dp.png => ic_kiosk_local_black_24dp.png} | Bin ...24dp.png => ic_kiosk_local_white_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_black_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_white_24dp.png} | Bin ...24dp.png => ic_kiosk_local_black_24dp.png} | Bin ...24dp.png => ic_kiosk_local_white_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_black_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_white_24dp.png} | Bin ...24dp.png => ic_kiosk_local_black_24dp.png} | Bin ...24dp.png => ic_kiosk_local_white_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_black_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_white_24dp.png} | Bin ...24dp.png => ic_kiosk_local_black_24dp.png} | Bin ...24dp.png => ic_kiosk_local_white_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_black_24dp.png} | Bin ...4dp.png => ic_kiosk_recent_white_24dp.png} | Bin app/src/main/res/values/colors_services.xml | 8 ++--- app/src/main/res/values/settings_keys.xml | 1 - app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 8 ++--- 27 files changed, 26 insertions(+), 32 deletions(-) rename app/src/main/res/drawable-hdpi/{ic_kiosklocal_black_24dp.png => ic_kiosk_local_black_24dp.png} (100%) rename app/src/main/res/drawable-hdpi/{ic_kiosklocal_white_24dp.png => ic_kiosk_local_white_24dp.png} (100%) rename app/src/main/res/drawable-hdpi/{ic_kioskrecent_black_24dp.png => ic_kiosk_recent_black_24dp.png} (100%) rename app/src/main/res/drawable-hdpi/{ic_kioskrecent_white_24dp.png => ic_kiosk_recent_white_24dp.png} (100%) rename app/src/main/res/drawable-mdpi/{ic_kiosklocal_black_24dp.png => ic_kiosk_local_black_24dp.png} (100%) rename app/src/main/res/drawable-mdpi/{ic_kiosklocal_white_24dp.png => ic_kiosk_local_white_24dp.png} (100%) rename app/src/main/res/drawable-mdpi/{ic_kioskrecent_black_24dp.png => ic_kiosk_recent_black_24dp.png} (100%) rename app/src/main/res/drawable-mdpi/{ic_kioskrecent_white_24dp.png => ic_kiosk_recent_white_24dp.png} (100%) rename app/src/main/res/drawable-xhdpi/{ic_kiosklocal_black_24dp.png => ic_kiosk_local_black_24dp.png} (100%) rename app/src/main/res/drawable-xhdpi/{ic_kiosklocal_white_24dp.png => ic_kiosk_local_white_24dp.png} (100%) rename app/src/main/res/drawable-xhdpi/{ic_kioskrecent_black_24dp.png => ic_kiosk_recent_black_24dp.png} (100%) rename app/src/main/res/drawable-xhdpi/{ic_kioskrecent_white_24dp.png => ic_kiosk_recent_white_24dp.png} (100%) rename app/src/main/res/drawable-xxhdpi/{ic_kiosklocal_black_24dp.png => ic_kiosk_local_black_24dp.png} (100%) rename app/src/main/res/drawable-xxhdpi/{ic_kiosklocal_white_24dp.png => ic_kiosk_local_white_24dp.png} (100%) rename app/src/main/res/drawable-xxhdpi/{ic_kioskrecent_black_24dp.png => ic_kiosk_recent_black_24dp.png} (100%) rename app/src/main/res/drawable-xxhdpi/{ic_kioskrecent_white_24dp.png => ic_kiosk_recent_white_24dp.png} (100%) rename app/src/main/res/drawable-xxxhdpi/{ic_kiosklocal_black_24dp.png => ic_kiosk_local_black_24dp.png} (100%) rename app/src/main/res/drawable-xxxhdpi/{ic_kiosklocal_white_24dp.png => ic_kiosk_local_white_24dp.png} (100%) rename app/src/main/res/drawable-xxxhdpi/{ic_kioskrecent_black_24dp.png => ic_kiosk_recent_black_24dp.png} (100%) rename app/src/main/res/drawable-xxxhdpi/{ic_kioskrecent_white_24dp.png => ic_kiosk_recent_white_24dp.png} (100%) diff --git a/app/build.gradle b/app/build.gradle index fbd9a7c8c..4259d45a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.yausername:NewPipeExtractor:4e0adbe' + implementation 'com.github.yausername:NewPipeExtractor:bc75c66' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index c14aac4b5..5e3562c7e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -24,6 +24,7 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; +import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.FilePickerActivityHelper; @@ -45,9 +46,8 @@ import java.util.Map; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import io.reactivex.Single; +import io.reactivex.Completable; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; @@ -68,8 +68,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment { private Localization initialSelectedLocalization; private ContentCountry initialSelectedContentCountry; - private CompositeDisposable disposables = new CompositeDisposable(); - @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -142,30 +140,24 @@ public class ContentSettingsFragment extends BasePreferenceFragment { Toast.LENGTH_SHORT).show(); } else { pEt.setSummary("fetching instance details.."); - Disposable disposable = Single.fromCallable(() -> { - ServiceList.PeerTube.setInstance(url); - return true; + Disposable disposable = Completable.fromAction(() -> { + PeertubeInstance instance = new PeertubeInstance(url); + instance.fetchInstanceMetaData(); + ServiceList.PeerTube.setInstance(instance); }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - if (result) { - pEt.setSummary(url); - pEt.setText(url); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(App.getApp().getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()).apply(); - editor.putString(App.getApp().getString(R.string.current_service_key), ServiceList.PeerTube.getServiceInfo().getName()).apply(); - NavigationHelper.openMainActivity(App.getApp()); - } else { - pEt.setSummary(ServiceList.PeerTube.getBaseUrl()); - Toast.makeText(getActivity(), "unable to update instance", - Toast.LENGTH_SHORT).show(); - } + .subscribe(() -> { + pEt.setSummary(url); + pEt.setText(url); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(App.getApp().getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()).apply(); + editor.putString(App.getApp().getString(R.string.current_service_key), ServiceList.PeerTube.getServiceInfo().getName()).apply(); + NavigationHelper.openMainActivity(App.getApp()); }, error -> { pEt.setSummary(ServiceList.PeerTube.getBaseUrl()); Toast.makeText(getActivity(), "unable to update instance", Toast.LENGTH_SHORT).show(); }); - disposables.add(disposable); } return false; }); diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index 429331dfd..084ab5878 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -11,6 +11,7 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import java.util.concurrent.TimeUnit; @@ -140,7 +141,8 @@ public class ServiceHelper { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); String peerTubeInstanceUrl = sharedPreferences.getString(context.getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl()); String peerTubeInstanceName = sharedPreferences.getString(context.getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()); - ServiceList.PeerTube.setInstance(peerTubeInstanceUrl, peerTubeInstanceName); + PeertubeInstance instance = new PeertubeInstance(peerTubeInstanceUrl, peerTubeInstanceName); + ServiceList.PeerTube.setInstance(instance); } } diff --git a/app/src/main/res/drawable-hdpi/ic_kiosklocal_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_kiosk_local_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-hdpi/ic_kiosklocal_black_24dp.png rename to app/src/main/res/drawable-hdpi/ic_kiosk_local_black_24dp.png diff --git a/app/src/main/res/drawable-hdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_kiosk_local_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-hdpi/ic_kiosklocal_white_24dp.png rename to app/src/main/res/drawable-hdpi/ic_kiosk_local_white_24dp.png diff --git a/app/src/main/res/drawable-hdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_kiosk_recent_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-hdpi/ic_kioskrecent_black_24dp.png rename to app/src/main/res/drawable-hdpi/ic_kiosk_recent_black_24dp.png diff --git a/app/src/main/res/drawable-hdpi/ic_kioskrecent_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_kiosk_recent_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-hdpi/ic_kioskrecent_white_24dp.png rename to app/src/main/res/drawable-hdpi/ic_kiosk_recent_white_24dp.png diff --git a/app/src/main/res/drawable-mdpi/ic_kiosklocal_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_kiosk_local_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-mdpi/ic_kiosklocal_black_24dp.png rename to app/src/main/res/drawable-mdpi/ic_kiosk_local_black_24dp.png diff --git a/app/src/main/res/drawable-mdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_kiosk_local_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-mdpi/ic_kiosklocal_white_24dp.png rename to app/src/main/res/drawable-mdpi/ic_kiosk_local_white_24dp.png diff --git a/app/src/main/res/drawable-mdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_kiosk_recent_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-mdpi/ic_kioskrecent_black_24dp.png rename to app/src/main/res/drawable-mdpi/ic_kiosk_recent_black_24dp.png diff --git a/app/src/main/res/drawable-mdpi/ic_kioskrecent_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_kiosk_recent_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-mdpi/ic_kioskrecent_white_24dp.png rename to app/src/main/res/drawable-mdpi/ic_kiosk_recent_white_24dp.png diff --git a/app/src/main/res/drawable-xhdpi/ic_kiosklocal_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_kiosk_local_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/ic_kiosklocal_black_24dp.png rename to app/src/main/res/drawable-xhdpi/ic_kiosk_local_black_24dp.png diff --git a/app/src/main/res/drawable-xhdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_kiosk_local_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/ic_kiosklocal_white_24dp.png rename to app/src/main/res/drawable-xhdpi/ic_kiosk_local_white_24dp.png diff --git a/app/src/main/res/drawable-xhdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_kiosk_recent_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/ic_kioskrecent_black_24dp.png rename to app/src/main/res/drawable-xhdpi/ic_kiosk_recent_black_24dp.png diff --git a/app/src/main/res/drawable-xhdpi/ic_kioskrecent_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_kiosk_recent_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/ic_kioskrecent_white_24dp.png rename to app/src/main/res/drawable-xhdpi/ic_kiosk_recent_white_24dp.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_kiosklocal_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_kiosk_local_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_kiosklocal_black_24dp.png rename to app/src/main/res/drawable-xxhdpi/ic_kiosk_local_black_24dp.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_kiosk_local_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_kiosklocal_white_24dp.png rename to app/src/main/res/drawable-xxhdpi/ic_kiosk_local_white_24dp.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_kiosk_recent_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_kioskrecent_black_24dp.png rename to app/src/main/res/drawable-xxhdpi/ic_kiosk_recent_black_24dp.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_kioskrecent_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_kiosk_recent_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_kioskrecent_white_24dp.png rename to app/src/main/res/drawable-xxhdpi/ic_kiosk_recent_white_24dp.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_kiosklocal_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_kiosk_local_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/ic_kiosklocal_black_24dp.png rename to app/src/main/res/drawable-xxxhdpi/ic_kiosk_local_black_24dp.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_kiosklocal_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_kiosk_local_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/ic_kiosklocal_white_24dp.png rename to app/src/main/res/drawable-xxxhdpi/ic_kiosk_local_white_24dp.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_kioskrecent_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_kiosk_recent_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/ic_kioskrecent_black_24dp.png rename to app/src/main/res/drawable-xxxhdpi/ic_kiosk_recent_black_24dp.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_kioskrecent_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_kiosk_recent_white_24dp.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/ic_kioskrecent_white_24dp.png rename to app/src/main/res/drawable-xxxhdpi/ic_kiosk_recent_white_24dp.png diff --git a/app/src/main/res/values/colors_services.xml b/app/src/main/res/values/colors_services.xml index d38df003e..0126ee9ae 100644 --- a/app/src/main/res/values/colors_services.xml +++ b/app/src/main/res/values/colors_services.xml @@ -23,13 +23,13 @@ #ff9100 - #e65100 - #ac1900 + #ff6f00 + #c43e00 #000000 #ff833a - #e65100 - #ac1900 + #ff6f00 + #c43e00 #FFFFFF #ff833a diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index c5f5cde78..dcf39b488 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -145,7 +145,6 @@ GB content_language peertube_instance_url - Find the instance that best suits you on https://instances.joinpeertube.org peertube_instance_name content_country show_age_restricted_content diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08978e014..52b56a7b8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -110,6 +110,7 @@ Service Default content language PeerTube instance + Find the instance that best suits you on https://instances.joinpeertube.org Player Behavior Video & audio diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index caa332261..ba3fe78d5 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -44,8 +44,8 @@ @drawable/ic_pause_black_24dp @drawable/ic_settings_black_24dp @drawable/ic_whatshot_black_24dp - @drawable/ic_kiosklocal_black_24dp - @drawable/ic_kioskrecent_black_24dp + @drawable/ic_kiosk_local_black_24dp + @drawable/ic_kiosk_recent_black_24dp @drawable/ic_channel_black_24dp @drawable/ic_bookmark_black_24dp @drawable/ic_playlist_add_black_24dp @@ -110,8 +110,8 @@ @drawable/ic_play_arrow_white_24dp @drawable/ic_settings_white_24dp @drawable/ic_whatshot_white_24dp - @drawable/ic_kiosklocal_white_24dp - @drawable/ic_kioskrecent_white_24dp + @drawable/ic_kiosk_local_white_24dp + @drawable/ic_kiosk_recent_white_24dp @drawable/ic_channel_white_24dp @drawable/ic_bookmark_white_24dp @drawable/ic_playlist_add_white_24dp From afef8d8d0b97f022b62c6241bcc3dcb408a01c3f Mon Sep 17 00:00:00 2001 From: yausername <13ritvik@gmail.com> Date: Sat, 23 Nov 2019 00:53:14 +0530 Subject: [PATCH 155/270] removed extra white spaces --- .../schabi/newpipe/settings/ContentSettingsFragment.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 5e3562c7e..dd40f0d60 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -235,7 +235,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); - } finally { + }finally { try { if (output != null) { output.flush(); @@ -259,8 +259,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { } finally { try { zipFile.close(); - } catch (Exception ignored) { - } + } catch (Exception ignored){} } try { @@ -283,7 +282,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { } //If settings file exist, ask if it should be imported. - if (ZipHelper.extractFileFromZip(filePath, newpipe_settings.getPath(), "newpipe.settings")) { + if(ZipHelper.extractFileFromZip(filePath, newpipe_settings.getPath(), "newpipe.settings")) { AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); alert.setTitle(R.string.import_settings); @@ -338,7 +337,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); - } finally { + }finally { try { if (input != null) { input.close(); From 31fab60701486d33245c14a13e9e868f019e043e Mon Sep 17 00:00:00 2001 From: chr56 Date: Fri, 22 Nov 2019 14:12:40 +0000 Subject: [PATCH 156/270] Translated using Weblate (Chinese (Simplified)) Currently translated at 95.1% (483 of 508 strings) --- .../main/res/values-b+zh+HANS+CN/strings.xml | 372 +++++++++++++++++- 1 file changed, 367 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index d97835b80..6a77962f9 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -62,7 +62,7 @@ 清除观看记录 最小化后台播放器 - 最小化小窗口播放器 + 最小化悬浮窗播放器 频道 播放列表 取消订阅 @@ -91,7 +91,7 @@ 存储访问权限已被拒绝 %1$s 次观看 - + 百万 @@ -111,7 +111,7 @@ 请稍候… 复制至剪贴板 reCAPTCHA验证码 - 弹出 + 悬浮窗播放 关于NewPipe 设置 关于 @@ -131,7 +131,7 @@ 没有订阅者 %s个订阅者 - + 没有视频 拖动以重新排序 @@ -143,7 +143,7 @@ 未安装用于播放此文件的应用程序 已删除1个项目。 哪些标签需要在主页上展示 - 列表观看模式 + 列表视图模式 已完成 等待中… 已暂停 @@ -155,4 +155,366 @@ %s 次下载已完成 没有评论 切换服务,当前选择: + 找不到串流播放器。您想安装 VLC 吗? + 旋转 + 使用第三方视频播放器 + 使用第三方视频播放器 + 音频下载文件夹 + 从其他应用调用 NewPipe 时播放视频 + 默认分辨率 + 找不到Kore。是否安装? + 显示“用Kodi播放”选项 + 显示“通过Kodi media center播放视频的选项” + 音频 + 默认音频格式 + 显示“下一个”和“类似的”视频 + 视频和音频 + 在后台播放 + 播放 + 内容 + 受年龄限制的内容 + 显示受年龄限制的视频。可从设置允许此类内容。 + 直播 + 下载 + 下载 + 错误报告 + 错误 + 无法加载所有缩略图 + 无法解密视频 URL 的签名 + 无法解析网址 + 无法完全解析网址 + 内容不可用 + 无法设置下载菜单 + 暂时不支持观看直播 + 无法获得任何信息流 + 无法加载图像 + App UI 崩溃 + 抱歉,这不应该发生的。 + 通过电子邮件报告错误 + 抱歉,发生了一些错误。 + 报告 + 信息: + 发生了什么: + 详情:\\n请求:\\n内容语言:\\n服务:\\nGMT时间:\\n包:\\n版本:\\n操作系统版本: + 您的注释(请用英文): + 详细信息: + 视频预览缩略图 + 播放视频,时长: + 视频上传者的头像缩略图 + 字节 + 错误的 URL 或未联网 + NewPipe下载中 + 请在稍后的设置中定义下载文件夹 + 用悬浮窗模式 +\n需要此权限 + reCAPTCHA验证 + 请求的新的CAPTCHA验证 + NewPipe悬浮窗模式 + 在悬浮窗中播放 + 默认悬浮窗分辨率 + 使用更高的分辨率 + 仅某些设备支持播放2K / 4K视频 + 清除 + 记住悬浮窗的尺寸与位置 + 记住最后一次使用悬浮窗的大小和位置 + 悬浮窗 + 调整大小 + 删除“某些”分辨率的音频 + 播放器手势控制 + 使用手势控制播放器的亮度和音量 + 显示搜索建议 + 最佳分辨率 + 开源小巧的Android媒体播放器。 + 在GitHub上查看 + NewPipe开源许可证 + 你是否有想:翻译、设计、清理或重型代码更改 ——我们始终欢迎你来贡献! + 阅读许可证 + 贡献 + 替换字符 + 取消订阅频道 + 无法修改订阅 + 无法更新订阅 + 主页面 + 订阅 + 新增功能 + 恢复前台焦点 + 中断后继续播放(例如突然来电后) + 搜索历史记录 + 在本地存储搜索查询记录 + 记录已观看视频 + 历史 + 已搜索 + 已观看 + 历史记录功能已关闭 + 历史 + 历史记录为空 + 清除历史记录 + NewPipe 通知 + NewPipe 后台播放和悬浮窗播放的通知 + 默认视频格式 + 行为 + 空空如也... + 无观看次数 + 项目已删除 + 是否要从搜索历史记录中删除此项目? + 显示在主页面内容 + 空白页 + 『时下流行』页-自定义 + 订阅页 + Feed 页面 + 频道页 + 选择一个频道 + 尚未订阅频道 + 选择一个时下流行页 + 『时下流行』 + 趋势 + 前50 + 最新与热门 + 显示 \"长按添加\" 说明 + 在视频详情页中,按下背景播放或悬浮窗播放按钮时显示提示 + 已加入后台播放播放列表 + 已加入悬浮窗播放列表 + 无法播放此串流 + 发生无法恢复播放器错误 + 恢复播放器错误 + 后台播放 + 悬浮窗播放器 + 移除 + 详情 + 音频设置 + 长按加入队列 + [未知] + 添加到后台部分队列 + 添加到新悬浮窗的队列 + 开始在此处开始播放 + 开始后台播放 + 开始在新悬浮窗中播放 + 捐赠 + NewPipe 是由志愿者花费时间为您带来最佳体验开发的。回馈帮助开发人员在享用一杯咖啡的同时,让 NewPipe 变得更好。 + 回馈 + 网站 + 请访问 NewPipe 网站了解更多信息和讯息。 + 默认国家/地区 + 切换方向 + 切换到背景播放 + 切换到悬浮窗播放 + 切换到主页面 + 服务 + 打开抽屉 + 关闭抽屉 + 第三方播放器不支持此类型链接 + 无效 URL + 未找到视频串流 + 找不到音频串流 + 视频播放器 + 后台播放器 + 悬浮窗播放器 + 正在获取信息… + 正在加载请求的内容 + 导入数据库 + 导出数据库 + 覆盖当前历史记录和订阅 + 导出历史记录、订阅和播放列表 + 导出成功 + 导入成功 + 没有有效的ZIP文件 + 警告:无法导入所有文件。 + 这将覆盖当前设置。 + 显示信息 + 已收藏 + 确定要从观看历史记录中删除该项吗? + 是否确实要从历史记录中删除所有项目? + 最后播放 + 播放最多 + 总是寻问 + 新建播放列表 + 删除 + 重 命名 + 名称 + 添加到播放列表 + 设为播放列表缩略图 + 收藏播放列表 + 删除收藏 + 删除此播放列表? + 新建播放列表成功 + 加入播放列表成功 + 播放列表缩略图更改成功。 + 无法删除播放列表 + 无字幕 + 适应屏幕 + 填充屏幕 + 缩放 + 敬请等待 + 调试 + 自动生成 + 启用LeakCanary + 『内存泄漏监视』可能导致应用在『核心转储』时无响应 + 报告『提前结束Android生命周期』错误 + 强制报告处理后的未送达的Activity或Fragment生命周期之外的Rx异常 + 使用快速不精确搜索 + 粗略定位播放:允许播放器以略低的精确度为代价换取更快的定位速度 + 为下一个串流自动排队 + 当播放完非循环列表中的最后一个视频时,自动加入一个相关视频到播放列表 + 没有此文件夹 + 没有此类文件/内容源 + 该文件不存在 或 缺少读写该文件的权限 + 文件名不能为空 + 发生错误: %1$s + 导入/导出 + 导入 + 从...导入 + 导出到... + 正在导入… + 正在导出… + 导入文件 + 以前的导出 + 无法导入订阅 + 无法导出订阅 + 通过下载导出文件来导入 YouTube 订阅: +\n +\n1. 转到此网站: %1$s +\n2. 登录(如果需要) +\n3. 应该立即开始下载(即导出文件) + 通过键入网址或你的 ID 导入 SoundCloud 配置文件: +\n +\n1. 在浏览器中启用\"电脑模式\"(该网站不适用于移动设备) +\n2. 转到此 URL: %1$s +\n3. 登录(如果需要) +\n4. 复制重定向的配置文件下载地址。 + 你的 ID:soundcloud.com/[你的ID] + 该操作消耗大量流量, +\n你想继续吗? + 关闭可防止加载缩略图,节已省数据和内存使用。(若现在更改会清除内存和储存中缓存) + 清除图像缓存成功 + 清空已缓存元数据 + 清除所有已缓存的网页数据 + 元数据缓存已擦除 + 播放速度控制 + 节奏 + 音调 + 解除关联(可能导致失真) + 首选“打开”操作 + 打开内容时默认操作: = %s + 没有可供下载的串流 + 字幕 + 修改播放器字幕比例和背景样式。需要重新启动应用程序才能生效。 + 删除串流的播放历史和播放位置 + 删除全部观看记录? + 清除搜索历史记录 + 清除搜索关键词的历史记录 + 是否删除全部搜索历史记录? + 搜索历史记录已删除。 + NewPipe 是版权自由软件:您可以随时使用、研究共享和改进它。您可以根据自由软件基金会发布的 GNU 通用公共许可证GPLv3或(由您选择的)任何更高版本的许可证重新分发或修改该许可证。 + 是否要同时导入设置? + NewPipe的隐私政策 + NewPipe 项目非常重视您的隐私。因此,未经您的同意,应用程序不会收集任何数据。 +\nNewPipe 的隐私政策详细解释了在发送崩溃报告时发送和存储的数据。 + 阅读隐私政策 + 为了遵守欧洲一般数据保护条例 (GDPR),我们提请您注意 NewPipe 的隐私政策。请仔细阅读。 +\n您必须接受它才能向我们发送错误报告。 + 接受 + 拒绝 + 无限制 + 使用移动数据时限制分辨率 + 最小化应用程序 + 从主视频播放器切换到其他应用时的操作 - %s + 静音时快进 + 滑块[比例尺] + 重 置 + 曲目 + 用户 + 选择标签 + 音量手势控制 + 使用手势控制播放器的音量 + 亮度手势控制 + 使用手势控制播放器的亮度 + 视频默认语言 + 应用更新通知 + NewPipe有新版本的通知 + 外置存储不可用 + 无法下载到外部 SD 卡。重置下载文件夹位置? + 读取已保存标签时发生错误,因此使用者默认标签 + 恢复默认 + 是否恢复默认值? + 选择 + 更新 + 列表 + 自动 + 切换视图 + 点击下载 + 后期处理 + 生成唯一名称 + 覆盖 + 正在使用此名称进行下载 + 显示错误 + 代码 + 无法创建目标文件夹 + 无法创建文件 + 权限被系统拒绝 + 安全连接失败 + 找不到服务器 + 无法连接到服务器 + 服务器未发送数据 + 请求范围不符合要求 + 找不到 NOT FOUND + 后期处理失败 + 清除已完成的下载 + 继续已暂停的下载 + 停止 + 最大重试次数 + 取消下载前的最多尝试次数 + 在切换到移动流量网络时中断播放 + 切换至移动数据时可能有用,尽管一些下载无法被暂停 + 事件 + 近期大会 + 显示评论 + 禁用,以停止显示评论 + + 评论 + + + 无法加载评论 + 关闭 + 恢复播放 + 恢复上次播放位置 + 列表中的位置 + 在列表中,显示视频最后一次播放时的播放位置 + 已删除播放位置记录。 + 文件被已移动或删除 + 该名称的文件已经存在 + 命名冲突,已存在具有此名称文件 + 无法覆盖文件 + 有此名称的已暂停下载 + 处理文件时,NewPipe 已关闭 + 设备上没有剩余储存空间 + 进度丢失,文件已被删除 + 连接超时 + 你确定吗? + 最大下载队列 + 同时只允许一个下载进行 + 开始下载 + 暂停下载 + 询问下载位置 + 系统将询问您将每次下载的保存位置 + 系统将询问您将每次下载的保存位置。 +\n(如果要下载到外部 SD 卡,请选择 SAF) + 使用 SAF + 存储访问框架(SAF)允许下载文件到外部SD卡。 +\n注:一些设备不兼容SAF + 删除播放位置记录 + 删除所有播放位置记录 + 删除所有播放位置记录? + 更改下载文件夹以生效 + 『时下流行』页-默认 + 没有人在看 + + %s 观看 + + + 没人在听 + + %s个听众 + + + 重新启动应用后,语言将更改。 \ No newline at end of file From cedfbf5f67651e0700c157e6e4298b7270acf592 Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Thu, 21 Nov 2019 16:14:32 +0000 Subject: [PATCH 157/270] Translated using Weblate (Russian) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 98f0e1c72..49b983ad2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -523,5 +523,5 @@ %s слушателя %s слушателей - Язык будет изменён после перезапуска. + Язык будет изменён после перезапуска \ No newline at end of file From 0f70aeb9109a08f8086457842780f204921f7670 Mon Sep 17 00:00:00 2001 From: narayaan Date: Thu, 21 Nov 2019 22:43:21 +0000 Subject: [PATCH 158/270] Translated using Weblate (Dutch) Currently translated at 90.2% (458 of 508 strings) --- app/src/main/res/values-nl/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index f7acba6ae..dcd8ba182 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -472,9 +472,12 @@ Automatisch afspelen Reacties - + Geen reacties Kan reacties niet laden Sluiten + Het Storage Acces Framework laat downloads naar een externe SD kaart toe. +\n +\nNota: niet alle toestellen zijn compatibel \ No newline at end of file From e21257b786140387c1ea8e1de08057c945addfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20M=C3=BCnster?= Date: Fri, 22 Nov 2019 17:14:34 +0000 Subject: [PATCH 159/270] Translated using Weblate (Swedish) Currently translated at 91.3% (464 of 508 strings) --- app/src/main/res/values-sv/strings.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 92785a950..cb3a53957 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -163,8 +163,8 @@ Inga videor - %s video - %s videor + Video + Videor Start Pausa @@ -457,6 +457,11 @@ Visa kommentarer Kommentarer - + + Inaktivera för att sluta visa kommentarer + Återuppta uppspelning + Återställ den senaste uppspelningspositionen + Positioner i listor + Ändra nedladdningsmapp för att träda i kraft \ No newline at end of file From 6452c7e08cca07bd460e156d8151c93840214d49 Mon Sep 17 00:00:00 2001 From: narayaan Date: Thu, 21 Nov 2019 19:41:01 +0000 Subject: [PATCH 160/270] Translated using Weblate (Flemish) Currently translated at 90.4% (459 of 508 strings) --- app/src/main/res/values-nl-rBE/strings.xml | 29 +++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml index 94feb4915..8d6508241 100644 --- a/app/src/main/res/values-nl-rBE/strings.xml +++ b/app/src/main/res/values-nl-rBE/strings.xml @@ -3,8 +3,8 @@ Tik op zoeken voor te beginnen %1$s keer bekeken Gepubliceerd op %1$s - Gene speler met streamondersteuning gevonden. Wilt ge VLC installeren? - Gene speler met streamondersteuning gevonden (ge kunt VLC installeren voor af te spelen). + Geen speler met streamondersteuning gevonden. Wil je VLC installeren\? + Geen speler met streamondersteuning gevonden (je kan VLC installeren om af te spelen). Installeren Annuleren In browser openen @@ -14,13 +14,13 @@ Streambestand downloaden Zoeken Instellingen - Bedoelde ge: %1$s\? + Bedoelde je: %1$s\? Delen met - Kies nen browser + Kies browser rotatie Externe videospeler gebruiken Verwijdert het geluid bij SOMMIGE resoluties - Externen audiospeler gebruiken + Externe audiospeler gebruiken NewPipe-pop-upmodus Abonneer Geabonneerd @@ -36,13 +36,13 @@ Pop-up Toevoegen aan Downloadlocatie voor video’s - Locatie voor gedownloade video’s in op te slaan + Gedownloade videobestanden worden hier opgeslaan Voer downloadlocatie in voor video’s Downloadmap voor audio - Gedownloade audio wordt hier opgeslagen + Gedownloade audiobestanden worden hier opgeslaan Voer downloadlocatie voor audiobestanden in Automatisch afspelen - Speeld video’s af wanneer da NewPipe vanuit nen anderen app word g’opend + Speelt video’s af wanneer dat NewPipe vanuit een anderen app word geopend Standaardresolutie Standaardresolutie voor pop-up Hogere resoluties weergeven @@ -63,13 +63,13 @@ Snel, minder exact spoelen gebruiken Minder exact spoelen laat de speler sneller posities zoeken met verminderde precisie Miniatuurvoorbeelden laden - Schakelt dit uit voor het laden van miniatuurvoorbeelden te verhinderen; dit bespaart mobiele gegevens en geheugen. Het wijzigen van deze instelling wist het geheugen en de afbeeldingscache. + Schakel dit uit voor het laden van miniatuurvoorbeelden te verhinderen; dit bespaart mobiele gegevens en geheugen. Het wijzigen van deze instelling wist het geheugen en de afbeeldingscache. Afbeeldingscache gewist Gecachete metagegevens wissen Alle gecachete webpagina-gegevens wissen Metagegevens-cache gewist Volgende stream automatisch in wachtrij plaatsen - Automatisch ne gerelateerde stream toekennen bij het afspelen van de laatste stream in een niet-herhalende wachtlijst + Automatisch een gerelateerde stream toekennen bij het afspelen van de laatste stream in een niet-herhalende wachtlijst Veegbesturing Gebruikt vegen voor de helderheid en het volume van de speler aan te passen Zoeksuggesties @@ -453,7 +453,7 @@ Kon de server niet vinden Kan geen verbinding maken met de server De server verzendt geen gegevens - De server aanvaardt geen meerdradige downloads, probeert het opnieuw met @string/msg_threads = 1 + De server aanvaardt geen meerdradige downloads, probeer het opnieuw met @string/msg_threads = 1 Gevraagd bereik niet beschikbaar Niet gevonden Nabewerking mislukt @@ -461,17 +461,18 @@ Zet uw %s wachtende downloads verder via Downloads Stoppen Maximaal aantal pogingen - Maximaal aantal pogingen vooraleer dat den download wordt geannuleerd + Maximaal aantal pogingen vooraleer dat de download wordt geannuleerd Pauzeren bij overschakelen naar mobiele gegevens - Downloads die dat niet kunnen gepauzeerd worden gaan herstart worden + Nuttig bij het gebruik van mobiele data, hoewel sommige downloads niet uitgesteld kunnen worden Commentaren weergeven Schakelt dit uit voor reacties niet meer weer te geven Automatisch afspelen Commentaren - + Geen commentaren Kan commentaren niet laden Sluiten + Vooruitgang verloren, omdat het bestand gedeletet werd \ No newline at end of file From 5e8746312597051a3082f15014138dc04f33dfc8 Mon Sep 17 00:00:00 2001 From: chr56 Date: Sat, 23 Nov 2019 05:22:34 +0000 Subject: [PATCH 161/270] Translated using Weblate (Chinese (Simplified)) Currently translated at 95.9% (487 of 508 strings) --- app/src/main/res/values-b+zh+HANS+CN/strings.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index 6a77962f9..3aafa6f4a 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -471,7 +471,7 @@ 禁用,以停止显示评论 评论 - + 无法加载评论 关闭 @@ -509,12 +509,13 @@ 没有人在看 %s 观看 - + 没人在听 %s个听众 - + 重新启动应用后,语言将更改。 + 『时下流行』页-默认 \ No newline at end of file From 0fb7eab2f9fb22c454b94543a7d147a1e50081a9 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Sat, 23 Nov 2019 20:04:40 +0100 Subject: [PATCH 162/270] Fix code formatting --- .../list/channel/ChannelFragment.java | 20 +++++++++---------- .../schabi/newpipe/util/ServiceHelper.java | 10 +++++----- .../org/schabi/newpipe/util/ThemeHelper.java | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 32bc6aeb3..ade96bdc4 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -99,7 +99,7 @@ public class ChannelFragment extends BaseListInfoFragment { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); - if(activity != null + if (activity != null && useAsFrontPage && isVisibleToUser) { setTitle(currentInfo != null ? currentInfo.getName() : name); @@ -153,7 +153,7 @@ public class ChannelFragment extends BaseListInfoFragment { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); ActionBar supportActionBar = activity.getSupportActionBar(); - if(useAsFrontPage && supportActionBar != null) { + if (useAsFrontPage && supportActionBar != null) { supportActionBar.setDisplayHomeAsUpEnabled(false); } else { inflater.inflate(R.menu.menu_channel, menu); @@ -166,7 +166,7 @@ public class ChannelFragment extends BaseListInfoFragment { private void openRssFeed() { final ChannelInfo info = currentInfo; - if(info != null) { + if (info != null) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl())); startActivity(intent); } @@ -219,7 +219,7 @@ public class ChannelFragment extends BaseListInfoFragment { .debounce(100, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe((List subscriptionEntities) -> - updateSubscribeButton(!subscriptionEntities.isEmpty()) + updateSubscribeButton(!subscriptionEntities.isEmpty()) , onError)); } @@ -360,9 +360,9 @@ public class ChannelFragment extends BaseListInfoFragment { headerRootLayout.setVisibility(View.VISIBLE); imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner, - ImageDisplayConstants.DISPLAY_BANNER_OPTIONS); + ImageDisplayConstants.DISPLAY_BANNER_OPTIONS); imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView, - ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); + ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); headerSubscribersTextView.setVisibility(View.VISIBLE); if (result.getSubscriberCount() >= 0) { @@ -398,8 +398,8 @@ public class ChannelFragment extends BaseListInfoFragment { private PlayQueue getPlayQueue(final int index) { final List streamItems = new ArrayList<>(); - for(InfoItem i : infoListAdapter.getItemsList()) { - if(i instanceof StreamInfoItem) { + for (InfoItem i : infoListAdapter.getItemsList()) { + if (i instanceof StreamInfoItem) { streamItems.add((StreamInfoItem) i); } } @@ -433,9 +433,9 @@ public class ChannelFragment extends BaseListInfoFragment { protected boolean onError(Throwable exception) { if (super.onError(exception)) return true; - if(exception instanceof ContentNotAvailableException){ + if (exception instanceof ContentNotAvailableException) { showError(getString(R.string.content_not_available), false); - }else{ + } else { int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error; onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL, diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index 084ab5878..066128ab9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -37,7 +37,7 @@ public class ServiceHelper { } public static String getTranslatedFilterString(String filter, Context c) { - switch(filter) { + switch (filter) { case "all": return c.getString(R.string.all); case "videos": return c.getString(R.string.videos); case "channels": return c.getString(R.string.channels); @@ -130,14 +130,14 @@ public class ServiceHelper { } public static boolean isBeta(final StreamingService s) { - switch(s.getServiceInfo().getName()) { + switch (s.getServiceInfo().getName()) { case "YouTube": return false; default: return true; } } public static void initService(Context context, int serviceId) { - if(serviceId == ServiceList.PeerTube.getServiceId()){ + if (serviceId == ServiceList.PeerTube.getServiceId()) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); String peerTubeInstanceUrl = sharedPreferences.getString(context.getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl()); String peerTubeInstanceName = sharedPreferences.getString(context.getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()); @@ -146,8 +146,8 @@ public class ServiceHelper { } } - public static void initServices(Context context){ - for(StreamingService s : ServiceList.all()){ + public static void initServices(Context context) { + for (StreamingService s : ServiceList.all()) { initService(context, s.getServiceId()); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index f476cf66b..6d888a91d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -137,10 +137,10 @@ public class ThemeHelper { else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme"; else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme"; - if(serviceId == ServiceList.PeerTube.getServiceId()){ + if (serviceId == ServiceList.PeerTube.getServiceId()) { //service name for peertube depends on the instance themeName += ".PeerTube"; - }else{ + } else { themeName += "." + service.getServiceInfo().getName(); } From 0c65f73180ecbb86c940506383d832fa13f7c45c Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Sat, 23 Nov 2019 19:18:35 +0000 Subject: [PATCH 163/270] Translated using Weblate (Russian) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-ru/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 49b983ad2..ca695b07c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -524,4 +524,5 @@ %s слушателей Язык будет изменён после перезапуска + Киоск по умолчанию \ No newline at end of file From 46e2f4e579cfe366f8475426e151fe5b8aadc254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sat, 23 Nov 2019 06:08:54 +0000 Subject: [PATCH 164/270] Translated using Weblate (Turkish) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-tr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f39be2b15..e57b4aa77 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -519,4 +519,5 @@ %s dinleyici Uygulama yeniden başlatıldıktan sonra dil değişecektir. + Varsayılan Kiosk \ No newline at end of file From 8227d85febdfaf5e9946382138b65586201c6631 Mon Sep 17 00:00:00 2001 From: ssantos Date: Sat, 23 Nov 2019 06:17:55 +0000 Subject: [PATCH 165/270] Translated using Weblate (Portuguese) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-pt/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 47513a600..7364e4736 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -518,4 +518,5 @@ %s ouvintes O idioma mudará quando a app for reiniciada. + Quiosque Padrão \ No newline at end of file From f122d737549b6fa307258a6cc035b07a79872ab1 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Sat, 23 Nov 2019 11:37:15 +0000 Subject: [PATCH 166/270] Translated using Weblate (Hebrew) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-he/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 80c47b001..659d1349e 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -531,4 +531,5 @@ %s מאזינים השפה תוחלף עם הפעלת היישומון מחדש. + קיוסק בררת מחדל \ No newline at end of file From 55f5f7627510008ebdf90cf669aa131b898a1f30 Mon Sep 17 00:00:00 2001 From: chr56 Date: Sat, 23 Nov 2019 05:32:46 +0000 Subject: [PATCH 167/270] Translated using Weblate (Chinese (Simplified)) Currently translated at 95.9% (487 of 508 strings) --- app/src/main/res/values-b+zh+HANS+CN/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index 3aafa6f4a..068851a2a 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -385,10 +385,10 @@ 该操作消耗大量流量, \n你想继续吗? 关闭可防止加载缩略图,节已省数据和内存使用。(若现在更改会清除内存和储存中缓存) - 清除图像缓存成功 + 清空图像缓存成功 清空已缓存元数据 - 清除所有已缓存的网页数据 - 元数据缓存已擦除 + 清空已缓存的网页数据 + 清空元数据缓存成功 播放速度控制 节奏 音调 From 1ce44b31e219ef55259c39498a295163505830da Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Sun, 24 Nov 2019 00:50:18 +0000 Subject: [PATCH 168/270] Translated using Weblate (Russian) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ca695b07c..0cfb91c5f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -468,7 +468,7 @@ Возобновить приостановленные загрузки (%s) Максимум попыток Количество попыток перед отменой загрузки - Некоторые загрузки не поддерживают докачку и начнутся с начала + Некоторые загрузки не поддерживают докачку и начнутся сначала Не удалось установить защищённое соединение Не удалось соединиться с сервером Не удалось получить данные с сервера From a68c8ceebe5411c8edfdcee42d099f1d4389c0ea Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Sun, 24 Nov 2019 00:56:37 +0000 Subject: [PATCH 169/270] Translated using Weblate (Russian) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0cfb91c5f..290dc3ebb 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -282,7 +282,7 @@ Плейлисты В плейлист Быстрый поиск позиции - Выполняется быстрее, но с меньшей точностью + Выполняется быстрее, но менее точно Автодополнение очереди Добавлять похожие потоки в очередь при воспроизведении последнего потока, если не включён повтор Отладка From 8e152df46d6b4f22c05344e8b437c9451d0b167e Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 24 Nov 2019 11:10:50 +0100 Subject: [PATCH 170/270] Remember caption option in player, closes #2811 --- .../schabi/newpipe/player/VideoPlayer.java | 34 +++++++++++++++++-- app/src/main/res/values/settings_keys.xml | 1 + 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 360475ba2..a92ab410e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -26,14 +26,13 @@ import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; import android.os.Build; import android.os.Handler; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; +import android.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -45,6 +44,10 @@ import android.widget.ProgressBar; import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; @@ -285,6 +288,12 @@ public abstract class VideoPlayer extends BasePlayer if (captionPopupMenu == null) return; captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId); + String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.caption_user_set_key), null); + // english (auto-generated) + boolean searchForAutogenerated = userPreferredLanguage == null || + !userPreferredLanguage.contains("("); + // Add option for turning off caption MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId, 0, Menu.NONE, R.string.caption_none); @@ -294,6 +303,8 @@ public abstract class VideoPlayer extends BasePlayer trackSelector.setParameters(trackSelector.buildUponParameters() .setRendererDisabled(textRendererIndex, true)); } + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + prefs.edit().remove(context.getString(R.string.caption_user_set_key)).commit(); return true; }); @@ -308,9 +319,26 @@ public abstract class VideoPlayer extends BasePlayer trackSelector.setPreferredTextLanguage(captionLanguage); trackSelector.setParameters(trackSelector.buildUponParameters() .setRendererDisabled(textRendererIndex, false)); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + prefs.edit().putString(context.getString(R.string.caption_user_set_key), + captionLanguage).commit(); } return true; }); + // apply caption language from previous user preference + if(userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage) || + searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage) || + userPreferredLanguage.contains("(") && + captionLanguage.startsWith(userPreferredLanguage.substring(0, + userPreferredLanguage.indexOf('('))))) { + final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT); + if (textRendererIndex != RENDERER_UNAVAILABLE) { + trackSelector.setPreferredTextLanguage(captionLanguage); + trackSelector.setParameters(trackSelector.buildUponParameters() + .setRendererDisabled(textRendererIndex, false)); + } + searchForAutogenerated = false; + } } captionPopupMenu.setOnDismissListener(this); } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 80f2bb1f4..783ab7b5e 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -133,6 +133,7 @@ caption_settings_key + caption_user_set_key show_search_suggestions From 52a21e4a246600dcc4dfcc1fb9c558575be53c8c Mon Sep 17 00:00:00 2001 From: kapodamy Date: Mon, 23 Sep 2019 21:38:29 -0300 Subject: [PATCH 171/270] implement webm to ogg demuxer * used for opus audio stream * update WebMReader and WebMWriter * new post-processing algorithm --- .../newpipe/download/DownloadDialog.java | 4 +- .../newpipe/streams/OggFromWebMWriter.java | 488 ++++++++++++++++++ .../schabi/newpipe/streams/WebMReader.java | 55 +- .../schabi/newpipe/streams/WebMWriter.java | 27 +- .../postprocessing/OggFromWebmDemuxer.java | 44 ++ .../giga/postprocessing/Postprocessing.java | 6 +- 6 files changed, 595 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java create mode 100644 app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 59bffa933..90258a6dc 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -561,7 +561,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck mainStorage = mainStorageVideo; format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat(); mime = format.mimeType; - filename += format.suffix; + filename += format == MediaFormat.OPUS ? "ogg" : format.suffix; break; case R.id.subtitle_button: mainStorage = mainStorageVideo;// subtitle & video files go together @@ -778,6 +778,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck if (selectedStream.getFormat() == MediaFormat.M4A) { psName = Postprocessing.ALGORITHM_M4A_NO_DASH; + } else if (selectedStream.getFormat() == MediaFormat.OPUS) { + psName = Postprocessing.ALGORITHM_OGG_FROM_WEBM_DEMUXER; } break; case R.id.video_button: diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java new file mode 100644 index 000000000..2b3d778c6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -0,0 +1,488 @@ +package org.schabi.newpipe.streams; + +import android.support.annotation.NonNull; + +import org.schabi.newpipe.streams.WebMReader.Cluster; +import org.schabi.newpipe.streams.WebMReader.Segment; +import org.schabi.newpipe.streams.WebMReader.SimpleBlock; +import org.schabi.newpipe.streams.WebMReader.WebMTrack; +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Random; + +import javax.annotation.Nullable; + +/** + * @author kapodamy + */ +public class OggFromWebMWriter implements Closeable { + + private static final byte FLAG_UNSET = 0x00; + //private static final byte FLAG_CONTINUED = 0x01; + private static final byte FLAG_FIRST = 0x02; + private static final byte FLAG_LAST = 0x04; + + private final static byte SEGMENTS_PER_PACKET = 50;// used in ffmpeg, which is near 1 second at 48kHz + private final static byte HEADER_CHECKSUM_OFFSET = 22; + + private boolean done = false; + private boolean parsed = false; + + private SharpStream source; + private SharpStream output; + + private int sequence_count = 0; + private final int STREAM_ID; + + private WebMReader webm = null; + private WebMTrack webm_track = null; + private int track_index = 0; + + public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) { + if (!source.canRead() || !source.canRewind()) { + throw new IllegalArgumentException("source stream must be readable and allows seeking"); + } + if (!target.canWrite() || !target.canRewind()) { + throw new IllegalArgumentException("output stream must be writable and allows seeking"); + } + + this.source = source; + this.output = target; + + this.STREAM_ID = (new Random(System.currentTimeMillis())).nextInt(); + + populate_crc32_table(); + } + + public boolean isDone() { + return done; + } + + public boolean isParsed() { + return parsed; + } + + public WebMTrack[] getTracksFromSource() throws IllegalStateException { + if (!parsed) { + throw new IllegalStateException("source must be parsed first"); + } + + return webm.getAvailableTracks(); + } + + public void parseSource() throws IOException, IllegalStateException { + if (done) { + throw new IllegalStateException("already done"); + } + if (parsed) { + throw new IllegalStateException("already parsed"); + } + + try { + webm = new WebMReader(source); + webm.parse(); + webm_segment = webm.getNextSegment(); + } finally { + parsed = true; + } + } + + public void selectTrack(int trackIndex) throws IOException { + if (!parsed) { + throw new IllegalStateException("source must be parsed first"); + } + if (done) { + throw new IOException("already done"); + } + if (webm_track != null) { + throw new IOException("tracks already selected"); + } + + switch (webm.getAvailableTracks()[trackIndex].kind) { + case Audio: + case Video: + break; + default: + throw new UnsupportedOperationException("the track must an audio or video stream"); + } + + try { + webm_track = webm.selectTrack(trackIndex); + track_index = trackIndex; + } finally { + parsed = true; + } + } + + @Override + public void close() throws IOException { + done = true; + parsed = true; + + webm_track = null; + webm = null; + + if (!output.isClosed()) { + output.flush(); + } + + source.close(); + output.close(); + } + + public void build() throws IOException { + float resolution; + int read; + byte[] buffer; + int checksum; + byte flag = FLAG_FIRST;// obligatory + + switch (webm_track.kind) { + case Audio: + resolution = getSampleFrequencyFromTrack(webm_track.bMetadata); + if (resolution == 0f) { + throw new RuntimeException("cannot get the audio sample rate"); + } + break; + case Video: + // WARNING: untested + if (webm_track.defaultDuration == 0) { + throw new RuntimeException("missing default frame time"); + } + resolution = 1000f / ((float) webm_track.defaultDuration / webm_segment.info.timecodeScale); + break; + default: + throw new RuntimeException("not implemented"); + } + + /* step 1.1: write codec init data, in most cases must be present */ + if (webm_track.codecPrivate != null) { + addPacketSegment(webm_track.codecPrivate.length); + dump_packetHeader(flag, 0x00, webm_track.codecPrivate); + flag = FLAG_UNSET; + } + + /* step 1.2: write metadata */ + buffer = make_metadata(); + if (buffer != null) { + addPacketSegment(buffer.length); + dump_packetHeader(flag, 0x00, buffer); + flag = FLAG_UNSET; + } + + buffer = new byte[8 * 1024]; + + /* step 1.3: write headers */ + long approx_packets = webm_segment.info.duration / webm_segment.info.timecodeScale; + approx_packets = approx_packets / (approx_packets / SEGMENTS_PER_PACKET); + + ArrayList pending_offsets = new ArrayList<>((int) approx_packets); + ArrayList pending_checksums = new ArrayList<>((int) approx_packets); + ArrayList data_offsets = new ArrayList<>((int) approx_packets); + + int page_size = 0; + SimpleBlock bloq; + + while (webm_segment != null) { + bloq = getNextBlock(); + + if (bloq != null && addPacketSegment(bloq.dataSize)) { + page_size += bloq.dataSize; + + if (segment_table_size < SEGMENTS_PER_PACKET) { + continue; + } + + // calculate the current packet duration using the next block + bloq = getNextBlock(); + } + + double elapsed_ns = webm_track.codecDelay; + + if (bloq == null) { + flag = FLAG_LAST; + elapsed_ns += webm_block_last_timecode; + + if (webm_track.defaultDuration > 0) { + elapsed_ns += webm_track.defaultDuration; + } else { + // hardcoded way, guess the sample duration + elapsed_ns += webm_block_near_duration; + } + } else { + elapsed_ns += bloq.absoluteTimeCodeNs; + } + + // get the sample count in the page + elapsed_ns = (elapsed_ns / 1000000000d) * resolution; + elapsed_ns = Math.ceil(elapsed_ns); + + long offset = output_offset + HEADER_CHECKSUM_OFFSET; + pending_offsets.add(offset); + + checksum = dump_packetHeader(flag, (long) elapsed_ns, null); + pending_checksums.add(checksum); + + data_offsets.add((short) (output_offset - offset)); + + // reserve space in the page + while (page_size > 0) { + int write = Math.min(page_size, buffer.length); + out_write(buffer, write); + page_size -= write; + } + + webm_block = bloq; + } + + /* step 2.1: write stream data */ + output.rewind(); + output_offset = 0; + + source.rewind(); + + webm = new WebMReader(source); + webm.parse(); + webm_track = webm.selectTrack(track_index); + + for (int i = 0; i < pending_offsets.size(); i++) { + checksum = pending_checksums.get(i); + segment_table_size = 0; + + out_seek(pending_offsets.get(i) + data_offsets.get(i)); + + while (segment_table_size < SEGMENTS_PER_PACKET) { + bloq = getNextBlock(); + + if (bloq == null || !addPacketSegment(bloq.dataSize)) { + webm_block = bloq;// use this block later (if not null) + break; + } + + // NOTE: calling bloq.data.close() is unnecessary + while ((read = bloq.data.read(buffer)) != -1) { + out_write(buffer, read); + checksum = calc_crc32(checksum, buffer, read); + } + } + + pending_checksums.set(i, checksum); + } + + /* step 2.2: write every checksum */ + output.rewind(); + output_offset = 0; + buffer = new byte[4]; + + ByteBuffer buff = ByteBuffer.wrap(buffer); + buff.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < pending_checksums.size(); i++) { + out_seek(pending_offsets.get(i)); + buff.putInt(0, pending_checksums.get(i)); + out_write(buffer); + } + } + + private int dump_packetHeader(byte flag, long gran_pos, byte[] immediate_page) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(27 + segment_table_size); + + buffer.putInt(0x4F676753);// "OggS" binary string + buffer.put((byte) 0x00);// version + buffer.put(flag);// type + + buffer.order(ByteOrder.LITTLE_ENDIAN); + + buffer.putLong(gran_pos);// granulate position + + buffer.putInt(STREAM_ID);// bitstream serial number + buffer.putInt(sequence_count++);// page sequence number + + buffer.putInt(0x00);// page checksum + + buffer.order(ByteOrder.BIG_ENDIAN); + + buffer.put((byte) segment_table_size);// segment table + buffer.put(segment_table, 0, segment_table_size);// segment size + + segment_table_size = 0;// clear segment table for next header + + byte[] buff = buffer.array(); + int checksum_crc32 = calc_crc32(0x00, buff, buff.length); + + if (immediate_page != null) { + checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32); + + out_write(buff); + out_write(immediate_page); + return 0; + } + + out_write(buff); + return checksum_crc32; + } + + @Nullable + private byte[] make_metadata() { + if ("A_OPUS".equals(webm_track.codecId)) { + return new byte[]{ + 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string + 0x07, 0x00, 0x00, 0x00,// writting application string size + 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string + 0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags) + }; + } else if ("A_VORBIS".equals(webm_track.codecId)) { + return new byte[]{ + 0x03,// ???????? + 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string + 0x07, 0x00, 0x00, 0x00,// writting application string size + 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string + 0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags) + + /* + // whole file duration (not implemented) + 0x44,// tag string size + 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, + 0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 + */ + 0x0F,// tag string size + 0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D,// "ENCODER=" binary string + 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00// ???????? + }; + } + + // not implemented for the desired codec + return null; + } + + // + private Segment webm_segment = null; + private Cluster webm_cluter = null; + private SimpleBlock webm_block = null; + private long webm_block_last_timecode = 0; + private long webm_block_near_duration = 0; + + private SimpleBlock getNextBlock() throws IOException { + SimpleBlock res; + + if (webm_block != null) { + res = webm_block; + webm_block = null; + return res; + } + + if (webm_segment == null) { + webm_segment = webm.getNextSegment(); + if (webm_segment == null) { + return null;// no more blocks in the selected track + } + } + + if (webm_cluter == null) { + webm_cluter = webm_segment.getNextCluster(); + if (webm_cluter == null) { + webm_segment = null; + return getNextBlock(); + } + } + + res = webm_cluter.getNextSimpleBlock(); + if (res == null) { + webm_cluter = null; + return getNextBlock(); + } + + webm_block_near_duration = res.absoluteTimeCodeNs - webm_block_last_timecode; + webm_block_last_timecode = res.absoluteTimeCodeNs; + + return res; + } + + private float getSampleFrequencyFromTrack(byte[] bMetadata) { + // hardcoded way + ByteBuffer buffer = ByteBuffer.wrap(bMetadata); + + while (buffer.remaining() >= 6) { + int id = buffer.getShort() & 0xFFFF; + if (id == 0x0000B584) { + return buffer.getFloat(); + } + } + + return 0f; + } + // + + // + private int segment_table_size = 0; + private final byte[] segment_table = new byte[255]; + + private boolean addPacketSegment(long size) { + // check if possible add the segment, without overflow the table + int available = (segment_table.length - segment_table_size) * 255; + if (available < size) { + return false;// not enough space on the page + } + + while (size > 0) { + segment_table[segment_table_size++] = (byte) Math.min(size, 255); + size -= 255; + } + + return true; + } + // + + // + private long output_offset = 0; + + private void out_write(byte[] buffer) throws IOException { + output.write(buffer); + output_offset += buffer.length; + } + + private void out_write(byte[] buffer, int size) throws IOException { + output.write(buffer, 0, size); + output_offset += size; + } + + private void out_seek(long offset) throws IOException { + //if (output.canSeek()) { output.seek(offset); } + output.skip(offset - output_offset); + output_offset = offset; + } + // + + // + private final int[] crc32_table = new int[256]; + + private void populate_crc32_table() { + for (int i = 0; i < 0x100; i++) { + int crc = i << 24; + for (int j = 0; j < 8; j++) { + long b = crc >>> 31; + crc <<= 1; + crc ^= (int) (0x100000000L - b) & 0x04c11db7; + } + crc32_table[i] = crc; + } + } + + private int calc_crc32(int initial_crc, byte[] buffer, int size) { + for (int i = 0; i < size; i++) { + int reg = (initial_crc >>> 24) & 0xff; + initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)]; + } + + return initial_crc; + } + // +} diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java index 0c635ebe3..13c15370d 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java @@ -15,7 +15,7 @@ import java.util.NoSuchElementException; */ public class WebMReader { - // + // private final static int ID_EMBL = 0x0A45DFA3; private final static int ID_EMBLReadVersion = 0x02F7; private final static int ID_EMBLDocType = 0x0282; @@ -37,10 +37,13 @@ public class WebMReader { private final static int ID_Audio = 0x61; private final static int ID_DefaultDuration = 0x3E383; private final static int ID_FlagLacing = 0x1C; + private final static int ID_CodecDelay = 0x16AA; private final static int ID_Cluster = 0x0F43B675; private final static int ID_Timecode = 0x67; private final static int ID_SimpleBlock = 0x23; + private final static int ID_Block = 0x21; + private final static int ID_GroupBlock = 0x20; // public enum TrackKind { @@ -96,7 +99,7 @@ public class WebMReader { } ensure(segment.ref); - + // WARNING: track cannot be the same or have different index in new segments Element elem = untilElement(null, ID_Segment); if (elem == null) { done = true; @@ -189,6 +192,9 @@ public class WebMReader { Element elem; while (ref == null ? stream.available() : (stream.position() < (ref.offset + ref.size))) { elem = readElement(); + if (expected.length < 1) { + return elem; + } for (int type : expected) { if (elem.type == type) { return elem; @@ -300,9 +306,7 @@ public class WebMReader { WebMTrack entry = new WebMTrack(); boolean drop = false; Element elem; - while ((elem = untilElement(elem_trackEntry, - ID_TrackNumber, ID_TrackType, ID_CodecID, ID_CodecPrivate, ID_FlagLacing, ID_DefaultDuration, ID_Audio, ID_Video - )) != null) { + while ((elem = untilElement(elem_trackEntry)) != null) { switch (elem.type) { case ID_TrackNumber: entry.trackNumber = readNumber(elem); @@ -326,8 +330,9 @@ public class WebMReader { case ID_FlagLacing: drop = readNumber(elem) != lacingExpected; break; + case ID_CodecDelay: + entry.codecDelay = readNumber(elem); default: - System.out.println(); break; } ensure(elem); @@ -360,12 +365,13 @@ public class WebMReader { private SimpleBlock readSimpleBlock(Element ref) throws IOException { SimpleBlock obj = new SimpleBlock(ref); - obj.dataSize = stream.position(); obj.trackNumber = readEncodedNumber(); obj.relativeTimeCode = stream.readShort(); obj.flags = (byte) stream.read(); obj.dataSize = (ref.offset + ref.size) - stream.position(); + obj.createdFromBlock = ref.type == ID_Block; + // NOTE: lacing is not implemented, and will be mixed with the stream data if (obj.dataSize < 0) { throw new IOException(String.format("Unexpected SimpleBlock element size, missing %s bytes", -obj.dataSize)); } @@ -409,6 +415,7 @@ public class WebMReader { public byte[] bMetadata; public TrackKind kind; public long defaultDuration; + public long codecDelay; } public class Segment { @@ -448,6 +455,7 @@ public class WebMReader { public class SimpleBlock { public InputStream data; + public boolean createdFromBlock; SimpleBlock(Element ref) { this.ref = ref; @@ -455,6 +463,7 @@ public class WebMReader { public long trackNumber; public short relativeTimeCode; + public long absoluteTimeCodeNs; public byte flags; public long dataSize; private final Element ref; @@ -468,33 +477,55 @@ public class WebMReader { Element ref; SimpleBlock currentSimpleBlock = null; + Element currentBlockGroup = null; public long timecode; Cluster(Element ref) { this.ref = ref; } - boolean check() { + boolean insideClusterBounds() { return stream.position() >= (ref.offset + ref.size); } public SimpleBlock getNextSimpleBlock() throws IOException { - if (check()) { + if (insideClusterBounds()) { return null; } - if (currentSimpleBlock != null) { + + if (currentBlockGroup != null) { + ensure(currentBlockGroup); + currentBlockGroup = null; + currentSimpleBlock = null; + } else if (currentSimpleBlock != null) { ensure(currentSimpleBlock.ref); } - while (!check()) { - Element elem = untilElement(ref, ID_SimpleBlock); + while (!insideClusterBounds()) { + Element elem = untilElement(ref, ID_SimpleBlock, ID_GroupBlock); if (elem == null) { return null; } + if (elem.type == ID_GroupBlock) { + currentBlockGroup = elem; + elem = untilElement(currentBlockGroup, ID_Block); + + if (elem == null) { + ensure(currentBlockGroup); + currentBlockGroup = null; + continue; + } + } + currentSimpleBlock = readSimpleBlock(elem); if (currentSimpleBlock.trackNumber == tracks[selectedTrack].trackNumber) { currentSimpleBlock.data = stream.getView((int) currentSimpleBlock.dataSize); + + // calculate the timestamp in nanoseconds + currentSimpleBlock.absoluteTimeCodeNs = currentSimpleBlock.relativeTimeCode + this.timecode; + currentSimpleBlock.absoluteTimeCodeNs *= segment.info.timecodeScale; + return currentSimpleBlock; } diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java index e5881fd0b..1bf994b1e 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java @@ -8,6 +8,7 @@ import org.schabi.newpipe.streams.WebMReader.SimpleBlock; import org.schabi.newpipe.streams.WebMReader.WebMTrack; import org.schabi.newpipe.streams.io.SharpStream; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; @@ -17,7 +18,7 @@ import java.util.ArrayList; /** * @author kapodamy */ -public class WebMWriter { +public class WebMWriter implements Closeable { private final static int BUFFER_SIZE = 8 * 1024; private final static int DEFAULT_TIMECODE_SCALE = 1000000; @@ -35,7 +36,7 @@ public class WebMWriter { private long written = 0; private Segment[] readersSegment; - private Cluster[] readersCluter; + private Cluster[] readersCluster; private int[] predefinedDurations; @@ -81,7 +82,7 @@ public class WebMWriter { public void selectTracks(int... trackIndex) throws IOException { try { readersSegment = new Segment[readers.length]; - readersCluter = new Cluster[readers.length]; + readersCluster = new Cluster[readers.length]; predefinedDurations = new int[readers.length]; for (int i = 0; i < readers.length; i++) { @@ -102,6 +103,7 @@ public class WebMWriter { return parsed; } + @Override public void close() { done = true; parsed = true; @@ -114,7 +116,7 @@ public class WebMWriter { readers = null; infoTracks = null; readersSegment = null; - readersCluter = null; + readersCluster = null; outBuffer = null; } @@ -334,17 +336,17 @@ public class WebMWriter { } } - if (readersCluter[internalTrackId] == null) { - readersCluter[internalTrackId] = readersSegment[internalTrackId].getNextCluster(); - if (readersCluter[internalTrackId] == null) { + if (readersCluster[internalTrackId] == null) { + readersCluster[internalTrackId] = readersSegment[internalTrackId].getNextCluster(); + if (readersCluster[internalTrackId] == null) { readersSegment[internalTrackId] = null; return getNextBlockFrom(internalTrackId); } } - SimpleBlock res = readersCluter[internalTrackId].getNextSimpleBlock(); + SimpleBlock res = readersCluster[internalTrackId].getNextSimpleBlock(); if (res == null) { - readersCluter[internalTrackId] = null; + readersCluster[internalTrackId] = null; return new Block();// fake block to indicate the end of the cluster } @@ -353,16 +355,11 @@ public class WebMWriter { bloq.dataSize = (int) res.dataSize; bloq.trackNumber = internalTrackId; bloq.flags = res.flags; - bloq.absoluteTimecode = convertTimecode(res.relativeTimeCode, readersSegment[internalTrackId].info.timecodeScale); - bloq.absoluteTimecode += readersCluter[internalTrackId].timecode; + bloq.absoluteTimecode = res.absoluteTimeCodeNs / DEFAULT_TIMECODE_SCALE; return bloq; } - private short convertTimecode(int time, long oldTimeScale) { - return (short) (time * (DEFAULT_TIMECODE_SCALE / oldTimeScale)); - } - private void seekTo(SharpStream stream, long offset) throws IOException { if (stream.canSeek()) { stream.seek(offset); diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java new file mode 100644 index 000000000..65aa30fa3 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java @@ -0,0 +1,44 @@ +package us.shandian.giga.postprocessing; + +import android.support.annotation.NonNull; + +import org.schabi.newpipe.streams.OggFromWebMWriter; +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.IOException; +import java.nio.ByteBuffer; + +class OggFromWebmDemuxer extends Postprocessing { + + OggFromWebmDemuxer() { + super(false, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); + } + + @Override + boolean test(SharpStream... sources) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(4); + sources[0].read(buffer.array()); + + // youtube uses WebM as container, but the file extension (format suffix) is "*.opus" + // check if the file is a webm/mkv file before proceed + + switch (buffer.getInt()) { + case 0x1a45dfa3: + return true;// webm + case 0x4F676753: + return false;// ogg + } + + throw new UnsupportedOperationException("file not recognized, failed to demux the audio stream"); + } + + @Override + int process(SharpStream out, @NonNull SharpStream... sources) throws IOException { + OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out); + demuxer.parseSource(); + demuxer.selectTrack(0); + demuxer.build(); + + return OK_RESULT; + } +} diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java index 22cc325d5..92510c3df 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java @@ -28,6 +28,7 @@ public abstract class Postprocessing implements Serializable { public transient static final String ALGORITHM_WEBM_MUXER = "webm"; public transient static final String ALGORITHM_MP4_FROM_DASH_MUXER = "mp4D-mp4"; public transient static final String ALGORITHM_M4A_NO_DASH = "mp4D-m4a"; + public transient static final String ALGORITHM_OGG_FROM_WEBM_DEMUXER = "webm-ogg-d"; public static Postprocessing getAlgorithm(@NonNull String algorithmName, String[] args) { Postprocessing instance; @@ -45,6 +46,9 @@ public abstract class Postprocessing implements Serializable { case ALGORITHM_M4A_NO_DASH: instance = new M4aNoDash(); break; + case ALGORITHM_OGG_FROM_WEBM_DEMUXER: + instance = new OggFromWebmDemuxer(); + break; /*case "example-algorithm": instance = new ExampleAlgorithm();*/ default: @@ -212,7 +216,7 @@ public abstract class Postprocessing implements Serializable { * * @param out output stream * @param sources files to be processed - * @return a error code, 0 means the operation was successful + * @return an error code, {@code OK_RESULT} means the operation was successful * @throws IOException if an I/O error occurs. */ abstract int process(SharpStream out, SharpStream... sources) throws IOException; From 0cdfa6e377e48002247ec515b7eb2a3353f5c007 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Wed, 25 Sep 2019 16:24:52 -0300 Subject: [PATCH 172/270] rewrite OggFromWebMWriter * reduce the number of iterations over the output file (less seeking) * fix audio samples with size of 255 do not handled correctly in the segment table (allows writing audio streams with 70kbps and 160kbps bitrate) * add support for VORBIS codec metadata * write packets based on the timestamp --- .../newpipe/streams/OggFromWebMWriter.java | 348 ++++++++++-------- .../postprocessing/OggFromWebmDemuxer.java | 4 +- 2 files changed, 203 insertions(+), 149 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index 2b3d778c6..091ae6d2a 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -23,12 +23,16 @@ import javax.annotation.Nullable; public class OggFromWebMWriter implements Closeable { private static final byte FLAG_UNSET = 0x00; - //private static final byte FLAG_CONTINUED = 0x01; + private static final byte FLAG_CONTINUED = 0x01; private static final byte FLAG_FIRST = 0x02; private static final byte FLAG_LAST = 0x04; - private final static byte SEGMENTS_PER_PACKET = 50;// used in ffmpeg, which is near 1 second at 48kHz private final static byte HEADER_CHECKSUM_OFFSET = 22; + private final static byte HEADER_SIZE = 27; + + private final static short BUFFER_SIZE = 8 * 1024;// 8KiB + + private final static int TIME_SCALE_NS = 1000000000; private boolean done = false; private boolean parsed = false; @@ -38,10 +42,23 @@ public class OggFromWebMWriter implements Closeable { private int sequence_count = 0; private final int STREAM_ID; + private byte packet_flag = FLAG_FIRST; + private int track_index = 0; private WebMReader webm = null; private WebMTrack webm_track = null; - private int track_index = 0; + private Segment webm_segment = null; + private Cluster webm_cluster = null; + private SimpleBlock webm_block = null; + + private long webm_block_last_timecode = 0; + private long webm_block_near_duration = 0; + + private short segment_table_size = 0; + private final byte[] segment_table = new byte[255]; + private long segment_table_next_timestamp = TIME_SCALE_NS; + + private final int[] crc32_table = new int[256]; public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) { if (!source.canRead() || !source.canRewind()) { @@ -139,9 +156,8 @@ public class OggFromWebMWriter implements Closeable { float resolution; int read; byte[] buffer; - int checksum; - byte flag = FLAG_FIRST;// obligatory + /* step 1: get the amount of frames per seconds */ switch (webm_track.kind) { case Audio: resolution = getSampleFrequencyFromTrack(webm_track.bMetadata); @@ -160,52 +176,65 @@ public class OggFromWebMWriter implements Closeable { throw new RuntimeException("not implemented"); } - /* step 1.1: write codec init data, in most cases must be present */ + /* step 2a: create packet with code init data */ + ArrayList data_extra = new ArrayList<>(4); + if (webm_track.codecPrivate != null) { addPacketSegment(webm_track.codecPrivate.length); - dump_packetHeader(flag, 0x00, webm_track.codecPrivate); - flag = FLAG_UNSET; + ByteBuffer buff = byte_buffer(HEADER_SIZE + segment_table_size + webm_track.codecPrivate.length); + + make_packetHeader(0x00, buff, webm_track.codecPrivate); + data_extra.add(buff.array()); } - /* step 1.2: write metadata */ + /* step 2b: create packet with metadata */ buffer = make_metadata(); if (buffer != null) { addPacketSegment(buffer.length); - dump_packetHeader(flag, 0x00, buffer); - flag = FLAG_UNSET; + ByteBuffer buff = byte_buffer(HEADER_SIZE + segment_table_size + buffer.length); + + make_packetHeader(0x00, buff, buffer); + data_extra.add(buff.array()); } - buffer = new byte[8 * 1024]; - /* step 1.3: write headers */ - long approx_packets = webm_segment.info.duration / webm_segment.info.timecodeScale; - approx_packets = approx_packets / (approx_packets / SEGMENTS_PER_PACKET); - - ArrayList pending_offsets = new ArrayList<>((int) approx_packets); - ArrayList pending_checksums = new ArrayList<>((int) approx_packets); - ArrayList data_offsets = new ArrayList<>((int) approx_packets); - - int page_size = 0; + /* step 3: calculate amount of packets */ SimpleBlock bloq; + int reserve_header = 0; + int headers_amount = 0; while (webm_segment != null) { bloq = getNextBlock(); - if (bloq != null && addPacketSegment(bloq.dataSize)) { - page_size += bloq.dataSize; - - if (segment_table_size < SEGMENTS_PER_PACKET) { - continue; - } - - // calculate the current packet duration using the next block - bloq = getNextBlock(); + if (addPacketSegment(bloq)) { + continue; } + reserve_header += HEADER_SIZE + segment_table_size;// header size + clearSegmentTable(); + webm_block = bloq; + headers_amount++; + } + + /* step 4: create packet headers */ + rewind_source(); + + ByteBuffer headers = byte_buffer(reserve_header); + short[] headers_size = new short[headers_amount]; + int header_index = 0; + + while (webm_segment != null) { + bloq = getNextBlock(); + + if (addPacketSegment(bloq)) { + continue; + } + + // calculate the current packet duration using the next block double elapsed_ns = webm_track.codecDelay; if (bloq == null) { - flag = FLAG_LAST; + packet_flag = FLAG_LAST;// note: if the flag is FLAG_CONTINUED, is changed elapsed_ns += webm_block_last_timecode; if (webm_track.defaultDuration > 0) { @@ -219,84 +248,83 @@ public class OggFromWebMWriter implements Closeable { } // get the sample count in the page - elapsed_ns = (elapsed_ns / 1000000000d) * resolution; - elapsed_ns = Math.ceil(elapsed_ns); - - long offset = output_offset + HEADER_CHECKSUM_OFFSET; - pending_offsets.add(offset); - - checksum = dump_packetHeader(flag, (long) elapsed_ns, null); - pending_checksums.add(checksum); - - data_offsets.add((short) (output_offset - offset)); - - // reserve space in the page - while (page_size > 0) { - int write = Math.min(page_size, buffer.length); - out_write(buffer, write); - page_size -= write; - } + elapsed_ns = elapsed_ns / TIME_SCALE_NS; + elapsed_ns = Math.ceil(elapsed_ns * resolution); + // create header + headers_size[header_index++] = make_packetHeader((long) elapsed_ns, headers, null); webm_block = bloq; } - /* step 2.1: write stream data */ - output.rewind(); - output_offset = 0; - source.rewind(); + /* step 5: calculate checksums */ + rewind_source(); - webm = new WebMReader(source); - webm.parse(); - webm_track = webm.selectTrack(track_index); + int offset = 0; + buffer = new byte[BUFFER_SIZE]; - for (int i = 0; i < pending_offsets.size(); i++) { - checksum = pending_checksums.get(i); - segment_table_size = 0; + for (header_index = 0; header_index < headers_size.length; header_index++) { + int checksum_offset = offset + HEADER_CHECKSUM_OFFSET; + int checksum = headers.getInt(checksum_offset); - out_seek(pending_offsets.get(i) + data_offsets.get(i)); - - while (segment_table_size < SEGMENTS_PER_PACKET) { + while (webm_segment != null) { bloq = getNextBlock(); - if (bloq == null || !addPacketSegment(bloq.dataSize)) { - webm_block = bloq;// use this block later (if not null) + if (!addPacketSegment(bloq)) { + clearSegmentTable(); + webm_block = bloq; break; } - // NOTE: calling bloq.data.close() is unnecessary - while ((read = bloq.data.read(buffer)) != -1) { - out_write(buffer, read); - checksum = calc_crc32(checksum, buffer, read); + // calculate page checksum + while ((read = bloq.data.read(buffer)) > 0) { + checksum = calc_crc32(checksum, buffer, 0, read); } } - pending_checksums.set(i, checksum); + headers.putInt(checksum_offset, checksum); + offset += headers_size[header_index]; } - /* step 2.2: write every checksum */ - output.rewind(); - output_offset = 0; - buffer = new byte[4]; + /* step 6: write extra headers */ + rewind_source(); - ByteBuffer buff = ByteBuffer.wrap(buffer); - buff.order(ByteOrder.LITTLE_ENDIAN); + for (byte[] buff : data_extra) { + output.write(buff); + } - for (int i = 0; i < pending_checksums.size(); i++) { - out_seek(pending_offsets.get(i)); - buff.putInt(0, pending_checksums.get(i)); - out_write(buffer); + /* step 7: write stream packets */ + byte[] headers_buffers = headers.array(); + offset = 0; + buffer = new byte[BUFFER_SIZE]; + + for (header_index = 0; header_index < headers_size.length; header_index++) { + output.write(headers_buffers, offset, headers_size[header_index]); + offset += headers_size[header_index]; + + while (webm_segment != null) { + bloq = getNextBlock(); + + if (addPacketSegment(bloq)) { + while ((read = bloq.data.read(buffer)) > 0) { + output.write(buffer, 0, read); + } + } else { + clearSegmentTable(); + webm_block = bloq; + break; + } + } } } - private int dump_packetHeader(byte flag, long gran_pos, byte[] immediate_page) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(27 + segment_table_size); + private short make_packetHeader(long gran_pos, ByteBuffer buffer, byte[] immediate_page) { + int offset = buffer.position(); + short length = HEADER_SIZE; - buffer.putInt(0x4F676753);// "OggS" binary string + buffer.putInt(0x5367674f);// "OggS" binary string in little-endian buffer.put((byte) 0x00);// version - buffer.put(flag);// type - - buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.put(packet_flag);// type buffer.putLong(gran_pos);// granulate position @@ -305,28 +333,24 @@ public class OggFromWebMWriter implements Closeable { buffer.putInt(0x00);// page checksum - buffer.order(ByteOrder.BIG_ENDIAN); - buffer.put((byte) segment_table_size);// segment table buffer.put(segment_table, 0, segment_table_size);// segment size - segment_table_size = 0;// clear segment table for next header + length += segment_table_size; - byte[] buff = buffer.array(); - int checksum_crc32 = calc_crc32(0x00, buff, buff.length); + clearSegmentTable();// clear segment table for next header + + int checksum_crc32 = calc_crc32(0x00, buffer.array(), offset, length); if (immediate_page != null) { - checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32); - - out_write(buff); - out_write(immediate_page); - return 0; + checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, 0, immediate_page.length); + System.arraycopy(immediate_page, 0, buffer.array(), length, immediate_page.length); + segment_table_next_timestamp -= TIME_SCALE_NS; } - out_write(buff); - return checksum_crc32; + buffer.putInt(offset + HEADER_CHECKSUM_OFFSET, checksum_crc32); + + return length; } @Nullable @@ -334,7 +358,7 @@ public class OggFromWebMWriter implements Closeable { if ("A_OPUS".equals(webm_track.codecId)) { return new byte[]{ 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string - 0x07, 0x00, 0x00, 0x00,// writting application string size + 0x07, 0x00, 0x00, 0x00,// writing application string size 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags) }; @@ -342,15 +366,15 @@ public class OggFromWebMWriter implements Closeable { return new byte[]{ 0x03,// ???????? 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string - 0x07, 0x00, 0x00, 0x00,// writting application string size + 0x07, 0x00, 0x00, 0x00,// writing application string size 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags) /* - // whole file duration (not implemented) - 0x44,// tag string size - 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, - 0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 + // whole file duration (not implemented) + 0x44,// tag string size + 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, + 0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 */ 0x0F,// tag string size 0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D,// "ENCODER=" binary string @@ -363,13 +387,26 @@ public class OggFromWebMWriter implements Closeable { return null; } - // - private Segment webm_segment = null; - private Cluster webm_cluter = null; - private SimpleBlock webm_block = null; - private long webm_block_last_timecode = 0; - private long webm_block_near_duration = 0; + private void rewind_source() throws IOException { + source.rewind(); + webm = new WebMReader(source); + webm.parse(); + webm_track = webm.selectTrack(track_index); + webm_segment = webm.getNextSegment(); + webm_cluster = null; + webm_block = null; + webm_block_last_timecode = 0L; + + segment_table_next_timestamp = TIME_SCALE_NS; + } + + private ByteBuffer byte_buffer(int size) { + return ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + } + + // + @Nullable private SimpleBlock getNextBlock() throws IOException { SimpleBlock res; @@ -386,17 +423,17 @@ public class OggFromWebMWriter implements Closeable { } } - if (webm_cluter == null) { - webm_cluter = webm_segment.getNextCluster(); - if (webm_cluter == null) { + if (webm_cluster == null) { + webm_cluster = webm_segment.getNextCluster(); + if (webm_cluster == null) { webm_segment = null; return getNextBlock(); } } - res = webm_cluter.getNextSimpleBlock(); + res = webm_cluster.getNextSimpleBlock(); if (res == null) { - webm_cluter = null; + webm_cluster = null; return getNextBlock(); } @@ -421,49 +458,64 @@ public class OggFromWebMWriter implements Closeable { } // - // - private int segment_table_size = 0; - private final byte[] segment_table = new byte[255]; + // + private void clearSegmentTable() { + if (packet_flag != FLAG_CONTINUED) { + segment_table_next_timestamp += TIME_SCALE_NS; + packet_flag = FLAG_UNSET; + } + segment_table_size = 0; + } - private boolean addPacketSegment(long size) { - // check if possible add the segment, without overflow the table + private boolean addPacketSegment(SimpleBlock block) { + if (block == null) { + return false; + } + + long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay; + + if (timestamp >= segment_table_next_timestamp) { + return false; + } + + boolean result = addPacketSegment((int) block.dataSize); + + if (!result && segment_table_next_timestamp < timestamp) { + // WARNING: ¡¡¡¡ not implemented (lack of documentation) !!!! + packet_flag = FLAG_CONTINUED; + } + + return result; + } + + private boolean addPacketSegment(int size) { int available = (segment_table.length - segment_table_size) * 255; + boolean extra = size == 255; + + if (extra) { + // add a zero byte entry in the table + // required to indicate the sample size is exactly 255 + available -= 255; + } + + // check if possible add the segment, without overflow the table if (available < size) { return false;// not enough space on the page } - while (size > 0) { + for (; size > 0; size -= 255) { segment_table[segment_table_size++] = (byte) Math.min(size, 255); - size -= 255; + } + + if (extra) { + segment_table[segment_table_size++] = 0x00; } return true; } // - // - private long output_offset = 0; - - private void out_write(byte[] buffer) throws IOException { - output.write(buffer); - output_offset += buffer.length; - } - - private void out_write(byte[] buffer, int size) throws IOException { - output.write(buffer, 0, size); - output_offset += size; - } - - private void out_seek(long offset) throws IOException { - //if (output.canSeek()) { output.seek(offset); } - output.skip(offset - output_offset); - output_offset = offset; - } - // - // - private final int[] crc32_table = new int[256]; - private void populate_crc32_table() { for (int i = 0; i < 0x100; i++) { int crc = i << 24; @@ -476,10 +528,12 @@ public class OggFromWebMWriter implements Closeable { } } - private int calc_crc32(int initial_crc, byte[] buffer, int size) { - for (int i = 0; i < size; i++) { + private int calc_crc32(int initial_crc, byte[] buffer, int offset, int size) { + size += offset; + + for (; offset < size; offset++) { int reg = (initial_crc >>> 24) & 0xff; - initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)]; + initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[offset] & 0xff)]; } return initial_crc; diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java index 65aa30fa3..605c0a88b 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java @@ -11,7 +11,7 @@ import java.nio.ByteBuffer; class OggFromWebmDemuxer extends Postprocessing { OggFromWebmDemuxer() { - super(false, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); + super(true, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); } @Override @@ -24,7 +24,7 @@ class OggFromWebmDemuxer extends Postprocessing { switch (buffer.getInt()) { case 0x1a45dfa3: - return true;// webm + return true;// webm/mkv case 0x4F676753: return false;// ogg } From 3108c903dd043de3a9d7fc4ac17bb8591d05b361 Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 24 Nov 2019 16:24:39 +0100 Subject: [PATCH 173/270] squashed commit --- .../java/org/schabi/newpipe/player/VideoPlayer.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index a92ab410e..0734139e1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -290,8 +290,13 @@ public abstract class VideoPlayer extends BasePlayer String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context) .getString(context.getString(R.string.caption_user_set_key), null); - // english (auto-generated) - boolean searchForAutogenerated = userPreferredLanguage == null || + /* + * only search for autogenerated cc as fallback + * if "(auto-generated)" was not already selected + * we are only looking for "(" instead of "(auto-generated)" to hopefully get all + * internationalized variants such as "(automatisch-erzeugt)" and so on + */ + boolean searchForAutogenerated = userPreferredLanguage != null && !userPreferredLanguage.contains("("); // Add option for turning off caption @@ -326,7 +331,7 @@ public abstract class VideoPlayer extends BasePlayer return true; }); // apply caption language from previous user preference - if(userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage) || + if (userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage) || searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage) || userPreferredLanguage.contains("(") && captionLanguage.startsWith(userPreferredLanguage.substring(0, From c891f2f1eda4ade08bda3e74d9454b98743980b4 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Sat, 28 Sep 2019 18:11:05 -0300 Subject: [PATCH 174/270] long-term downloads resume * recovery infrastructure * bump serialVersionUID of DownloadMission * misc cleanup in DownloadMission.java * remove unused/redundant from strings.xml --- .../newpipe/download/DownloadDialog.java | 34 ++- .../giga/get/DownloadInitializer.java | 15 ++ .../us/shandian/giga/get/DownloadMission.java | 96 ++++++-- .../giga/get/DownloadMissionRecover.java | 222 ++++++++++++++++++ .../shandian/giga/get/DownloadRunnable.java | 27 ++- .../giga/get/DownloadRunnableFallback.java | 11 +- .../giga/get/MissionRecoveryInfo.java | 79 +++++++ .../giga/service/DownloadManager.java | 1 - .../giga/service/DownloadManagerService.java | 36 ++- .../giga/ui/adapter/MissionAdapter.java | 12 +- app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-be/strings.xml | 1 - app/src/main/res/values-cmn/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-da/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-el/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 9 +- app/src/main/res/values-et/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-he/strings.xml | 1 - app/src/main/res/values-hr/strings.xml | 1 - app/src/main/res/values-id/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ko/strings.xml | 1 - app/src/main/res/values-ms/strings.xml | 1 - app/src/main/res/values-nb-rNO/strings.xml | 2 +- app/src/main/res/values-nl-rBE/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pa/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 2 +- 42 files changed, 478 insertions(+), 97 deletions(-) create mode 100644 app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java create mode 100644 app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 90258a6dc..0006b3c12 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -68,6 +68,7 @@ import java.util.Locale; import icepick.Icepick; import icepick.State; import io.reactivex.disposables.CompositeDisposable; +import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.io.StoredDirectoryHelper; import us.shandian.giga.io.StoredFileHelper; import us.shandian.giga.postprocessing.Postprocessing; @@ -762,12 +763,13 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck } Stream selectedStream; + Stream secondaryStream = null; char kind; int threads = threadsSeekBar.getProgress() + 1; String[] urls; + MissionRecoveryInfo[] recoveryInfo; String psName = null; String[] psArgs = null; - String secondaryStreamUrl = null; long nearLength = 0; // more download logic: select muxer, subtitle converter, etc. @@ -786,12 +788,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck kind = 'v'; selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex); - SecondaryStreamHelper secondaryStream = videoStreamsAdapter + SecondaryStreamHelper secondary = videoStreamsAdapter .getAllSecondary() .get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream)); - if (secondaryStream != null) { - secondaryStreamUrl = secondaryStream.getStream().getUrl(); + if (secondary != null) { + secondaryStream = secondary.getStream(); if (selectedStream.getFormat() == MediaFormat.MPEG_4) psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER; @@ -803,8 +805,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck // set nearLength, only, if both sizes are fetched or known. This probably // does not work on slow networks but is later updated in the downloader - if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) { - nearLength = secondaryStream.getSizeInBytes() + videoSize; + if (secondary.getSizeInBytes() > 0 && videoSize > 0) { + nearLength = secondary.getSizeInBytes() + videoSize; } } break; @@ -826,13 +828,25 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck return; } - if (secondaryStreamUrl == null) { - urls = new String[]{selectedStream.getUrl()}; + if (secondaryStream == null) { + urls = new String[]{ + selectedStream.getUrl() + }; + recoveryInfo = new MissionRecoveryInfo[]{ + new MissionRecoveryInfo(selectedStream) + }; } else { - urls = new String[]{selectedStream.getUrl(), secondaryStreamUrl}; + urls = new String[]{ + selectedStream.getUrl(), secondaryStream.getUrl() + }; + recoveryInfo = new MissionRecoveryInfo[]{ + new MissionRecoveryInfo(selectedStream), new MissionRecoveryInfo(secondaryStream) + }; } - DownloadManagerService.startMission(context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength); + DownloadManagerService.startMission( + context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo + ); dismiss(); } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java index 247faeb6d..593feafa7 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java @@ -1,6 +1,7 @@ package us.shandian.giga.get; import androidx.annotation.NonNull; +import android.text.TextUtils; import android.util.Log; import org.schabi.newpipe.streams.io.SharpStream; @@ -151,6 +152,20 @@ public class DownloadInitializer extends Thread { if (!mMission.running || Thread.interrupted()) return; + if (!mMission.unknownLength && mMission.recoveryInfo != null) { + String entityTag = mConn.getHeaderField("ETAG"); + String lastModified = mConn.getHeaderField("Last-Modified"); + MissionRecoveryInfo recovery = mMission.recoveryInfo[mMission.current]; + + if (!TextUtils.isEmpty(entityTag)) { + recovery.validateCondition = entityTag; + } else if (!TextUtils.isEmpty(lastModified)) { + recovery.validateCondition = lastModified;// Note: this is less precise + } else { + recovery.validateCondition = null; + } + } + mMission.running = false; break; } catch (InterruptedIOException | ClosedByInterruptException e) { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index d78f8e32b..77b417118 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -27,7 +27,7 @@ import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; public class DownloadMission extends Mission { - private static final long serialVersionUID = 5L;// last bump: 30 june 2019 + private static final long serialVersionUID = 6L;// last bump: 28 september 2019 static final int BUFFER_SIZE = 64 * 1024; static final int BLOCK_SIZE = 512 * 1024; @@ -51,8 +51,9 @@ public class DownloadMission extends Mission { public static final int ERROR_INSUFFICIENT_STORAGE = 1010; public static final int ERROR_PROGRESS_LOST = 1011; public static final int ERROR_TIMEOUT = 1012; + public static final int ERROR_RESOURCE_GONE = 1013; public static final int ERROR_HTTP_NO_CONTENT = 204; - public static final int ERROR_HTTP_UNSUPPORTED_RANGE = 206; + static final int ERROR_HTTP_FORBIDDEN = 403; /** * The urls of the file to download @@ -125,6 +126,11 @@ public class DownloadMission extends Mission { */ public int threadCount = 3; + /** + * information required to recover a download + */ + public MissionRecoveryInfo[] recoveryInfo; + private transient int finishCount; public transient boolean running; public boolean enqueued; @@ -132,7 +138,6 @@ public class DownloadMission extends Mission { public int errCode = ERROR_NOTHING; public Exception errObject = null; - public transient boolean recovered; public transient Handler mHandler; private transient boolean mWritingToFile; private transient boolean[] blockAcquired; @@ -197,9 +202,9 @@ public class DownloadMission extends Mission { } /** - * Open connection + * Opens a connection * - * @param threadId id of the calling thread, used only for debug + * @param threadId id of the calling thread, used only for debugging * @param rangeStart range start * @param rangeEnd range end * @return a {@link java.net.URLConnection URLConnection} linking to the URL. @@ -251,7 +256,7 @@ public class DownloadMission extends Mission { case 204: case 205: case 207: - throw new HttpError(conn.getResponseCode()); + throw new HttpError(statusCode); case 416: return;// let the download thread handle this error default: @@ -270,10 +275,6 @@ public class DownloadMission extends Mission { synchronized void notifyProgress(long deltaLen) { if (!running) return; - if (recovered) { - recovered = false; - } - if (unknownLength) { length += deltaLen;// Update length before proceeding } @@ -344,7 +345,6 @@ public class DownloadMission extends Mission { if (running) { running = false; - recovered = true; if (threads != null) selfPause(); } } @@ -409,12 +409,13 @@ public class DownloadMission extends Mission { * Start downloading with multiple threads. */ public void start() { - if (running || isFinished()) return; + if (running || isFinished() || urls.length < 1) return; // ensure that the previous state is completely paused. - joinForThread(init); + int maxWait = 10000;// 10 seconds + joinForThread(init, maxWait); if (threads != null) { - for (Thread thread : threads) joinForThread(thread); + for (Thread thread : threads) joinForThread(thread, maxWait); threads = null; } @@ -431,6 +432,11 @@ public class DownloadMission extends Mission { return; } + if (urls[current] == null) { + doRecover(null); + return; + } + if (blocks == null) { initializer(); return; @@ -478,7 +484,6 @@ public class DownloadMission extends Mission { } running = false; - recovered = true; if (init != null && init.isAlive()) { // NOTE: if start() method is running ¡will no have effect! @@ -563,7 +568,7 @@ public class DownloadMission extends Mission { * Write this {@link DownloadMission} to the meta file asynchronously * if no thread is already running. */ - private void writeThisToFile() { + void writeThisToFile() { synchronized (LOCK) { if (deleted) return; Utility.writeToFile(metadata, DownloadMission.this); @@ -667,6 +672,7 @@ public class DownloadMission extends Mission { * @return {@code true} is this mission its "healthy", otherwise, {@code false} */ public boolean isCorrupt() { + if (urls.length < 1) return false; return (isPsFailed() || errCode == ERROR_POSTPROCESSING_HOLD) || isFinished(); } @@ -710,6 +716,48 @@ public class DownloadMission extends Mission { return true; } + /** + * Attempts to recover the download + * + * @param fromError exception which require update the url from the source + */ + void doRecover(Exception fromError) { + Log.i(TAG, "Attempting to recover the mission: " + storage.getName()); + + if (recoveryInfo == null) { + if (fromError == null) + notifyError(ERROR_RESOURCE_GONE, null); + else + notifyError(fromError); + + urls = new String[0];// mark this mission as dead + return; + } + + if (threads != null) { + for (Thread thread : threads) { + if (thread == Thread.currentThread()) continue; + thread.interrupt(); + joinForThread(thread, 0); + } + } + + // set the current download url to null in case if the recovery + // process is canceled. Next time start() method is called the + // recovery will be executed, saving time + urls[current] = null; + + if (recoveryInfo[current].attempts >= maxRetry) { + recoveryInfo[current].attempts = 0; + notifyError(fromError); + return; + } + + threads = new Thread[]{ + runAsync(DownloadMissionRecover.mID, new DownloadMissionRecover(this, fromError)) + }; + } + private boolean deleteThisFromFile() { synchronized (LOCK) { return metadata.delete(); @@ -749,7 +797,13 @@ public class DownloadMission extends Mission { return who; } - private void joinForThread(Thread thread) { + /** + * Waits at most {@code millis} milliseconds for the thread to die + * + * @param thread the desired thread + * @param millis the time to wait in milliseconds + */ + private void joinForThread(Thread thread, int millis) { if (thread == null || !thread.isAlive()) return; if (thread == Thread.currentThread()) return; @@ -764,7 +818,7 @@ public class DownloadMission extends Mission { // start() method called quickly after pause() try { - thread.join(10000); + thread.join(millis); } catch (InterruptedException e) { Log.d(TAG, "timeout on join : " + thread.getName()); throw new RuntimeException("A thread is still running:\n" + thread.getName()); @@ -785,9 +839,9 @@ public class DownloadMission extends Mission { } } - static class Block { - int position; - int done; + public static class Block { + public int position; + public int done; } private static class Lock implements Serializable { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java new file mode 100644 index 000000000..9abd93717 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -0,0 +1,222 @@ +package us.shandian.giga.get; + +import android.util.Log; + +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.StreamExtractor; +import org.schabi.newpipe.extractor.stream.SubtitlesStream; +import org.schabi.newpipe.extractor.stream.VideoStream; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.nio.channels.ClosedByInterruptException; +import java.util.List; + +import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; + +public class DownloadMissionRecover extends Thread { + private static final String TAG = "DownloadMissionRecover"; + static final int mID = -3; + + private final DownloadMission mMission; + private final MissionRecoveryInfo mRecovery; + private final Exception mFromError; + private HttpURLConnection mConn; + + DownloadMissionRecover(DownloadMission mission, Exception originError) { + mMission = mission; + mFromError = originError; + mRecovery = mission.recoveryInfo[mission.current]; + } + + @Override + public void run() { + if (mMission.source == null) { + mMission.notifyError(mFromError); + return; + } + + try { + /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { + resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); + return; + }*/ + + StreamingService svr = NewPipe.getServiceByUrl(mMission.source); + + if (svr == null) { + throw new RuntimeException("Unknown source service"); + } + + StreamExtractor extractor = svr.getStreamExtractor(mMission.source); + extractor.fetchPage(); + + if (!mMission.running || super.isInterrupted()) return; + + String url = null; + + switch (mMission.kind) { + case 'a': + for (AudioStream audio : extractor.getAudioStreams()) { + if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { + url = audio.getUrl(); + break; + } + } + break; + case 'v': + List videoStreams; + if (mRecovery.desired2) + videoStreams = extractor.getVideoOnlyStreams(); + else + videoStreams = extractor.getVideoStreams(); + for (VideoStream video : videoStreams) { + if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { + url = video.getUrl(); + break; + } + } + break; + case 's': + for (SubtitlesStream subtitles : extractor.getSubtitles(mRecovery.format)) { + String tag = subtitles.getLanguageTag(); + if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { + url = subtitles.getURL(); + break; + } + } + break; + default: + throw new RuntimeException("Unknown stream type"); + } + + resolve(url); + } catch (Exception e) { + if (!mMission.running || e instanceof ClosedByInterruptException) return; + mRecovery.attempts++; + mMission.notifyError(e); + } + } + + private void resolve(String url) throws IOException, DownloadMission.HttpError { + if (mRecovery.validateCondition == null) { + Log.w(TAG, "validation condition not defined, the resource can be stale"); + } + + if (mMission.unknownLength || mRecovery.validateCondition == null) { + recover(url, false); + return; + } + + /////////////////////////////////////////////////////////////////////// + ////// Validate the http resource doing a range request + ///////////////////// + try { + mConn = mMission.openConnection(url, mID, mMission.length - 10, mMission.length); + mConn.setRequestProperty("If-Range", mRecovery.validateCondition); + mMission.establishConnection(mID, mConn); + + int code = mConn.getResponseCode(); + + switch (code) { + case 200: + case 413: + // stale + recover(url, true); + return; + case 206: + // in case of validation using the Last-Modified date, check the resource length + long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range")); + boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length; + + recover(url, lengthMismatch); + return; + } + + throw new DownloadMission.HttpError(code); + } catch (Exception e) { + if (!mMission.running || e instanceof ClosedByInterruptException) return; + throw e; + } finally { + this.interrupt(); + } + } + + private void recover(String url, boolean stale) { + Log.i(TAG, + String.format("download recovered name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) + ); + + if (url == null) { + mMission.notifyError(ERROR_RESOURCE_GONE, null); + return; + } + + mMission.urls[mMission.current] = url; + mRecovery.attempts = 0; + + if (stale) { + mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); + } + + mMission.writeThisToFile(); + + if (!mMission.running || super.isInterrupted()) return; + + mMission.running = false; + mMission.start(); + } + + private long[] parseContentRange(String value) { + long[] range = new long[3]; + + if (value == null) { + // this never should happen + return range; + } + + try { + value = value.trim(); + + if (!value.startsWith("bytes")) { + return range;// unknown range type + } + + int space = value.lastIndexOf(' ') + 1; + int dash = value.indexOf('-', space) + 1; + int bar = value.indexOf('/', dash); + + // start + range[0] = Long.parseLong(value.substring(space, dash - 1)); + + // end + range[1] = Long.parseLong(value.substring(dash, bar)); + + // resource length + value = value.substring(bar + 1); + if (value.equals("*")) { + range[2] = -1;// unknown length received from the server but should be valid + } else { + range[2] = Long.parseLong(value); + } + } catch (Exception e) { + // nothing to do + } + + return range; + } + + @Override + public void interrupt() { + super.interrupt(); + if (mConn != null) { + try { + mConn.disconnect(); + } catch (Exception e) { + // nothing to do + } + } + } +} diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index f5b9b06d4..1d2a4eee7 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -10,8 +10,10 @@ import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; import us.shandian.giga.get.DownloadMission.Block; +import us.shandian.giga.get.DownloadMission.HttpError; import static org.schabi.newpipe.BuildConfig.DEBUG; +import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN; /** @@ -19,7 +21,7 @@ import static org.schabi.newpipe.BuildConfig.DEBUG; * an error occurs or the process is stopped. */ public class DownloadRunnable extends Thread { - private static final String TAG = DownloadRunnable.class.getSimpleName(); + private static final String TAG = "DownloadRunnable"; private final DownloadMission mMission; private final int mId; @@ -41,13 +43,7 @@ public class DownloadRunnable extends Thread { public void run() { boolean retry = false; Block block = null; - int retryCount = 0; - - if (DEBUG) { - Log.d(TAG, mId + ":recovered: " + mMission.recovered); - } - SharpStream f; try { @@ -133,6 +129,17 @@ public class DownloadRunnable extends Thread { } catch (Exception e) { if (!mMission.running || e instanceof ClosedByInterruptException) break; + if (e instanceof HttpError && ((HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { + // for youtube streams. The url has expired, recover + f.close(); + + if (mId == 1) { + // only the first thread will execute the recovery procedure + mMission.doRecover(e); + } + return; + } + if (retryCount++ >= mMission.maxRetry) { mMission.notifyError(e); break; @@ -144,11 +151,7 @@ public class DownloadRunnable extends Thread { } } - try { - f.close(); - } catch (Exception err) { - // ¿ejected media storage? ¿file deleted? ¿storage ran out of space? - } + f.close(); if (DEBUG) { Log.d(TAG, "thread " + mId + " exited from main download loop"); diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index 7fb1f0c77..b5937c577 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -10,9 +10,11 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; +import us.shandian.giga.get.DownloadMission.HttpError; import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; +import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN; /** * Single-threaded fallback mode @@ -85,7 +87,7 @@ public class DownloadRunnableFallback extends Thread { mIs = mConn.getInputStream(); - byte[] buf = new byte[64 * 1024]; + byte[] buf = new byte[DownloadMission.BUFFER_SIZE]; int len = 0; while (mMission.running && (len = mIs.read(buf, 0, buf.length)) != -1) { @@ -103,6 +105,13 @@ public class DownloadRunnableFallback extends Thread { if (!mMission.running || e instanceof ClosedByInterruptException) return; + if (e instanceof HttpError && ((HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { + // for youtube streams. The url has expired, recover + mMission.doRecover(e); + dispose(); + return; + } + if (mRetryCount++ >= mMission.maxRetry) { mMission.notifyError(e); return; diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java new file mode 100644 index 000000000..553ba6d89 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java @@ -0,0 +1,79 @@ +package us.shandian.giga.get; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.Stream; +import org.schabi.newpipe.extractor.stream.SubtitlesStream; +import org.schabi.newpipe.extractor.stream.VideoStream; + +import java.io.Serializable; + +public class MissionRecoveryInfo implements Serializable, Parcelable { + private static final long serialVersionUID = 0L; + //public static final String DIRECT_SOURCE = "direct-source://"; + + public MediaFormat format; + String desired; + boolean desired2; + int desiredBitrate; + + transient int attempts = 0; + + String validateCondition = null; + + public MissionRecoveryInfo(@NonNull Stream stream) { + if (stream instanceof AudioStream) { + desiredBitrate = ((AudioStream) stream).average_bitrate; + desired2 = false; + } else if (stream instanceof VideoStream) { + desired = ((VideoStream) stream).getResolution(); + desired2 = ((VideoStream) stream).isVideoOnly(); + } else if (stream instanceof SubtitlesStream) { + desired = ((SubtitlesStream) stream).getLanguageTag(); + desired2 = ((SubtitlesStream) stream).isAutoGenerated(); + } else { + throw new RuntimeException("Unknown stream kind"); + } + + format = stream.getFormat(); + if (format == null) throw new NullPointerException("Stream format cannot be null"); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(this.format.ordinal()); + parcel.writeString(this.desired); + parcel.writeInt(this.desired2 ? 0x01 : 0x00); + parcel.writeInt(this.desiredBitrate); + parcel.writeString(this.validateCondition); + } + + private MissionRecoveryInfo(Parcel parcel) { + this.format = MediaFormat.values()[parcel.readInt()]; + this.desired = parcel.readString(); + this.desired2 = parcel.readInt() != 0x00; + this.desiredBitrate = parcel.readInt(); + this.validateCondition = parcel.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public MissionRecoveryInfo createFromParcel(Parcel source) { + return new MissionRecoveryInfo(source); + } + + @Override + public MissionRecoveryInfo[] newArray(int size) { + return new MissionRecoveryInfo[size]; + } + }; +} diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index 3d34411b9..a859a87ca 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -177,7 +177,6 @@ public class DownloadManager { mis.psAlgorithm.setTemporalDir(pickAvailableTemporalDir(ctx)); } - mis.recovered = exists; mis.metadata = sub; mis.maxRetry = mPrefMaxRetry; mis.mHandler = mHandler; diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index 461787b62..ea9029c0b 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.Handler.Callback; import android.os.IBinder; import android.os.Message; +import android.os.Parcelable; import android.preference.PreferenceManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -40,8 +41,11 @@ import org.schabi.newpipe.player.helper.LockManager; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import us.shandian.giga.get.DownloadMission; +import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.io.StoredDirectoryHelper; import us.shandian.giga.io.StoredFileHelper; import us.shandian.giga.postprocessing.Postprocessing; @@ -73,6 +77,7 @@ public class DownloadManagerService extends Service { private static final String EXTRA_PATH = "DownloadManagerService.extra.storagePath"; private static final String EXTRA_PARENT_PATH = "DownloadManagerService.extra.storageParentPath"; private static final String EXTRA_STORAGE_TAG = "DownloadManagerService.extra.storageTag"; + private static final String EXTRA_RECOVERY_INFO = "DownloadManagerService.extra.recoveryInfo"; private static final String ACTION_RESET_DOWNLOAD_FINISHED = APPLICATION_ID + ".reset_download_finished"; private static final String ACTION_OPEN_DOWNLOADS_FINISHED = APPLICATION_ID + ".open_downloads_finished"; @@ -364,18 +369,20 @@ public class DownloadManagerService extends Service { /** * Start a new download mission * - * @param context the activity context - * @param urls the list of urls to download - * @param storage where the file is saved - * @param kind type of file (a: audio v: video s: subtitle ?: file-extension defined) - * @param threads the number of threads maximal used to download chunks of the file. - * @param psName the name of the required post-processing algorithm, or {@code null} to ignore. - * @param source source url of the resource - * @param psArgs the arguments for the post-processing algorithm. - * @param nearLength the approximated final length of the file + * @param context the activity context + * @param urls array of urls to download + * @param storage where the file is saved + * @param kind type of file (a: audio v: video s: subtitle ?: file-extension defined) + * @param threads the number of threads maximal used to download chunks of the file. + * @param psName the name of the required post-processing algorithm, or {@code null} to ignore. + * @param source source url of the resource + * @param psArgs the arguments for the post-processing algorithm. + * @param nearLength the approximated final length of the file + * @param recoveryInfo array of MissionRecoveryInfo, in case is required recover the download */ - public static void startMission(Context context, String[] urls, StoredFileHelper storage, char kind, - int threads, String source, String psName, String[] psArgs, long nearLength) { + public static void startMission(Context context, String[] urls, StoredFileHelper storage, + char kind, int threads, String source, String psName, + String[] psArgs, long nearLength, MissionRecoveryInfo[] recoveryInfo) { Intent intent = new Intent(context, DownloadManagerService.class); intent.setAction(Intent.ACTION_RUN); intent.putExtra(EXTRA_URLS, urls); @@ -385,6 +392,7 @@ public class DownloadManagerService extends Service { intent.putExtra(EXTRA_POSTPROCESSING_NAME, psName); intent.putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs); intent.putExtra(EXTRA_NEAR_LENGTH, nearLength); + intent.putExtra(EXTRA_RECOVERY_INFO, recoveryInfo); intent.putExtra(EXTRA_PARENT_PATH, storage.getParentUri()); intent.putExtra(EXTRA_PATH, storage.getUri()); @@ -404,6 +412,7 @@ public class DownloadManagerService extends Service { String source = intent.getStringExtra(EXTRA_SOURCE); long nearLength = intent.getLongExtra(EXTRA_NEAR_LENGTH, 0); String tag = intent.getStringExtra(EXTRA_STORAGE_TAG); + Parcelable[] parcelRecovery = intent.getParcelableArrayExtra(EXTRA_RECOVERY_INFO); StoredFileHelper storage; try { @@ -418,10 +427,15 @@ public class DownloadManagerService extends Service { else ps = Postprocessing.getAlgorithm(psName, psArgs); + MissionRecoveryInfo[] recovery = new MissionRecoveryInfo[parcelRecovery.length]; + for (int i = 0; i < parcelRecovery.length; i++) + recovery[i] = (MissionRecoveryInfo) parcelRecovery[i]; + final DownloadMission mission = new DownloadMission(urls, storage, kind, ps); mission.threadCount = threads; mission.source = source; mission.nearLength = nearLength; + mission.recoveryInfo = recovery; if (ps != null) ps.setTemporalDir(DownloadManager.pickAvailableTemporalDir(this)); diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 6d1169031..6c6198750 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -62,7 +62,6 @@ import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; import static us.shandian.giga.get.DownloadMission.ERROR_CONNECT_HOST; import static us.shandian.giga.get.DownloadMission.ERROR_FILE_CREATION; import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_NO_CONTENT; -import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_UNSUPPORTED_RANGE; import static us.shandian.giga.get.DownloadMission.ERROR_INSUFFICIENT_STORAGE; import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; import static us.shandian.giga.get.DownloadMission.ERROR_PATH_CREATION; @@ -71,6 +70,7 @@ import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING; import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_HOLD; import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_STOPPED; import static us.shandian.giga.get.DownloadMission.ERROR_PROGRESS_LOST; +import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; import static us.shandian.giga.get.DownloadMission.ERROR_SSL_EXCEPTION; import static us.shandian.giga.get.DownloadMission.ERROR_TIMEOUT; import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION; @@ -430,7 +430,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb switch (mission.errCode) { case 416: - msg = R.string.error_http_requested_range_not_satisfiable; + msg = R.string.error_http_unsupported_range; break; case 404: msg = R.string.error_http_not_found; @@ -443,9 +443,6 @@ public class MissionAdapter extends Adapter implements Handler.Callb case ERROR_HTTP_NO_CONTENT: msg = R.string.error_http_no_content; break; - case ERROR_HTTP_UNSUPPORTED_RANGE: - msg = R.string.error_http_unsupported_range; - break; case ERROR_PATH_CREATION: msg = R.string.error_path_creation; break; @@ -480,6 +477,9 @@ public class MissionAdapter extends Adapter implements Handler.Callb case ERROR_TIMEOUT: msg = R.string.error_timeout; break; + case ERROR_RESOURCE_GONE: + msg = R.string.error_download_resource_gone; + break; default: if (mission.errCode >= 100 && mission.errCode < 600) { msgEx = "HTTP " + mission.errCode; @@ -859,7 +859,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb delete.setVisible(true); - boolean flag = !mission.isPsFailed(); + boolean flag = !mission.isPsFailed() && mission.urls.length > 0; start.setVisible(flag); queue.setVisible(flag); } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 7156d08ba..43b45d15e 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -468,7 +468,6 @@ لا يمكن الاتصال بالخادم الخادم لايقوم بإرسال البيانات الخادم لا يقبل التنزيل المتعدد، إعادة المحاولة مع @string/msg_threads = 1 - عدم استيفاء النطاق المطلوب غير موجود فشلت المعالجة الاولية حذف التنزيلات المنتهية diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 93307cbcf..3c79a96d3 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -455,7 +455,6 @@ Немагчыма злучыцца з серверам Не атрымалася атрымаць дадзеныя з сервера Сервер не падтрымлівае шматструменную загрузку, паспрабуйце з @string/msg_threads = 1 - Запытаны дыяпазон недапушчальны Не знойдзена Пасляапрацоўка не ўдалася Ачысціць завершаныя diff --git a/app/src/main/res/values-cmn/strings.xml b/app/src/main/res/values-cmn/strings.xml index 49801a190..bcb145c16 100644 --- a/app/src/main/res/values-cmn/strings.xml +++ b/app/src/main/res/values-cmn/strings.xml @@ -460,7 +460,6 @@ NewPipe 更新可用! 无法创建目标文件夹 服务器不接受多线程下载, 请使用 @string/msg_threads = 1重试 - 请求范围无法满足 继续进行%s个待下载转移 切换至移动数据时有用,尽管一些下载无法被暂停 显示评论 diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index d539923fe..b741e0d16 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -463,7 +463,6 @@ otevření ve vyskakovacím okně Nelze se připojit k serveru Server neposílá data Server neakceptuje vícevláknové stahování, opakujte akci s @string/msg_threads = 1 - Požadovaný rozsah nelze splnit Nenalezeno Post-processing selhal Vyčistit dokončená stahování diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 42ffd474b..199c2f85d 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -380,7 +380,6 @@ Kan ikke forbinde til serveren Serveren sender ikke data Serveren accepterer ikke multitrådede downloads; prøv igen med @string/msg_threads = 1 - Det anmodede interval er ikke gyldigt Ikke fundet Efterbehandling fejlede Stop diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2d6b5b6d2..3279e919c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -454,7 +454,6 @@ Kann nicht mit dem Server verbinden Der Server sendet keine Daten Der Server erlaubt kein mehrfädiges Herunterladen – wiederhole mit @string/msg_threads = 1 - Gewünschter Bereich ist nicht verfügbar Nicht gefunden Nachbearbeitung fehlgeschlagen Um fertige Downloads bereinigen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 4f3499cfd..372cbb1a2 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -456,7 +456,6 @@ Αδυναμία σύνδεσης με τον εξυπηρετητή Ο εξυπηρετητής δεν μπορεί να στείλει τα δεδομένα Ο εξυπηρετητής δέν υποστηρίζει πολυνηματικές λήψεις, ξαναπροσπαθήστε με @string/msg_threads = 1 - Το ζητούμενο εύρος δεν μπορεί να εξυπηρετηθεί Δεν βρέθηκε Μετεπεξεργασία απέτυχε Εκκαθάριση ολοκληρωμένων λήψεων diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3aa0bac66..2f69e62cb 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -351,8 +351,8 @@ \n3. Inicie sesión cuando se le pida \n4. Copie la URL del perfil a la que fue redireccionado. suID, soundcloud.com/suID - Observe que esta operación puede causar un uso intensivo de la red. -\n + Observe que esta operación puede causar un uso intensivo de la red. +\n \n¿Quiere continuar\? Cargar miniaturas Desactívela para evitar la carga de miniaturas y ahorrar datos y memoria. Se vaciará la antememoria de imágenes en la memoria volátil y en el disco. @@ -444,8 +444,8 @@ Fallo la conexión segura No se pudo encontrar el servidor No se puede conectar con el servidor - El servidor no está enviando datos - El servidor no acepta descargas multiproceso; intente de nuevo con @string/msg_threads = 1 + El servidor no devolvio datos + El servidor no acepta descargas multi-hilos, intente de nuevo con @string/msg_threads = 1 No se puede satisfacer el intervalo seleccionado No encontrado Falló el posprocesamiento @@ -453,6 +453,7 @@ No hay suficiente espacio disponible en el dispositivo Se perdió el progreso porque el archivo fue eliminado Tiempo de espera excedido + El recurso solicitado ya no esta disponible Preguntar dónde descargar Se preguntará dónde guardar cada descarga Se le preguntará dónde guardar cada descarga. diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index baad94b5d..4dfcc3d0e 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -457,7 +457,6 @@ Serveriga ei saadud ühendust Server ei saada andmeid Server ei toeta mitmelõimelisi allalaadimisi. Proovi uuesti kasutades @string/msg_threads = 1 - Taotletud vahemik ei ole rahuldatav Ei leitud Järeltöötlemine nurjus Eemalda lõpetatud allalaadimised diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7da39393e..7b636d383 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -456,7 +456,6 @@ Ezin da zerbitzariarekin konektatu Zerbitzariak ez du daturik bidaltzen Zerbitzariak ez ditu hainbat hariko deskargak onartzen, saiatu @string/msg_threads = 1 erabilita - Eskatutako barrutia ezin da bete Ez aurkitua Post-prozesuak huts egin du Garbitu amaitutako deskargak diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 147502088..b4388e39f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -467,7 +467,6 @@ Utilisation des onglets par défaut, erreur lors de la lecture des onglets enregistrés Le serveur n’accepte pas les téléchargements multi-fils, veuillez réessayer avec @string/msg_threads = 1 Continuer vos %s transferts en attente depuis Téléchargement - Le domaine désiré n\'est pas disponible Afficher les commentaires Désactiver pour ne pas afficher les commentaires Lecture automatique diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index b5a0778d4..5e340d8b3 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -461,7 +461,6 @@ לא ניתן להתחבר לשרת השרת לא שולח נתונים "השרת לא מקבל הורדות רב ערוציות, מוטב לנסות שוב עם ‎@string/msg_threads = 1 " - הטווח המבוקש לא מתאים לא נמצא העיבוד המאוחר נכשל פינוי ההורדות שהסתיימו diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index e85d5810e..aa4ff9113 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -454,7 +454,6 @@ Nije moguće povezati se s serverom Server ne šalje podatke Poslužitelj ne prihvaća preuzimanja s više niti, pokušaj ponovo s @string/msg_threads = 1 - Traženi raspon nije zadovoljavajući Nije pronađeno Naknadna obrada nije uspjela Obriši završena preuzimanja diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index db738d749..d52f5fafa 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -450,7 +450,6 @@ Tidak dapat terhubung ke server Server tidak mengirim data Server tidak menerima unduhan multi-utas, coba lagi dengan @string/msg_threads = 1 - Rentang yang diminta tidak memuaskan Tidak ditemukan Pengolahan-pasca gagal Hapus unduhan yang sudah selesai diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 35fdebeda..c92292f99 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -454,7 +454,6 @@ Impossibile connettersi al server Il server non invia dati Il server non accetta download multipli, riprovare con @string/msg_threads = 1 - Intervallo richiesto non soddisfatto Non trovato Post-processing fallito Pulisci i download completati diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b67da798c..58ca2ebff 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -440,7 +440,6 @@ サーバに接続できません サーバがデータを送信していません サーバが同時接続ダウンロードを受け付けません。再試行してください @string/msg_threads = 1 - 必要な範囲が満たされていません 見つかりません 保存処理に失敗しました 完了済みを一覧から削除します diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 333891910..fdc76b04e 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -451,7 +451,6 @@ 서버에 접속할 수 없습니다 서버가 데이터를 전송하지 않고 있습니다 서버가 다중 스레드 다운로드를 받아들이지 않습니다, @string/msg_threads = 1 를 사용해 다시 시도해보세요 - 요청된 HTTP 범위가 충분하지 않습니다 HTTP 찾을 수 없습니다 후처리 작업이 실패하였습니다 완료된 다운로드 비우기 diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index c7fa5de92..daa120ea2 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -450,7 +450,6 @@ Tidak dapat menyambung ke server Server tidak menghantar data Server tidak menerima muat turun berbilang thread, cuba lagi dengan @string/msg_threads = 1 - Julat yang diminta tidak memuaskan Tidak ditemui Pemprosesan-pasca gagal Hapuskan senarai muat turun yang selesai diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index d26886844..6262480b0 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -496,7 +496,7 @@ Sett nedlastinger på pause Spør om hvor ting skal lastes ned til Du vil bli spurt om hvor hver nedlasting skal plasseres - Du vil bli spurt om hvor hver nedlasting skal plasseres. + Du vil bli spurt om hvor hver nedlasting skal plasseres. \nSkru på SAF hvis du vil laste ned til eksternt SD-kort Bruk SAF Lagringstilgangsrammeverk (SAF) tillater nedlastinger til eksternt SD-kort. diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml index 94feb4915..f64ff6bf9 100644 --- a/app/src/main/res/values-nl-rBE/strings.xml +++ b/app/src/main/res/values-nl-rBE/strings.xml @@ -454,7 +454,6 @@ Kan geen verbinding maken met de server De server verzendt geen gegevens De server aanvaardt geen meerdradige downloads, probeert het opnieuw met @string/msg_threads = 1 - Gevraagd bereik niet beschikbaar Niet gevonden Nabewerking mislukt Voltooide downloads wissen diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index f7acba6ae..6aecc2cd1 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -454,7 +454,6 @@ Kan niet met de server verbinden De server verzendt geen gegevens De server accepteert geen multi-threaded downloads, probeer het opnieuw met @string/msg_threads = 1 - Gevraagde bereik niet beschikbaar Niet gevonden Nabewerking mislukt Voltooide downloads wissen diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index c31eb805d..b57564eba 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -450,7 +450,6 @@ ਸਰਵਰ ਨਾਲ ਜੁੜ ਨਹੀਂ ਸਕਦਾ ਸਰਵਰ ਨੇ ਡਾਟਾ ਨਹੀਂ ਭੇਜਿਆ ਸਰਵਰ ਮਲਟੀ-Threaded ਡਾਊਨਲੋਡਸ ਨੂੰ ਸਵੀਕਾਰ ਨਹੀਂ ਕਰਦਾ, ਇਸ ਨਾਲ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ @string/msg_threads = 1 - ਬੇਨਤੀ ਕੀਤੀ ਸੀਮਾ ਤਸੱਲੀਬਖਸ਼ ਨਹੀਂ ਹੈ ਨਹੀਂ ਲਭਿਆ Post-processing ਫੇਲ੍ਹ ਮੁਕੰਮਲ ਹੋਈਆਂ ਡਾਊਨਲੋਡ ਸਾਫ਼ ਕਰੋ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d3c84aa22..ca1e52ff2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -456,7 +456,6 @@ Nie można połączyć się z serwerem Serwer nie wysyła danych Serwer nie akceptuje pobierania wielowątkowego, spróbuj ponownie za pomocą @string/msg_threads = 1 - Niewłaściwy zakres Nie znaleziono Przetwarzanie końcowe nie powiodło się Wyczyść ukończone pobieranie diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index aaac4fd4c..0bdf4d006 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -463,7 +463,6 @@ abrir em modo popup Não foi possível conectar ao servidor O servidor não envia dados O servidor não aceita downloads em multi-thread, tente com @string/msg_threads = 1 - Intervalo solicitado não aceito Não encontrado Falha no pós processamento Limpar downloads finalizados diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5d7cd8146..6d55023d1 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -452,7 +452,6 @@ Não é possível ligar ao servidor O servidor não envia dados O servidor não aceita transferências de vários processos, tente novamente com @string/msg_threads = 1 - Intervalo solicitado não satisfatório Não encontrado Pós-processamento falhado Limpar transferências concluídas diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6f079a221..51771e1b1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -454,7 +454,6 @@ Доступ запрещён системой Сервер не найден Сервер не принимает многопоточные загрузки, повторная попытка с @string/msg_threads = 1 - Запрашиваемый диапазон недопустим Не найдено Очистить завершённые Остановить diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 09502f60a..36c0afd84 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -462,7 +462,6 @@ Nepodarilo sa pripojiť k serveru Server neposiela údaje Server neakceptuje preberanie viacerých vlákien, zopakujte s @string/msg_threads = 1 - Požadovaný rozsah nie je uspokojivý Nenájdené Post-spracovanie zlyhalo Vyčistiť dokončené sťahovania diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c17b58f50..6c9c66f69 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -449,7 +449,6 @@ Sunucuya bağlanılamıyor Sunucu veri göndermiyor Sunucu, çok iş parçacıklı indirmeleri kabul etmez, @string/msg_threads = 1 ile yeniden deneyin - İstenen aralık karşılanamıyor Bulunamadı İşlem sonrası başarısız Tamamlanan indirmeleri temizle diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 375557b04..fcce99e89 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -471,7 +471,6 @@ Помилка зчитування збережених вкладок. Використовую типові вкладки. Вкладки, що відображаються на головній сторінці Показувати сповіщення з пропозицією оновити застосунок за наявності нової версії - Запитуваний діапазон неприпустимий Продовжити ваші %s відкладених переміщень із Завантажень Корисно під час переходу на мобільні дані, хоча деякі завантаження не можуть бути призупинені Показувати коментарі diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 74b8b395c..f8860acfd 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -449,7 +449,6 @@ Không thế kết nối với máy chủ Máy chủ không gửi dữ liệu về Máy chủ không chấp nhận tải đa luồng, thử lại với số luồng = 1 - (HTTP) Không thể đáp ứng khoảng dữ liệu đã yêu cầu Không tìm thấy Xử lý thất bại Dọn các tải về đã hoàn thành diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index fe4c1b00a..310bae3a3 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -447,7 +447,6 @@ 無法連線到伺服器 伺服器沒有傳送資料 伺服器不接受多執行緒下載,請以 @string/msg_threads = 1 重試 - 請求範圍無法滿足 找不到 後處理失敗 清除已結束的下載 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a34b00ea9..2917fb9fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -551,13 +551,13 @@ Can not connect to the server The server does not send data The server does not accept multi-threaded downloads, retry with @string/msg_threads = 1 - Requested range not satisfiable Not found Post-processing failed NewPipe was closed while working on the file No space left on device Progress lost, because the file was deleted Connection timeout + The solicited resource is not available anymore Clear finished downloads Are you sure? Continue your %s pending transfers from Downloads From 429ee7eb9381034a6cbbd55427b312c3363ba3c0 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Sun, 29 Sep 2019 01:44:13 -0300 Subject: [PATCH 175/270] Mp4FromDashWriter fixes * correct calculation of "co64" box and usage of 64bits offsets * generate one chunk for audio streams like ffmpeg does, attempt to fix cut-off audio * misc. cleanup --- .../newpipe/streams/Mp4FromDashWriter.java | 79 ++++++++++++------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 03aab447c..420f77955 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -6,6 +6,7 @@ import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashChunk; import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashSample; import org.schabi.newpipe.streams.Mp4DashReader.Mp4Track; import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry; +import org.schabi.newpipe.streams.Mp4DashReader.TrackKind; import org.schabi.newpipe.streams.io.SharpStream; import java.io.IOException; @@ -22,6 +23,7 @@ public class Mp4FromDashWriter { private final static byte SAMPLES_PER_CHUNK = 6;// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6 private final static long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;// near 3.999 GiB private final static int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024); // 2.2 MiB enough for: 1080p 60fps 00h35m00s + private final static short SINGLE_CHUNK_SAMPLE_BUFFER = 256; private final long time; @@ -145,7 +147,7 @@ public class Mp4FromDashWriter { // not allowed for very short tracks (less than 0.5 seconds) // outStream = output; - int read = 8;// mdat box header size + long read = 8;// mdat box header size long totalSampleSize = 0; int[] sampleExtra = new int[readers.length]; int[] defaultMediaTime = new int[readers.length]; @@ -157,6 +159,8 @@ public class Mp4FromDashWriter { tablesInfo[i] = new TablesInfo(); } + boolean singleChunk = tracks.length == 1 && tracks[0].kind == TrackKind.Audio; + // for (int i = 0; i < readers.length; i++) { int samplesSize = 0; @@ -210,14 +214,21 @@ public class Mp4FromDashWriter { tablesInfo[i].stco = (tmp / SAMPLES_PER_CHUNK) + 1;// +1 for samples in first chunk tmp = tmp % SAMPLES_PER_CHUNK; - if (tmp == 0) { + if (singleChunk) { + // avoid split audio streams in chunks + tablesInfo[i].stsc = 1; + tablesInfo[i].stsc_bEntries = new int[]{ + 1, tablesInfo[i].stsz, 1 + }; + tablesInfo[i].stco = 1; + } else if (tmp == 0) { tablesInfo[i].stsc = 2;// first chunk (init) and succesive chunks tablesInfo[i].stsc_bEntries = new int[]{ 1, SAMPLES_PER_CHUNK_INIT, 1, 2, SAMPLES_PER_CHUNK, 1 }; } else { - tablesInfo[i].stsc = 3;// first chunk (init) and succesive chunks and remain chunk + tablesInfo[i].stsc = 3;// first chunk (init) and successive chunks and remain chunk tablesInfo[i].stsc_bEntries = new int[]{ 1, SAMPLES_PER_CHUNK_INIT, 1, 2, SAMPLES_PER_CHUNK, 1, @@ -268,10 +279,10 @@ public class Mp4FromDashWriter { } else {*/ if (auxSize > 0) { int length = auxSize; - byte[] buffer = new byte[8 * 1024];// 8 KiB + byte[] buffer = new byte[64 * 1024];// 64 KiB while (length > 0) { int count = Math.min(length, buffer.length); - outWrite(buffer, 0, count); + outWrite(buffer, count); length -= count; } } @@ -280,7 +291,7 @@ public class Mp4FromDashWriter { outSeek(ftyp_size); } - // tablesInfo contais row counts + // tablesInfo contains row counts // and after returning from make_moov() will contain table offsets make_moov(defaultMediaTime, tablesInfo, is64); @@ -291,7 +302,7 @@ public class Mp4FromDashWriter { writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stsc_bEntries.length, tablesInfo[i].stsc_bEntries); tablesInfo[i].stsc_bEntries = null; if (tablesInfo[i].ctts > 0) { - sampleCount[i] = 1;// index is not base zero + sampleCount[i] = 1;// the index is not base zero sampleExtra[i] = -1; } } @@ -303,8 +314,8 @@ public class Mp4FromDashWriter { outWrite(make_mdat(totalSampleSize, is64)); int[] sampleIndex = new int[readers.length]; - int[] sizes = new int[SAMPLES_PER_CHUNK]; - int[] sync = new int[SAMPLES_PER_CHUNK]; + int[] sizes = new int[singleChunk ? SINGLE_CHUNK_SAMPLE_BUFFER : SAMPLES_PER_CHUNK]; + int[] sync = new int[singleChunk ? SINGLE_CHUNK_SAMPLE_BUFFER : SAMPLES_PER_CHUNK]; int written = readers.length; while (written > 0) { @@ -317,7 +328,12 @@ public class Mp4FromDashWriter { long chunkOffset = writeOffset; int syncCount = 0; - int limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK; + int limit; + if (singleChunk) { + limit = SINGLE_CHUNK_SAMPLE_BUFFER; + } else { + limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK; + } int j = 0; for (; j < limit; j++) { @@ -354,7 +370,7 @@ public class Mp4FromDashWriter { sizes[j] = sample.data.length; } - outWrite(sample.data, 0, sample.data.length); + outWrite(sample.data, sample.data.length); } if (j > 0) { @@ -368,10 +384,16 @@ public class Mp4FromDashWriter { tablesInfo[i].stss = writeEntryArray(tablesInfo[i].stss, syncCount, sync); } - if (is64) { - tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset); - } else { - tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1, (int) chunkOffset); + if (tablesInfo[i].stco > 0) { + if (is64) { + tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset); + } else { + tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1, (int) chunkOffset); + } + + if (singleChunk) { + tablesInfo[i].stco = -1; + } } outRestore(); @@ -451,12 +473,12 @@ public class Mp4FromDashWriter { // private void outWrite(byte[] buffer) throws IOException { - outWrite(buffer, 0, buffer.length); + outWrite(buffer, buffer.length); } - private void outWrite(byte[] buffer, int offset, int count) throws IOException { + private void outWrite(byte[] buffer, int count) throws IOException { writeOffset += count; - outStream.write(buffer, offset, count); + outStream.write(buffer, 0, count); } private void outSeek(long offset) throws IOException { @@ -509,7 +531,6 @@ public class Mp4FromDashWriter { ); if (extra >= 0) { - //size += 4;// commented for auxiliar buffer !!! offset += 4; auxWrite(extra); } @@ -531,7 +552,7 @@ public class Mp4FromDashWriter { if (moovSimulation) { writeOffset += buffer.length; } else if (auxBuffer == null) { - outWrite(buffer, 0, buffer.length); + outWrite(buffer, buffer.length); } else { auxBuffer.put(buffer); } @@ -703,7 +724,7 @@ public class Mp4FromDashWriter { int mediaTime; if (tracks[index].trak.edst_elst == null) { - // is a audio track ¿is edst/elst opcional for audio tracks? + // is a audio track ¿is edst/elst optional for audio tracks? mediaTime = 0x00;// ffmpeg set this value as zero, instead of defaultMediaTime bMediaRate = 0x00010000; } else { @@ -798,13 +819,13 @@ public class Mp4FromDashWriter { class TablesInfo { - public int stts; - public int stsc; - public int[] stsc_bEntries; - public int ctts; - public int stsz; - public int stsz_default; - public int stss; - public int stco; + int stts; + int stsc; + int[] stsc_bEntries; + int ctts; + int stsz; + int stsz_default; + int stss; + int stco; } } From 160a33e8c844a7aa7534fe798a3472a015315480 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Mon, 30 Sep 2019 23:52:49 -0300 Subject: [PATCH 176/270] misc changes * OggFromWebMWriter: rewrite (again), reduce iterations over the input. Works as-is (video streams are not supported) * WebMReader: use int for SimpleBlock.dataSize instead of long * Download Recovery: allow recovering uninitialized downloads * check range-requests using HEAD method instead of GET * DownloadRunnableFallback: add workaround for 32kB/s issue, unknown issue origin, wont fix * reporting downloads errors now include the source url with the selected quality and format --- .../newpipe/streams/OggFromWebMWriter.java | 216 +++++------------- .../schabi/newpipe/streams/WebMReader.java | 4 +- .../giga/get/DownloadInitializer.java | 35 +-- .../us/shandian/giga/get/DownloadMission.java | 36 +-- .../giga/get/DownloadMissionRecover.java | 146 +++++++++--- .../shandian/giga/get/DownloadRunnable.java | 2 +- .../giga/get/DownloadRunnableFallback.java | 20 +- .../giga/get/MissionRecoveryInfo.java | 43 +++- .../giga/ui/adapter/MissionAdapter.java | 36 ++- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 11 files changed, 294 insertions(+), 248 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index 091ae6d2a..e6363e423 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -12,8 +12,6 @@ import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Random; import javax.annotation.Nullable; @@ -23,15 +21,13 @@ import javax.annotation.Nullable; public class OggFromWebMWriter implements Closeable { private static final byte FLAG_UNSET = 0x00; - private static final byte FLAG_CONTINUED = 0x01; + //private static final byte FLAG_CONTINUED = 0x01; private static final byte FLAG_FIRST = 0x02; private static final byte FLAG_LAST = 0x04; private final static byte HEADER_CHECKSUM_OFFSET = 22; private final static byte HEADER_SIZE = 27; - private final static short BUFFER_SIZE = 8 * 1024;// 8KiB - private final static int TIME_SCALE_NS = 1000000000; private boolean done = false; @@ -43,7 +39,6 @@ public class OggFromWebMWriter implements Closeable { private int sequence_count = 0; private final int STREAM_ID; private byte packet_flag = FLAG_FIRST; - private int track_index = 0; private WebMReader webm = null; private WebMTrack webm_track = null; @@ -71,7 +66,7 @@ public class OggFromWebMWriter implements Closeable { this.source = source; this.output = target; - this.STREAM_ID = (new Random(System.currentTimeMillis())).nextInt(); + this.STREAM_ID = (int) System.currentTimeMillis(); populate_crc32_table(); } @@ -130,7 +125,6 @@ public class OggFromWebMWriter implements Closeable { try { webm_track = webm.selectTrack(trackIndex); - track_index = trackIndex; } finally { parsed = true; } @@ -154,8 +148,11 @@ public class OggFromWebMWriter implements Closeable { public void build() throws IOException { float resolution; - int read; - byte[] buffer; + SimpleBlock bloq; + ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255)); + ByteBuffer page = ByteBuffer.allocate(64 * 1024); + + header.order(ByteOrder.LITTLE_ENDIAN); /* step 1: get the amount of frames per seconds */ switch (webm_track.kind) { @@ -176,57 +173,32 @@ public class OggFromWebMWriter implements Closeable { throw new RuntimeException("not implemented"); } - /* step 2a: create packet with code init data */ - ArrayList data_extra = new ArrayList<>(4); - + /* step 2: create packet with code init data */ if (webm_track.codecPrivate != null) { addPacketSegment(webm_track.codecPrivate.length); - ByteBuffer buff = byte_buffer(HEADER_SIZE + segment_table_size + webm_track.codecPrivate.length); - - make_packetHeader(0x00, buff, webm_track.codecPrivate); - data_extra.add(buff.array()); + make_packetHeader(0x00, header, webm_track.codecPrivate); + write(header); + output.write(webm_track.codecPrivate); } - /* step 2b: create packet with metadata */ - buffer = make_metadata(); + /* step 3: create packet with metadata */ + byte[] buffer = make_metadata(); if (buffer != null) { addPacketSegment(buffer.length); - ByteBuffer buff = byte_buffer(HEADER_SIZE + segment_table_size + buffer.length); - - make_packetHeader(0x00, buff, buffer); - data_extra.add(buff.array()); + make_packetHeader(0x00, header, buffer); + write(header); + output.write(buffer); } - - /* step 3: calculate amount of packets */ - SimpleBlock bloq; - int reserve_header = 0; - int headers_amount = 0; - + /* step 4: calculate amount of packets */ while (webm_segment != null) { bloq = getNextBlock(); - if (addPacketSegment(bloq)) { - continue; - } - - reserve_header += HEADER_SIZE + segment_table_size;// header size - clearSegmentTable(); - webm_block = bloq; - headers_amount++; - } - - /* step 4: create packet headers */ - rewind_source(); - - ByteBuffer headers = byte_buffer(reserve_header); - short[] headers_size = new short[headers_amount]; - int header_index = 0; - - while (webm_segment != null) { - bloq = getNextBlock(); - - if (addPacketSegment(bloq)) { + if (bloq != null && addPacketSegment(bloq)) { + int pos = page.position(); + //noinspection ResultOfMethodCallIgnored + bloq.data.read(page.array(), pos, bloq.dataSize); + page.position(pos + bloq.dataSize); continue; } @@ -251,75 +223,21 @@ public class OggFromWebMWriter implements Closeable { elapsed_ns = elapsed_ns / TIME_SCALE_NS; elapsed_ns = Math.ceil(elapsed_ns * resolution); - // create header - headers_size[header_index++] = make_packetHeader((long) elapsed_ns, headers, null); + // create header and calculate page checksum + int checksum = make_packetHeader((long) elapsed_ns, header, null); + checksum = calc_crc32(checksum, page.array(), page.position()); + + header.putInt(HEADER_CHECKSUM_OFFSET, checksum); + + // dump data + write(header); + write(page); + webm_block = bloq; } - - - /* step 5: calculate checksums */ - rewind_source(); - - int offset = 0; - buffer = new byte[BUFFER_SIZE]; - - for (header_index = 0; header_index < headers_size.length; header_index++) { - int checksum_offset = offset + HEADER_CHECKSUM_OFFSET; - int checksum = headers.getInt(checksum_offset); - - while (webm_segment != null) { - bloq = getNextBlock(); - - if (!addPacketSegment(bloq)) { - clearSegmentTable(); - webm_block = bloq; - break; - } - - // calculate page checksum - while ((read = bloq.data.read(buffer)) > 0) { - checksum = calc_crc32(checksum, buffer, 0, read); - } - } - - headers.putInt(checksum_offset, checksum); - offset += headers_size[header_index]; - } - - /* step 6: write extra headers */ - rewind_source(); - - for (byte[] buff : data_extra) { - output.write(buff); - } - - /* step 7: write stream packets */ - byte[] headers_buffers = headers.array(); - offset = 0; - buffer = new byte[BUFFER_SIZE]; - - for (header_index = 0; header_index < headers_size.length; header_index++) { - output.write(headers_buffers, offset, headers_size[header_index]); - offset += headers_size[header_index]; - - while (webm_segment != null) { - bloq = getNextBlock(); - - if (addPacketSegment(bloq)) { - while ((read = bloq.data.read(buffer)) > 0) { - output.write(buffer, 0, read); - } - } else { - clearSegmentTable(); - webm_block = bloq; - break; - } - } - } } - private short make_packetHeader(long gran_pos, ByteBuffer buffer, byte[] immediate_page) { - int offset = buffer.position(); + private int make_packetHeader(long gran_pos, @NonNull ByteBuffer buffer, byte[] immediate_page) { short length = HEADER_SIZE; buffer.putInt(0x5367674f);// "OggS" binary string in little-endian @@ -340,17 +258,15 @@ public class OggFromWebMWriter implements Closeable { clearSegmentTable();// clear segment table for next header - int checksum_crc32 = calc_crc32(0x00, buffer.array(), offset, length); + int checksum_crc32 = calc_crc32(0x00, buffer.array(), length); if (immediate_page != null) { - checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, 0, immediate_page.length); - System.arraycopy(immediate_page, 0, buffer.array(), length, immediate_page.length); + checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length); + buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32); segment_table_next_timestamp -= TIME_SCALE_NS; } - buffer.putInt(offset + HEADER_CHECKSUM_OFFSET, checksum_crc32); - - return length; + return checksum_crc32; } @Nullable @@ -358,7 +274,7 @@ public class OggFromWebMWriter implements Closeable { if ("A_OPUS".equals(webm_track.codecId)) { return new byte[]{ 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string - 0x07, 0x00, 0x00, 0x00,// writing application string size + 0x07, 0x00, 0x00, 0x00,// writting application string size 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags) }; @@ -366,7 +282,7 @@ public class OggFromWebMWriter implements Closeable { return new byte[]{ 0x03,// ???????? 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string - 0x07, 0x00, 0x00, 0x00,// writing application string size + 0x07, 0x00, 0x00, 0x00,// writting application string size 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags) @@ -387,22 +303,9 @@ public class OggFromWebMWriter implements Closeable { return null; } - private void rewind_source() throws IOException { - source.rewind(); - - webm = new WebMReader(source); - webm.parse(); - webm_track = webm.selectTrack(track_index); - webm_segment = webm.getNextSegment(); - webm_cluster = null; - webm_block = null; - webm_block_last_timecode = 0L; - - segment_table_next_timestamp = TIME_SCALE_NS; - } - - private ByteBuffer byte_buffer(int size) { - return ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + private void write(ByteBuffer buffer) throws IOException { + output.write(buffer.array(), 0, buffer.position()); + buffer.position(0); } // @@ -460,41 +363,32 @@ public class OggFromWebMWriter implements Closeable { // private void clearSegmentTable() { - if (packet_flag != FLAG_CONTINUED) { - segment_table_next_timestamp += TIME_SCALE_NS; - packet_flag = FLAG_UNSET; - } + segment_table_next_timestamp += TIME_SCALE_NS; + packet_flag = FLAG_UNSET; segment_table_size = 0; } private boolean addPacketSegment(SimpleBlock block) { - if (block == null) { - return false; - } - long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay; if (timestamp >= segment_table_next_timestamp) { return false; } - boolean result = addPacketSegment((int) block.dataSize); - - if (!result && segment_table_next_timestamp < timestamp) { - // WARNING: ¡¡¡¡ not implemented (lack of documentation) !!!! - packet_flag = FLAG_CONTINUED; - } - - return result; + return addPacketSegment(block.dataSize); } private boolean addPacketSegment(int size) { + if (size > 65025) { + throw new UnsupportedOperationException("page size cannot be larger than 65025"); + } + int available = (segment_table.length - segment_table_size) * 255; - boolean extra = size == 255; + boolean extra = (size % 255) == 0; if (extra) { // add a zero byte entry in the table - // required to indicate the sample size is exactly 255 + // required to indicate the sample size is multiple of 255 available -= 255; } @@ -528,12 +422,10 @@ public class OggFromWebMWriter implements Closeable { } } - private int calc_crc32(int initial_crc, byte[] buffer, int offset, int size) { - size += offset; - - for (; offset < size; offset++) { + private int calc_crc32(int initial_crc, byte[] buffer, int size) { + for (int i = 0; i < size; i++) { int reg = (initial_crc >>> 24) & 0xff; - initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[offset] & 0xff)]; + initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)]; } return initial_crc; diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java index 13c15370d..4cb96d901 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java @@ -368,7 +368,7 @@ public class WebMReader { obj.trackNumber = readEncodedNumber(); obj.relativeTimeCode = stream.readShort(); obj.flags = (byte) stream.read(); - obj.dataSize = (ref.offset + ref.size) - stream.position(); + obj.dataSize = (int) ((ref.offset + ref.size) - stream.position()); obj.createdFromBlock = ref.type == ID_Block; // NOTE: lacing is not implemented, and will be mixed with the stream data @@ -465,7 +465,7 @@ public class WebMReader { public short relativeTimeCode; public long absoluteTimeCodeNs; public byte flags; - public long dataSize; + public int dataSize; private final Element ref; public boolean isKeyframe() { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java index 593feafa7..17a2a7403 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java @@ -14,6 +14,7 @@ import java.nio.channels.ClosedByInterruptException; import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; +import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN; public class DownloadInitializer extends Thread { private final static String TAG = "DownloadInitializer"; @@ -29,9 +30,9 @@ public class DownloadInitializer extends Thread { mConn = null; } - private static void safeClose(HttpURLConnection con) { + private void dispose() { try { - con.getInputStream().close(); + mConn.getInputStream().close(); } catch (Exception e) { // nothing to do } @@ -52,9 +53,9 @@ public class DownloadInitializer extends Thread { long lowestSize = Long.MAX_VALUE; for (int i = 0; i < mMission.urls.length && mMission.running; i++) { - mConn = mMission.openConnection(mMission.urls[i], mId, -1, -1); + mConn = mMission.openConnection(mMission.urls[i], true, -1, -1); mMission.establishConnection(mId, mConn); - safeClose(mConn); + dispose(); if (Thread.interrupted()) return; long length = Utility.getContentLength(mConn); @@ -82,9 +83,9 @@ public class DownloadInitializer extends Thread { } } else { // ask for the current resource length - mConn = mMission.openConnection(mId, -1, -1); + mConn = mMission.openConnection(true, -1, -1); mMission.establishConnection(mId, mConn); - safeClose(mConn); + dispose(); if (!mMission.running || Thread.interrupted()) return; @@ -108,9 +109,9 @@ public class DownloadInitializer extends Thread { } } else { // Open again - mConn = mMission.openConnection(mId, mMission.length - 10, mMission.length); + mConn = mMission.openConnection(true, mMission.length - 10, mMission.length); mMission.establishConnection(mId, mConn); - safeClose(mConn); + dispose(); if (!mMission.running || Thread.interrupted()) return; @@ -171,7 +172,14 @@ public class DownloadInitializer extends Thread { } catch (InterruptedIOException | ClosedByInterruptException e) { return; } catch (Exception e) { - if (!mMission.running) return; + if (!mMission.running || super.isInterrupted()) return; + + if (e instanceof DownloadMission.HttpError && ((DownloadMission.HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { + // for youtube streams. The url has expired + interrupt(); + mMission.doRecover(e); + return; + } if (e instanceof IOException && e.getMessage().contains("Permission denied")) { mMission.notifyError(DownloadMission.ERROR_PERMISSION_DENIED, e); @@ -194,13 +202,6 @@ public class DownloadInitializer extends Thread { @Override public void interrupt() { super.interrupt(); - - if (mConn != null) { - try { - mConn.disconnect(); - } catch (Exception e) { - // nothing to do - } - } + if (mConn != null) dispose(); } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 77b417118..918d6dbea 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -204,22 +204,24 @@ public class DownloadMission extends Mission { /** * Opens a connection * - * @param threadId id of the calling thread, used only for debugging - * @param rangeStart range start - * @param rangeEnd range end + * @param headRequest {@code true} for use {@code HEAD} request method, otherwise, {@code GET} is used + * @param rangeStart range start + * @param rangeEnd range end * @return a {@link java.net.URLConnection URLConnection} linking to the URL. * @throws IOException if an I/O exception occurs. */ - HttpURLConnection openConnection(int threadId, long rangeStart, long rangeEnd) throws IOException { - return openConnection(urls[current], threadId, rangeStart, rangeEnd); + HttpURLConnection openConnection(boolean headRequest, long rangeStart, long rangeEnd) throws IOException { + return openConnection(urls[current], headRequest, rangeStart, rangeEnd); } - HttpURLConnection openConnection(String url, int threadId, long rangeStart, long rangeEnd) throws IOException { + HttpURLConnection openConnection(String url, boolean headRequest, long rangeStart, long rangeEnd) throws IOException { HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setInstanceFollowRedirects(true); conn.setRequestProperty("User-Agent", DownloaderImpl.USER_AGENT); conn.setRequestProperty("Accept", "*/*"); + if (headRequest) conn.setRequestMethod("HEAD"); + // BUG workaround: switching between networks can freeze the download forever conn.setConnectTimeout(30000); conn.setReadTimeout(10000); @@ -229,10 +231,6 @@ public class DownloadMission extends Mission { if (rangeEnd > 0) req += rangeEnd; conn.setRequestProperty("Range", req); - - if (DEBUG) { - Log.d(TAG, threadId + ":" + conn.getRequestProperty("Range")); - } } return conn; @@ -245,13 +243,14 @@ public class DownloadMission extends Mission { * @throws HttpError if the HTTP Status-Code is not satisfiable */ void establishConnection(int threadId, HttpURLConnection conn) throws IOException, HttpError { - conn.connect(); int statusCode = conn.getResponseCode(); if (DEBUG) { + Log.d(TAG, threadId + ":Range=" + conn.getRequestProperty("Range")); Log.d(TAG, threadId + ":Content-Length=" + conn.getContentLength() + " Code:" + statusCode); } + switch (statusCode) { case 204: case 205: @@ -676,6 +675,15 @@ public class DownloadMission extends Mission { return (isPsFailed() || errCode == ERROR_POSTPROCESSING_HOLD) || isFinished(); } + /** + * Indicates if mission urls has expired and there an attempt to renovate them + * + * @return {@code true} if the mission is running a recovery procedure, otherwise, {@code false} + */ + public boolean isRecovering() { + return threads != null && threads.length > 0 && threads[0] instanceof DownloadRunnable && threads[0].isAlive(); + } + private boolean doPostprocessing() { if (psAlgorithm == null || psState == 2) return true; @@ -742,10 +750,8 @@ public class DownloadMission extends Mission { } } - // set the current download url to null in case if the recovery - // process is canceled. Next time start() method is called the - // recovery will be executed, saving time - urls[current] = null; + errCode = ERROR_NOTHING; + errObject = null; if (recoveryInfo[current].attempts >= maxRetry) { recoveryInfo[current].attempts = 0; diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java index 9abd93717..5efbd1153 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -10,10 +10,12 @@ import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; import java.io.IOException; +import java.io.InterruptedIOException; import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; import java.util.List; +import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; public class DownloadMissionRecover extends Thread { @@ -21,14 +23,17 @@ public class DownloadMissionRecover extends Thread { static final int mID = -3; private final DownloadMission mMission; - private final MissionRecoveryInfo mRecovery; private final Exception mFromError; + private final boolean notInitialized; + private HttpURLConnection mConn; + private MissionRecoveryInfo mRecovery; + private StreamExtractor mExtractor; DownloadMissionRecover(DownloadMission mission, Exception originError) { mMission = mission; mFromError = originError; - mRecovery = mission.recoveryInfo[mission.current]; + notInitialized = mission.blocks == null && mission.current == 0; } @Override @@ -38,28 +43,78 @@ public class DownloadMissionRecover extends Thread { return; } + /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { + resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); + return; + }*/ + try { - /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { - resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); - return; - }*/ - StreamingService svr = NewPipe.getServiceByUrl(mMission.source); - - if (svr == null) { - throw new RuntimeException("Unknown source service"); - } - - StreamExtractor extractor = svr.getStreamExtractor(mMission.source); - extractor.fetchPage(); - + mExtractor = svr.getStreamExtractor(mMission.source); + mExtractor.fetchPage(); + } catch (InterruptedIOException | ClosedByInterruptException e) { + return; + } catch (Exception e) { if (!mMission.running || super.isInterrupted()) return; + mMission.notifyError(e); + return; + } + // maybe the following check is redundant + if (!mMission.running || super.isInterrupted()) return; + + if (!notInitialized) { + // set the current download url to null in case if the recovery + // process is canceled. Next time start() method is called the + // recovery will be executed, saving time + mMission.urls[mMission.current] = null; + + mRecovery = mMission.recoveryInfo[mMission.current]; + resolveStream(); + return; + } + + Log.w(TAG, "mission is not fully initialized, this will take a while"); + + try { + for (; mMission.current < mMission.urls.length; mMission.current++) { + mRecovery = mMission.recoveryInfo[mMission.current]; + + if (test()) continue; + if (!mMission.running) return; + + resolveStream(); + if (!mMission.running) return; + + // before continue, check if the current stream was resolved + if (mMission.urls[mMission.current] == null || mMission.errCode != ERROR_NOTHING) { + break; + } + } + } finally { + mMission.current = 0; + } + + mMission.writeThisToFile(); + + if (!mMission.running || super.isInterrupted()) return; + + mMission.running = false; + mMission.start(); + } + + private void resolveStream() { + if (mExtractor.getErrorMessage() != null) { + mMission.notifyError(mFromError); + return; + } + + try { String url = null; - switch (mMission.kind) { + switch (mRecovery.kind) { case 'a': - for (AudioStream audio : extractor.getAudioStreams()) { + for (AudioStream audio : mExtractor.getAudioStreams()) { if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { url = audio.getUrl(); break; @@ -69,9 +124,9 @@ public class DownloadMissionRecover extends Thread { case 'v': List videoStreams; if (mRecovery.desired2) - videoStreams = extractor.getVideoOnlyStreams(); + videoStreams = mExtractor.getVideoOnlyStreams(); else - videoStreams = extractor.getVideoStreams(); + videoStreams = mExtractor.getVideoStreams(); for (VideoStream video : videoStreams) { if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { url = video.getUrl(); @@ -80,7 +135,7 @@ public class DownloadMissionRecover extends Thread { } break; case 's': - for (SubtitlesStream subtitles : extractor.getSubtitles(mRecovery.format)) { + for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.format)) { String tag = subtitles.getLanguageTag(); if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { url = subtitles.getURL(); @@ -114,7 +169,7 @@ public class DownloadMissionRecover extends Thread { ////// Validate the http resource doing a range request ///////////////////// try { - mConn = mMission.openConnection(url, mID, mMission.length - 10, mMission.length); + mConn = mMission.openConnection(url, true, mMission.length - 10, mMission.length); mConn.setRequestProperty("If-Range", mRecovery.validateCondition); mMission.establishConnection(mID, mConn); @@ -140,22 +195,24 @@ public class DownloadMissionRecover extends Thread { if (!mMission.running || e instanceof ClosedByInterruptException) return; throw e; } finally { - this.interrupt(); + disconnect(); } } private void recover(String url, boolean stale) { Log.i(TAG, - String.format("download recovered name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) + String.format("recover() name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) ); + mMission.urls[mMission.current] = url; + mRecovery.attempts = 0; + if (url == null) { mMission.notifyError(ERROR_RESOURCE_GONE, null); return; } - mMission.urls[mMission.current] = url; - mRecovery.attempts = 0; + if (notInitialized) return; if (stale) { mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); @@ -208,15 +265,40 @@ public class DownloadMissionRecover extends Thread { return range; } + private boolean test() { + if (mMission.urls[mMission.current] == null) return false; + + try { + mConn = mMission.openConnection(mMission.urls[mMission.current], true, -1, -1); + mMission.establishConnection(mID, mConn); + + if (mConn.getResponseCode() == 200) return true; + } catch (Exception e) { + // nothing to do + } finally { + disconnect(); + } + + return false; + } + + private void disconnect() { + try { + try { + mConn.getInputStream().close(); + } finally { + mConn.disconnect(); + } + } catch (Exception e) { + // nothing to do + } finally { + mConn = null; + } + } + @Override public void interrupt() { super.interrupt(); - if (mConn != null) { - try { - mConn.disconnect(); - } catch (Exception e) { - // nothing to do - } - } + if (mConn != null) disconnect(); } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index 1d2a4eee7..b0dc793bc 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -80,7 +80,7 @@ public class DownloadRunnable extends Thread { } try { - mConn = mMission.openConnection(mId, start, end); + mConn = mMission.openConnection(false, start, end); mMission.establishConnection(mId, mConn); // check if the download can be resumed diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index b5937c577..e64322b48 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -35,7 +35,11 @@ public class DownloadRunnableFallback extends Thread { private void dispose() { try { - if (mIs != null) mIs.close(); + try { + if (mIs != null) mIs.close(); + } finally { + mConn.disconnect(); + } } catch (IOException e) { // nothing to do } @@ -68,7 +72,13 @@ public class DownloadRunnableFallback extends Thread { long rangeStart = (mMission.unknownLength || start < 1) ? -1 : start; int mId = 1; - mConn = mMission.openConnection(mId, rangeStart, -1); + mConn = mMission.openConnection(false, rangeStart, -1); + + if (mRetryCount == 0 && rangeStart == -1) { + // workaround: bypass android connection pool + mConn.setRequestProperty("Range", "bytes=0-"); + } + mMission.establishConnection(mId, mConn); // check if the download can be resumed @@ -96,6 +106,8 @@ public class DownloadRunnableFallback extends Thread { mMission.notifyProgress(len); } + dispose(); + // if thread goes interrupted check if the last part is written. This avoid re-download the whole file done = len == -1; } catch (Exception e) { @@ -107,8 +119,8 @@ public class DownloadRunnableFallback extends Thread { if (e instanceof HttpError && ((HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { // for youtube streams. The url has expired, recover - mMission.doRecover(e); dispose(); + mMission.doRecover(e); return; } @@ -125,8 +137,6 @@ public class DownloadRunnableFallback extends Thread { return; } - dispose(); - if (done) { mMission.notifyFinished(); } else { diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java index 553ba6d89..bd1d9bc49 100644 --- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java @@ -16,25 +16,28 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { private static final long serialVersionUID = 0L; //public static final String DIRECT_SOURCE = "direct-source://"; - public MediaFormat format; + MediaFormat format; String desired; boolean desired2; int desiredBitrate; + byte kind; + String validateCondition = null; transient int attempts = 0; - String validateCondition = null; - public MissionRecoveryInfo(@NonNull Stream stream) { if (stream instanceof AudioStream) { desiredBitrate = ((AudioStream) stream).average_bitrate; desired2 = false; + kind = 'a'; } else if (stream instanceof VideoStream) { desired = ((VideoStream) stream).getResolution(); desired2 = ((VideoStream) stream).isVideoOnly(); + kind = 'v'; } else if (stream instanceof SubtitlesStream) { desired = ((SubtitlesStream) stream).getLanguageTag(); desired2 = ((SubtitlesStream) stream).isAutoGenerated(); + kind = 's'; } else { throw new RuntimeException("Unknown stream kind"); } @@ -43,6 +46,38 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { if (format == null) throw new NullPointerException("Stream format cannot be null"); } + @NonNull + @Override + public String toString() { + String info; + StringBuilder str = new StringBuilder(); + str.append("type="); + switch (kind) { + case 'a': + str.append("audio"); + info = "bitrate=" + desiredBitrate; + break; + case 'v': + str.append("video"); + info = "quality=" + desired + " videoOnly=" + desired2; + break; + case 's': + str.append("subtitles"); + info = "language=" + desired + " autoGenerated=" + desired2; + break; + default: + info = ""; + str.append("other"); + } + + str.append(" format=") + .append(format.getName()) + .append(' ') + .append(info); + + return str.toString(); + } + @Override public int describeContents() { return 0; @@ -54,6 +89,7 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { parcel.writeString(this.desired); parcel.writeInt(this.desired2 ? 0x01 : 0x00); parcel.writeInt(this.desiredBitrate); + parcel.writeByte(this.kind); parcel.writeString(this.validateCondition); } @@ -62,6 +98,7 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { this.desired = parcel.readString(); this.desired2 = parcel.readInt() != 0x00; this.desiredBitrate = parcel.readInt(); + this.kind = parcel.readByte(); this.validateCondition = parcel.readString(); } diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 6c6198750..78fd7ea9d 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -36,6 +36,7 @@ import android.widget.Toast; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.NavigationHelper; @@ -44,11 +45,11 @@ import java.io.File; import java.lang.ref.WeakReference; import java.net.URI; import java.util.ArrayList; -import java.util.Collections; import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.Mission; +import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.io.StoredFileHelper; import us.shandian.giga.service.DownloadManager; import us.shandian.giga.service.DownloadManagerService; @@ -234,7 +235,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb // hide on error // show if current resource length is not fetched // show if length is unknown - h.progress.setMarquee(!hasError && (!mission.isInitialized() || mission.unknownLength)); + h.progress.setMarquee(mission.isRecovering() || !hasError && (!mission.isInitialized() || mission.unknownLength)); float progress; if (mission.unknownLength) { @@ -463,13 +464,13 @@ public class MissionAdapter extends Adapter implements Handler.Callb break; case ERROR_POSTPROCESSING: case ERROR_POSTPROCESSING_HOLD: - showError(mission.errObject, UserAction.DOWNLOAD_POSTPROCESSING, R.string.error_postprocessing_failed); + showError(mission, UserAction.DOWNLOAD_POSTPROCESSING, R.string.error_postprocessing_failed); return; case ERROR_INSUFFICIENT_STORAGE: msg = R.string.error_insufficient_storage; break; case ERROR_UNKNOWN_EXCEPTION: - showError(mission.errObject, UserAction.DOWNLOAD_FAILED, R.string.general_error); + showError(mission, UserAction.DOWNLOAD_FAILED, R.string.general_error); return; case ERROR_PROGRESS_LOST: msg = R.string.error_progress_lost; @@ -486,7 +487,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb } else if (mission.errObject == null) { msgEx = "(not_decelerated_error_code)"; } else { - showError(mission.errObject, UserAction.DOWNLOAD_FAILED, msg); + showError(mission, UserAction.DOWNLOAD_FAILED, msg); return; } break; @@ -503,7 +504,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb if (mission.errObject != null && (mission.errCode < 100 || mission.errCode >= 600)) { @StringRes final int mMsg = msg; builder.setPositiveButton(R.string.error_report_title, (dialog, which) -> - showError(mission.errObject, UserAction.DOWNLOAD_FAILED, mMsg) + showError(mission, UserAction.DOWNLOAD_FAILED, mMsg) ); } @@ -513,13 +514,30 @@ public class MissionAdapter extends Adapter implements Handler.Callb .show(); } - private void showError(Exception exception, UserAction action, @StringRes int reason) { + private void showError(DownloadMission mission, UserAction action, @StringRes int reason) { + StringBuilder request = new StringBuilder(256); + request.append(mission.source); + + request.append(" ["); + if (mission.recoveryInfo != null) { + for (MissionRecoveryInfo recovery : mission.recoveryInfo) + request.append(" {").append(recovery.toString()).append("} "); + } + request.append("]"); + + String service; + try { + service = NewPipe.getServiceByUrl(mission.source).getServiceInfo().getName(); + } catch (Exception e) { + service = "-"; + } + ErrorActivity.reportError( mContext, - Collections.singletonList(exception), + mission.errObject, null, null, - ErrorActivity.ErrorInfo.make(action, "-", "-", reason) + ErrorActivity.ErrorInfo.make(action, service, request.toString(), reason) ); } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2f69e62cb..b14aab94b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -453,7 +453,7 @@ No hay suficiente espacio disponible en el dispositivo Se perdió el progreso porque el archivo fue eliminado Tiempo de espera excedido - El recurso solicitado ya no esta disponible + No se puede recuperar esta descarga Preguntar dónde descargar Se preguntará dónde guardar cada descarga Se le preguntará dónde guardar cada descarga. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2917fb9fd..f929e0d2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -557,7 +557,7 @@ No space left on device Progress lost, because the file was deleted Connection timeout - The solicited resource is not available anymore + Cannot recover this download Clear finished downloads Are you sure? Continue your %s pending transfers from Downloads From d092e39c56900358b3caa1897d22df18fafad8da Mon Sep 17 00:00:00 2001 From: kapodamy Date: Tue, 1 Oct 2019 13:00:16 -0300 Subject: [PATCH 177/270] fallback for pending downloads directory --- .../giga/service/DownloadManager.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index a859a87ca..89c44638d 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -37,6 +37,7 @@ public class DownloadManager { public static final String TAG_AUDIO = "audio"; public static final String TAG_VIDEO = "video"; + private static final String DOWNLOADS_METADATA_FOLDER = "pending_downloads"; private final FinishedMissionStore mFinishedMissionStore; @@ -75,24 +76,33 @@ public class DownloadManager { mPendingMissionsDir = getPendingDir(context); if (!Utility.mkdir(mPendingMissionsDir, false)) { - throw new RuntimeException("failed to create pending_downloads in data directory"); + throw new RuntimeException("failed to create " + DOWNLOADS_METADATA_FOLDER + " directory"); } loadPendingMissions(context); } private static File getPendingDir(@NonNull Context context) { - //File dir = new File(ContextCompat.getDataDir(context), "pending_downloads"); - File dir = context.getExternalFilesDir("pending_downloads"); + File dir = context.getExternalFilesDir(DOWNLOADS_METADATA_FOLDER); + if (testDir(dir)) return dir; - if (dir == null) { - // One of the following paths are not accessible ¿unmounted internal memory? - // /storage/emulated/0/Android/data/org.schabi.newpipe[.debug]/pending_downloads - // /sdcard/Android/data/org.schabi.newpipe[.debug]/pending_downloads - Log.w(TAG, "path to pending downloads are not accessible"); + dir = new File(context.getFilesDir(), DOWNLOADS_METADATA_FOLDER); + if (testDir(dir)) return dir; + + throw new RuntimeException("path to pending downloads are not accessible"); + } + + private static boolean testDir(@Nullable File dir) { + if (dir == null) return false; + + try { + File tmp = new File(dir, ".tmp"); + if (!tmp.createNewFile()) return false; + return tmp.delete();// if the file was created, SHOULD BE deleted too + } catch (Exception e) { + Log.e(TAG, "testDir() failed: " + dir.getAbsolutePath(), e); + return false; } - - return dir; } /** @@ -132,6 +142,7 @@ public class DownloadManager { for (File sub : subs) { if (!sub.isFile()) continue; + if (sub.getName().equals(".tmp")) continue; DownloadMission mis = Utility.readFromFile(sub); if (mis == null || mis.isFinished()) { From 9339fc80b4f40c62df65c04de328fc4fd9ae964c Mon Sep 17 00:00:00 2001 From: kapodamy Date: Tue, 1 Oct 2019 15:01:17 -0300 Subject: [PATCH 178/270] update DownloadManager.java * check if the directory pending_downloads was created --- .../java/us/shandian/giga/service/DownloadManager.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index 89c44638d..2d1e9cd00 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -75,10 +75,6 @@ public class DownloadManager { mMissionsFinished = loadFinishedMissions(); mPendingMissionsDir = getPendingDir(context); - if (!Utility.mkdir(mPendingMissionsDir, false)) { - throw new RuntimeException("failed to create " + DOWNLOADS_METADATA_FOLDER + " directory"); - } - loadPendingMissions(context); } @@ -96,6 +92,11 @@ public class DownloadManager { if (dir == null) return false; try { + if (!Utility.mkdir(dir, false)) { + Log.e(TAG, "testDir() cannot create the directory in path: " + dir.getAbsolutePath()); + return false; + } + File tmp = new File(dir, ".tmp"); if (!tmp.createNewFile()) return false; return tmp.delete();// if the file was created, SHOULD BE deleted too From 94e23142a5cfcca74e0345c76ecccfc54a87c450 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Tue, 1 Oct 2019 16:28:45 -0300 Subject: [PATCH 179/270] update WebMWriter.java fix wrong cue generation --- app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java index 1bf994b1e..8525fabd2 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java @@ -249,7 +249,7 @@ public class WebMWriter implements Closeable { nextCueTime += DEFAULT_CUES_EACH_MS; } keyFrames.add( - new KeyFrame(baseSegmentOffset, currentClusterOffset - 7, written, bTimecode.length, bloq.absoluteTimecode) + new KeyFrame(baseSegmentOffset, currentClusterOffset - 8, written, bTimecode.length, bloq.absoluteTimecode) ); } } From 844f80a5f1b0762a043afd2b4aec63b402830e53 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Wed, 2 Oct 2019 13:31:45 -0300 Subject: [PATCH 180/270] update DownloadDialog.java keep *.opus extension --- .../main/java/org/schabi/newpipe/download/DownloadDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 0006b3c12..60b6192be 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -562,7 +562,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck mainStorage = mainStorageVideo; format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat(); mime = format.mimeType; - filename += format == MediaFormat.OPUS ? "ogg" : format.suffix; + filename += format.suffix; break; case R.id.subtitle_button: mainStorage = mainStorageVideo;// subtitle & video files go together From f62a7919a5f592bfe62396dccd054150094498d1 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Wed, 9 Oct 2019 23:49:23 -0300 Subject: [PATCH 181/270] code cleanup * migrate few annotations to androidx * mission recovery: better error handling (except StreamExtractor.getErrorMessage() method always returns an error) * post-processing: more detailed progress [file specific changes] DownloadMission.java * remove redundant/boilerplate code (again) * make few variables volatile * better file "length" approximation * use "done" variable to count the amount of bytes downloaded (simplify percent calc in UI code) Postprocessing.java * if case of error use "ERROR_POSTPROCESSING" instead of "ERROR_UNKNOWN_EXCEPTION" * simplify source stream init DownloadManager.java * move all "service message sending" code to DownloadMission * remove not implemented method "notifyUserPendingDownloads()" also his unused strings DownloadManagerService.java * use START_STICKY instead of START_NOT_STICKY * simplify addMissionEventListener()/removeMissionEventListener() methods (always are called from the main thread) Deleter.java * better method definition MissionAdapter.java * better method definition * code cleanup * the UI is now refreshed every 750ms * simplify download progress calculation * indicates if the download is actually recovering * smooth download speed measure * show estimated remain time MainFragment.java: * check if viewPager is null (issued by "Apply changes" feature of Android Studio) --- .../newpipe/fragments/MainFragment.java | 9 + .../newpipe/streams/OggFromWebMWriter.java | 2 +- .../giga/get/DownloadInitializer.java | 5 +- .../us/shandian/giga/get/DownloadMission.java | 287 ++++++++--------- .../giga/get/DownloadMissionRecover.java | 160 +++++----- .../shandian/giga/get/DownloadRunnable.java | 5 +- .../giga/get/DownloadRunnableFallback.java | 29 +- .../us/shandian/giga/get/FinishedMission.java | 6 +- .../giga/get/MissionRecoveryInfo.java | 10 +- .../giga/io/ChunkFileInputStream.java | 19 +- .../shandian/giga/io/CircularFileWriter.java | 30 +- .../us/shandian/giga/io/ProgressReport.java | 11 + .../postprocessing/OggFromWebmDemuxer.java | 2 +- .../giga/postprocessing/Postprocessing.java | 54 ++-- .../giga/service/DownloadManager.java | 57 +--- .../giga/service/DownloadManagerService.java | 45 ++- .../giga/ui/adapter/MissionAdapter.java | 302 +++++++++--------- .../us/shandian/giga/ui/common/Deleter.java | 9 +- .../giga/ui/common/ProgressDrawable.java | 5 +- .../giga/ui/fragment/MissionsFragment.java | 45 ++- .../java/us/shandian/giga/util/Utility.java | 50 ++- app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-be/strings.xml | 1 - app/src/main/res/values-cmn/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-da/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-el/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-et/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-he/strings.xml | 1 - app/src/main/res/values-hr/strings.xml | 1 - app/src/main/res/values-id/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ko/strings.xml | 1 - app/src/main/res/values-ms/strings.xml | 1 - app/src/main/res/values-nb-rNO/strings.xml | 1 - app/src/main/res/values-nl-rBE/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pa/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 2 +- 53 files changed, 554 insertions(+), 622 deletions(-) create mode 100644 app/src/main/java/us/shandian/giga/io/ProgressReport.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 720e0f216..70e0d9fb1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -2,6 +2,15 @@ package org.schabi.newpipe.fragments; import android.content.Context; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.tabs.TabLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index e6363e423..37bf9c6d7 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -1,6 +1,6 @@ package org.schabi.newpipe.streams; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.schabi.newpipe.streams.WebMReader.Cluster; import org.schabi.newpipe.streams.WebMReader.Segment; diff --git a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java index 17a2a7403..618200f27 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java @@ -1,9 +1,10 @@ package us.shandian.giga.get; -import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; + import org.schabi.newpipe.streams.io.SharpStream; import java.io.IOException; @@ -177,7 +178,7 @@ public class DownloadInitializer extends Thread { if (e instanceof DownloadMission.HttpError && ((DownloadMission.HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { // for youtube streams. The url has expired interrupt(); - mMission.doRecover(e); + mMission.doRecover(ERROR_HTTP_FORBIDDEN); return; } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 918d6dbea..5ef72162c 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -4,18 +4,21 @@ import android.os.Handler; import android.util.Log; import androidx.annotation.Nullable; +import androidx.annotation.NonNull; import org.schabi.newpipe.DownloaderImpl; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InterruptedIOException; import java.io.Serializable; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; import java.net.UnknownHostException; +import java.nio.channels.ClosedByInterruptException; import javax.net.ssl.SSLException; @@ -27,7 +30,7 @@ import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; public class DownloadMission extends Mission { - private static final long serialVersionUID = 6L;// last bump: 28 september 2019 + private static final long serialVersionUID = 6L;// last bump: 07 october 2019 static final int BUFFER_SIZE = 64 * 1024; static final int BLOCK_SIZE = 512 * 1024; @@ -61,9 +64,9 @@ public class DownloadMission extends Mission { public String[] urls; /** - * Number of bytes downloaded + * Number of bytes downloaded and written */ - public long done; + public volatile long done; /** * Indicates a file generated dynamically on the web server @@ -119,7 +122,7 @@ public class DownloadMission extends Mission { /** * Download/File resume offset in fallback mode (if applicable) {@link DownloadRunnableFallback} */ - long fallbackResumeOffset; + volatile long fallbackResumeOffset; /** * Maximum of download threads running, chosen by the user @@ -132,22 +135,23 @@ public class DownloadMission extends Mission { public MissionRecoveryInfo[] recoveryInfo; private transient int finishCount; - public transient boolean running; + public transient volatile boolean running; public boolean enqueued; public int errCode = ERROR_NOTHING; public Exception errObject = null; public transient Handler mHandler; - private transient boolean mWritingToFile; private transient boolean[] blockAcquired; + private transient long writingToFileNext; + private transient volatile boolean writingToFile; + final Object LOCK = new Lock(); - private transient boolean deleted; - - public transient volatile Thread[] threads = new Thread[0]; - private transient Thread init = null; + @NonNull + public transient Thread[] threads = new Thread[0]; + public transient Thread init = null; public DownloadMission(String[] urls, StoredFileHelper storage, char kind, Postprocessing psInstance) { if (urls == null) throw new NullPointerException("urls is null"); @@ -246,8 +250,10 @@ public class DownloadMission extends Mission { int statusCode = conn.getResponseCode(); if (DEBUG) { - Log.d(TAG, threadId + ":Range=" + conn.getRequestProperty("Range")); - Log.d(TAG, threadId + ":Content-Length=" + conn.getContentLength() + " Code:" + statusCode); + Log.d(TAG, threadId + ":[request] Range=" + conn.getRequestProperty("Range")); + Log.d(TAG, threadId + ":[response] Code=" + statusCode); + Log.d(TAG, threadId + ":[response] Content-Length=" + conn.getContentLength()); + Log.d(TAG, threadId + ":[response] Content-Range=" + conn.getHeaderField("Content-Range")); } @@ -272,24 +278,19 @@ public class DownloadMission extends Mission { } synchronized void notifyProgress(long deltaLen) { - if (!running) return; - if (unknownLength) { length += deltaLen;// Update length before proceeding } done += deltaLen; - if (done > length) { - done = length; - } + if (metadata == null) return; - if (done != length && !deleted && !mWritingToFile) { - mWritingToFile = true; - runAsync(-2, this::writeThisToFile); + if (!writingToFile && (done > writingToFileNext || deltaLen < 0)) { + writingToFile = true; + writingToFileNext = done + BLOCK_SIZE; + writeThisToFileAsync(); } - - notify(DownloadManagerService.MESSAGE_PROGRESS); } synchronized void notifyError(Exception err) { @@ -342,43 +343,42 @@ public class DownloadMission extends Mission { notify(DownloadManagerService.MESSAGE_ERROR); - if (running) { - running = false; - if (threads != null) selfPause(); - } + if (running) pauseThreads(); } synchronized void notifyFinished() { - if (errCode > ERROR_NOTHING) return; - - finishCount++; - - if (blocks.length < 1 || threads == null || finishCount == threads.length) { - if (errCode != ERROR_NOTHING) return; + if (current < urls.length) { + if (++finishCount < threads.length) return; if (DEBUG) { - Log.d(TAG, "onFinish: " + (current + 1) + "/" + urls.length); - } - - if ((current + 1) < urls.length) { - // prepare next sub-mission - long current_offset = offsets[current++]; - offsets[current] = current_offset + length; - initializer(); - return; + Log.d(TAG, "onFinish: downloaded " + (current + 1) + "/" + urls.length); } current++; - unknownLength = false; - - if (!doPostprocessing()) return; - - enqueued = false; - running = false; - deleteThisFromFile(); - - notify(DownloadManagerService.MESSAGE_FINISHED); + if (current < urls.length) { + // prepare next sub-mission + offsets[current] = offsets[current - 1] + length; + initializer(); + return; + } } + + if (psAlgorithm != null && psState == 0) { + threads = new Thread[]{ + runAsync(1, this::doPostprocessing) + }; + return; + } + + + // this mission is fully finished + + unknownLength = false; + enqueued = false; + running = false; + + deleteThisFromFile(); + notify(DownloadManagerService.MESSAGE_FINISHED); } private void notifyPostProcessing(int state) { @@ -396,10 +396,15 @@ public class DownloadMission extends Mission { Log.d(TAG, action + " postprocessing on " + storage.getName()); + if (state == 2) { + psState = state; + return; + } + synchronized (LOCK) { // don't return without fully write the current state psState = state; - Utility.writeToFile(metadata, DownloadMission.this); + writeThisToFile(); } } @@ -411,12 +416,7 @@ public class DownloadMission extends Mission { if (running || isFinished() || urls.length < 1) return; // ensure that the previous state is completely paused. - int maxWait = 10000;// 10 seconds - joinForThread(init, maxWait); - if (threads != null) { - for (Thread thread : threads) joinForThread(thread, maxWait); - threads = null; - } + joinForThreads(10000); running = true; errCode = ERROR_NOTHING; @@ -427,12 +427,14 @@ public class DownloadMission extends Mission { } if (current >= urls.length) { - runAsync(1, this::notifyFinished); + notifyFinished(); return; } + notify(DownloadManagerService.MESSAGE_RUNNING); + if (urls[current] == null) { - doRecover(null); + doRecover(ERROR_RESOURCE_GONE); return; } @@ -446,18 +448,13 @@ public class DownloadMission extends Mission { blockAcquired = new boolean[blocks.length]; if (blocks.length < 1) { - if (unknownLength) { - done = 0; - length = 0; - } - threads = new Thread[]{runAsync(1, new DownloadRunnableFallback(this))}; } else { int remainingBlocks = 0; for (int block : blocks) if (block >= 0) remainingBlocks++; if (remainingBlocks < 1) { - runAsync(1, this::notifyFinished); + notifyFinished(); return; } @@ -483,6 +480,7 @@ public class DownloadMission extends Mission { } running = false; + notify(DownloadManagerService.MESSAGE_PAUSED); if (init != null && init.isAlive()) { // NOTE: if start() method is running ¡will no have effect! @@ -497,29 +495,14 @@ public class DownloadMission extends Mission { Log.w(TAG, "pausing a download that can not be resumed (range requests not allowed by the server)."); } - // check if the calling thread (alias UI thread) is interrupted - if (Thread.currentThread().isInterrupted()) { - writeThisToFile(); - return; - } - - // wait for all threads are suspended before save the state - if (threads != null) runAsync(-1, this::selfPause); + init = null; + pauseThreads(); } - private void selfPause() { - try { - for (Thread thread : threads) { - if (thread.isAlive()) { - thread.interrupt(); - thread.join(5000); - } - } - } catch (Exception e) { - // nothing to do - } finally { - writeThisToFile(); - } + private void pauseThreads() { + running = false; + joinForThreads(-1); + writeThisToFile(); } /** @@ -527,9 +510,10 @@ public class DownloadMission extends Mission { */ @Override public boolean delete() { - deleted = true; if (psAlgorithm != null) psAlgorithm.cleanupTemporalDir(); + notify(DownloadManagerService.MESSAGE_DELETED); + boolean res = deleteThisFromFile(); if (!super.delete()) return false; @@ -544,35 +528,37 @@ public class DownloadMission extends Mission { * @param persistChanges {@code true} to commit changes to the metadata file, otherwise, {@code false} */ public void resetState(boolean rollback, boolean persistChanges, int errorCode) { - done = 0; + length = 0; errCode = errorCode; errObject = null; unknownLength = false; - threads = null; + threads = new Thread[0]; fallbackResumeOffset = 0; blocks = null; blockAcquired = null; if (rollback) current = 0; - - if (persistChanges) - Utility.writeToFile(metadata, DownloadMission.this); + if (persistChanges) writeThisToFile(); } private void initializer() { init = runAsync(DownloadInitializer.mId, new DownloadInitializer(this)); } + private void writeThisToFileAsync() { + runAsync(-2, this::writeThisToFile); + } + /** * Write this {@link DownloadMission} to the meta file asynchronously * if no thread is already running. */ void writeThisToFile() { synchronized (LOCK) { - if (deleted) return; - Utility.writeToFile(metadata, DownloadMission.this); + if (metadata == null) return; + Utility.writeToFile(metadata, this); + writingToFile = false; } - mWritingToFile = false; } /** @@ -625,11 +611,10 @@ public class DownloadMission extends Mission { public long getLength() { long calculated; if (psState == 1 || psState == 3) { - calculated = length; - } else { - calculated = offsets[current < offsets.length ? current : (offsets.length - 1)] + length; + return length; } + calculated = offsets[current < offsets.length ? current : (offsets.length - 1)] + length; calculated -= offsets[0];// don't count reserved space return calculated > nearLength ? calculated : nearLength; @@ -642,7 +627,7 @@ public class DownloadMission extends Mission { */ public void setEnqueued(boolean queue) { enqueued = queue; - runAsync(-2, this::writeThisToFile); + writeThisToFileAsync(); } /** @@ -681,24 +666,19 @@ public class DownloadMission extends Mission { * @return {@code true} if the mission is running a recovery procedure, otherwise, {@code false} */ public boolean isRecovering() { - return threads != null && threads.length > 0 && threads[0] instanceof DownloadRunnable && threads[0].isAlive(); + return threads.length > 0 && threads[0] instanceof DownloadMissionRecover && threads[0].isAlive(); } - private boolean doPostprocessing() { - if (psAlgorithm == null || psState == 2) return true; - + private void doPostprocessing() { + errCode = ERROR_NOTHING; errObject = null; + Thread thread = Thread.currentThread(); notifyPostProcessing(1); - notifyProgress(0); - if (DEBUG) - Thread.currentThread().setName("[" + TAG + "] ps = " + - psAlgorithm.getClass().getSimpleName() + - " filename = " + storage.getName() - ); - - threads = new Thread[]{Thread.currentThread()}; + if (DEBUG) { + thread.setName("[" + TAG + "] ps = " + psAlgorithm + " filename = " + storage.getName()); + } Exception exception = null; @@ -707,6 +687,11 @@ public class DownloadMission extends Mission { } catch (Exception err) { Log.e(TAG, "Post-processing failed. " + psAlgorithm.toString(), err); + if (err instanceof InterruptedIOException || err instanceof ClosedByInterruptException || thread.isInterrupted()) { + notifyError(DownloadMission.ERROR_POSTPROCESSING_STOPPED, null); + return; + } + if (errCode == ERROR_NOTHING) errCode = ERROR_POSTPROCESSING; exception = err; @@ -717,56 +702,38 @@ public class DownloadMission extends Mission { if (errCode != ERROR_NOTHING) { if (exception == null) exception = errObject; notifyError(ERROR_POSTPROCESSING, exception); - - return false; + return; } - return true; + notifyFinished(); } /** * Attempts to recover the download * - * @param fromError exception which require update the url from the source + * @param errorCode error code which trigger the recovery procedure */ - void doRecover(Exception fromError) { + void doRecover(int errorCode) { Log.i(TAG, "Attempting to recover the mission: " + storage.getName()); if (recoveryInfo == null) { - if (fromError == null) - notifyError(ERROR_RESOURCE_GONE, null); - else - notifyError(fromError); - + notifyError(errorCode, null); urls = new String[0];// mark this mission as dead return; } - if (threads != null) { - for (Thread thread : threads) { - if (thread == Thread.currentThread()) continue; - thread.interrupt(); - joinForThread(thread, 0); - } - } - - errCode = ERROR_NOTHING; - errObject = null; - - if (recoveryInfo[current].attempts >= maxRetry) { - recoveryInfo[current].attempts = 0; - notifyError(fromError); - return; - } + joinForThreads(0); threads = new Thread[]{ - runAsync(DownloadMissionRecover.mID, new DownloadMissionRecover(this, fromError)) + runAsync(DownloadMissionRecover.mID, new DownloadMissionRecover(this, errorCode)) }; } private boolean deleteThisFromFile() { synchronized (LOCK) { - return metadata.delete(); + boolean res = metadata.delete(); + metadata = null; + return res; } } @@ -776,8 +743,8 @@ public class DownloadMission extends Mission { * @param id id of new thread (used for debugging only) * @param who the Runnable whose {@code run} method is invoked. */ - private void runAsync(int id, Runnable who) { - runAsync(id, new Thread(who)); + private Thread runAsync(int id, Runnable who) { + return runAsync(id, new Thread(who)); } /** @@ -806,28 +773,44 @@ public class DownloadMission extends Mission { /** * Waits at most {@code millis} milliseconds for the thread to die * - * @param thread the desired thread * @param millis the time to wait in milliseconds */ - private void joinForThread(Thread thread, int millis) { - if (thread == null || !thread.isAlive()) return; - if (thread == Thread.currentThread()) return; + private void joinForThreads(int millis) { + final Thread currentThread = Thread.currentThread(); - if (DEBUG) { - Log.w(TAG, "a thread is !still alive!: " + thread.getName()); + if (init != null && init != currentThread && init.isAlive()) { + init.interrupt(); + + if (millis > 0) { + try { + init.join(millis); + } catch (InterruptedException e) { + Log.w(TAG, "Initializer thread is still running", e); + return; + } + } } - // still alive, this should not happen. - // Possible reasons: + // if a thread is still alive, possible reasons: // slow device // the user is spamming start/pause buttons // start() method called quickly after pause() + for (Thread thread : threads) { + if (!thread.isAlive() || thread == Thread.currentThread()) continue; + thread.interrupt(); + } + try { - thread.join(millis); + for (Thread thread : threads) { + if (!thread.isAlive()) continue; + if (DEBUG) { + Log.w(TAG, "thread alive: " + thread.getName()); + } + if (millis > 0) thread.join(millis); + } } catch (InterruptedException e) { - Log.d(TAG, "timeout on join : " + thread.getName()); - throw new RuntimeException("A thread is still running:\n" + thread.getName()); + throw new RuntimeException("A download thread is still running", e); } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java index 5efbd1153..eb660e564 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -4,6 +4,7 @@ import android.util.Log; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.stream.SubtitlesStream; @@ -15,7 +16,8 @@ import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; import java.util.List; -import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; +import us.shandian.giga.get.DownloadMission.HttpError; + import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; public class DownloadMissionRecover extends Thread { @@ -23,47 +25,67 @@ public class DownloadMissionRecover extends Thread { static final int mID = -3; private final DownloadMission mMission; - private final Exception mFromError; - private final boolean notInitialized; + private final boolean mNotInitialized; + + private final int mErrCode; private HttpURLConnection mConn; private MissionRecoveryInfo mRecovery; private StreamExtractor mExtractor; - DownloadMissionRecover(DownloadMission mission, Exception originError) { + DownloadMissionRecover(DownloadMission mission, int errCode) { mMission = mission; - mFromError = originError; - notInitialized = mission.blocks == null && mission.current == 0; + mNotInitialized = mission.blocks == null && mission.current == 0; + mErrCode = errCode; } @Override public void run() { if (mMission.source == null) { - mMission.notifyError(mFromError); + mMission.notifyError(mErrCode, null); return; } + Exception err = null; + int attempt = 0; + + while (attempt++ < mMission.maxRetry) { + try { + tryRecover(); + return; + } catch (InterruptedIOException | ClosedByInterruptException e) { + return; + } catch (Exception e) { + if (!mMission.running || super.isInterrupted()) return; + err = e; + } + } + + // give up + mMission.notifyError(mErrCode, err); + } + + private void tryRecover() throws ExtractionException, IOException, HttpError { /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); return; }*/ - try { - StreamingService svr = NewPipe.getServiceByUrl(mMission.source); - mExtractor = svr.getStreamExtractor(mMission.source); - mExtractor.fetchPage(); - } catch (InterruptedIOException | ClosedByInterruptException e) { - return; - } catch (Exception e) { - if (!mMission.running || super.isInterrupted()) return; - mMission.notifyError(e); - return; + if (mExtractor == null) { + try { + StreamingService svr = NewPipe.getServiceByUrl(mMission.source); + mExtractor = svr.getStreamExtractor(mMission.source); + mExtractor.fetchPage(); + } catch (ExtractionException e) { + mExtractor = null; + throw e; + } } // maybe the following check is redundant if (!mMission.running || super.isInterrupted()) return; - if (!notInitialized) { + if (!mNotInitialized) { // set the current download url to null in case if the recovery // process is canceled. Next time start() method is called the // recovery will be executed, saving time @@ -87,7 +109,7 @@ public class DownloadMissionRecover extends Thread { if (!mMission.running) return; // before continue, check if the current stream was resolved - if (mMission.urls[mMission.current] == null || mMission.errCode != ERROR_NOTHING) { + if (mMission.urls[mMission.current] == null) { break; } } @@ -103,59 +125,54 @@ public class DownloadMissionRecover extends Thread { mMission.start(); } - private void resolveStream() { - if (mExtractor.getErrorMessage() != null) { - mMission.notifyError(mFromError); + private void resolveStream() throws IOException, ExtractionException, HttpError { + // FIXME: this getErrorMessage() always returns "video is unavailable" + /*if (mExtractor.getErrorMessage() != null) { + mMission.notifyError(mErrCode, new ExtractionException(mExtractor.getErrorMessage())); return; + }*/ + + String url = null; + + switch (mRecovery.kind) { + case 'a': + for (AudioStream audio : mExtractor.getAudioStreams()) { + if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { + url = audio.getUrl(); + break; + } + } + break; + case 'v': + List videoStreams; + if (mRecovery.desired2) + videoStreams = mExtractor.getVideoOnlyStreams(); + else + videoStreams = mExtractor.getVideoStreams(); + for (VideoStream video : videoStreams) { + if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { + url = video.getUrl(); + break; + } + } + break; + case 's': + for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.format)) { + String tag = subtitles.getLanguageTag(); + if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { + url = subtitles.getURL(); + break; + } + } + break; + default: + throw new RuntimeException("Unknown stream type"); } - try { - String url = null; - - switch (mRecovery.kind) { - case 'a': - for (AudioStream audio : mExtractor.getAudioStreams()) { - if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { - url = audio.getUrl(); - break; - } - } - break; - case 'v': - List videoStreams; - if (mRecovery.desired2) - videoStreams = mExtractor.getVideoOnlyStreams(); - else - videoStreams = mExtractor.getVideoStreams(); - for (VideoStream video : videoStreams) { - if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { - url = video.getUrl(); - break; - } - } - break; - case 's': - for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.format)) { - String tag = subtitles.getLanguageTag(); - if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { - url = subtitles.getURL(); - break; - } - } - break; - default: - throw new RuntimeException("Unknown stream type"); - } - - resolve(url); - } catch (Exception e) { - if (!mMission.running || e instanceof ClosedByInterruptException) return; - mRecovery.attempts++; - mMission.notifyError(e); - } + resolve(url); } - private void resolve(String url) throws IOException, DownloadMission.HttpError { + private void resolve(String url) throws IOException, HttpError { if (mRecovery.validateCondition == null) { Log.w(TAG, "validation condition not defined, the resource can be stale"); } @@ -190,10 +207,7 @@ public class DownloadMissionRecover extends Thread { return; } - throw new DownloadMission.HttpError(code); - } catch (Exception e) { - if (!mMission.running || e instanceof ClosedByInterruptException) return; - throw e; + throw new HttpError(code); } finally { disconnect(); } @@ -205,14 +219,14 @@ public class DownloadMissionRecover extends Thread { ); mMission.urls[mMission.current] = url; - mRecovery.attempts = 0; if (url == null) { + mMission.urls = new String[0]; mMission.notifyError(ERROR_RESOURCE_GONE, null); return; } - if (notInitialized) return; + if (mNotInitialized) return; if (stale) { mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index b0dc793bc..4aa6e912e 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -87,6 +87,7 @@ public class DownloadRunnable extends Thread { if (mConn.getResponseCode() == 416) { if (block.done > 0) { // try again from the start (of the block) + mMission.notifyProgress(-block.done); block.done = 0; retry = true; mConn.disconnect(); @@ -114,7 +115,7 @@ public class DownloadRunnable extends Thread { int len; // use always start <= end - // fixes a deadlock in DownloadRunnable because youtube is sending one byte alone after downloading 26MiB exactly + // fixes a deadlock because in some videos, youtube is sending one byte alone while (start <= end && mMission.running && (len = is.read(buf, 0, buf.length)) != -1) { f.write(buf, 0, len); start += len; @@ -135,7 +136,7 @@ public class DownloadRunnable extends Thread { if (mId == 1) { // only the first thread will execute the recovery procedure - mMission.doRecover(e); + mMission.doRecover(ERROR_HTTP_FORBIDDEN); } return; } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index e64322b48..9cb40cb32 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -1,8 +1,9 @@ package us.shandian.giga.get; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import org.schabi.newpipe.streams.io.SharpStream; import java.io.IOException; @@ -47,22 +48,10 @@ public class DownloadRunnableFallback extends Thread { if (mF != null) mF.close(); } - private long loadPosition() { - synchronized (mMission.LOCK) { - return mMission.fallbackResumeOffset; - } - } - - private void savePosition(long position) { - synchronized (mMission.LOCK) { - mMission.fallbackResumeOffset = position; - } - } - @Override public void run() { boolean done; - long start = loadPosition(); + long start = mMission.fallbackResumeOffset; if (DEBUG && !mMission.unknownLength && start > 0) { Log.i(TAG, "Resuming a single-thread download at " + start); @@ -83,6 +72,7 @@ public class DownloadRunnableFallback extends Thread { // check if the download can be resumed if (mConn.getResponseCode() == 416 && start > 0) { + mMission.notifyProgress(-start); start = 0; mRetryCount--; throw new DownloadMission.HttpError(416); @@ -92,6 +82,11 @@ public class DownloadRunnableFallback extends Thread { if (!mMission.unknownLength) mMission.unknownLength = Utility.getContentLength(mConn) == -1; + if (mMission.unknownLength || mConn.getResponseCode() == 200) { + // restart amount of bytes downloaded + mMission.done = mMission.offsets[mMission.current] - mMission.offsets[0]; + } + mF = mMission.storage.getStream(); mF.seek(mMission.offsets[mMission.current] + start); @@ -113,14 +108,14 @@ public class DownloadRunnableFallback extends Thread { } catch (Exception e) { dispose(); - savePosition(start); + mMission.fallbackResumeOffset = start; if (!mMission.running || e instanceof ClosedByInterruptException) return; if (e instanceof HttpError && ((HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { // for youtube streams. The url has expired, recover dispose(); - mMission.doRecover(e); + mMission.doRecover(ERROR_HTTP_FORBIDDEN); return; } @@ -140,7 +135,7 @@ public class DownloadRunnableFallback extends Thread { if (done) { mMission.notifyFinished(); } else { - savePosition(start); + mMission.fallbackResumeOffset = start; } } diff --git a/app/src/main/java/us/shandian/giga/get/FinishedMission.java b/app/src/main/java/us/shandian/giga/get/FinishedMission.java index b468f3c76..6bc5423b8 100644 --- a/app/src/main/java/us/shandian/giga/get/FinishedMission.java +++ b/app/src/main/java/us/shandian/giga/get/FinishedMission.java @@ -2,17 +2,17 @@ package us.shandian.giga.get; import androidx.annotation.NonNull; -public class FinishedMission extends Mission { +public class FinishedMission extends Mission { public FinishedMission() { } public FinishedMission(@NonNull DownloadMission mission) { source = mission.source; - length = mission.length;// ¿or mission.done? + length = mission.length; timestamp = mission.timestamp; kind = mission.kind; storage = mission.storage; - } + } diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java index bd1d9bc49..f6a3a3984 100644 --- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java @@ -2,7 +2,8 @@ package us.shandian.giga.get; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; + +import androidx.annotation.NonNull; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.AudioStream; @@ -23,8 +24,6 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { byte kind; String validateCondition = null; - transient int attempts = 0; - public MissionRecoveryInfo(@NonNull Stream stream) { if (stream instanceof AudioStream) { desiredBitrate = ((AudioStream) stream).average_bitrate; @@ -51,7 +50,7 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { public String toString() { String info; StringBuilder str = new StringBuilder(); - str.append("type="); + str.append("{type="); switch (kind) { case 'a': str.append("audio"); @@ -73,7 +72,8 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { str.append(" format=") .append(format.getName()) .append(' ') - .append(info); + .append(info) + .append('}'); return str.toString(); } diff --git a/app/src/main/java/us/shandian/giga/io/ChunkFileInputStream.java b/app/src/main/java/us/shandian/giga/io/ChunkFileInputStream.java index 16a90fcee..98015e37e 100644 --- a/app/src/main/java/us/shandian/giga/io/ChunkFileInputStream.java +++ b/app/src/main/java/us/shandian/giga/io/ChunkFileInputStream.java @@ -5,21 +5,23 @@ import org.schabi.newpipe.streams.io.SharpStream; import java.io.IOException; public class ChunkFileInputStream extends SharpStream { + private static final int REPORT_INTERVAL = 256 * 1024; private SharpStream source; private final long offset; private final long length; private long position; - public ChunkFileInputStream(SharpStream target, long start) throws IOException { - this(target, start, target.length()); - } + private long progressReport; + private final ProgressReport onProgress; - public ChunkFileInputStream(SharpStream target, long start, long end) throws IOException { + public ChunkFileInputStream(SharpStream target, long start, long end, ProgressReport callback) throws IOException { source = target; offset = start; length = end - start; position = 0; + onProgress = callback; + progressReport = REPORT_INTERVAL; if (length < 1) { source.close(); @@ -60,12 +62,12 @@ public class ChunkFileInputStream extends SharpStream { } @Override - public int read(byte b[]) throws IOException { + public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override - public int read(byte b[], int off, int len) throws IOException { + public int read(byte[] b, int off, int len) throws IOException { if ((position + len) > length) { len = (int) (length - position); } @@ -76,6 +78,11 @@ public class ChunkFileInputStream extends SharpStream { int res = source.read(b, off, len); position += res; + if (onProgress != null && position > progressReport) { + onProgress.report(position); + progressReport = position + REPORT_INTERVAL; + } + return res; } diff --git a/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java b/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java index e2afb9202..102580570 100644 --- a/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java +++ b/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java @@ -174,12 +174,12 @@ public class CircularFileWriter extends SharpStream { } @Override - public void write(byte b[]) throws IOException { + public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override - public void write(byte b[], int off, int len) throws IOException { + public void write(byte[] b, int off, int len) throws IOException { if (len == 0) { return; } @@ -261,7 +261,7 @@ public class CircularFileWriter extends SharpStream { @Override public void rewind() throws IOException { if (onProgress != null) { - onProgress.report(-out.length - aux.length);// rollback the whole progress + onProgress.report(0);// rollback the whole progress } seek(0); @@ -357,16 +357,6 @@ public class CircularFileWriter extends SharpStream { long check(); } - public interface ProgressReport { - - /** - * Report the size of the new file - * - * @param progress the new size - */ - void report(long progress); - } - public interface WriteErrorHandle { /** @@ -381,10 +371,10 @@ public class CircularFileWriter extends SharpStream { class BufferedFile { - protected final SharpStream target; + final SharpStream target; private long offset; - protected long length; + long length; private byte[] queue = new byte[QUEUE_BUFFER_SIZE]; private int queueSize; @@ -397,16 +387,16 @@ public class CircularFileWriter extends SharpStream { this.target = target; } - protected long getOffset() { + long getOffset() { return offset + queueSize;// absolute offset in the file } - protected void close() { + void close() { queue = null; target.close(); } - protected void write(byte b[], int off, int len) throws IOException { + void write(byte[] b, int off, int len) throws IOException { while (len > 0) { // if the queue is full, the method available() will flush the queue int read = Math.min(available(), len); @@ -436,7 +426,7 @@ public class CircularFileWriter extends SharpStream { target.seek(0); } - protected int available() throws IOException { + int available() throws IOException { if (queueSize >= queue.length) { flush(); return queue.length; @@ -451,7 +441,7 @@ public class CircularFileWriter extends SharpStream { target.seek(0); } - protected void seek(long absoluteOffset) throws IOException { + void seek(long absoluteOffset) throws IOException { if (absoluteOffset == offset) { return;// nothing to do } diff --git a/app/src/main/java/us/shandian/giga/io/ProgressReport.java b/app/src/main/java/us/shandian/giga/io/ProgressReport.java new file mode 100644 index 000000000..14ae9ded9 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/io/ProgressReport.java @@ -0,0 +1,11 @@ +package us.shandian.giga.io; + +public interface ProgressReport { + + /** + * Report the size of the new file + * + * @param progress the new size + */ + void report(long progress); +} \ No newline at end of file diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java index 605c0a88b..04958c495 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java @@ -1,6 +1,6 @@ package us.shandian.giga.postprocessing; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.schabi.newpipe.streams.OggFromWebMWriter; import org.schabi.newpipe.streams.io.SharpStream; diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java index 92510c3df..773ff92d1 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java @@ -1,9 +1,9 @@ package us.shandian.giga.postprocessing; -import android.os.Message; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import org.schabi.newpipe.streams.io.SharpStream; import java.io.File; @@ -14,11 +14,11 @@ import us.shandian.giga.get.DownloadMission; import us.shandian.giga.io.ChunkFileInputStream; import us.shandian.giga.io.CircularFileWriter; import us.shandian.giga.io.CircularFileWriter.OffsetChecker; -import us.shandian.giga.service.DownloadManagerService; +import us.shandian.giga.io.ProgressReport; import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; +import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING; import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_HOLD; -import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION; public abstract class Postprocessing implements Serializable { @@ -63,22 +63,22 @@ public abstract class Postprocessing implements Serializable { * Get a boolean value that indicate if the given algorithm work on the same * file */ - public final boolean worksOnSameFile; + public boolean worksOnSameFile; /** * Indicates whether the selected algorithm needs space reserved at the beginning of the file */ - public final boolean reserveSpace; + public boolean reserveSpace; /** * Gets the given algorithm short name */ - private final String name; + private String name; private String[] args; - protected transient DownloadMission mission; + private transient DownloadMission mission; private File tempFile; @@ -109,16 +109,24 @@ public abstract class Postprocessing implements Serializable { long finalLength = -1; mission.done = 0; - mission.length = mission.storage.length(); + + long length = mission.storage.length() - mission.offsets[0]; + mission.length = length > mission.nearLength ? length : mission.nearLength; + + final ProgressReport readProgress = (long position) -> { + position -= mission.offsets[0]; + if (position > mission.done) mission.done = position; + }; if (worksOnSameFile) { ChunkFileInputStream[] sources = new ChunkFileInputStream[mission.urls.length]; try { - int i = 0; - for (; i < sources.length - 1; i++) { - sources[i] = new ChunkFileInputStream(mission.storage.getStream(), mission.offsets[i], mission.offsets[i + 1]); + for (int i = 0, j = 1; i < sources.length; i++, j++) { + SharpStream source = mission.storage.getStream(); + long end = j < sources.length ? mission.offsets[j] : source.length(); + + sources[i] = new ChunkFileInputStream(source, mission.offsets[i], end, readProgress); } - sources[i] = new ChunkFileInputStream(mission.storage.getStream(), mission.offsets[i]); if (test(sources)) { for (SharpStream source : sources) source.rewind(); @@ -140,7 +148,7 @@ public abstract class Postprocessing implements Serializable { }; out = new CircularFileWriter(mission.storage.getStream(), tempFile, checker); - out.onProgress = this::progressReport; + out.onProgress = (long position) -> mission.done = position; out.onWriteError = (err) -> { mission.psState = 3; @@ -187,11 +195,10 @@ public abstract class Postprocessing implements Serializable { if (result == OK_RESULT) { if (finalLength != -1) { - mission.done = finalLength; mission.length = finalLength; } } else { - mission.errCode = ERROR_UNKNOWN_EXCEPTION; + mission.errCode = ERROR_POSTPROCESSING; mission.errObject = new RuntimeException("post-processing algorithm returned " + result); } @@ -229,23 +236,12 @@ public abstract class Postprocessing implements Serializable { return args[index]; } - private void progressReport(long done) { - mission.done = done; - if (mission.length < mission.done) mission.length = mission.done; - - Message m = new Message(); - m.what = DownloadManagerService.MESSAGE_PROGRESS; - m.obj = mission; - - mission.mHandler.sendMessage(m); - } - @NonNull @Override public String toString() { StringBuilder str = new StringBuilder(); - str.append("name=").append(name).append('['); + str.append("{ name=").append(name).append('['); if (args != null) { for (String arg : args) { @@ -255,6 +251,6 @@ public abstract class Postprocessing implements Serializable { str.delete(0, 1); } - return str.append(']').toString(); + return str.append("] }").toString(); } } diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index 2d1e9cd00..e8bc468e9 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -2,13 +2,11 @@ package us.shandian.giga.service; import android.content.Context; import android.os.Handler; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.DiffUtil; -import android.util.Log; -import android.widget.Toast; - -import org.schabi.newpipe.R; import java.io.File; import java.io.IOException; @@ -152,6 +150,8 @@ public class DownloadManager { continue; } + mis.threads = new Thread[0]; + boolean exists; try { mis.storage = StoredFileHelper.deserialize(mis.storage, ctx); @@ -170,8 +170,6 @@ public class DownloadManager { // is Java IO (avoid showing the "Save as..." dialog) if (exists && mis.storage.isDirect() && !mis.storage.delete()) Log.w(TAG, "Unable to delete incomplete download file: " + sub.getPath()); - - exists = true; } mis.psState = 0; @@ -243,7 +241,6 @@ public class DownloadManager { boolean start = !mPrefQueueLimit || getRunningMissionsCount() < 1; if (canDownloadInCurrentNetwork() && start) { - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS); mission.start(); } } @@ -252,7 +249,6 @@ public class DownloadManager { public void resumeMission(DownloadMission mission) { if (!mission.running) { - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS); mission.start(); } } @@ -261,7 +257,6 @@ public class DownloadManager { if (mission.running) { mission.setEnqueued(false); mission.pause(); - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED); } } @@ -274,7 +269,6 @@ public class DownloadManager { mFinishedMissionStore.deleteMission(mission); } - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_DELETED); mission.delete(); } } @@ -291,7 +285,6 @@ public class DownloadManager { mFinishedMissionStore.deleteMission(mission); } - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_DELETED); mission.storage = null; mission.delete(); } @@ -374,35 +367,29 @@ public class DownloadManager { } public void pauseAllMissions(boolean force) { - boolean flag = false; - synchronized (this) { for (DownloadMission mission : mMissionsPending) { if (!mission.running || mission.isPsRunning() || mission.isFinished()) continue; - if (force) mission.threads = null;// avoid waiting for threads + if (force) { + // avoid waiting for threads + mission.init = null; + mission.threads = new Thread[0]; + } mission.pause(); - flag = true; } } - - if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED); } public void startAllMissions() { - boolean flag = false; - synchronized (this) { for (DownloadMission mission : mMissionsPending) { if (mission.running || mission.isCorrupt()) continue; - flag = true; mission.start(); } } - - if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS); } /** @@ -483,28 +470,18 @@ public class DownloadManager { boolean isMetered = mPrefMeteredDownloads && mLastNetworkStatus == NetworkState.MeteredOperating; - int running = 0; - int paused = 0; synchronized (this) { for (DownloadMission mission : mMissionsPending) { if (mission.isCorrupt() || mission.isPsRunning()) continue; if (mission.running && isMetered) { - paused++; mission.pause(); } else if (!mission.running && !isMetered && mission.enqueued) { - running++; mission.start(); if (mPrefQueueLimit) break; } } } - - if (running > 0) { - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS); - return; - } - if (paused > 0) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED); } void updateMaximumAttempts() { @@ -513,22 +490,6 @@ public class DownloadManager { } } - /** - * Fast check for pending downloads. If exists, the user will be notified - * TODO: call this method in somewhere - * - * @param context the application context - */ - public static void notifyUserPendingDownloads(Context context) { - int pending = getPendingDir(context).list().length; - if (pending < 1) return; - - Toast.makeText(context, context.getString( - R.string.msg_pending_downloads, - String.valueOf(pending) - ), Toast.LENGTH_LONG).show(); - } - public MissionState checkForExistingMission(StoredFileHelper storage) { synchronized (this) { DownloadMission pending = getPendingMission(storage); diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index ea9029c0b..3da0e75b8 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -25,14 +25,15 @@ import android.os.IBinder; import android.os.Message; import android.os.Parcelable; import android.preference.PreferenceManager; +import android.util.Log; +import android.util.SparseArray; +import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat.Builder; -import android.util.Log; -import android.util.SparseArray; -import android.widget.Toast; import org.schabi.newpipe.R; import org.schabi.newpipe.download.DownloadActivity; @@ -41,8 +42,6 @@ import org.schabi.newpipe.player.helper.LockManager; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.MissionRecoveryInfo; @@ -58,11 +57,11 @@ public class DownloadManagerService extends Service { private static final String TAG = "DownloadManagerService"; + public static final int MESSAGE_RUNNING = 0; public static final int MESSAGE_PAUSED = 1; public static final int MESSAGE_FINISHED = 2; - public static final int MESSAGE_PROGRESS = 3; - public static final int MESSAGE_ERROR = 4; - public static final int MESSAGE_DELETED = 5; + public static final int MESSAGE_ERROR = 3; + public static final int MESSAGE_DELETED = 4; private static final int FOREGROUND_NOTIFICATION_ID = 1000; private static final int DOWNLOADS_NOTIFICATION_ID = 1001; @@ -217,9 +216,11 @@ public class DownloadManagerService extends Service { .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ); } + return START_NOT_STICKY; } } - return START_NOT_STICKY; + + return START_STICKY; } @Override @@ -250,6 +251,7 @@ public class DownloadManagerService extends Service { if (icDownloadFailed != null) icDownloadFailed.recycle(); if (icLauncher != null) icLauncher.recycle(); + mHandler = null; mManager.pauseAllMissions(true); } @@ -274,6 +276,8 @@ public class DownloadManagerService extends Service { } private boolean handleMessage(@NonNull Message msg) { + if (mHandler == null) return true; + DownloadMission mission = (DownloadMission) msg.obj; switch (msg.what) { @@ -284,7 +288,7 @@ public class DownloadManagerService extends Service { handleConnectivityState(false); updateForegroundState(mManager.runMissions()); break; - case MESSAGE_PROGRESS: + case MESSAGE_RUNNING: updateForegroundState(true); break; case MESSAGE_ERROR: @@ -300,11 +304,8 @@ public class DownloadManagerService extends Service { if (msg.what != MESSAGE_ERROR) mFailedDownloads.delete(mFailedDownloads.indexOfValue(mission)); - synchronized (mEchoObservers) { - for (Callback observer : mEchoObservers) { - observer.handleMessage(msg); - } - } + for (Callback observer : mEchoObservers) + observer.handleMessage(msg); return true; } @@ -523,16 +524,6 @@ public class DownloadManagerService extends Service { return PendingIntent.getService(this, intent.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT); } - private void manageObservers(Callback handler, boolean add) { - synchronized (mEchoObservers) { - if (add) { - mEchoObservers.add(handler); - } else { - mEchoObservers.remove(handler); - } - } - } - private void manageLock(boolean acquire) { if (acquire == mLockAcquired) return; @@ -605,11 +596,11 @@ public class DownloadManagerService extends Service { } public void addMissionEventListener(Callback handler) { - manageObservers(handler, true); + mEchoObservers.add(handler); } public void removeMissionEventListener(Callback handler) { - manageObservers(handler, false); + mEchoObservers.remove(handler); } public void clearDownloadNotifications() { diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 78fd7ea9d..e3a7f112a 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -10,16 +10,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.os.Message; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.core.content.FileProvider; -import androidx.core.view.ViewCompat; -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.Log; import android.util.SparseArray; import android.view.HapticFeedbackConstants; @@ -34,6 +24,17 @@ import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.FileProvider; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; + import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; @@ -82,6 +83,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb private static final String TAG = "MissionAdapter"; private static final String UNDEFINED_PROGRESS = "--.-%"; private static final String DEFAULT_MIME_TYPE = "*/*"; + private static final String UNDEFINED_ETA = "--:--"; static { @@ -103,10 +105,11 @@ public class MissionAdapter extends Adapter implements Handler.Callb private View mEmptyMessage; private RecoverHelper mRecover; - public MissionAdapter(Context context, @NonNull DownloadManager downloadManager, View emptyMessage) { + private final Runnable rUpdater = this::updater; + + public MissionAdapter(Context context, @NonNull DownloadManager downloadManager, View emptyMessage, View root) { mContext = context; mDownloadManager = downloadManager; - mDeleter = null; mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mLayout = R.layout.mission_item; @@ -117,7 +120,10 @@ public class MissionAdapter extends Adapter implements Handler.Callb mIterator = downloadManager.getIterator(); + mDeleter = new Deleter(root, mContext, this, mDownloadManager, mIterator, mHandler); + checkEmptyMessageVisibility(); + onResume(); } @Override @@ -142,17 +148,13 @@ public class MissionAdapter extends Adapter implements Handler.Callb if (h.item.mission instanceof DownloadMission) { mPendingDownloadsItems.remove(h); if (mPendingDownloadsItems.size() < 1) { - setAutoRefresh(false); checkMasterButtonsVisibility(); } } h.popupMenu.dismiss(); h.item = null; - h.lastTimeStamp = -1; - h.lastDone = -1; - h.lastCurrent = -1; - h.state = 0; + h.resetSpeedMeasure(); } @Override @@ -191,7 +193,6 @@ public class MissionAdapter extends Adapter implements Handler.Callb h.size.setText(length); h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause); - h.lastCurrent = mission.current; updateProgress(h); mPendingDownloadsItems.add(h); } else { @@ -216,20 +217,10 @@ public class MissionAdapter extends Adapter implements Handler.Callb private void updateProgress(ViewHolderItem h) { if (h == null || h.item == null || h.item.mission instanceof FinishedMission) return; - long now = System.currentTimeMillis(); DownloadMission mission = (DownloadMission) h.item.mission; - - if (h.lastCurrent != mission.current) { - h.lastCurrent = mission.current; - h.lastTimeStamp = now; - h.lastDone = 0; - } else { - if (h.lastTimeStamp == -1) h.lastTimeStamp = now; - if (h.lastDone == -1) h.lastDone = mission.done; - } - - long deltaTime = now - h.lastTimeStamp; - long deltaDone = mission.done - h.lastDone; + double done = mission.done; + long length = mission.getLength(); + long now = System.currentTimeMillis(); boolean hasError = mission.errCode != ERROR_NOTHING; // hide on error @@ -237,19 +228,16 @@ public class MissionAdapter extends Adapter implements Handler.Callb // show if length is unknown h.progress.setMarquee(mission.isRecovering() || !hasError && (!mission.isInitialized() || mission.unknownLength)); - float progress; + double progress; if (mission.unknownLength) { - progress = Float.NaN; + progress = Double.NaN; h.progress.setProgress(0f); } else { - progress = (float) ((double) mission.done / mission.length); - if (mission.urls.length > 1 && mission.current < mission.urls.length) { - progress = (progress / mission.urls.length) + ((float) mission.current / mission.urls.length); - } + progress = done / length; } if (hasError) { - h.progress.setProgress(isNotFinite(progress) ? 1f : progress); + h.progress.setProgress(isNotFinite(progress) ? 1d : progress); h.status.setText(R.string.msg_error); } else if (isNotFinite(progress)) { h.status.setText(UNDEFINED_PROGRESS); @@ -258,59 +246,78 @@ public class MissionAdapter extends Adapter implements Handler.Callb h.progress.setProgress(progress); } - long length = mission.getLength(); + @StringRes int state; + String sizeStr = Utility.formatBytes(length).concat(" "); - int state; if (mission.isPsFailed() || mission.errCode == ERROR_POSTPROCESSING_HOLD) { - state = 0; + h.size.setText(sizeStr); + return; } else if (!mission.running) { - state = mission.enqueued ? 1 : 2; + state = mission.enqueued ? R.string.queued : R.string.paused; } else if (mission.isPsRunning()) { - state = 3; + state = R.string.post_processing; + } else if (mission.isRecovering()) { + state = R.string.recovering; } else { state = 0; } if (state != 0) { // update state without download speed - if (h.state != state) { - String statusStr; - h.state = state; + h.size.setText(sizeStr.concat("(").concat(mContext.getString(state)).concat(")")); + h.resetSpeedMeasure(); + return; + } - switch (state) { - case 1: - statusStr = mContext.getString(R.string.queued); - break; - case 2: - statusStr = mContext.getString(R.string.paused); - break; - case 3: - statusStr = mContext.getString(R.string.post_processing); - break; - default: - statusStr = "?"; - break; - } + if (h.lastTimestamp < 0) { + h.size.setText(sizeStr); + h.lastTimestamp = now; + h.lastDone = done; + return; + } - h.size.setText(Utility.formatBytes(length).concat(" (").concat(statusStr).concat(")")); - } else if (deltaDone > 0) { - h.lastTimeStamp = now; - h.lastDone = mission.done; - } + long deltaTime = now - h.lastTimestamp; + double deltaDone = done - h.lastDone; + if (h.lastDone > done) { + h.lastDone = done; + h.size.setText(sizeStr); return; } if (deltaDone > 0 && deltaTime > 0) { - float speed = (deltaDone * 1000f) / deltaTime; + float speed = (float) ((deltaDone * 1000d) / deltaTime); + float averageSpeed = speed; - String speedStr = Utility.formatSpeed(speed); - String sizeStr = Utility.formatBytes(length); + if (h.lastSpeedIdx < 0) { + for (int i = 0; i < h.lastSpeed.length; i++) { + h.lastSpeed[i] = speed; + } + h.lastSpeedIdx = 0; + } else { + for (int i = 0; i < h.lastSpeed.length; i++) { + averageSpeed += h.lastSpeed[i]; + } + averageSpeed /= h.lastSpeed.length + 1f; + } - h.size.setText(sizeStr.concat(" ").concat(speedStr)); + String speedStr = Utility.formatSpeed(averageSpeed); + String etaStr; - h.lastTimeStamp = now; - h.lastDone = mission.done; + if (mission.unknownLength) { + etaStr = ""; + } else { + long eta = (long) Math.ceil((length - done) / averageSpeed); + etaStr = " @ ".concat(Utility.stringifySeconds(eta)); + } + + h.size.setText(sizeStr.concat(speedStr).concat(etaStr)); + + h.lastTimestamp = now; + h.lastDone = done; + h.lastSpeed[h.lastSpeedIdx++] = speed; + + if (h.lastSpeedIdx >= h.lastSpeed.length) h.lastSpeedIdx = 0; } } @@ -389,6 +396,13 @@ public class MissionAdapter extends Adapter implements Handler.Callb return true; } + private ViewHolderItem getViewHolder(Object mission) { + for (ViewHolderItem h : mPendingDownloadsItems) { + if (h.item.mission == mission) return h; + } + return null; + } + @Override public boolean handleMessage(@NonNull Message msg) { if (mStartButton != null && mPauseButton != null) { @@ -396,33 +410,28 @@ public class MissionAdapter extends Adapter implements Handler.Callb } switch (msg.what) { - case DownloadManagerService.MESSAGE_PROGRESS: case DownloadManagerService.MESSAGE_ERROR: case DownloadManagerService.MESSAGE_FINISHED: + case DownloadManagerService.MESSAGE_DELETED: + case DownloadManagerService.MESSAGE_PAUSED: break; default: return false; } - if (msg.what == DownloadManagerService.MESSAGE_PROGRESS) { - setAutoRefresh(true); - return true; - } + ViewHolderItem h = getViewHolder(msg.obj); + if (h == null) return false; - for (ViewHolderItem h : mPendingDownloadsItems) { - if (h.item.mission != msg.obj) continue; - - if (msg.what == DownloadManagerService.MESSAGE_FINISHED) { + switch (msg.what) { + case DownloadManagerService.MESSAGE_FINISHED: + case DownloadManagerService.MESSAGE_DELETED: // DownloadManager should mark the download as finished applyChanges(); return true; - } - - updateProgress(h); - return true; } - return false; + updateProgress(h); + return true; } private void showError(@NonNull DownloadMission mission) { @@ -470,8 +479,13 @@ public class MissionAdapter extends Adapter implements Handler.Callb msg = R.string.error_insufficient_storage; break; case ERROR_UNKNOWN_EXCEPTION: - showError(mission, UserAction.DOWNLOAD_FAILED, R.string.general_error); - return; + if (mission.errObject != null) { + showError(mission, UserAction.DOWNLOAD_FAILED, R.string.general_error); + return; + } else { + msg = R.string.msg_error; + break; + } case ERROR_PROGRESS_LOST: msg = R.string.error_progress_lost; break; @@ -521,7 +535,9 @@ public class MissionAdapter extends Adapter implements Handler.Callb request.append(" ["); if (mission.recoveryInfo != null) { for (MissionRecoveryInfo recovery : mission.recoveryInfo) - request.append(" {").append(recovery.toString()).append("} "); + request.append(' ') + .append(recovery.toString()) + .append(' '); } request.append("]"); @@ -556,16 +572,10 @@ public class MissionAdapter extends Adapter implements Handler.Callb switch (id) { case R.id.start: h.status.setText(UNDEFINED_PROGRESS); - h.state = -1; - h.size.setText(Utility.formatBytes(mission.getLength())); mDownloadManager.resumeMission(mission); return true; case R.id.pause: - h.state = -1; mDownloadManager.pauseMission(mission); - updateProgress(h); - h.lastTimeStamp = -1; - h.lastDone = -1; return true; case R.id.error_message_view: showError(mission); @@ -598,12 +608,9 @@ public class MissionAdapter extends Adapter implements Handler.Callb shareFile(h.item.mission); return true; case R.id.delete: - if (mDeleter == null) { - mDownloadManager.deleteMission(h.item.mission); - } else { - mDeleter.append(h.item.mission); - } + mDeleter.append(h.item.mission); applyChanges(); + checkMasterButtonsVisibility(); return true; case R.id.md5: case R.id.sha1: @@ -639,7 +646,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb mIterator.end(); for (ViewHolderItem item : mPendingDownloadsItems) { - item.lastTimeStamp = -1; + item.resetSpeedMeasure(); } notifyDataSetChanged(); @@ -672,6 +679,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb public void checkMasterButtonsVisibility() { boolean[] state = mIterator.hasValidPendingMissions(); + Log.d(TAG, "checkMasterButtonsVisibility() running=" + state[0] + " paused=" + state[1]); setButtonVisible(mPauseButton, state[0]); setButtonVisible(mStartButton, state[1]); } @@ -681,86 +689,57 @@ public class MissionAdapter extends Adapter implements Handler.Callb button.setVisible(visible); } - public void ensurePausedMissions() { + public void refreshMissionItems() { for (ViewHolderItem h : mPendingDownloadsItems) { if (((DownloadMission) h.item.mission).running) continue; updateProgress(h); - h.lastTimeStamp = -1; - h.lastDone = -1; + h.resetSpeedMeasure(); } } - public void deleterDispose(boolean commitChanges) { - if (mDeleter != null) mDeleter.dispose(commitChanges); + public void onDestroy() { + mDeleter.dispose(); } - public void deleterLoad(View view) { - if (mDeleter == null) - mDeleter = new Deleter(view, mContext, this, mDownloadManager, mIterator, mHandler); + public void onResume() { + mDeleter.resume(); + mHandler.post(rUpdater); } - public void deleterResume() { - if (mDeleter != null) mDeleter.resume(); - } - - public void recoverMission(DownloadMission mission) { - for (ViewHolderItem h : mPendingDownloadsItems) { - if (mission != h.item.mission) continue; - - mission.errObject = null; - mission.resetState(true, false, DownloadMission.ERROR_NOTHING); - - h.status.setText(UNDEFINED_PROGRESS); - h.state = -1; - h.size.setText(Utility.formatBytes(mission.getLength())); - h.progress.setMarquee(true); - - mDownloadManager.resumeMission(mission); - return; - } - - } - - - private boolean mUpdaterRunning = false; - private final Runnable rUpdater = this::updater; - public void onPaused() { - setAutoRefresh(false); + mDeleter.pause(); + mHandler.removeCallbacks(rUpdater); } - private void setAutoRefresh(boolean enabled) { - if (enabled && !mUpdaterRunning) { - mUpdaterRunning = true; - updater(); - } else if (!enabled && mUpdaterRunning) { - mUpdaterRunning = false; - mHandler.removeCallbacks(rUpdater); - } + + public void recoverMission(DownloadMission mission) { + ViewHolderItem h = getViewHolder(mission); + if (h == null) return; + + mission.errObject = null; + mission.resetState(true, false, DownloadMission.ERROR_NOTHING); + + h.status.setText(UNDEFINED_PROGRESS); + h.size.setText(Utility.formatBytes(mission.getLength())); + h.progress.setMarquee(true); + + mDownloadManager.resumeMission(mission); } private void updater() { - if (!mUpdaterRunning) return; - - boolean running = false; for (ViewHolderItem h : mPendingDownloadsItems) { // check if the mission is running first if (!((DownloadMission) h.item.mission).running) continue; updateProgress(h); - running = true; } - if (running) { - mHandler.postDelayed(rUpdater, 1000); - } else { - mUpdaterRunning = false; - } + mHandler.postDelayed(rUpdater, 1000); } - private boolean isNotFinite(Float value) { - return Float.isNaN(value) || Float.isInfinite(value); + private boolean isNotFinite(double value) { + return Double.isNaN(value) || Double.isInfinite(value); } public void setRecover(@NonNull RecoverHelper callback) { @@ -789,10 +768,11 @@ public class MissionAdapter extends Adapter implements Handler.Callb MenuItem source; MenuItem checksum; - long lastTimeStamp = -1; - long lastDone = -1; - int lastCurrent = -1; - int state = 0; + long lastTimestamp = -1; + double lastDone; + int lastSpeedIdx; + float[] lastSpeed = new float[3]; + String estimatedTimeArrival = UNDEFINED_ETA; ViewHolderItem(View view) { super(view); @@ -902,6 +882,12 @@ public class MissionAdapter extends Adapter implements Handler.Callb return popup; } + + private void resetSpeedMeasure() { + estimatedTimeArrival = UNDEFINED_ETA; + lastTimestamp = -1; + lastSpeedIdx = -1; + } } class ViewHolderHeader extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java index 81b4e33e8..a0828c23d 100644 --- a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java +++ b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java @@ -4,9 +4,10 @@ import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.os.Handler; -import com.google.android.material.snackbar.Snackbar; import android.view.View; +import com.google.android.material.snackbar.Snackbar; + import org.schabi.newpipe.R; import java.util.ArrayList; @@ -113,7 +114,7 @@ public class Deleter { show(); } - private void pause() { + public void pause() { running = false; mHandler.removeCallbacks(rNext); mHandler.removeCallbacks(rShow); @@ -126,13 +127,11 @@ public class Deleter { mHandler.postDelayed(rShow, DELAY_RESUME); } - public void dispose(boolean commitChanges) { + public void dispose() { if (items.size() < 1) return; pause(); - if (!commitChanges) return; - for (Mission mission : items) mDownloadManager.deleteMission(mission); items = null; } diff --git a/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java b/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java index a0ff24aaa..3f638d418 100644 --- a/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java +++ b/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java @@ -9,6 +9,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; + import androidx.annotation.ColorInt; import androidx.annotation.NonNull; @@ -35,8 +36,8 @@ public class ProgressDrawable extends Drawable { mForegroundColor = foreground; } - public void setProgress(float progress) { - mProgress = progress; + public void setProgress(double progress) { + mProgress = (float) progress; invalidateSelf(); } diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 26da47b1f..921eaff5c 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -12,11 +12,6 @@ import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.preference.PreferenceManager; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -24,6 +19,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.R; @@ -72,8 +73,7 @@ public class MissionsFragment extends Fragment { mBinder = (DownloadManagerBinder) binder; mBinder.clearDownloadNotifications(); - mAdapter = new MissionAdapter(mContext, mBinder.getDownloadManager(), mEmpty); - mAdapter.deleterLoad(getView()); + mAdapter = new MissionAdapter(mContext, mBinder.getDownloadManager(), mEmpty, getView()); mAdapter.setRecover(MissionsFragment.this::recoverMission); @@ -132,7 +132,7 @@ public class MissionsFragment extends Fragment { * Added in API level 23. */ @Override - public void onAttach(Context context) { + public void onAttach(@NonNull Context context) { super.onAttach(context); // Bug: in api< 23 this is never called @@ -147,7 +147,7 @@ public class MissionsFragment extends Fragment { */ @SuppressWarnings("deprecation") @Override - public void onAttach(Activity activity) { + public void onAttach(@NonNull Activity activity) { super.onAttach(activity); mContext = activity; @@ -162,7 +162,7 @@ public class MissionsFragment extends Fragment { mBinder.removeMissionEventListener(mAdapter); mBinder.enableNotifications(true); mContext.unbindService(mConnection); - mAdapter.deleterDispose(true); + mAdapter.onDestroy(); mBinder = null; mAdapter = null; @@ -196,13 +196,11 @@ public class MissionsFragment extends Fragment { prompt.create().show(); return true; case R.id.start_downloads: - item.setVisible(false); mBinder.getDownloadManager().startAllMissions(); return true; case R.id.pause_downloads: - item.setVisible(false); mBinder.getDownloadManager().pauseAllMissions(false); - mAdapter.ensurePausedMissions();// update items view + mAdapter.refreshMissionItems();// update items view default: return super.onOptionsItemSelected(item); } @@ -271,23 +269,12 @@ public class MissionsFragment extends Fragment { } } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - - if (mAdapter != null) { - mAdapter.deleterDispose(false); - mForceUpdate = true; - mBinder.removeMissionEventListener(mAdapter); - } - } - @Override public void onResume() { super.onResume(); if (mAdapter != null) { - mAdapter.deleterResume(); + mAdapter.onResume(); if (mForceUpdate) { mForceUpdate = false; @@ -303,7 +290,13 @@ public class MissionsFragment extends Fragment { @Override public void onPause() { super.onPause(); - if (mAdapter != null) mAdapter.onPaused(); + + if (mAdapter != null) { + mForceUpdate = true; + mBinder.removeMissionEventListener(mAdapter); + mAdapter.onPaused(); + } + if (mBinder != null) mBinder.enableNotifications(true); } diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java index 21fdd72ad..46207777a 100644 --- a/app/src/main/java/us/shandian/giga/util/Utility.java +++ b/app/src/main/java/us/shandian/giga/util/Utility.java @@ -4,13 +4,14 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.os.Build; +import android.util.Log; +import android.widget.Toast; + import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import android.util.Log; -import android.widget.Toast; import org.schabi.newpipe.R; import org.schabi.newpipe.streams.io.SharpStream; @@ -26,6 +27,7 @@ import java.io.Serializable; import java.net.HttpURLConnection; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Locale; import us.shandian.giga.io.StoredFileHelper; @@ -39,26 +41,28 @@ public class Utility { } public static String formatBytes(long bytes) { + Locale locale = Locale.getDefault(); if (bytes < 1024) { - return String.format("%d B", bytes); + return String.format(locale, "%d B", bytes); } else if (bytes < 1024 * 1024) { - return String.format("%.2f kB", bytes / 1024d); + return String.format(locale, "%.2f kB", bytes / 1024d); } else if (bytes < 1024 * 1024 * 1024) { - return String.format("%.2f MB", bytes / 1024d / 1024d); + return String.format(locale, "%.2f MB", bytes / 1024d / 1024d); } else { - return String.format("%.2f GB", bytes / 1024d / 1024d / 1024d); + return String.format(locale, "%.2f GB", bytes / 1024d / 1024d / 1024d); } } - public static String formatSpeed(float speed) { + public static String formatSpeed(double speed) { + Locale locale = Locale.getDefault(); if (speed < 1024) { - return String.format("%.2f B/s", speed); + return String.format(locale, "%.2f B/s", speed); } else if (speed < 1024 * 1024) { - return String.format("%.2f kB/s", speed / 1024); + return String.format(locale, "%.2f kB/s", speed / 1024); } else if (speed < 1024 * 1024 * 1024) { - return String.format("%.2f MB/s", speed / 1024 / 1024); + return String.format(locale, "%.2f MB/s", speed / 1024 / 1024); } else { - return String.format("%.2f GB/s", speed / 1024 / 1024 / 1024); + return String.format(locale, "%.2f GB/s", speed / 1024 / 1024 / 1024); } } @@ -188,12 +192,11 @@ public class Utility { switch (type) { case MUSIC: return R.drawable.music; + default: case VIDEO: return R.drawable.video; case SUBTITLE: return R.drawable.subtitle; - default: - return R.drawable.video; } } @@ -274,4 +277,25 @@ public class Utility { return -1; } + + private static String pad(int number) { + return number < 10 ? ("0" + number) : String.valueOf(number); + } + + public static String stringifySeconds(double seconds) { + int h = (int) Math.floor(seconds / 3600); + int m = (int) Math.floor((seconds - (h * 3600)) / 60); + int s = (int) (seconds - (h * 3600) - (m * 60)); + + String str = ""; + + if (h < 1 && m < 1) { + str = "00:"; + } else { + if (h > 0) str = pad(h) + ":"; + if (m > 0) str += pad(m) + ":"; + } + + return str + pad(s); + } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 43b45d15e..86cbbb59a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -471,7 +471,6 @@ غير موجود فشلت المعالجة الاولية حذف التنزيلات المنتهية - "قم بإستكمال %s حيثما يتم التحويل من التنزيلات" توقف أقصى عدد للمحاولات الحد الأقصى لعدد محاولات قبل إلغاء التحميل diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 3c79a96d3..1cf3abd7e 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -458,7 +458,6 @@ Не знойдзена Пасляапрацоўка не ўдалася Ачысціць завершаныя - Аднавіць прыпыненыя загрузкі (%s) Спыніць Максімум спробаў Колькасць спробаў перад адменай загрузкі diff --git a/app/src/main/res/values-cmn/strings.xml b/app/src/main/res/values-cmn/strings.xml index bcb145c16..3ff479bfd 100644 --- a/app/src/main/res/values-cmn/strings.xml +++ b/app/src/main/res/values-cmn/strings.xml @@ -460,7 +460,6 @@ NewPipe 更新可用! 无法创建目标文件夹 服务器不接受多线程下载, 请使用 @string/msg_threads = 1重试 - 继续进行%s个待下载转移 切换至移动数据时有用,尽管一些下载无法被暂停 显示评论 禁用停止显示评论 diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index b741e0d16..9a9cc8654 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -466,7 +466,6 @@ otevření ve vyskakovacím okně Nenalezeno Post-processing selhal Vyčistit dokončená stahování - Pokračovat ve stahování %s souborů, čekajících na stažení Zastavit Maximální počet pokusů o opakování Maximální počet pokusů před zrušením stahování diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 199c2f85d..5e44aab61 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -447,7 +447,6 @@ sat på pause sat i kø Ryd færdige downloads - Fortsæt dine %s ventende overførsler fra Downloads Maksimalt antal genforsøg Maksimalt antal forsøg før downloaden opgives Sæt på pause ved skift til mobildata diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3279e919c..0dc0de8b4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -457,7 +457,6 @@ Nicht gefunden Nachbearbeitung fehlgeschlagen Um fertige Downloads bereinigen - Setze deine %s ausstehenden Übertragungen von Downloads fort Anhalten Maximale Wiederholungen Maximalanzahl der Versuche, bevor der Download abgebrochen wird diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 372cbb1a2..115b8d0b3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -459,7 +459,6 @@ Δεν βρέθηκε Μετεπεξεργασία απέτυχε Εκκαθάριση ολοκληρωμένων λήψεων - Συνέχιση των %s εκκρεμών σας λήψεων Διακοπή Μέγιστες επαναπροσπάθειες Μέγιστος αριθμός προσπαθειών προτού γίνει ακύρωση της λήψης diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b14aab94b..6fcbc9fa7 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -406,6 +406,7 @@ pausado en cola posprocesamiento + recuperando Añadir a cola Acción denegada por el sistema Se eliminó el archivo @@ -424,7 +425,6 @@ Mostrar como grilla Mostrar como lista Limpiar descargas finalizadas - Tienes %s descargas pendientes, ve a Descargas para continuarlas ¿Lo confirma\? Detener Intentos máximos diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 4dfcc3d0e..99dc6cc80 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -460,7 +460,6 @@ Ei leitud Järeltöötlemine nurjus Eemalda lõpetatud allalaadimised - Jätka %s pooleliolevat allalaadimist Stopp Korduskatseid Suurim katsete arv enne allalaadimise tühistamist diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7b636d383..743c6b3fb 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -459,7 +459,6 @@ Ez aurkitua Post-prozesuak huts egin du Garbitu amaitutako deskargak - Berrekin burutzeke dauden %s transferentzia deskargetatik Gelditu Gehienezko saiakerak Deskarga ezeztatu aurretik saiatu beharreko aldi kopurua diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b4388e39f..2091a62fe 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -466,7 +466,6 @@ Nombre maximum de tentatives avant d’annuler le téléchargement Utilisation des onglets par défaut, erreur lors de la lecture des onglets enregistrés Le serveur n’accepte pas les téléchargements multi-fils, veuillez réessayer avec @string/msg_threads = 1 - Continuer vos %s transferts en attente depuis Téléchargement Afficher les commentaires Désactiver pour ne pas afficher les commentaires Lecture automatique diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 5e340d8b3..565f815a1 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -464,7 +464,6 @@ לא נמצא העיבוד המאוחר נכשל פינוי ההורדות שהסתיימו - ניתן להמשיך את %s ההורדות הממתינות שלך דרך ההורדות עצירה מספר הניסיונות החוזרים המרבי מספר הניסיונות החוזרים המרבי בטרם ביטול ההורדה diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index aa4ff9113..a981dcf5e 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -457,7 +457,6 @@ Nije pronađeno Naknadna obrada nije uspjela Obriši završena preuzimanja - Nastavite s prijenosima na čekanju za %s s preuzimanja Stop Maksimalnih ponovnih pokušaja Maksimalni broj pokušaja prije poništavanja preuzimanja diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index d52f5fafa..5fbdcffc1 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -453,7 +453,6 @@ Tidak ditemukan Pengolahan-pasca gagal Hapus unduhan yang sudah selesai - Lanjutkan %s transfer anda yang tertunda dari Unduhan Berhenti Percobaan maksimum Jumlah upaya maksimum sebelum membatalkan unduhan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c92292f99..73633ab03 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -457,7 +457,6 @@ Non trovato Post-processing fallito Pulisci i download completati - Continua i %s trasferimenti in corso dai Download Ferma Tentativi massimi Tentativi massimi prima di cancellare il download diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 58ca2ebff..4c3aeb5c1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -456,7 +456,6 @@ デフォルトのタブを使用します。保存されたタブの読み込みエラーが発生しました メインページに表示されるタブ 新しいバージョンが利用可能なときにアプリの更新を確認する通知を表示します - ダウンロードから %s の保留中の転送を続行します 従量制課金ネットワークの割り込み モバイルデータ通信に切り替える場合に便利ですが、一部のダウンロードは一時停止できません コメントを表示 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index fdc76b04e..39b08347c 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -454,7 +454,6 @@ HTTP 찾을 수 없습니다 후처리 작업이 실패하였습니다 완료된 다운로드 비우기 - 대기중인 %s 다운로드를 지속하세요 멈추기 최대 재시도 횟수 다운로드를 취소하기 전까지 다시 시도할 최대 횟수 diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index daa120ea2..354e7b7de 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -453,7 +453,6 @@ Tidak ditemui Pemprosesan-pasca gagal Hapuskan senarai muat turun yang selesai - Teruskan %s pemindahan anda yang menunggu dari muat turun Berhenti Percubaan maksimum Jumlah percubaan maksimum sebelum membatalkan muat turun diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 6262480b0..e0a08d0a7 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -458,7 +458,6 @@ Ikke funnet Etterbehandling mislyktes Tøm fullførte nedlastinger - Fortsett dine %s ventende overføringer fra Nedlastinger Stopp Maksimalt antall forsøk Maksimalt antall tilkoblingsforsøk før nedlastingen avblåses diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml index f64ff6bf9..5c42bfd23 100644 --- a/app/src/main/res/values-nl-rBE/strings.xml +++ b/app/src/main/res/values-nl-rBE/strings.xml @@ -457,7 +457,6 @@ Niet gevonden Nabewerking mislukt Voltooide downloads wissen - Zet uw %s wachtende downloads verder via Downloads Stoppen Maximaal aantal pogingen Maximaal aantal pogingen vooraleer dat den download wordt geannuleerd diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 6aecc2cd1..b9b86a292 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -457,7 +457,6 @@ Niet gevonden Nabewerking mislukt Voltooide downloads wissen - Zet je %s wachtende downloads voort via Downloads Stop Maximum aantal keer proberen Maximum aantal pogingen voordat de download wordt geannuleerd diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index b57564eba..0e579720a 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -453,7 +453,6 @@ ਨਹੀਂ ਲਭਿਆ Post-processing ਫੇਲ੍ਹ ਮੁਕੰਮਲ ਹੋਈਆਂ ਡਾਊਨਲੋਡ ਸਾਫ਼ ਕਰੋ - ਡਾਉਨਲੋਡਸ ਤੋਂ ਆਪਣੀਆਂ %s ਬਕਾਇਆ ਟ੍ਰਾਂਸਫਰ ਜਾਰੀ ਰੱਖੋ ਰੁੱਕੋ ਵੱਧ ਤੋਂ ਵੱਧ ਕੋਸ਼ਿਸ਼ਾਂ ਡਾਉਨਲੋਡ ਰੱਦ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਵੱਧ ਤੋਂ ਵੱਧ ਕੋਸ਼ਿਸ਼ਾਂ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ca1e52ff2..b7086b34f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -459,7 +459,6 @@ Nie znaleziono Przetwarzanie końcowe nie powiodło się Wyczyść ukończone pobieranie - Kontynuuj %s oczekujące transfery z plików do pobrania Zatrzymaj Maksymalna liczba powtórzeń Maksymalna liczba prób przed anulowaniem pobierania diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0bdf4d006..5de1e6610 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -466,7 +466,6 @@ abrir em modo popup Não encontrado Falha no pós processamento Limpar downloads finalizados - Continuar seus %s downloads pendentes Parar Tentativas Máximas Número máximo de tentativas antes de cancelar o download diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 6d55023d1..88fbb72a6 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -455,7 +455,6 @@ Não encontrado Pós-processamento falhado Limpar transferências concluídas - Continue as suas %s transferências pendentes das Transferências Parar Tentativas máximas Número máximo de tentativas antes de cancelar a transferência diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 51771e1b1..80b587657 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -464,7 +464,6 @@ Загрузка завершена %s загрузок завершено Создать уникальное имя - Возобновить приостановленные загрузки (%s) Максимум попыток Количество попыток перед отменой загрузки Некоторые загрузки не поддерживают докачку и начнутся с начала diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 36c0afd84..cbc201fd5 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -465,7 +465,6 @@ Nenájdené Post-spracovanie zlyhalo Vyčistiť dokončené sťahovania - Pokračujte v preberaní %s zo súborov na prevzatie Stop Maximum opakovaní Maximálny počet pokusov pred zrušením stiahnutia diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 6c9c66f69..1cb6fafd4 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -452,7 +452,6 @@ Bulunamadı İşlem sonrası başarısız Tamamlanan indirmeleri temizle - Beklemedeki %s transferinize İndirmeler\'den devam edin Durdur Azami deneme sayısı İndirmeyi iptal etmeden önce maksimum deneme sayısı diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index fcce99e89..d43b8be66 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -471,7 +471,6 @@ Помилка зчитування збережених вкладок. Використовую типові вкладки. Вкладки, що відображаються на головній сторінці Показувати сповіщення з пропозицією оновити застосунок за наявності нової версії - Продовжити ваші %s відкладених переміщень із Завантажень Корисно під час переходу на мобільні дані, хоча деякі завантаження не можуть бути призупинені Показувати коментарі Вимнути відображення дописів diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index f8860acfd..ab0983e7a 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -452,7 +452,6 @@ Không tìm thấy Xử lý thất bại Dọn các tải về đã hoàn thành - Hãy tiếp tục %s tải về đang chờ Dừng Số lượt thử lại tối đa Số lượt thử lại trước khi hủy tải về diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 310bae3a3..98b9cf381 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -450,7 +450,6 @@ 找不到 後處理失敗 清除已結束的下載 - 繼續從您所擱置中的下載 %s 傳輸 停止 最大重試次數 在取消下載前的最大嘗試數 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f929e0d2b..c2d8d70f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -526,6 +526,7 @@ paused queued post-processing + recovering Queue Action denied by the system @@ -560,7 +561,6 @@ Cannot recover this download Clear finished downloads Are you sure? - Continue your %s pending transfers from Downloads Stop Maximum retries Maximum number of attempts before canceling the download From 527c38adf9ac1c43cd33dd371f7e37efaecb4e9f Mon Sep 17 00:00:00 2001 From: yausername <13ritvik@gmail.com> Date: Sun, 24 Nov 2019 21:08:06 +0530 Subject: [PATCH 182/270] easily switch between multiple peertube instances --- app/build.gradle | 2 +- .../java/org/schabi/newpipe/MainActivity.java | 57 ++- .../settings/ContentSettingsFragment.java | 46 +- .../PeertubeInstanceListFragment.java | 418 ++++++++++++++++++ .../schabi/newpipe/util/PeertubeHelper.java | 65 +++ .../schabi/newpipe/util/ServiceHelper.java | 24 +- .../org/schabi/newpipe/util/ThemeHelper.java | 7 +- .../res/layout/fragment_instance_list.xml | 51 +++ .../main/res/layout/instance_spinner_item.xml | 6 + .../res/layout/instance_spinner_layout.xml | 9 + app/src/main/res/layout/item_instance.xml | 83 ++++ app/src/main/res/values/settings_keys.xml | 5 +- app/src/main/res/values/strings.xml | 7 +- app/src/main/res/xml/content_settings.xml | 8 +- 14 files changed, 724 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java create mode 100644 app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java create mode 100644 app/src/main/res/layout/fragment_instance_list.xml create mode 100644 app/src/main/res/layout/instance_spinner_item.xml create mode 100644 app/src/main/res/layout/instance_spinner_layout.xml create mode 100644 app/src/main/res/layout/item_instance.xml diff --git a/app/build.gradle b/app/build.gradle index 4259d45a2..a128d8841 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.yausername:NewPipeExtractor:bc75c66' + implementation 'com.github.yausername:NewPipeExtractor:6a7680c' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 82d4e4063..927fc1589 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -29,14 +29,18 @@ import android.os.Handler; import android.os.Looper; import android.preference.PreferenceManager; import android.util.Log; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; +import android.widget.Spinner; import android.widget.TextView; import androidx.annotation.NonNull; @@ -47,12 +51,15 @@ import androidx.appcompat.widget.Toolbar; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import com.google.android.material.navigation.NavigationView; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; @@ -61,11 +68,15 @@ import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.PeertubeHelper; import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.ThemeHelper; +import java.util.ArrayList; +import java.util.List; + public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); @@ -300,13 +311,57 @@ public class MainActivity extends AppCompatActivity { final String title = s.getServiceInfo().getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""); - drawerItems.getMenu() + MenuItem menuItem = drawerItems.getMenu() .add(R.id.menu_services_group, s.getServiceId(), ORDER, title) .setIcon(ServiceHelper.getIcon(s.getServiceId())); + + // peertube specifics + if(s.getServiceId() == 3){ + enhancePeertubeMenu(s, menuItem); + } } drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); } + private void enhancePeertubeMenu(StreamingService s, MenuItem menuItem) { + PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance(); + menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : "")); + Spinner spinner = (Spinner) LayoutInflater.from(this).inflate(R.layout.instance_spinner_layout, null); + List instances = PeertubeHelper.getInstanceList(this); + List items = new ArrayList<>(); + int defaultSelect = 0; + for(PeertubeInstance instance: instances){ + items.add(instance.getName()); + if(instance.getUrl().equals(currentInstace.getUrl())){ + defaultSelect = items.size()-1; + } + } + ArrayAdapter adapter = new ArrayAdapter<>(this, R.layout.instance_spinner_item, items); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setSelection(defaultSelect, false); + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + PeertubeInstance newInstance = instances.get(position); + if(newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) return; + PeertubeHelper.selectInstance(newInstance, getApplicationContext()); + changeService(menuItem); + drawer.closeDrawers(); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); + recreate(); + }, 300); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + menuItem.setActionView(spinner); + } + private void showTabs() throws ExtractionException { serviceArrow.setImageResource(R.drawable.ic_arrow_down_white); diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index dd40f0d60..67098964d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -12,23 +12,18 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.preference.EditTextPreference; import androidx.preference.Preference; import com.nononsenseapps.filepicker.Utils; import com.nostra13.universalimageloader.core.ImageLoader; -import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; -import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.FilePickerActivityHelper; -import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ZipHelper; import java.io.BufferedOutputStream; @@ -46,11 +41,6 @@ import java.util.Map; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import io.reactivex.Completable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - public class ContentSettingsFragment extends BasePreferenceFragment { private static final int REQUEST_IMPORT_PATH = 8945; @@ -127,41 +117,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { return true; }); - Preference peerTubeInstance = findPreference(getString(R.string.peertube_instance_url_key)); - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); - peerTubeInstance.setDefaultValue(sharedPreferences.getString(getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl())); - peerTubeInstance.setSummary(sharedPreferences.getString(getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl())); - - peerTubeInstance.setOnPreferenceChangeListener((Preference p, Object newInstance) -> { - EditTextPreference pEt = (EditTextPreference) p; - String url = (String) newInstance; - if (!url.startsWith("https://")) { - Toast.makeText(getActivity(), "instance url should start with https://", - Toast.LENGTH_SHORT).show(); - } else { - pEt.setSummary("fetching instance details.."); - Disposable disposable = Completable.fromAction(() -> { - PeertubeInstance instance = new PeertubeInstance(url); - instance.fetchInstanceMetaData(); - ServiceList.PeerTube.setInstance(instance); - }).subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { - pEt.setSummary(url); - pEt.setText(url); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(App.getApp().getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()).apply(); - editor.putString(App.getApp().getString(R.string.current_service_key), ServiceList.PeerTube.getServiceInfo().getName()).apply(); - NavigationHelper.openMainActivity(App.getApp()); - }, error -> { - pEt.setSummary(ServiceList.PeerTube.getBaseUrl()); - Toast.makeText(getActivity(), "unable to update instance", - Toast.LENGTH_SHORT).show(); - }); - } - return false; - }); - } + } @Override public void onDestroy() { diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java new file mode 100644 index 000000000..097d96d20 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -0,0 +1,418 @@ +package org.schabi.newpipe.settings; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RadioButton; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.grack.nanojson.JsonStringWriter; +import com.grack.nanojson.JsonWriter; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.ServiceList; +import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; +import org.schabi.newpipe.util.Constants; +import org.schabi.newpipe.util.PeertubeHelper; +import org.schabi.newpipe.util.ThemeHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +public class PeertubeInstanceListFragment extends Fragment { + + private List instanceList = new ArrayList<>(); + private PeertubeInstance selectedInstance; + private String savedInstanceListKey; + public InstanceListAdapter instanceListAdapter; + + private ProgressBar progressBar; + private SharedPreferences sharedPreferences; + + private CompositeDisposable disposables = new CompositeDisposable(); + + /*////////////////////////////////////////////////////////////////////////// + // Lifecycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); + savedInstanceListKey = getString(R.string.peertube_instance_list_key); + selectedInstance = PeertubeHelper.getCurrentInstance(); + updateInstanceList(); + + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_instance_list, container, false); + } + + @Override + public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) { + super.onViewCreated(rootView, savedInstanceState); + + initButton(rootView); + + RecyclerView listInstances = rootView.findViewById(R.id.instances); + listInstances.setLayoutManager(new LinearLayoutManager(requireContext())); + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); + itemTouchHelper.attachToRecyclerView(listInstances); + + instanceListAdapter = new InstanceListAdapter(requireContext(), itemTouchHelper); + listInstances.setAdapter(instanceListAdapter); + + progressBar = rootView.findViewById(R.id.loading_progress_bar); + } + + @Override + public void onResume() { + super.onResume(); + updateTitle(); + } + + @Override + public void onPause() { + super.onPause(); + saveChanges(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposables != null) disposables.clear(); + disposables = null; + } + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + + private final int MENU_ITEM_RESTORE_ID = 123456; + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + + final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults); + restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + + final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults); + restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon)); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == MENU_ITEM_RESTORE_ID) { + restoreDefaults(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + + private void updateInstanceList() { + instanceList.clear(); + instanceList.addAll(PeertubeHelper.getInstanceList(requireContext())); + } + + private void selectInstance(PeertubeInstance instance) { + selectedInstance = PeertubeHelper.selectInstance(instance, requireContext()); + sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); + } + + private void updateTitle() { + if (getActivity() instanceof AppCompatActivity) { + ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); + if (actionBar != null) actionBar.setTitle(R.string.peertube_instance_url_title); + } + } + + private void saveChanges() { + JsonStringWriter jsonWriter = JsonWriter.string().object().array("instances"); + for (PeertubeInstance instance : instanceList) { + jsonWriter.object(); + jsonWriter.value("name", instance.getName()); + jsonWriter.value("url", instance.getUrl()); + jsonWriter.end(); + } + String jsonToSave = jsonWriter.end().end().done(); + sharedPreferences.edit().putString(savedInstanceListKey, jsonToSave).apply(); + } + + private void restoreDefaults() { + new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext())) + .setTitle(R.string.restore_defaults) + .setMessage(R.string.restore_defaults_confirmation) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.yes, (dialog, which) -> { + sharedPreferences.edit().remove(savedInstanceListKey).apply(); + selectInstance(PeertubeInstance.defaultInstance); + updateInstanceList(); + instanceListAdapter.notifyDataSetChanged(); + }) + .show(); + } + + private void initButton(View rootView) { + final FloatingActionButton fab = rootView.findViewById(R.id.addInstanceButton); + fab.setOnClickListener(v -> { + showAddItemDialog(requireContext()); + }); + } + + private void showAddItemDialog(Context c) { + final EditText urlET = new EditText(c); + urlET.setHint(R.string.peertube_instance_add_help); + AlertDialog dialog = new AlertDialog.Builder(c) + .setTitle(R.string.peertube_instance_add_title) + .setIcon(R.drawable.place_holder_peertube) + .setView(urlET) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.finish, (dialog1, which) -> { + String url = urlET.getText().toString(); + addInstance(url); + }) + .create(); + dialog.show(); + } + + private void addInstance(String url) { + String cleanUrl = verifyUrl(url); + if(null == cleanUrl) return; + progressBar.setVisibility(View.VISIBLE); + Disposable disposable = Single.fromCallable(() -> { + PeertubeInstance instance = new PeertubeInstance(cleanUrl); + instance.fetchInstanceMetaData(); + return instance; + }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe((instance) -> { + progressBar.setVisibility(View.GONE); + add(instance); + }, e -> { + progressBar.setVisibility(View.GONE); + Toast.makeText(getActivity(), "failed to validate instance", Toast.LENGTH_SHORT).show(); + }); + disposables.add(disposable); + } + + @Nullable + private String verifyUrl(String url){ + // if protocol not present, add https + if(!url.startsWith("http")){ + url = "https://" + url; + } + // remove trailing slash + url = url.replaceAll("/$", ""); + // only allow https + if (!url.startsWith("https://")) { + Toast.makeText(getActivity(), "instance url should start with https://", Toast.LENGTH_SHORT).show(); + return null; + } + // only allow if not already exists + for (PeertubeInstance instance : instanceList) { + if (instance.getUrl().equals(url)) { + Toast.makeText(getActivity(), "instance already exists", Toast.LENGTH_SHORT).show(); + return null; + } + } + return url; + } + + private void add(final PeertubeInstance instance) { + instanceList.add(instance); + instanceListAdapter.notifyDataSetChanged(); + } + + /*////////////////////////////////////////////////////////////////////////// + // List Handling + //////////////////////////////////////////////////////////////////////////*/ + + private class InstanceListAdapter extends RecyclerView.Adapter { + private ItemTouchHelper itemTouchHelper; + private final LayoutInflater inflater; + private RadioButton lastChecked; + + InstanceListAdapter(Context context, ItemTouchHelper itemTouchHelper) { + this.itemTouchHelper = itemTouchHelper; + this.inflater = LayoutInflater.from(context); + } + + public void swapItems(int fromPosition, int toPosition) { + Collections.swap(instanceList, fromPosition, toPosition); + notifyItemMoved(fromPosition, toPosition); + } + + @NonNull + @Override + public InstanceListAdapter.TabViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = inflater.inflate(R.layout.item_instance, parent, false); + return new InstanceListAdapter.TabViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull InstanceListAdapter.TabViewHolder holder, int position) { + holder.bind(position, holder); + } + + @Override + public int getItemCount() { + return instanceList.size(); + } + + class TabViewHolder extends RecyclerView.ViewHolder { + private AppCompatImageView instanceIconView; + private TextView instanceNameView; + private TextView instanceUrlView; + private RadioButton instanceRB; + private ImageView handle; + + TabViewHolder(View itemView) { + super(itemView); + + instanceIconView = itemView.findViewById(R.id.instanceIcon); + instanceNameView = itemView.findViewById(R.id.instanceName); + instanceUrlView = itemView.findViewById(R.id.instanceUrl); + instanceRB = itemView.findViewById(R.id.selectInstanceRB); + handle = itemView.findViewById(R.id.handle); + } + + @SuppressLint("ClickableViewAccessibility") + void bind(int position, TabViewHolder holder) { + handle.setOnTouchListener(getOnTouchListener(holder)); + + final PeertubeInstance instance = instanceList.get(position); + instanceNameView.setText(instance.getName()); + instanceUrlView.setText(instance.getUrl()); + instanceRB.setOnCheckedChangeListener(null); + if (selectedInstance.getUrl().equals(instance.getUrl())) { + if (lastChecked != null && lastChecked != instanceRB) { + lastChecked.setChecked(false); + } + instanceRB.setChecked(true); + lastChecked = instanceRB; + } + instanceRB.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + selectInstance(instance); + if (lastChecked != null && lastChecked != instanceRB) { + lastChecked.setChecked(false); + } + lastChecked = instanceRB; + } + }); + instanceIconView.setImageResource(R.drawable.place_holder_peertube); + } + + @SuppressLint("ClickableViewAccessibility") + private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) { + return (view, motionEvent) -> { + if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (itemTouchHelper != null && getItemCount() > 1) { + itemTouchHelper.startDrag(item); + return true; + } + } + return false; + }; + } + } + } + + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { + return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, + ItemTouchHelper.START | ItemTouchHelper.END) { + @Override + public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, + int viewSizeOutOfBounds, int totalSize, + long msSinceStartScroll) { + final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, + viewSizeOutOfBounds, totalSize, msSinceStartScroll); + final int minimumAbsVelocity = Math.max(12, + Math.abs(standardSpeed)); + return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, + RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType() || + instanceListAdapter == null) { + return false; + } + + final int sourceIndex = source.getAdapterPosition(); + final int targetIndex = target.getAdapterPosition(); + instanceListAdapter.swapItems(sourceIndex, targetIndex); + return true; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + int position = viewHolder.getAdapterPosition(); + // do not allow swiping the selected instance + if(instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) { + instanceListAdapter.notifyItemChanged(position); + return; + } + instanceList.remove(position); + instanceListAdapter.notifyItemRemoved(position); + + if (instanceList.isEmpty()) { + instanceList.add(selectedInstance); + instanceListAdapter.notifyItemInserted(0); + } + } + }; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java new file mode 100644 index 000000000..0d695e275 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -0,0 +1,65 @@ +package org.schabi.newpipe.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; +import com.grack.nanojson.JsonStringWriter; +import com.grack.nanojson.JsonWriter; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.ServiceList; +import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class PeertubeHelper { + + public static List getInstanceList(Context context) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + String savedInstanceListKey = context.getString(R.string.peertube_instance_list_key); + final String savedJson = sharedPreferences.getString(savedInstanceListKey, null); + if (null == savedJson) { + return Collections.singletonList(getCurrentInstance()); + } + + try { + JsonArray array = JsonParser.object().from(savedJson).getArray("instances"); + List result = new ArrayList<>(); + for (Object o : array) { + if (o instanceof JsonObject) { + JsonObject instance = (JsonObject) o; + String name = instance.getString("name"); + String url = instance.getString("url"); + result.add(new PeertubeInstance(url, name)); + } + } + return result; + } catch (JsonParserException e) { + return Collections.singletonList(getCurrentInstance()); + } + + } + + public static PeertubeInstance selectInstance(PeertubeInstance instance, Context context) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + String selectedInstanceKey = context.getString(R.string.peertube_selected_instance_key); + JsonStringWriter jsonWriter = JsonWriter.string().object(); + jsonWriter.value("name", instance.getName()); + jsonWriter.value("url", instance.getUrl()); + String jsonToSave = jsonWriter.end().done(); + sharedPreferences.edit().putString(selectedInstanceKey, jsonToSave).apply(); + ServiceList.PeerTube.setInstance(instance); + return instance; + } + + public static PeertubeInstance getCurrentInstance(){ + return ServiceList.PeerTube.getInstance(); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index 084ab5878..a25d4c9ec 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -3,9 +3,14 @@ package org.schabi.newpipe.util; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; + import androidx.annotation.DrawableRes; import androidx.annotation.StringRes; +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; + import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.ServiceList; @@ -137,11 +142,22 @@ public class ServiceHelper { } public static void initService(Context context, int serviceId) { - if(serviceId == ServiceList.PeerTube.getServiceId()){ + if (serviceId == ServiceList.PeerTube.getServiceId()) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - String peerTubeInstanceUrl = sharedPreferences.getString(context.getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl()); - String peerTubeInstanceName = sharedPreferences.getString(context.getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()); - PeertubeInstance instance = new PeertubeInstance(peerTubeInstanceUrl, peerTubeInstanceName); + String json = sharedPreferences.getString(context.getString(R.string.peertube_selected_instance_key), null); + if (null == json) { + return; + } + + JsonObject jsonObject = null; + try { + jsonObject = JsonParser.object().from(json); + } catch (JsonParserException e) { + return; + } + String name = jsonObject.getString("name"); + String url = jsonObject.getString("url"); + PeertubeInstance instance = new PeertubeInstance(url, name); ServiceList.PeerTube.setInstance(instance); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index f476cf66b..530c8cefe 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -137,12 +137,7 @@ public class ThemeHelper { else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme"; else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme"; - if(serviceId == ServiceList.PeerTube.getServiceId()){ - //service name for peertube depends on the instance - themeName += ".PeerTube"; - }else{ - themeName += "." + service.getServiceInfo().getName(); - } + themeName += "." + service.getServiceInfo().getName(); int resourceId = context .getResources() diff --git a/app/src/main/res/layout/fragment_instance_list.xml b/app/src/main/res/layout/fragment_instance_list.xml new file mode 100644 index 000000000..970b67c26 --- /dev/null +++ b/app/src/main/res/layout/fragment_instance_list.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/instance_spinner_item.xml b/app/src/main/res/layout/instance_spinner_item.xml new file mode 100644 index 000000000..1edac71af --- /dev/null +++ b/app/src/main/res/layout/instance_spinner_item.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout/instance_spinner_layout.xml b/app/src/main/res/layout/instance_spinner_layout.xml new file mode 100644 index 000000000..63e910d96 --- /dev/null +++ b/app/src/main/res/layout/instance_spinner_layout.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/item_instance.xml b/app/src/main/res/layout/item_instance.xml new file mode 100644 index 000000000..b0e4e25bd --- /dev/null +++ b/app/src/main/res/layout/item_instance.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index dcf39b488..2249d1ec0 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -144,8 +144,9 @@ en GB content_language - peertube_instance_url - peertube_instance_name + peertube_instance_setup + peertube_selected_instance + peertube_instance_list content_country show_age_restricted_content use_tor diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52b56a7b8..c652b7f65 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -109,8 +109,11 @@ Default content country Service Default content language - PeerTube instance + PeerTube instances + Set your favorite peertube instances Find the instance that best suits you on https://instances.joinpeertube.org + Add instance + enter instance url Player Behavior Video & audio @@ -578,4 +581,6 @@ You will be asked where to save each download.\nChoose SAF if you want to download to an external SD card Use SAF The Storage Access Framework allows downloads to an external SD card.\nNote: some devices are not compatible + Choose an instance + diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index 6d2329310..0d579ba35 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -12,12 +12,12 @@ android:summary="%s" android:title="@string/content_language_title"/> - + android:summary="@string/peertube_instance_url_summary"/> Date: Sun, 24 Nov 2019 21:42:05 +0530 Subject: [PATCH 183/270] white space changes --- .../schabi/newpipe/settings/ContentSettingsFragment.java | 8 +++----- .../main/java/org/schabi/newpipe/util/ThemeHelper.java | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 67098964d..0c7a4b46e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -7,12 +7,11 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; -import android.util.Log; -import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.Preference; +import android.util.Log; +import android.widget.Toast; import com.nononsenseapps.filepicker.Utils; import com.nostra13.universalimageloader.core.ImageLoader; @@ -116,8 +115,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { startActivityForResult(i, REQUEST_EXPORT_PATH); return true; }); - - } + } @Override public void onDestroy() { diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index 530c8cefe..661aa47c1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -30,7 +30,6 @@ import android.view.ContextThemeWrapper; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; @@ -138,7 +137,6 @@ public class ThemeHelper { else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme"; themeName += "." + service.getServiceInfo().getName(); - int resourceId = context .getResources() .getIdentifier(themeName, "style", context.getPackageName()); From ea1be11a8031e25dcf90c9af54a35372dd92744d Mon Sep 17 00:00:00 2001 From: kapodamy Date: Sun, 24 Nov 2019 14:00:22 -0300 Subject: [PATCH 184/270] Merge branch 'dev' into dl-last-features --- .../us/shandian/giga/get/DownloadMission.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 5ef72162c..917a0a148 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -1,6 +1,9 @@ package us.shandian.giga.get; +import android.os.Build; import android.os.Handler; +import android.system.ErrnoException; +import android.system.OsConstants; import android.util.Log; import androidx.annotation.Nullable; @@ -35,9 +38,6 @@ public class DownloadMission extends Mission { static final int BUFFER_SIZE = 64 * 1024; static final int BLOCK_SIZE = 512 * 1024; - @SuppressWarnings("SpellCheckingInspection") - private static final String INSUFFICIENT_STORAGE = "ENOSPC"; - private static final String TAG = "DownloadMission"; public static final int ERROR_NOTHING = -1; @@ -315,13 +315,29 @@ public class DownloadMission extends Mission { public synchronized void notifyError(int code, Exception err) { Log.e(TAG, "notifyError() code = " + code, err); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (err.getCause() instanceof ErrnoException) { + int errno = ((ErrnoException) err.getCause()).errno; + if (errno == OsConstants.ENOSPC) { + code = ERROR_INSUFFICIENT_STORAGE; + err = null; + } else if (errno == OsConstants.EACCES) { + code = ERROR_PERMISSION_DENIED; + err = null; + } + } + } + if (err instanceof IOException) { - if (!storage.canWrite() || err.getMessage().contains("Permission denied")) { + if (err.getMessage().contains("Permission denied")) { code = ERROR_PERMISSION_DENIED; err = null; - } else if (err.getMessage().contains(INSUFFICIENT_STORAGE)) { + } else if (err.getMessage().contains("ENOSPC")) { code = ERROR_INSUFFICIENT_STORAGE; err = null; + } else if (!storage.canWrite()) { + code = ERROR_FILE_CREATION; + err = null; } } From 773aa1eff006f5de824bd5739ac7e78ed2f35168 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Mon, 23 Sep 2019 21:38:29 -0300 Subject: [PATCH 185/270] implement webm to ogg demuxer * used for opus audio stream * update WebMReader and WebMWriter * new post-processing algorithm --- .../newpipe/download/DownloadDialog.java | 4 +- .../newpipe/streams/OggFromWebMWriter.java | 488 ++++++++++++++++++ .../schabi/newpipe/streams/WebMReader.java | 55 +- .../schabi/newpipe/streams/WebMWriter.java | 27 +- .../postprocessing/OggFromWebmDemuxer.java | 44 ++ .../giga/postprocessing/Postprocessing.java | 6 +- 6 files changed, 595 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java create mode 100644 app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 59bffa933..90258a6dc 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -561,7 +561,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck mainStorage = mainStorageVideo; format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat(); mime = format.mimeType; - filename += format.suffix; + filename += format == MediaFormat.OPUS ? "ogg" : format.suffix; break; case R.id.subtitle_button: mainStorage = mainStorageVideo;// subtitle & video files go together @@ -778,6 +778,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck if (selectedStream.getFormat() == MediaFormat.M4A) { psName = Postprocessing.ALGORITHM_M4A_NO_DASH; + } else if (selectedStream.getFormat() == MediaFormat.OPUS) { + psName = Postprocessing.ALGORITHM_OGG_FROM_WEBM_DEMUXER; } break; case R.id.video_button: diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java new file mode 100644 index 000000000..2b3d778c6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -0,0 +1,488 @@ +package org.schabi.newpipe.streams; + +import android.support.annotation.NonNull; + +import org.schabi.newpipe.streams.WebMReader.Cluster; +import org.schabi.newpipe.streams.WebMReader.Segment; +import org.schabi.newpipe.streams.WebMReader.SimpleBlock; +import org.schabi.newpipe.streams.WebMReader.WebMTrack; +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Random; + +import javax.annotation.Nullable; + +/** + * @author kapodamy + */ +public class OggFromWebMWriter implements Closeable { + + private static final byte FLAG_UNSET = 0x00; + //private static final byte FLAG_CONTINUED = 0x01; + private static final byte FLAG_FIRST = 0x02; + private static final byte FLAG_LAST = 0x04; + + private final static byte SEGMENTS_PER_PACKET = 50;// used in ffmpeg, which is near 1 second at 48kHz + private final static byte HEADER_CHECKSUM_OFFSET = 22; + + private boolean done = false; + private boolean parsed = false; + + private SharpStream source; + private SharpStream output; + + private int sequence_count = 0; + private final int STREAM_ID; + + private WebMReader webm = null; + private WebMTrack webm_track = null; + private int track_index = 0; + + public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) { + if (!source.canRead() || !source.canRewind()) { + throw new IllegalArgumentException("source stream must be readable and allows seeking"); + } + if (!target.canWrite() || !target.canRewind()) { + throw new IllegalArgumentException("output stream must be writable and allows seeking"); + } + + this.source = source; + this.output = target; + + this.STREAM_ID = (new Random(System.currentTimeMillis())).nextInt(); + + populate_crc32_table(); + } + + public boolean isDone() { + return done; + } + + public boolean isParsed() { + return parsed; + } + + public WebMTrack[] getTracksFromSource() throws IllegalStateException { + if (!parsed) { + throw new IllegalStateException("source must be parsed first"); + } + + return webm.getAvailableTracks(); + } + + public void parseSource() throws IOException, IllegalStateException { + if (done) { + throw new IllegalStateException("already done"); + } + if (parsed) { + throw new IllegalStateException("already parsed"); + } + + try { + webm = new WebMReader(source); + webm.parse(); + webm_segment = webm.getNextSegment(); + } finally { + parsed = true; + } + } + + public void selectTrack(int trackIndex) throws IOException { + if (!parsed) { + throw new IllegalStateException("source must be parsed first"); + } + if (done) { + throw new IOException("already done"); + } + if (webm_track != null) { + throw new IOException("tracks already selected"); + } + + switch (webm.getAvailableTracks()[trackIndex].kind) { + case Audio: + case Video: + break; + default: + throw new UnsupportedOperationException("the track must an audio or video stream"); + } + + try { + webm_track = webm.selectTrack(trackIndex); + track_index = trackIndex; + } finally { + parsed = true; + } + } + + @Override + public void close() throws IOException { + done = true; + parsed = true; + + webm_track = null; + webm = null; + + if (!output.isClosed()) { + output.flush(); + } + + source.close(); + output.close(); + } + + public void build() throws IOException { + float resolution; + int read; + byte[] buffer; + int checksum; + byte flag = FLAG_FIRST;// obligatory + + switch (webm_track.kind) { + case Audio: + resolution = getSampleFrequencyFromTrack(webm_track.bMetadata); + if (resolution == 0f) { + throw new RuntimeException("cannot get the audio sample rate"); + } + break; + case Video: + // WARNING: untested + if (webm_track.defaultDuration == 0) { + throw new RuntimeException("missing default frame time"); + } + resolution = 1000f / ((float) webm_track.defaultDuration / webm_segment.info.timecodeScale); + break; + default: + throw new RuntimeException("not implemented"); + } + + /* step 1.1: write codec init data, in most cases must be present */ + if (webm_track.codecPrivate != null) { + addPacketSegment(webm_track.codecPrivate.length); + dump_packetHeader(flag, 0x00, webm_track.codecPrivate); + flag = FLAG_UNSET; + } + + /* step 1.2: write metadata */ + buffer = make_metadata(); + if (buffer != null) { + addPacketSegment(buffer.length); + dump_packetHeader(flag, 0x00, buffer); + flag = FLAG_UNSET; + } + + buffer = new byte[8 * 1024]; + + /* step 1.3: write headers */ + long approx_packets = webm_segment.info.duration / webm_segment.info.timecodeScale; + approx_packets = approx_packets / (approx_packets / SEGMENTS_PER_PACKET); + + ArrayList pending_offsets = new ArrayList<>((int) approx_packets); + ArrayList pending_checksums = new ArrayList<>((int) approx_packets); + ArrayList data_offsets = new ArrayList<>((int) approx_packets); + + int page_size = 0; + SimpleBlock bloq; + + while (webm_segment != null) { + bloq = getNextBlock(); + + if (bloq != null && addPacketSegment(bloq.dataSize)) { + page_size += bloq.dataSize; + + if (segment_table_size < SEGMENTS_PER_PACKET) { + continue; + } + + // calculate the current packet duration using the next block + bloq = getNextBlock(); + } + + double elapsed_ns = webm_track.codecDelay; + + if (bloq == null) { + flag = FLAG_LAST; + elapsed_ns += webm_block_last_timecode; + + if (webm_track.defaultDuration > 0) { + elapsed_ns += webm_track.defaultDuration; + } else { + // hardcoded way, guess the sample duration + elapsed_ns += webm_block_near_duration; + } + } else { + elapsed_ns += bloq.absoluteTimeCodeNs; + } + + // get the sample count in the page + elapsed_ns = (elapsed_ns / 1000000000d) * resolution; + elapsed_ns = Math.ceil(elapsed_ns); + + long offset = output_offset + HEADER_CHECKSUM_OFFSET; + pending_offsets.add(offset); + + checksum = dump_packetHeader(flag, (long) elapsed_ns, null); + pending_checksums.add(checksum); + + data_offsets.add((short) (output_offset - offset)); + + // reserve space in the page + while (page_size > 0) { + int write = Math.min(page_size, buffer.length); + out_write(buffer, write); + page_size -= write; + } + + webm_block = bloq; + } + + /* step 2.1: write stream data */ + output.rewind(); + output_offset = 0; + + source.rewind(); + + webm = new WebMReader(source); + webm.parse(); + webm_track = webm.selectTrack(track_index); + + for (int i = 0; i < pending_offsets.size(); i++) { + checksum = pending_checksums.get(i); + segment_table_size = 0; + + out_seek(pending_offsets.get(i) + data_offsets.get(i)); + + while (segment_table_size < SEGMENTS_PER_PACKET) { + bloq = getNextBlock(); + + if (bloq == null || !addPacketSegment(bloq.dataSize)) { + webm_block = bloq;// use this block later (if not null) + break; + } + + // NOTE: calling bloq.data.close() is unnecessary + while ((read = bloq.data.read(buffer)) != -1) { + out_write(buffer, read); + checksum = calc_crc32(checksum, buffer, read); + } + } + + pending_checksums.set(i, checksum); + } + + /* step 2.2: write every checksum */ + output.rewind(); + output_offset = 0; + buffer = new byte[4]; + + ByteBuffer buff = ByteBuffer.wrap(buffer); + buff.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < pending_checksums.size(); i++) { + out_seek(pending_offsets.get(i)); + buff.putInt(0, pending_checksums.get(i)); + out_write(buffer); + } + } + + private int dump_packetHeader(byte flag, long gran_pos, byte[] immediate_page) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(27 + segment_table_size); + + buffer.putInt(0x4F676753);// "OggS" binary string + buffer.put((byte) 0x00);// version + buffer.put(flag);// type + + buffer.order(ByteOrder.LITTLE_ENDIAN); + + buffer.putLong(gran_pos);// granulate position + + buffer.putInt(STREAM_ID);// bitstream serial number + buffer.putInt(sequence_count++);// page sequence number + + buffer.putInt(0x00);// page checksum + + buffer.order(ByteOrder.BIG_ENDIAN); + + buffer.put((byte) segment_table_size);// segment table + buffer.put(segment_table, 0, segment_table_size);// segment size + + segment_table_size = 0;// clear segment table for next header + + byte[] buff = buffer.array(); + int checksum_crc32 = calc_crc32(0x00, buff, buff.length); + + if (immediate_page != null) { + checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32); + + out_write(buff); + out_write(immediate_page); + return 0; + } + + out_write(buff); + return checksum_crc32; + } + + @Nullable + private byte[] make_metadata() { + if ("A_OPUS".equals(webm_track.codecId)) { + return new byte[]{ + 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string + 0x07, 0x00, 0x00, 0x00,// writting application string size + 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string + 0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags) + }; + } else if ("A_VORBIS".equals(webm_track.codecId)) { + return new byte[]{ + 0x03,// ???????? + 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string + 0x07, 0x00, 0x00, 0x00,// writting application string size + 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string + 0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags) + + /* + // whole file duration (not implemented) + 0x44,// tag string size + 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, + 0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 + */ + 0x0F,// tag string size + 0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D,// "ENCODER=" binary string + 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00// ???????? + }; + } + + // not implemented for the desired codec + return null; + } + + // + private Segment webm_segment = null; + private Cluster webm_cluter = null; + private SimpleBlock webm_block = null; + private long webm_block_last_timecode = 0; + private long webm_block_near_duration = 0; + + private SimpleBlock getNextBlock() throws IOException { + SimpleBlock res; + + if (webm_block != null) { + res = webm_block; + webm_block = null; + return res; + } + + if (webm_segment == null) { + webm_segment = webm.getNextSegment(); + if (webm_segment == null) { + return null;// no more blocks in the selected track + } + } + + if (webm_cluter == null) { + webm_cluter = webm_segment.getNextCluster(); + if (webm_cluter == null) { + webm_segment = null; + return getNextBlock(); + } + } + + res = webm_cluter.getNextSimpleBlock(); + if (res == null) { + webm_cluter = null; + return getNextBlock(); + } + + webm_block_near_duration = res.absoluteTimeCodeNs - webm_block_last_timecode; + webm_block_last_timecode = res.absoluteTimeCodeNs; + + return res; + } + + private float getSampleFrequencyFromTrack(byte[] bMetadata) { + // hardcoded way + ByteBuffer buffer = ByteBuffer.wrap(bMetadata); + + while (buffer.remaining() >= 6) { + int id = buffer.getShort() & 0xFFFF; + if (id == 0x0000B584) { + return buffer.getFloat(); + } + } + + return 0f; + } + // + + // + private int segment_table_size = 0; + private final byte[] segment_table = new byte[255]; + + private boolean addPacketSegment(long size) { + // check if possible add the segment, without overflow the table + int available = (segment_table.length - segment_table_size) * 255; + if (available < size) { + return false;// not enough space on the page + } + + while (size > 0) { + segment_table[segment_table_size++] = (byte) Math.min(size, 255); + size -= 255; + } + + return true; + } + // + + // + private long output_offset = 0; + + private void out_write(byte[] buffer) throws IOException { + output.write(buffer); + output_offset += buffer.length; + } + + private void out_write(byte[] buffer, int size) throws IOException { + output.write(buffer, 0, size); + output_offset += size; + } + + private void out_seek(long offset) throws IOException { + //if (output.canSeek()) { output.seek(offset); } + output.skip(offset - output_offset); + output_offset = offset; + } + // + + // + private final int[] crc32_table = new int[256]; + + private void populate_crc32_table() { + for (int i = 0; i < 0x100; i++) { + int crc = i << 24; + for (int j = 0; j < 8; j++) { + long b = crc >>> 31; + crc <<= 1; + crc ^= (int) (0x100000000L - b) & 0x04c11db7; + } + crc32_table[i] = crc; + } + } + + private int calc_crc32(int initial_crc, byte[] buffer, int size) { + for (int i = 0; i < size; i++) { + int reg = (initial_crc >>> 24) & 0xff; + initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)]; + } + + return initial_crc; + } + // +} diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java index 0c635ebe3..13c15370d 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java @@ -15,7 +15,7 @@ import java.util.NoSuchElementException; */ public class WebMReader { - // + // private final static int ID_EMBL = 0x0A45DFA3; private final static int ID_EMBLReadVersion = 0x02F7; private final static int ID_EMBLDocType = 0x0282; @@ -37,10 +37,13 @@ public class WebMReader { private final static int ID_Audio = 0x61; private final static int ID_DefaultDuration = 0x3E383; private final static int ID_FlagLacing = 0x1C; + private final static int ID_CodecDelay = 0x16AA; private final static int ID_Cluster = 0x0F43B675; private final static int ID_Timecode = 0x67; private final static int ID_SimpleBlock = 0x23; + private final static int ID_Block = 0x21; + private final static int ID_GroupBlock = 0x20; // public enum TrackKind { @@ -96,7 +99,7 @@ public class WebMReader { } ensure(segment.ref); - + // WARNING: track cannot be the same or have different index in new segments Element elem = untilElement(null, ID_Segment); if (elem == null) { done = true; @@ -189,6 +192,9 @@ public class WebMReader { Element elem; while (ref == null ? stream.available() : (stream.position() < (ref.offset + ref.size))) { elem = readElement(); + if (expected.length < 1) { + return elem; + } for (int type : expected) { if (elem.type == type) { return elem; @@ -300,9 +306,7 @@ public class WebMReader { WebMTrack entry = new WebMTrack(); boolean drop = false; Element elem; - while ((elem = untilElement(elem_trackEntry, - ID_TrackNumber, ID_TrackType, ID_CodecID, ID_CodecPrivate, ID_FlagLacing, ID_DefaultDuration, ID_Audio, ID_Video - )) != null) { + while ((elem = untilElement(elem_trackEntry)) != null) { switch (elem.type) { case ID_TrackNumber: entry.trackNumber = readNumber(elem); @@ -326,8 +330,9 @@ public class WebMReader { case ID_FlagLacing: drop = readNumber(elem) != lacingExpected; break; + case ID_CodecDelay: + entry.codecDelay = readNumber(elem); default: - System.out.println(); break; } ensure(elem); @@ -360,12 +365,13 @@ public class WebMReader { private SimpleBlock readSimpleBlock(Element ref) throws IOException { SimpleBlock obj = new SimpleBlock(ref); - obj.dataSize = stream.position(); obj.trackNumber = readEncodedNumber(); obj.relativeTimeCode = stream.readShort(); obj.flags = (byte) stream.read(); obj.dataSize = (ref.offset + ref.size) - stream.position(); + obj.createdFromBlock = ref.type == ID_Block; + // NOTE: lacing is not implemented, and will be mixed with the stream data if (obj.dataSize < 0) { throw new IOException(String.format("Unexpected SimpleBlock element size, missing %s bytes", -obj.dataSize)); } @@ -409,6 +415,7 @@ public class WebMReader { public byte[] bMetadata; public TrackKind kind; public long defaultDuration; + public long codecDelay; } public class Segment { @@ -448,6 +455,7 @@ public class WebMReader { public class SimpleBlock { public InputStream data; + public boolean createdFromBlock; SimpleBlock(Element ref) { this.ref = ref; @@ -455,6 +463,7 @@ public class WebMReader { public long trackNumber; public short relativeTimeCode; + public long absoluteTimeCodeNs; public byte flags; public long dataSize; private final Element ref; @@ -468,33 +477,55 @@ public class WebMReader { Element ref; SimpleBlock currentSimpleBlock = null; + Element currentBlockGroup = null; public long timecode; Cluster(Element ref) { this.ref = ref; } - boolean check() { + boolean insideClusterBounds() { return stream.position() >= (ref.offset + ref.size); } public SimpleBlock getNextSimpleBlock() throws IOException { - if (check()) { + if (insideClusterBounds()) { return null; } - if (currentSimpleBlock != null) { + + if (currentBlockGroup != null) { + ensure(currentBlockGroup); + currentBlockGroup = null; + currentSimpleBlock = null; + } else if (currentSimpleBlock != null) { ensure(currentSimpleBlock.ref); } - while (!check()) { - Element elem = untilElement(ref, ID_SimpleBlock); + while (!insideClusterBounds()) { + Element elem = untilElement(ref, ID_SimpleBlock, ID_GroupBlock); if (elem == null) { return null; } + if (elem.type == ID_GroupBlock) { + currentBlockGroup = elem; + elem = untilElement(currentBlockGroup, ID_Block); + + if (elem == null) { + ensure(currentBlockGroup); + currentBlockGroup = null; + continue; + } + } + currentSimpleBlock = readSimpleBlock(elem); if (currentSimpleBlock.trackNumber == tracks[selectedTrack].trackNumber) { currentSimpleBlock.data = stream.getView((int) currentSimpleBlock.dataSize); + + // calculate the timestamp in nanoseconds + currentSimpleBlock.absoluteTimeCodeNs = currentSimpleBlock.relativeTimeCode + this.timecode; + currentSimpleBlock.absoluteTimeCodeNs *= segment.info.timecodeScale; + return currentSimpleBlock; } diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java index e5881fd0b..1bf994b1e 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java @@ -8,6 +8,7 @@ import org.schabi.newpipe.streams.WebMReader.SimpleBlock; import org.schabi.newpipe.streams.WebMReader.WebMTrack; import org.schabi.newpipe.streams.io.SharpStream; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; @@ -17,7 +18,7 @@ import java.util.ArrayList; /** * @author kapodamy */ -public class WebMWriter { +public class WebMWriter implements Closeable { private final static int BUFFER_SIZE = 8 * 1024; private final static int DEFAULT_TIMECODE_SCALE = 1000000; @@ -35,7 +36,7 @@ public class WebMWriter { private long written = 0; private Segment[] readersSegment; - private Cluster[] readersCluter; + private Cluster[] readersCluster; private int[] predefinedDurations; @@ -81,7 +82,7 @@ public class WebMWriter { public void selectTracks(int... trackIndex) throws IOException { try { readersSegment = new Segment[readers.length]; - readersCluter = new Cluster[readers.length]; + readersCluster = new Cluster[readers.length]; predefinedDurations = new int[readers.length]; for (int i = 0; i < readers.length; i++) { @@ -102,6 +103,7 @@ public class WebMWriter { return parsed; } + @Override public void close() { done = true; parsed = true; @@ -114,7 +116,7 @@ public class WebMWriter { readers = null; infoTracks = null; readersSegment = null; - readersCluter = null; + readersCluster = null; outBuffer = null; } @@ -334,17 +336,17 @@ public class WebMWriter { } } - if (readersCluter[internalTrackId] == null) { - readersCluter[internalTrackId] = readersSegment[internalTrackId].getNextCluster(); - if (readersCluter[internalTrackId] == null) { + if (readersCluster[internalTrackId] == null) { + readersCluster[internalTrackId] = readersSegment[internalTrackId].getNextCluster(); + if (readersCluster[internalTrackId] == null) { readersSegment[internalTrackId] = null; return getNextBlockFrom(internalTrackId); } } - SimpleBlock res = readersCluter[internalTrackId].getNextSimpleBlock(); + SimpleBlock res = readersCluster[internalTrackId].getNextSimpleBlock(); if (res == null) { - readersCluter[internalTrackId] = null; + readersCluster[internalTrackId] = null; return new Block();// fake block to indicate the end of the cluster } @@ -353,16 +355,11 @@ public class WebMWriter { bloq.dataSize = (int) res.dataSize; bloq.trackNumber = internalTrackId; bloq.flags = res.flags; - bloq.absoluteTimecode = convertTimecode(res.relativeTimeCode, readersSegment[internalTrackId].info.timecodeScale); - bloq.absoluteTimecode += readersCluter[internalTrackId].timecode; + bloq.absoluteTimecode = res.absoluteTimeCodeNs / DEFAULT_TIMECODE_SCALE; return bloq; } - private short convertTimecode(int time, long oldTimeScale) { - return (short) (time * (DEFAULT_TIMECODE_SCALE / oldTimeScale)); - } - private void seekTo(SharpStream stream, long offset) throws IOException { if (stream.canSeek()) { stream.seek(offset); diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java new file mode 100644 index 000000000..65aa30fa3 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java @@ -0,0 +1,44 @@ +package us.shandian.giga.postprocessing; + +import android.support.annotation.NonNull; + +import org.schabi.newpipe.streams.OggFromWebMWriter; +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.IOException; +import java.nio.ByteBuffer; + +class OggFromWebmDemuxer extends Postprocessing { + + OggFromWebmDemuxer() { + super(false, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); + } + + @Override + boolean test(SharpStream... sources) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(4); + sources[0].read(buffer.array()); + + // youtube uses WebM as container, but the file extension (format suffix) is "*.opus" + // check if the file is a webm/mkv file before proceed + + switch (buffer.getInt()) { + case 0x1a45dfa3: + return true;// webm + case 0x4F676753: + return false;// ogg + } + + throw new UnsupportedOperationException("file not recognized, failed to demux the audio stream"); + } + + @Override + int process(SharpStream out, @NonNull SharpStream... sources) throws IOException { + OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out); + demuxer.parseSource(); + demuxer.selectTrack(0); + demuxer.build(); + + return OK_RESULT; + } +} diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java index 22cc325d5..92510c3df 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java @@ -28,6 +28,7 @@ public abstract class Postprocessing implements Serializable { public transient static final String ALGORITHM_WEBM_MUXER = "webm"; public transient static final String ALGORITHM_MP4_FROM_DASH_MUXER = "mp4D-mp4"; public transient static final String ALGORITHM_M4A_NO_DASH = "mp4D-m4a"; + public transient static final String ALGORITHM_OGG_FROM_WEBM_DEMUXER = "webm-ogg-d"; public static Postprocessing getAlgorithm(@NonNull String algorithmName, String[] args) { Postprocessing instance; @@ -45,6 +46,9 @@ public abstract class Postprocessing implements Serializable { case ALGORITHM_M4A_NO_DASH: instance = new M4aNoDash(); break; + case ALGORITHM_OGG_FROM_WEBM_DEMUXER: + instance = new OggFromWebmDemuxer(); + break; /*case "example-algorithm": instance = new ExampleAlgorithm();*/ default: @@ -212,7 +216,7 @@ public abstract class Postprocessing implements Serializable { * * @param out output stream * @param sources files to be processed - * @return a error code, 0 means the operation was successful + * @return an error code, {@code OK_RESULT} means the operation was successful * @throws IOException if an I/O error occurs. */ abstract int process(SharpStream out, SharpStream... sources) throws IOException; From dab53450c13ad1c7ddf58097541f12681ddbdb39 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Wed, 25 Sep 2019 16:24:52 -0300 Subject: [PATCH 186/270] rewrite OggFromWebMWriter * reduce the number of iterations over the output file (less seeking) * fix audio samples with size of 255 do not handled correctly in the segment table (allows writing audio streams with 70kbps and 160kbps bitrate) * add support for VORBIS codec metadata * write packets based on the timestamp --- .../newpipe/streams/OggFromWebMWriter.java | 348 ++++++++++-------- .../postprocessing/OggFromWebmDemuxer.java | 4 +- 2 files changed, 203 insertions(+), 149 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index 2b3d778c6..091ae6d2a 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -23,12 +23,16 @@ import javax.annotation.Nullable; public class OggFromWebMWriter implements Closeable { private static final byte FLAG_UNSET = 0x00; - //private static final byte FLAG_CONTINUED = 0x01; + private static final byte FLAG_CONTINUED = 0x01; private static final byte FLAG_FIRST = 0x02; private static final byte FLAG_LAST = 0x04; - private final static byte SEGMENTS_PER_PACKET = 50;// used in ffmpeg, which is near 1 second at 48kHz private final static byte HEADER_CHECKSUM_OFFSET = 22; + private final static byte HEADER_SIZE = 27; + + private final static short BUFFER_SIZE = 8 * 1024;// 8KiB + + private final static int TIME_SCALE_NS = 1000000000; private boolean done = false; private boolean parsed = false; @@ -38,10 +42,23 @@ public class OggFromWebMWriter implements Closeable { private int sequence_count = 0; private final int STREAM_ID; + private byte packet_flag = FLAG_FIRST; + private int track_index = 0; private WebMReader webm = null; private WebMTrack webm_track = null; - private int track_index = 0; + private Segment webm_segment = null; + private Cluster webm_cluster = null; + private SimpleBlock webm_block = null; + + private long webm_block_last_timecode = 0; + private long webm_block_near_duration = 0; + + private short segment_table_size = 0; + private final byte[] segment_table = new byte[255]; + private long segment_table_next_timestamp = TIME_SCALE_NS; + + private final int[] crc32_table = new int[256]; public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) { if (!source.canRead() || !source.canRewind()) { @@ -139,9 +156,8 @@ public class OggFromWebMWriter implements Closeable { float resolution; int read; byte[] buffer; - int checksum; - byte flag = FLAG_FIRST;// obligatory + /* step 1: get the amount of frames per seconds */ switch (webm_track.kind) { case Audio: resolution = getSampleFrequencyFromTrack(webm_track.bMetadata); @@ -160,52 +176,65 @@ public class OggFromWebMWriter implements Closeable { throw new RuntimeException("not implemented"); } - /* step 1.1: write codec init data, in most cases must be present */ + /* step 2a: create packet with code init data */ + ArrayList data_extra = new ArrayList<>(4); + if (webm_track.codecPrivate != null) { addPacketSegment(webm_track.codecPrivate.length); - dump_packetHeader(flag, 0x00, webm_track.codecPrivate); - flag = FLAG_UNSET; + ByteBuffer buff = byte_buffer(HEADER_SIZE + segment_table_size + webm_track.codecPrivate.length); + + make_packetHeader(0x00, buff, webm_track.codecPrivate); + data_extra.add(buff.array()); } - /* step 1.2: write metadata */ + /* step 2b: create packet with metadata */ buffer = make_metadata(); if (buffer != null) { addPacketSegment(buffer.length); - dump_packetHeader(flag, 0x00, buffer); - flag = FLAG_UNSET; + ByteBuffer buff = byte_buffer(HEADER_SIZE + segment_table_size + buffer.length); + + make_packetHeader(0x00, buff, buffer); + data_extra.add(buff.array()); } - buffer = new byte[8 * 1024]; - /* step 1.3: write headers */ - long approx_packets = webm_segment.info.duration / webm_segment.info.timecodeScale; - approx_packets = approx_packets / (approx_packets / SEGMENTS_PER_PACKET); - - ArrayList pending_offsets = new ArrayList<>((int) approx_packets); - ArrayList pending_checksums = new ArrayList<>((int) approx_packets); - ArrayList data_offsets = new ArrayList<>((int) approx_packets); - - int page_size = 0; + /* step 3: calculate amount of packets */ SimpleBlock bloq; + int reserve_header = 0; + int headers_amount = 0; while (webm_segment != null) { bloq = getNextBlock(); - if (bloq != null && addPacketSegment(bloq.dataSize)) { - page_size += bloq.dataSize; - - if (segment_table_size < SEGMENTS_PER_PACKET) { - continue; - } - - // calculate the current packet duration using the next block - bloq = getNextBlock(); + if (addPacketSegment(bloq)) { + continue; } + reserve_header += HEADER_SIZE + segment_table_size;// header size + clearSegmentTable(); + webm_block = bloq; + headers_amount++; + } + + /* step 4: create packet headers */ + rewind_source(); + + ByteBuffer headers = byte_buffer(reserve_header); + short[] headers_size = new short[headers_amount]; + int header_index = 0; + + while (webm_segment != null) { + bloq = getNextBlock(); + + if (addPacketSegment(bloq)) { + continue; + } + + // calculate the current packet duration using the next block double elapsed_ns = webm_track.codecDelay; if (bloq == null) { - flag = FLAG_LAST; + packet_flag = FLAG_LAST;// note: if the flag is FLAG_CONTINUED, is changed elapsed_ns += webm_block_last_timecode; if (webm_track.defaultDuration > 0) { @@ -219,84 +248,83 @@ public class OggFromWebMWriter implements Closeable { } // get the sample count in the page - elapsed_ns = (elapsed_ns / 1000000000d) * resolution; - elapsed_ns = Math.ceil(elapsed_ns); - - long offset = output_offset + HEADER_CHECKSUM_OFFSET; - pending_offsets.add(offset); - - checksum = dump_packetHeader(flag, (long) elapsed_ns, null); - pending_checksums.add(checksum); - - data_offsets.add((short) (output_offset - offset)); - - // reserve space in the page - while (page_size > 0) { - int write = Math.min(page_size, buffer.length); - out_write(buffer, write); - page_size -= write; - } + elapsed_ns = elapsed_ns / TIME_SCALE_NS; + elapsed_ns = Math.ceil(elapsed_ns * resolution); + // create header + headers_size[header_index++] = make_packetHeader((long) elapsed_ns, headers, null); webm_block = bloq; } - /* step 2.1: write stream data */ - output.rewind(); - output_offset = 0; - source.rewind(); + /* step 5: calculate checksums */ + rewind_source(); - webm = new WebMReader(source); - webm.parse(); - webm_track = webm.selectTrack(track_index); + int offset = 0; + buffer = new byte[BUFFER_SIZE]; - for (int i = 0; i < pending_offsets.size(); i++) { - checksum = pending_checksums.get(i); - segment_table_size = 0; + for (header_index = 0; header_index < headers_size.length; header_index++) { + int checksum_offset = offset + HEADER_CHECKSUM_OFFSET; + int checksum = headers.getInt(checksum_offset); - out_seek(pending_offsets.get(i) + data_offsets.get(i)); - - while (segment_table_size < SEGMENTS_PER_PACKET) { + while (webm_segment != null) { bloq = getNextBlock(); - if (bloq == null || !addPacketSegment(bloq.dataSize)) { - webm_block = bloq;// use this block later (if not null) + if (!addPacketSegment(bloq)) { + clearSegmentTable(); + webm_block = bloq; break; } - // NOTE: calling bloq.data.close() is unnecessary - while ((read = bloq.data.read(buffer)) != -1) { - out_write(buffer, read); - checksum = calc_crc32(checksum, buffer, read); + // calculate page checksum + while ((read = bloq.data.read(buffer)) > 0) { + checksum = calc_crc32(checksum, buffer, 0, read); } } - pending_checksums.set(i, checksum); + headers.putInt(checksum_offset, checksum); + offset += headers_size[header_index]; } - /* step 2.2: write every checksum */ - output.rewind(); - output_offset = 0; - buffer = new byte[4]; + /* step 6: write extra headers */ + rewind_source(); - ByteBuffer buff = ByteBuffer.wrap(buffer); - buff.order(ByteOrder.LITTLE_ENDIAN); + for (byte[] buff : data_extra) { + output.write(buff); + } - for (int i = 0; i < pending_checksums.size(); i++) { - out_seek(pending_offsets.get(i)); - buff.putInt(0, pending_checksums.get(i)); - out_write(buffer); + /* step 7: write stream packets */ + byte[] headers_buffers = headers.array(); + offset = 0; + buffer = new byte[BUFFER_SIZE]; + + for (header_index = 0; header_index < headers_size.length; header_index++) { + output.write(headers_buffers, offset, headers_size[header_index]); + offset += headers_size[header_index]; + + while (webm_segment != null) { + bloq = getNextBlock(); + + if (addPacketSegment(bloq)) { + while ((read = bloq.data.read(buffer)) > 0) { + output.write(buffer, 0, read); + } + } else { + clearSegmentTable(); + webm_block = bloq; + break; + } + } } } - private int dump_packetHeader(byte flag, long gran_pos, byte[] immediate_page) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(27 + segment_table_size); + private short make_packetHeader(long gran_pos, ByteBuffer buffer, byte[] immediate_page) { + int offset = buffer.position(); + short length = HEADER_SIZE; - buffer.putInt(0x4F676753);// "OggS" binary string + buffer.putInt(0x5367674f);// "OggS" binary string in little-endian buffer.put((byte) 0x00);// version - buffer.put(flag);// type - - buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.put(packet_flag);// type buffer.putLong(gran_pos);// granulate position @@ -305,28 +333,24 @@ public class OggFromWebMWriter implements Closeable { buffer.putInt(0x00);// page checksum - buffer.order(ByteOrder.BIG_ENDIAN); - buffer.put((byte) segment_table_size);// segment table buffer.put(segment_table, 0, segment_table_size);// segment size - segment_table_size = 0;// clear segment table for next header + length += segment_table_size; - byte[] buff = buffer.array(); - int checksum_crc32 = calc_crc32(0x00, buff, buff.length); + clearSegmentTable();// clear segment table for next header + + int checksum_crc32 = calc_crc32(0x00, buffer.array(), offset, length); if (immediate_page != null) { - checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32); - - out_write(buff); - out_write(immediate_page); - return 0; + checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, 0, immediate_page.length); + System.arraycopy(immediate_page, 0, buffer.array(), length, immediate_page.length); + segment_table_next_timestamp -= TIME_SCALE_NS; } - out_write(buff); - return checksum_crc32; + buffer.putInt(offset + HEADER_CHECKSUM_OFFSET, checksum_crc32); + + return length; } @Nullable @@ -334,7 +358,7 @@ public class OggFromWebMWriter implements Closeable { if ("A_OPUS".equals(webm_track.codecId)) { return new byte[]{ 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string - 0x07, 0x00, 0x00, 0x00,// writting application string size + 0x07, 0x00, 0x00, 0x00,// writing application string size 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags) }; @@ -342,15 +366,15 @@ public class OggFromWebMWriter implements Closeable { return new byte[]{ 0x03,// ???????? 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string - 0x07, 0x00, 0x00, 0x00,// writting application string size + 0x07, 0x00, 0x00, 0x00,// writing application string size 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags) /* - // whole file duration (not implemented) - 0x44,// tag string size - 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, - 0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 + // whole file duration (not implemented) + 0x44,// tag string size + 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, + 0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 */ 0x0F,// tag string size 0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D,// "ENCODER=" binary string @@ -363,13 +387,26 @@ public class OggFromWebMWriter implements Closeable { return null; } - // - private Segment webm_segment = null; - private Cluster webm_cluter = null; - private SimpleBlock webm_block = null; - private long webm_block_last_timecode = 0; - private long webm_block_near_duration = 0; + private void rewind_source() throws IOException { + source.rewind(); + webm = new WebMReader(source); + webm.parse(); + webm_track = webm.selectTrack(track_index); + webm_segment = webm.getNextSegment(); + webm_cluster = null; + webm_block = null; + webm_block_last_timecode = 0L; + + segment_table_next_timestamp = TIME_SCALE_NS; + } + + private ByteBuffer byte_buffer(int size) { + return ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + } + + // + @Nullable private SimpleBlock getNextBlock() throws IOException { SimpleBlock res; @@ -386,17 +423,17 @@ public class OggFromWebMWriter implements Closeable { } } - if (webm_cluter == null) { - webm_cluter = webm_segment.getNextCluster(); - if (webm_cluter == null) { + if (webm_cluster == null) { + webm_cluster = webm_segment.getNextCluster(); + if (webm_cluster == null) { webm_segment = null; return getNextBlock(); } } - res = webm_cluter.getNextSimpleBlock(); + res = webm_cluster.getNextSimpleBlock(); if (res == null) { - webm_cluter = null; + webm_cluster = null; return getNextBlock(); } @@ -421,49 +458,64 @@ public class OggFromWebMWriter implements Closeable { } // - // - private int segment_table_size = 0; - private final byte[] segment_table = new byte[255]; + // + private void clearSegmentTable() { + if (packet_flag != FLAG_CONTINUED) { + segment_table_next_timestamp += TIME_SCALE_NS; + packet_flag = FLAG_UNSET; + } + segment_table_size = 0; + } - private boolean addPacketSegment(long size) { - // check if possible add the segment, without overflow the table + private boolean addPacketSegment(SimpleBlock block) { + if (block == null) { + return false; + } + + long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay; + + if (timestamp >= segment_table_next_timestamp) { + return false; + } + + boolean result = addPacketSegment((int) block.dataSize); + + if (!result && segment_table_next_timestamp < timestamp) { + // WARNING: ¡¡¡¡ not implemented (lack of documentation) !!!! + packet_flag = FLAG_CONTINUED; + } + + return result; + } + + private boolean addPacketSegment(int size) { int available = (segment_table.length - segment_table_size) * 255; + boolean extra = size == 255; + + if (extra) { + // add a zero byte entry in the table + // required to indicate the sample size is exactly 255 + available -= 255; + } + + // check if possible add the segment, without overflow the table if (available < size) { return false;// not enough space on the page } - while (size > 0) { + for (; size > 0; size -= 255) { segment_table[segment_table_size++] = (byte) Math.min(size, 255); - size -= 255; + } + + if (extra) { + segment_table[segment_table_size++] = 0x00; } return true; } // - // - private long output_offset = 0; - - private void out_write(byte[] buffer) throws IOException { - output.write(buffer); - output_offset += buffer.length; - } - - private void out_write(byte[] buffer, int size) throws IOException { - output.write(buffer, 0, size); - output_offset += size; - } - - private void out_seek(long offset) throws IOException { - //if (output.canSeek()) { output.seek(offset); } - output.skip(offset - output_offset); - output_offset = offset; - } - // - // - private final int[] crc32_table = new int[256]; - private void populate_crc32_table() { for (int i = 0; i < 0x100; i++) { int crc = i << 24; @@ -476,10 +528,12 @@ public class OggFromWebMWriter implements Closeable { } } - private int calc_crc32(int initial_crc, byte[] buffer, int size) { - for (int i = 0; i < size; i++) { + private int calc_crc32(int initial_crc, byte[] buffer, int offset, int size) { + size += offset; + + for (; offset < size; offset++) { int reg = (initial_crc >>> 24) & 0xff; - initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)]; + initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[offset] & 0xff)]; } return initial_crc; diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java index 65aa30fa3..605c0a88b 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java @@ -11,7 +11,7 @@ import java.nio.ByteBuffer; class OggFromWebmDemuxer extends Postprocessing { OggFromWebmDemuxer() { - super(false, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); + super(true, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); } @Override @@ -24,7 +24,7 @@ class OggFromWebmDemuxer extends Postprocessing { switch (buffer.getInt()) { case 0x1a45dfa3: - return true;// webm + return true;// webm/mkv case 0x4F676753: return false;// ogg } From 86dafdd92b200e295494e335d024f99d950af6cb Mon Sep 17 00:00:00 2001 From: kapodamy Date: Sat, 28 Sep 2019 18:11:05 -0300 Subject: [PATCH 187/270] long-term downloads resume * recovery infrastructure * bump serialVersionUID of DownloadMission * misc cleanup in DownloadMission.java * remove unused/redundant from strings.xml --- .../newpipe/download/DownloadDialog.java | 34 ++- .../giga/get/DownloadInitializer.java | 15 ++ .../us/shandian/giga/get/DownloadMission.java | 96 ++++++-- .../giga/get/DownloadMissionRecover.java | 222 ++++++++++++++++++ .../shandian/giga/get/DownloadRunnable.java | 27 ++- .../giga/get/DownloadRunnableFallback.java | 11 +- .../giga/get/MissionRecoveryInfo.java | 79 +++++++ .../giga/service/DownloadManager.java | 1 - .../giga/service/DownloadManagerService.java | 36 ++- .../giga/ui/adapter/MissionAdapter.java | 12 +- app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-be/strings.xml | 1 - app/src/main/res/values-cmn/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-da/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-el/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 9 +- app/src/main/res/values-et/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-he/strings.xml | 1 - app/src/main/res/values-hr/strings.xml | 1 - app/src/main/res/values-id/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ko/strings.xml | 1 - app/src/main/res/values-ms/strings.xml | 1 - app/src/main/res/values-nb-rNO/strings.xml | 2 +- app/src/main/res/values-nl-rBE/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pa/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 2 +- 42 files changed, 478 insertions(+), 97 deletions(-) create mode 100644 app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java create mode 100644 app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 90258a6dc..0006b3c12 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -68,6 +68,7 @@ import java.util.Locale; import icepick.Icepick; import icepick.State; import io.reactivex.disposables.CompositeDisposable; +import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.io.StoredDirectoryHelper; import us.shandian.giga.io.StoredFileHelper; import us.shandian.giga.postprocessing.Postprocessing; @@ -762,12 +763,13 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck } Stream selectedStream; + Stream secondaryStream = null; char kind; int threads = threadsSeekBar.getProgress() + 1; String[] urls; + MissionRecoveryInfo[] recoveryInfo; String psName = null; String[] psArgs = null; - String secondaryStreamUrl = null; long nearLength = 0; // more download logic: select muxer, subtitle converter, etc. @@ -786,12 +788,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck kind = 'v'; selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex); - SecondaryStreamHelper secondaryStream = videoStreamsAdapter + SecondaryStreamHelper secondary = videoStreamsAdapter .getAllSecondary() .get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream)); - if (secondaryStream != null) { - secondaryStreamUrl = secondaryStream.getStream().getUrl(); + if (secondary != null) { + secondaryStream = secondary.getStream(); if (selectedStream.getFormat() == MediaFormat.MPEG_4) psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER; @@ -803,8 +805,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck // set nearLength, only, if both sizes are fetched or known. This probably // does not work on slow networks but is later updated in the downloader - if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) { - nearLength = secondaryStream.getSizeInBytes() + videoSize; + if (secondary.getSizeInBytes() > 0 && videoSize > 0) { + nearLength = secondary.getSizeInBytes() + videoSize; } } break; @@ -826,13 +828,25 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck return; } - if (secondaryStreamUrl == null) { - urls = new String[]{selectedStream.getUrl()}; + if (secondaryStream == null) { + urls = new String[]{ + selectedStream.getUrl() + }; + recoveryInfo = new MissionRecoveryInfo[]{ + new MissionRecoveryInfo(selectedStream) + }; } else { - urls = new String[]{selectedStream.getUrl(), secondaryStreamUrl}; + urls = new String[]{ + selectedStream.getUrl(), secondaryStream.getUrl() + }; + recoveryInfo = new MissionRecoveryInfo[]{ + new MissionRecoveryInfo(selectedStream), new MissionRecoveryInfo(secondaryStream) + }; } - DownloadManagerService.startMission(context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength); + DownloadManagerService.startMission( + context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo + ); dismiss(); } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java index 247faeb6d..593feafa7 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java @@ -1,6 +1,7 @@ package us.shandian.giga.get; import androidx.annotation.NonNull; +import android.text.TextUtils; import android.util.Log; import org.schabi.newpipe.streams.io.SharpStream; @@ -151,6 +152,20 @@ public class DownloadInitializer extends Thread { if (!mMission.running || Thread.interrupted()) return; + if (!mMission.unknownLength && mMission.recoveryInfo != null) { + String entityTag = mConn.getHeaderField("ETAG"); + String lastModified = mConn.getHeaderField("Last-Modified"); + MissionRecoveryInfo recovery = mMission.recoveryInfo[mMission.current]; + + if (!TextUtils.isEmpty(entityTag)) { + recovery.validateCondition = entityTag; + } else if (!TextUtils.isEmpty(lastModified)) { + recovery.validateCondition = lastModified;// Note: this is less precise + } else { + recovery.validateCondition = null; + } + } + mMission.running = false; break; } catch (InterruptedIOException | ClosedByInterruptException e) { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index d78f8e32b..77b417118 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -27,7 +27,7 @@ import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; public class DownloadMission extends Mission { - private static final long serialVersionUID = 5L;// last bump: 30 june 2019 + private static final long serialVersionUID = 6L;// last bump: 28 september 2019 static final int BUFFER_SIZE = 64 * 1024; static final int BLOCK_SIZE = 512 * 1024; @@ -51,8 +51,9 @@ public class DownloadMission extends Mission { public static final int ERROR_INSUFFICIENT_STORAGE = 1010; public static final int ERROR_PROGRESS_LOST = 1011; public static final int ERROR_TIMEOUT = 1012; + public static final int ERROR_RESOURCE_GONE = 1013; public static final int ERROR_HTTP_NO_CONTENT = 204; - public static final int ERROR_HTTP_UNSUPPORTED_RANGE = 206; + static final int ERROR_HTTP_FORBIDDEN = 403; /** * The urls of the file to download @@ -125,6 +126,11 @@ public class DownloadMission extends Mission { */ public int threadCount = 3; + /** + * information required to recover a download + */ + public MissionRecoveryInfo[] recoveryInfo; + private transient int finishCount; public transient boolean running; public boolean enqueued; @@ -132,7 +138,6 @@ public class DownloadMission extends Mission { public int errCode = ERROR_NOTHING; public Exception errObject = null; - public transient boolean recovered; public transient Handler mHandler; private transient boolean mWritingToFile; private transient boolean[] blockAcquired; @@ -197,9 +202,9 @@ public class DownloadMission extends Mission { } /** - * Open connection + * Opens a connection * - * @param threadId id of the calling thread, used only for debug + * @param threadId id of the calling thread, used only for debugging * @param rangeStart range start * @param rangeEnd range end * @return a {@link java.net.URLConnection URLConnection} linking to the URL. @@ -251,7 +256,7 @@ public class DownloadMission extends Mission { case 204: case 205: case 207: - throw new HttpError(conn.getResponseCode()); + throw new HttpError(statusCode); case 416: return;// let the download thread handle this error default: @@ -270,10 +275,6 @@ public class DownloadMission extends Mission { synchronized void notifyProgress(long deltaLen) { if (!running) return; - if (recovered) { - recovered = false; - } - if (unknownLength) { length += deltaLen;// Update length before proceeding } @@ -344,7 +345,6 @@ public class DownloadMission extends Mission { if (running) { running = false; - recovered = true; if (threads != null) selfPause(); } } @@ -409,12 +409,13 @@ public class DownloadMission extends Mission { * Start downloading with multiple threads. */ public void start() { - if (running || isFinished()) return; + if (running || isFinished() || urls.length < 1) return; // ensure that the previous state is completely paused. - joinForThread(init); + int maxWait = 10000;// 10 seconds + joinForThread(init, maxWait); if (threads != null) { - for (Thread thread : threads) joinForThread(thread); + for (Thread thread : threads) joinForThread(thread, maxWait); threads = null; } @@ -431,6 +432,11 @@ public class DownloadMission extends Mission { return; } + if (urls[current] == null) { + doRecover(null); + return; + } + if (blocks == null) { initializer(); return; @@ -478,7 +484,6 @@ public class DownloadMission extends Mission { } running = false; - recovered = true; if (init != null && init.isAlive()) { // NOTE: if start() method is running ¡will no have effect! @@ -563,7 +568,7 @@ public class DownloadMission extends Mission { * Write this {@link DownloadMission} to the meta file asynchronously * if no thread is already running. */ - private void writeThisToFile() { + void writeThisToFile() { synchronized (LOCK) { if (deleted) return; Utility.writeToFile(metadata, DownloadMission.this); @@ -667,6 +672,7 @@ public class DownloadMission extends Mission { * @return {@code true} is this mission its "healthy", otherwise, {@code false} */ public boolean isCorrupt() { + if (urls.length < 1) return false; return (isPsFailed() || errCode == ERROR_POSTPROCESSING_HOLD) || isFinished(); } @@ -710,6 +716,48 @@ public class DownloadMission extends Mission { return true; } + /** + * Attempts to recover the download + * + * @param fromError exception which require update the url from the source + */ + void doRecover(Exception fromError) { + Log.i(TAG, "Attempting to recover the mission: " + storage.getName()); + + if (recoveryInfo == null) { + if (fromError == null) + notifyError(ERROR_RESOURCE_GONE, null); + else + notifyError(fromError); + + urls = new String[0];// mark this mission as dead + return; + } + + if (threads != null) { + for (Thread thread : threads) { + if (thread == Thread.currentThread()) continue; + thread.interrupt(); + joinForThread(thread, 0); + } + } + + // set the current download url to null in case if the recovery + // process is canceled. Next time start() method is called the + // recovery will be executed, saving time + urls[current] = null; + + if (recoveryInfo[current].attempts >= maxRetry) { + recoveryInfo[current].attempts = 0; + notifyError(fromError); + return; + } + + threads = new Thread[]{ + runAsync(DownloadMissionRecover.mID, new DownloadMissionRecover(this, fromError)) + }; + } + private boolean deleteThisFromFile() { synchronized (LOCK) { return metadata.delete(); @@ -749,7 +797,13 @@ public class DownloadMission extends Mission { return who; } - private void joinForThread(Thread thread) { + /** + * Waits at most {@code millis} milliseconds for the thread to die + * + * @param thread the desired thread + * @param millis the time to wait in milliseconds + */ + private void joinForThread(Thread thread, int millis) { if (thread == null || !thread.isAlive()) return; if (thread == Thread.currentThread()) return; @@ -764,7 +818,7 @@ public class DownloadMission extends Mission { // start() method called quickly after pause() try { - thread.join(10000); + thread.join(millis); } catch (InterruptedException e) { Log.d(TAG, "timeout on join : " + thread.getName()); throw new RuntimeException("A thread is still running:\n" + thread.getName()); @@ -785,9 +839,9 @@ public class DownloadMission extends Mission { } } - static class Block { - int position; - int done; + public static class Block { + public int position; + public int done; } private static class Lock implements Serializable { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java new file mode 100644 index 000000000..9abd93717 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -0,0 +1,222 @@ +package us.shandian.giga.get; + +import android.util.Log; + +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.StreamExtractor; +import org.schabi.newpipe.extractor.stream.SubtitlesStream; +import org.schabi.newpipe.extractor.stream.VideoStream; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.nio.channels.ClosedByInterruptException; +import java.util.List; + +import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; + +public class DownloadMissionRecover extends Thread { + private static final String TAG = "DownloadMissionRecover"; + static final int mID = -3; + + private final DownloadMission mMission; + private final MissionRecoveryInfo mRecovery; + private final Exception mFromError; + private HttpURLConnection mConn; + + DownloadMissionRecover(DownloadMission mission, Exception originError) { + mMission = mission; + mFromError = originError; + mRecovery = mission.recoveryInfo[mission.current]; + } + + @Override + public void run() { + if (mMission.source == null) { + mMission.notifyError(mFromError); + return; + } + + try { + /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { + resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); + return; + }*/ + + StreamingService svr = NewPipe.getServiceByUrl(mMission.source); + + if (svr == null) { + throw new RuntimeException("Unknown source service"); + } + + StreamExtractor extractor = svr.getStreamExtractor(mMission.source); + extractor.fetchPage(); + + if (!mMission.running || super.isInterrupted()) return; + + String url = null; + + switch (mMission.kind) { + case 'a': + for (AudioStream audio : extractor.getAudioStreams()) { + if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { + url = audio.getUrl(); + break; + } + } + break; + case 'v': + List videoStreams; + if (mRecovery.desired2) + videoStreams = extractor.getVideoOnlyStreams(); + else + videoStreams = extractor.getVideoStreams(); + for (VideoStream video : videoStreams) { + if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { + url = video.getUrl(); + break; + } + } + break; + case 's': + for (SubtitlesStream subtitles : extractor.getSubtitles(mRecovery.format)) { + String tag = subtitles.getLanguageTag(); + if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { + url = subtitles.getURL(); + break; + } + } + break; + default: + throw new RuntimeException("Unknown stream type"); + } + + resolve(url); + } catch (Exception e) { + if (!mMission.running || e instanceof ClosedByInterruptException) return; + mRecovery.attempts++; + mMission.notifyError(e); + } + } + + private void resolve(String url) throws IOException, DownloadMission.HttpError { + if (mRecovery.validateCondition == null) { + Log.w(TAG, "validation condition not defined, the resource can be stale"); + } + + if (mMission.unknownLength || mRecovery.validateCondition == null) { + recover(url, false); + return; + } + + /////////////////////////////////////////////////////////////////////// + ////// Validate the http resource doing a range request + ///////////////////// + try { + mConn = mMission.openConnection(url, mID, mMission.length - 10, mMission.length); + mConn.setRequestProperty("If-Range", mRecovery.validateCondition); + mMission.establishConnection(mID, mConn); + + int code = mConn.getResponseCode(); + + switch (code) { + case 200: + case 413: + // stale + recover(url, true); + return; + case 206: + // in case of validation using the Last-Modified date, check the resource length + long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range")); + boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length; + + recover(url, lengthMismatch); + return; + } + + throw new DownloadMission.HttpError(code); + } catch (Exception e) { + if (!mMission.running || e instanceof ClosedByInterruptException) return; + throw e; + } finally { + this.interrupt(); + } + } + + private void recover(String url, boolean stale) { + Log.i(TAG, + String.format("download recovered name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) + ); + + if (url == null) { + mMission.notifyError(ERROR_RESOURCE_GONE, null); + return; + } + + mMission.urls[mMission.current] = url; + mRecovery.attempts = 0; + + if (stale) { + mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); + } + + mMission.writeThisToFile(); + + if (!mMission.running || super.isInterrupted()) return; + + mMission.running = false; + mMission.start(); + } + + private long[] parseContentRange(String value) { + long[] range = new long[3]; + + if (value == null) { + // this never should happen + return range; + } + + try { + value = value.trim(); + + if (!value.startsWith("bytes")) { + return range;// unknown range type + } + + int space = value.lastIndexOf(' ') + 1; + int dash = value.indexOf('-', space) + 1; + int bar = value.indexOf('/', dash); + + // start + range[0] = Long.parseLong(value.substring(space, dash - 1)); + + // end + range[1] = Long.parseLong(value.substring(dash, bar)); + + // resource length + value = value.substring(bar + 1); + if (value.equals("*")) { + range[2] = -1;// unknown length received from the server but should be valid + } else { + range[2] = Long.parseLong(value); + } + } catch (Exception e) { + // nothing to do + } + + return range; + } + + @Override + public void interrupt() { + super.interrupt(); + if (mConn != null) { + try { + mConn.disconnect(); + } catch (Exception e) { + // nothing to do + } + } + } +} diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index f5b9b06d4..1d2a4eee7 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -10,8 +10,10 @@ import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; import us.shandian.giga.get.DownloadMission.Block; +import us.shandian.giga.get.DownloadMission.HttpError; import static org.schabi.newpipe.BuildConfig.DEBUG; +import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN; /** @@ -19,7 +21,7 @@ import static org.schabi.newpipe.BuildConfig.DEBUG; * an error occurs or the process is stopped. */ public class DownloadRunnable extends Thread { - private static final String TAG = DownloadRunnable.class.getSimpleName(); + private static final String TAG = "DownloadRunnable"; private final DownloadMission mMission; private final int mId; @@ -41,13 +43,7 @@ public class DownloadRunnable extends Thread { public void run() { boolean retry = false; Block block = null; - int retryCount = 0; - - if (DEBUG) { - Log.d(TAG, mId + ":recovered: " + mMission.recovered); - } - SharpStream f; try { @@ -133,6 +129,17 @@ public class DownloadRunnable extends Thread { } catch (Exception e) { if (!mMission.running || e instanceof ClosedByInterruptException) break; + if (e instanceof HttpError && ((HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { + // for youtube streams. The url has expired, recover + f.close(); + + if (mId == 1) { + // only the first thread will execute the recovery procedure + mMission.doRecover(e); + } + return; + } + if (retryCount++ >= mMission.maxRetry) { mMission.notifyError(e); break; @@ -144,11 +151,7 @@ public class DownloadRunnable extends Thread { } } - try { - f.close(); - } catch (Exception err) { - // ¿ejected media storage? ¿file deleted? ¿storage ran out of space? - } + f.close(); if (DEBUG) { Log.d(TAG, "thread " + mId + " exited from main download loop"); diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index 7fb1f0c77..b5937c577 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -10,9 +10,11 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; +import us.shandian.giga.get.DownloadMission.HttpError; import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; +import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN; /** * Single-threaded fallback mode @@ -85,7 +87,7 @@ public class DownloadRunnableFallback extends Thread { mIs = mConn.getInputStream(); - byte[] buf = new byte[64 * 1024]; + byte[] buf = new byte[DownloadMission.BUFFER_SIZE]; int len = 0; while (mMission.running && (len = mIs.read(buf, 0, buf.length)) != -1) { @@ -103,6 +105,13 @@ public class DownloadRunnableFallback extends Thread { if (!mMission.running || e instanceof ClosedByInterruptException) return; + if (e instanceof HttpError && ((HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { + // for youtube streams. The url has expired, recover + mMission.doRecover(e); + dispose(); + return; + } + if (mRetryCount++ >= mMission.maxRetry) { mMission.notifyError(e); return; diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java new file mode 100644 index 000000000..553ba6d89 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java @@ -0,0 +1,79 @@ +package us.shandian.giga.get; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.Stream; +import org.schabi.newpipe.extractor.stream.SubtitlesStream; +import org.schabi.newpipe.extractor.stream.VideoStream; + +import java.io.Serializable; + +public class MissionRecoveryInfo implements Serializable, Parcelable { + private static final long serialVersionUID = 0L; + //public static final String DIRECT_SOURCE = "direct-source://"; + + public MediaFormat format; + String desired; + boolean desired2; + int desiredBitrate; + + transient int attempts = 0; + + String validateCondition = null; + + public MissionRecoveryInfo(@NonNull Stream stream) { + if (stream instanceof AudioStream) { + desiredBitrate = ((AudioStream) stream).average_bitrate; + desired2 = false; + } else if (stream instanceof VideoStream) { + desired = ((VideoStream) stream).getResolution(); + desired2 = ((VideoStream) stream).isVideoOnly(); + } else if (stream instanceof SubtitlesStream) { + desired = ((SubtitlesStream) stream).getLanguageTag(); + desired2 = ((SubtitlesStream) stream).isAutoGenerated(); + } else { + throw new RuntimeException("Unknown stream kind"); + } + + format = stream.getFormat(); + if (format == null) throw new NullPointerException("Stream format cannot be null"); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(this.format.ordinal()); + parcel.writeString(this.desired); + parcel.writeInt(this.desired2 ? 0x01 : 0x00); + parcel.writeInt(this.desiredBitrate); + parcel.writeString(this.validateCondition); + } + + private MissionRecoveryInfo(Parcel parcel) { + this.format = MediaFormat.values()[parcel.readInt()]; + this.desired = parcel.readString(); + this.desired2 = parcel.readInt() != 0x00; + this.desiredBitrate = parcel.readInt(); + this.validateCondition = parcel.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public MissionRecoveryInfo createFromParcel(Parcel source) { + return new MissionRecoveryInfo(source); + } + + @Override + public MissionRecoveryInfo[] newArray(int size) { + return new MissionRecoveryInfo[size]; + } + }; +} diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index 3d34411b9..a859a87ca 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -177,7 +177,6 @@ public class DownloadManager { mis.psAlgorithm.setTemporalDir(pickAvailableTemporalDir(ctx)); } - mis.recovered = exists; mis.metadata = sub; mis.maxRetry = mPrefMaxRetry; mis.mHandler = mHandler; diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index 461787b62..ea9029c0b 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.Handler.Callback; import android.os.IBinder; import android.os.Message; +import android.os.Parcelable; import android.preference.PreferenceManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -40,8 +41,11 @@ import org.schabi.newpipe.player.helper.LockManager; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import us.shandian.giga.get.DownloadMission; +import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.io.StoredDirectoryHelper; import us.shandian.giga.io.StoredFileHelper; import us.shandian.giga.postprocessing.Postprocessing; @@ -73,6 +77,7 @@ public class DownloadManagerService extends Service { private static final String EXTRA_PATH = "DownloadManagerService.extra.storagePath"; private static final String EXTRA_PARENT_PATH = "DownloadManagerService.extra.storageParentPath"; private static final String EXTRA_STORAGE_TAG = "DownloadManagerService.extra.storageTag"; + private static final String EXTRA_RECOVERY_INFO = "DownloadManagerService.extra.recoveryInfo"; private static final String ACTION_RESET_DOWNLOAD_FINISHED = APPLICATION_ID + ".reset_download_finished"; private static final String ACTION_OPEN_DOWNLOADS_FINISHED = APPLICATION_ID + ".open_downloads_finished"; @@ -364,18 +369,20 @@ public class DownloadManagerService extends Service { /** * Start a new download mission * - * @param context the activity context - * @param urls the list of urls to download - * @param storage where the file is saved - * @param kind type of file (a: audio v: video s: subtitle ?: file-extension defined) - * @param threads the number of threads maximal used to download chunks of the file. - * @param psName the name of the required post-processing algorithm, or {@code null} to ignore. - * @param source source url of the resource - * @param psArgs the arguments for the post-processing algorithm. - * @param nearLength the approximated final length of the file + * @param context the activity context + * @param urls array of urls to download + * @param storage where the file is saved + * @param kind type of file (a: audio v: video s: subtitle ?: file-extension defined) + * @param threads the number of threads maximal used to download chunks of the file. + * @param psName the name of the required post-processing algorithm, or {@code null} to ignore. + * @param source source url of the resource + * @param psArgs the arguments for the post-processing algorithm. + * @param nearLength the approximated final length of the file + * @param recoveryInfo array of MissionRecoveryInfo, in case is required recover the download */ - public static void startMission(Context context, String[] urls, StoredFileHelper storage, char kind, - int threads, String source, String psName, String[] psArgs, long nearLength) { + public static void startMission(Context context, String[] urls, StoredFileHelper storage, + char kind, int threads, String source, String psName, + String[] psArgs, long nearLength, MissionRecoveryInfo[] recoveryInfo) { Intent intent = new Intent(context, DownloadManagerService.class); intent.setAction(Intent.ACTION_RUN); intent.putExtra(EXTRA_URLS, urls); @@ -385,6 +392,7 @@ public class DownloadManagerService extends Service { intent.putExtra(EXTRA_POSTPROCESSING_NAME, psName); intent.putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs); intent.putExtra(EXTRA_NEAR_LENGTH, nearLength); + intent.putExtra(EXTRA_RECOVERY_INFO, recoveryInfo); intent.putExtra(EXTRA_PARENT_PATH, storage.getParentUri()); intent.putExtra(EXTRA_PATH, storage.getUri()); @@ -404,6 +412,7 @@ public class DownloadManagerService extends Service { String source = intent.getStringExtra(EXTRA_SOURCE); long nearLength = intent.getLongExtra(EXTRA_NEAR_LENGTH, 0); String tag = intent.getStringExtra(EXTRA_STORAGE_TAG); + Parcelable[] parcelRecovery = intent.getParcelableArrayExtra(EXTRA_RECOVERY_INFO); StoredFileHelper storage; try { @@ -418,10 +427,15 @@ public class DownloadManagerService extends Service { else ps = Postprocessing.getAlgorithm(psName, psArgs); + MissionRecoveryInfo[] recovery = new MissionRecoveryInfo[parcelRecovery.length]; + for (int i = 0; i < parcelRecovery.length; i++) + recovery[i] = (MissionRecoveryInfo) parcelRecovery[i]; + final DownloadMission mission = new DownloadMission(urls, storage, kind, ps); mission.threadCount = threads; mission.source = source; mission.nearLength = nearLength; + mission.recoveryInfo = recovery; if (ps != null) ps.setTemporalDir(DownloadManager.pickAvailableTemporalDir(this)); diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 6d1169031..6c6198750 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -62,7 +62,6 @@ import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; import static us.shandian.giga.get.DownloadMission.ERROR_CONNECT_HOST; import static us.shandian.giga.get.DownloadMission.ERROR_FILE_CREATION; import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_NO_CONTENT; -import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_UNSUPPORTED_RANGE; import static us.shandian.giga.get.DownloadMission.ERROR_INSUFFICIENT_STORAGE; import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; import static us.shandian.giga.get.DownloadMission.ERROR_PATH_CREATION; @@ -71,6 +70,7 @@ import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING; import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_HOLD; import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_STOPPED; import static us.shandian.giga.get.DownloadMission.ERROR_PROGRESS_LOST; +import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; import static us.shandian.giga.get.DownloadMission.ERROR_SSL_EXCEPTION; import static us.shandian.giga.get.DownloadMission.ERROR_TIMEOUT; import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION; @@ -430,7 +430,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb switch (mission.errCode) { case 416: - msg = R.string.error_http_requested_range_not_satisfiable; + msg = R.string.error_http_unsupported_range; break; case 404: msg = R.string.error_http_not_found; @@ -443,9 +443,6 @@ public class MissionAdapter extends Adapter implements Handler.Callb case ERROR_HTTP_NO_CONTENT: msg = R.string.error_http_no_content; break; - case ERROR_HTTP_UNSUPPORTED_RANGE: - msg = R.string.error_http_unsupported_range; - break; case ERROR_PATH_CREATION: msg = R.string.error_path_creation; break; @@ -480,6 +477,9 @@ public class MissionAdapter extends Adapter implements Handler.Callb case ERROR_TIMEOUT: msg = R.string.error_timeout; break; + case ERROR_RESOURCE_GONE: + msg = R.string.error_download_resource_gone; + break; default: if (mission.errCode >= 100 && mission.errCode < 600) { msgEx = "HTTP " + mission.errCode; @@ -859,7 +859,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb delete.setVisible(true); - boolean flag = !mission.isPsFailed(); + boolean flag = !mission.isPsFailed() && mission.urls.length > 0; start.setVisible(flag); queue.setVisible(flag); } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 7156d08ba..43b45d15e 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -468,7 +468,6 @@ لا يمكن الاتصال بالخادم الخادم لايقوم بإرسال البيانات الخادم لا يقبل التنزيل المتعدد، إعادة المحاولة مع @string/msg_threads = 1 - عدم استيفاء النطاق المطلوب غير موجود فشلت المعالجة الاولية حذف التنزيلات المنتهية diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 93307cbcf..3c79a96d3 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -455,7 +455,6 @@ Немагчыма злучыцца з серверам Не атрымалася атрымаць дадзеныя з сервера Сервер не падтрымлівае шматструменную загрузку, паспрабуйце з @string/msg_threads = 1 - Запытаны дыяпазон недапушчальны Не знойдзена Пасляапрацоўка не ўдалася Ачысціць завершаныя diff --git a/app/src/main/res/values-cmn/strings.xml b/app/src/main/res/values-cmn/strings.xml index 49801a190..bcb145c16 100644 --- a/app/src/main/res/values-cmn/strings.xml +++ b/app/src/main/res/values-cmn/strings.xml @@ -460,7 +460,6 @@ NewPipe 更新可用! 无法创建目标文件夹 服务器不接受多线程下载, 请使用 @string/msg_threads = 1重试 - 请求范围无法满足 继续进行%s个待下载转移 切换至移动数据时有用,尽管一些下载无法被暂停 显示评论 diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index d539923fe..b741e0d16 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -463,7 +463,6 @@ otevření ve vyskakovacím okně Nelze se připojit k serveru Server neposílá data Server neakceptuje vícevláknové stahování, opakujte akci s @string/msg_threads = 1 - Požadovaný rozsah nelze splnit Nenalezeno Post-processing selhal Vyčistit dokončená stahování diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 42ffd474b..199c2f85d 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -380,7 +380,6 @@ Kan ikke forbinde til serveren Serveren sender ikke data Serveren accepterer ikke multitrådede downloads; prøv igen med @string/msg_threads = 1 - Det anmodede interval er ikke gyldigt Ikke fundet Efterbehandling fejlede Stop diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2d6b5b6d2..3279e919c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -454,7 +454,6 @@ Kann nicht mit dem Server verbinden Der Server sendet keine Daten Der Server erlaubt kein mehrfädiges Herunterladen – wiederhole mit @string/msg_threads = 1 - Gewünschter Bereich ist nicht verfügbar Nicht gefunden Nachbearbeitung fehlgeschlagen Um fertige Downloads bereinigen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 4f3499cfd..372cbb1a2 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -456,7 +456,6 @@ Αδυναμία σύνδεσης με τον εξυπηρετητή Ο εξυπηρετητής δεν μπορεί να στείλει τα δεδομένα Ο εξυπηρετητής δέν υποστηρίζει πολυνηματικές λήψεις, ξαναπροσπαθήστε με @string/msg_threads = 1 - Το ζητούμενο εύρος δεν μπορεί να εξυπηρετηθεί Δεν βρέθηκε Μετεπεξεργασία απέτυχε Εκκαθάριση ολοκληρωμένων λήψεων diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3aa0bac66..2f69e62cb 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -351,8 +351,8 @@ \n3. Inicie sesión cuando se le pida \n4. Copie la URL del perfil a la que fue redireccionado. suID, soundcloud.com/suID - Observe que esta operación puede causar un uso intensivo de la red. -\n + Observe que esta operación puede causar un uso intensivo de la red. +\n \n¿Quiere continuar\? Cargar miniaturas Desactívela para evitar la carga de miniaturas y ahorrar datos y memoria. Se vaciará la antememoria de imágenes en la memoria volátil y en el disco. @@ -444,8 +444,8 @@ Fallo la conexión segura No se pudo encontrar el servidor No se puede conectar con el servidor - El servidor no está enviando datos - El servidor no acepta descargas multiproceso; intente de nuevo con @string/msg_threads = 1 + El servidor no devolvio datos + El servidor no acepta descargas multi-hilos, intente de nuevo con @string/msg_threads = 1 No se puede satisfacer el intervalo seleccionado No encontrado Falló el posprocesamiento @@ -453,6 +453,7 @@ No hay suficiente espacio disponible en el dispositivo Se perdió el progreso porque el archivo fue eliminado Tiempo de espera excedido + El recurso solicitado ya no esta disponible Preguntar dónde descargar Se preguntará dónde guardar cada descarga Se le preguntará dónde guardar cada descarga. diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index baad94b5d..4dfcc3d0e 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -457,7 +457,6 @@ Serveriga ei saadud ühendust Server ei saada andmeid Server ei toeta mitmelõimelisi allalaadimisi. Proovi uuesti kasutades @string/msg_threads = 1 - Taotletud vahemik ei ole rahuldatav Ei leitud Järeltöötlemine nurjus Eemalda lõpetatud allalaadimised diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7da39393e..7b636d383 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -456,7 +456,6 @@ Ezin da zerbitzariarekin konektatu Zerbitzariak ez du daturik bidaltzen Zerbitzariak ez ditu hainbat hariko deskargak onartzen, saiatu @string/msg_threads = 1 erabilita - Eskatutako barrutia ezin da bete Ez aurkitua Post-prozesuak huts egin du Garbitu amaitutako deskargak diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 147502088..b4388e39f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -467,7 +467,6 @@ Utilisation des onglets par défaut, erreur lors de la lecture des onglets enregistrés Le serveur n’accepte pas les téléchargements multi-fils, veuillez réessayer avec @string/msg_threads = 1 Continuer vos %s transferts en attente depuis Téléchargement - Le domaine désiré n\'est pas disponible Afficher les commentaires Désactiver pour ne pas afficher les commentaires Lecture automatique diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index b5a0778d4..5e340d8b3 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -461,7 +461,6 @@ לא ניתן להתחבר לשרת השרת לא שולח נתונים "השרת לא מקבל הורדות רב ערוציות, מוטב לנסות שוב עם ‎@string/msg_threads = 1 " - הטווח המבוקש לא מתאים לא נמצא העיבוד המאוחר נכשל פינוי ההורדות שהסתיימו diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index e85d5810e..aa4ff9113 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -454,7 +454,6 @@ Nije moguće povezati se s serverom Server ne šalje podatke Poslužitelj ne prihvaća preuzimanja s više niti, pokušaj ponovo s @string/msg_threads = 1 - Traženi raspon nije zadovoljavajući Nije pronađeno Naknadna obrada nije uspjela Obriši završena preuzimanja diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index db738d749..d52f5fafa 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -450,7 +450,6 @@ Tidak dapat terhubung ke server Server tidak mengirim data Server tidak menerima unduhan multi-utas, coba lagi dengan @string/msg_threads = 1 - Rentang yang diminta tidak memuaskan Tidak ditemukan Pengolahan-pasca gagal Hapus unduhan yang sudah selesai diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 35fdebeda..c92292f99 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -454,7 +454,6 @@ Impossibile connettersi al server Il server non invia dati Il server non accetta download multipli, riprovare con @string/msg_threads = 1 - Intervallo richiesto non soddisfatto Non trovato Post-processing fallito Pulisci i download completati diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b67da798c..58ca2ebff 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -440,7 +440,6 @@ サーバに接続できません サーバがデータを送信していません サーバが同時接続ダウンロードを受け付けません。再試行してください @string/msg_threads = 1 - 必要な範囲が満たされていません 見つかりません 保存処理に失敗しました 完了済みを一覧から削除します diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 333891910..fdc76b04e 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -451,7 +451,6 @@ 서버에 접속할 수 없습니다 서버가 데이터를 전송하지 않고 있습니다 서버가 다중 스레드 다운로드를 받아들이지 않습니다, @string/msg_threads = 1 를 사용해 다시 시도해보세요 - 요청된 HTTP 범위가 충분하지 않습니다 HTTP 찾을 수 없습니다 후처리 작업이 실패하였습니다 완료된 다운로드 비우기 diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index c7fa5de92..daa120ea2 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -450,7 +450,6 @@ Tidak dapat menyambung ke server Server tidak menghantar data Server tidak menerima muat turun berbilang thread, cuba lagi dengan @string/msg_threads = 1 - Julat yang diminta tidak memuaskan Tidak ditemui Pemprosesan-pasca gagal Hapuskan senarai muat turun yang selesai diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index d26886844..6262480b0 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -496,7 +496,7 @@ Sett nedlastinger på pause Spør om hvor ting skal lastes ned til Du vil bli spurt om hvor hver nedlasting skal plasseres - Du vil bli spurt om hvor hver nedlasting skal plasseres. + Du vil bli spurt om hvor hver nedlasting skal plasseres. \nSkru på SAF hvis du vil laste ned til eksternt SD-kort Bruk SAF Lagringstilgangsrammeverk (SAF) tillater nedlastinger til eksternt SD-kort. diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml index 94feb4915..f64ff6bf9 100644 --- a/app/src/main/res/values-nl-rBE/strings.xml +++ b/app/src/main/res/values-nl-rBE/strings.xml @@ -454,7 +454,6 @@ Kan geen verbinding maken met de server De server verzendt geen gegevens De server aanvaardt geen meerdradige downloads, probeert het opnieuw met @string/msg_threads = 1 - Gevraagd bereik niet beschikbaar Niet gevonden Nabewerking mislukt Voltooide downloads wissen diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index f7acba6ae..6aecc2cd1 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -454,7 +454,6 @@ Kan niet met de server verbinden De server verzendt geen gegevens De server accepteert geen multi-threaded downloads, probeer het opnieuw met @string/msg_threads = 1 - Gevraagde bereik niet beschikbaar Niet gevonden Nabewerking mislukt Voltooide downloads wissen diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index c31eb805d..b57564eba 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -450,7 +450,6 @@ ਸਰਵਰ ਨਾਲ ਜੁੜ ਨਹੀਂ ਸਕਦਾ ਸਰਵਰ ਨੇ ਡਾਟਾ ਨਹੀਂ ਭੇਜਿਆ ਸਰਵਰ ਮਲਟੀ-Threaded ਡਾਊਨਲੋਡਸ ਨੂੰ ਸਵੀਕਾਰ ਨਹੀਂ ਕਰਦਾ, ਇਸ ਨਾਲ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ @string/msg_threads = 1 - ਬੇਨਤੀ ਕੀਤੀ ਸੀਮਾ ਤਸੱਲੀਬਖਸ਼ ਨਹੀਂ ਹੈ ਨਹੀਂ ਲਭਿਆ Post-processing ਫੇਲ੍ਹ ਮੁਕੰਮਲ ਹੋਈਆਂ ਡਾਊਨਲੋਡ ਸਾਫ਼ ਕਰੋ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d3c84aa22..ca1e52ff2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -456,7 +456,6 @@ Nie można połączyć się z serwerem Serwer nie wysyła danych Serwer nie akceptuje pobierania wielowątkowego, spróbuj ponownie za pomocą @string/msg_threads = 1 - Niewłaściwy zakres Nie znaleziono Przetwarzanie końcowe nie powiodło się Wyczyść ukończone pobieranie diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index aaac4fd4c..0bdf4d006 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -463,7 +463,6 @@ abrir em modo popup Não foi possível conectar ao servidor O servidor não envia dados O servidor não aceita downloads em multi-thread, tente com @string/msg_threads = 1 - Intervalo solicitado não aceito Não encontrado Falha no pós processamento Limpar downloads finalizados diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5d7cd8146..6d55023d1 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -452,7 +452,6 @@ Não é possível ligar ao servidor O servidor não envia dados O servidor não aceita transferências de vários processos, tente novamente com @string/msg_threads = 1 - Intervalo solicitado não satisfatório Não encontrado Pós-processamento falhado Limpar transferências concluídas diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6f079a221..51771e1b1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -454,7 +454,6 @@ Доступ запрещён системой Сервер не найден Сервер не принимает многопоточные загрузки, повторная попытка с @string/msg_threads = 1 - Запрашиваемый диапазон недопустим Не найдено Очистить завершённые Остановить diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 09502f60a..36c0afd84 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -462,7 +462,6 @@ Nepodarilo sa pripojiť k serveru Server neposiela údaje Server neakceptuje preberanie viacerých vlákien, zopakujte s @string/msg_threads = 1 - Požadovaný rozsah nie je uspokojivý Nenájdené Post-spracovanie zlyhalo Vyčistiť dokončené sťahovania diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c17b58f50..6c9c66f69 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -449,7 +449,6 @@ Sunucuya bağlanılamıyor Sunucu veri göndermiyor Sunucu, çok iş parçacıklı indirmeleri kabul etmez, @string/msg_threads = 1 ile yeniden deneyin - İstenen aralık karşılanamıyor Bulunamadı İşlem sonrası başarısız Tamamlanan indirmeleri temizle diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 375557b04..fcce99e89 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -471,7 +471,6 @@ Помилка зчитування збережених вкладок. Використовую типові вкладки. Вкладки, що відображаються на головній сторінці Показувати сповіщення з пропозицією оновити застосунок за наявності нової версії - Запитуваний діапазон неприпустимий Продовжити ваші %s відкладених переміщень із Завантажень Корисно під час переходу на мобільні дані, хоча деякі завантаження не можуть бути призупинені Показувати коментарі diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 74b8b395c..f8860acfd 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -449,7 +449,6 @@ Không thế kết nối với máy chủ Máy chủ không gửi dữ liệu về Máy chủ không chấp nhận tải đa luồng, thử lại với số luồng = 1 - (HTTP) Không thể đáp ứng khoảng dữ liệu đã yêu cầu Không tìm thấy Xử lý thất bại Dọn các tải về đã hoàn thành diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index fe4c1b00a..310bae3a3 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -447,7 +447,6 @@ 無法連線到伺服器 伺服器沒有傳送資料 伺服器不接受多執行緒下載,請以 @string/msg_threads = 1 重試 - 請求範圍無法滿足 找不到 後處理失敗 清除已結束的下載 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a34b00ea9..2917fb9fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -551,13 +551,13 @@ Can not connect to the server The server does not send data The server does not accept multi-threaded downloads, retry with @string/msg_threads = 1 - Requested range not satisfiable Not found Post-processing failed NewPipe was closed while working on the file No space left on device Progress lost, because the file was deleted Connection timeout + The solicited resource is not available anymore Clear finished downloads Are you sure? Continue your %s pending transfers from Downloads From 570738190d3afb354445517e86781e2fe91f3459 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Sun, 29 Sep 2019 01:44:13 -0300 Subject: [PATCH 188/270] Mp4FromDashWriter fixes * correct calculation of "co64" box and usage of 64bits offsets * generate one chunk for audio streams like ffmpeg does, attempt to fix cut-off audio * misc. cleanup --- .../newpipe/streams/Mp4FromDashWriter.java | 79 ++++++++++++------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 03aab447c..420f77955 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -6,6 +6,7 @@ import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashChunk; import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashSample; import org.schabi.newpipe.streams.Mp4DashReader.Mp4Track; import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry; +import org.schabi.newpipe.streams.Mp4DashReader.TrackKind; import org.schabi.newpipe.streams.io.SharpStream; import java.io.IOException; @@ -22,6 +23,7 @@ public class Mp4FromDashWriter { private final static byte SAMPLES_PER_CHUNK = 6;// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6 private final static long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;// near 3.999 GiB private final static int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024); // 2.2 MiB enough for: 1080p 60fps 00h35m00s + private final static short SINGLE_CHUNK_SAMPLE_BUFFER = 256; private final long time; @@ -145,7 +147,7 @@ public class Mp4FromDashWriter { // not allowed for very short tracks (less than 0.5 seconds) // outStream = output; - int read = 8;// mdat box header size + long read = 8;// mdat box header size long totalSampleSize = 0; int[] sampleExtra = new int[readers.length]; int[] defaultMediaTime = new int[readers.length]; @@ -157,6 +159,8 @@ public class Mp4FromDashWriter { tablesInfo[i] = new TablesInfo(); } + boolean singleChunk = tracks.length == 1 && tracks[0].kind == TrackKind.Audio; + // for (int i = 0; i < readers.length; i++) { int samplesSize = 0; @@ -210,14 +214,21 @@ public class Mp4FromDashWriter { tablesInfo[i].stco = (tmp / SAMPLES_PER_CHUNK) + 1;// +1 for samples in first chunk tmp = tmp % SAMPLES_PER_CHUNK; - if (tmp == 0) { + if (singleChunk) { + // avoid split audio streams in chunks + tablesInfo[i].stsc = 1; + tablesInfo[i].stsc_bEntries = new int[]{ + 1, tablesInfo[i].stsz, 1 + }; + tablesInfo[i].stco = 1; + } else if (tmp == 0) { tablesInfo[i].stsc = 2;// first chunk (init) and succesive chunks tablesInfo[i].stsc_bEntries = new int[]{ 1, SAMPLES_PER_CHUNK_INIT, 1, 2, SAMPLES_PER_CHUNK, 1 }; } else { - tablesInfo[i].stsc = 3;// first chunk (init) and succesive chunks and remain chunk + tablesInfo[i].stsc = 3;// first chunk (init) and successive chunks and remain chunk tablesInfo[i].stsc_bEntries = new int[]{ 1, SAMPLES_PER_CHUNK_INIT, 1, 2, SAMPLES_PER_CHUNK, 1, @@ -268,10 +279,10 @@ public class Mp4FromDashWriter { } else {*/ if (auxSize > 0) { int length = auxSize; - byte[] buffer = new byte[8 * 1024];// 8 KiB + byte[] buffer = new byte[64 * 1024];// 64 KiB while (length > 0) { int count = Math.min(length, buffer.length); - outWrite(buffer, 0, count); + outWrite(buffer, count); length -= count; } } @@ -280,7 +291,7 @@ public class Mp4FromDashWriter { outSeek(ftyp_size); } - // tablesInfo contais row counts + // tablesInfo contains row counts // and after returning from make_moov() will contain table offsets make_moov(defaultMediaTime, tablesInfo, is64); @@ -291,7 +302,7 @@ public class Mp4FromDashWriter { writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stsc_bEntries.length, tablesInfo[i].stsc_bEntries); tablesInfo[i].stsc_bEntries = null; if (tablesInfo[i].ctts > 0) { - sampleCount[i] = 1;// index is not base zero + sampleCount[i] = 1;// the index is not base zero sampleExtra[i] = -1; } } @@ -303,8 +314,8 @@ public class Mp4FromDashWriter { outWrite(make_mdat(totalSampleSize, is64)); int[] sampleIndex = new int[readers.length]; - int[] sizes = new int[SAMPLES_PER_CHUNK]; - int[] sync = new int[SAMPLES_PER_CHUNK]; + int[] sizes = new int[singleChunk ? SINGLE_CHUNK_SAMPLE_BUFFER : SAMPLES_PER_CHUNK]; + int[] sync = new int[singleChunk ? SINGLE_CHUNK_SAMPLE_BUFFER : SAMPLES_PER_CHUNK]; int written = readers.length; while (written > 0) { @@ -317,7 +328,12 @@ public class Mp4FromDashWriter { long chunkOffset = writeOffset; int syncCount = 0; - int limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK; + int limit; + if (singleChunk) { + limit = SINGLE_CHUNK_SAMPLE_BUFFER; + } else { + limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK; + } int j = 0; for (; j < limit; j++) { @@ -354,7 +370,7 @@ public class Mp4FromDashWriter { sizes[j] = sample.data.length; } - outWrite(sample.data, 0, sample.data.length); + outWrite(sample.data, sample.data.length); } if (j > 0) { @@ -368,10 +384,16 @@ public class Mp4FromDashWriter { tablesInfo[i].stss = writeEntryArray(tablesInfo[i].stss, syncCount, sync); } - if (is64) { - tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset); - } else { - tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1, (int) chunkOffset); + if (tablesInfo[i].stco > 0) { + if (is64) { + tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset); + } else { + tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1, (int) chunkOffset); + } + + if (singleChunk) { + tablesInfo[i].stco = -1; + } } outRestore(); @@ -451,12 +473,12 @@ public class Mp4FromDashWriter { // private void outWrite(byte[] buffer) throws IOException { - outWrite(buffer, 0, buffer.length); + outWrite(buffer, buffer.length); } - private void outWrite(byte[] buffer, int offset, int count) throws IOException { + private void outWrite(byte[] buffer, int count) throws IOException { writeOffset += count; - outStream.write(buffer, offset, count); + outStream.write(buffer, 0, count); } private void outSeek(long offset) throws IOException { @@ -509,7 +531,6 @@ public class Mp4FromDashWriter { ); if (extra >= 0) { - //size += 4;// commented for auxiliar buffer !!! offset += 4; auxWrite(extra); } @@ -531,7 +552,7 @@ public class Mp4FromDashWriter { if (moovSimulation) { writeOffset += buffer.length; } else if (auxBuffer == null) { - outWrite(buffer, 0, buffer.length); + outWrite(buffer, buffer.length); } else { auxBuffer.put(buffer); } @@ -703,7 +724,7 @@ public class Mp4FromDashWriter { int mediaTime; if (tracks[index].trak.edst_elst == null) { - // is a audio track ¿is edst/elst opcional for audio tracks? + // is a audio track ¿is edst/elst optional for audio tracks? mediaTime = 0x00;// ffmpeg set this value as zero, instead of defaultMediaTime bMediaRate = 0x00010000; } else { @@ -798,13 +819,13 @@ public class Mp4FromDashWriter { class TablesInfo { - public int stts; - public int stsc; - public int[] stsc_bEntries; - public int ctts; - public int stsz; - public int stsz_default; - public int stss; - public int stco; + int stts; + int stsc; + int[] stsc_bEntries; + int ctts; + int stsz; + int stsz_default; + int stss; + int stco; } } From 4292ca94ff6d36602bd3834f9ebc544a61c19272 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Mon, 30 Sep 2019 23:52:49 -0300 Subject: [PATCH 189/270] misc changes * OggFromWebMWriter: rewrite (again), reduce iterations over the input. Works as-is (video streams are not supported) * WebMReader: use int for SimpleBlock.dataSize instead of long * Download Recovery: allow recovering uninitialized downloads * check range-requests using HEAD method instead of GET * DownloadRunnableFallback: add workaround for 32kB/s issue, unknown issue origin, wont fix * reporting downloads errors now include the source url with the selected quality and format --- .../newpipe/streams/OggFromWebMWriter.java | 216 +++++------------- .../schabi/newpipe/streams/WebMReader.java | 4 +- .../giga/get/DownloadInitializer.java | 35 +-- .../us/shandian/giga/get/DownloadMission.java | 36 +-- .../giga/get/DownloadMissionRecover.java | 146 +++++++++--- .../shandian/giga/get/DownloadRunnable.java | 2 +- .../giga/get/DownloadRunnableFallback.java | 20 +- .../giga/get/MissionRecoveryInfo.java | 43 +++- .../giga/ui/adapter/MissionAdapter.java | 36 ++- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 11 files changed, 294 insertions(+), 248 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index 091ae6d2a..e6363e423 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -12,8 +12,6 @@ import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Random; import javax.annotation.Nullable; @@ -23,15 +21,13 @@ import javax.annotation.Nullable; public class OggFromWebMWriter implements Closeable { private static final byte FLAG_UNSET = 0x00; - private static final byte FLAG_CONTINUED = 0x01; + //private static final byte FLAG_CONTINUED = 0x01; private static final byte FLAG_FIRST = 0x02; private static final byte FLAG_LAST = 0x04; private final static byte HEADER_CHECKSUM_OFFSET = 22; private final static byte HEADER_SIZE = 27; - private final static short BUFFER_SIZE = 8 * 1024;// 8KiB - private final static int TIME_SCALE_NS = 1000000000; private boolean done = false; @@ -43,7 +39,6 @@ public class OggFromWebMWriter implements Closeable { private int sequence_count = 0; private final int STREAM_ID; private byte packet_flag = FLAG_FIRST; - private int track_index = 0; private WebMReader webm = null; private WebMTrack webm_track = null; @@ -71,7 +66,7 @@ public class OggFromWebMWriter implements Closeable { this.source = source; this.output = target; - this.STREAM_ID = (new Random(System.currentTimeMillis())).nextInt(); + this.STREAM_ID = (int) System.currentTimeMillis(); populate_crc32_table(); } @@ -130,7 +125,6 @@ public class OggFromWebMWriter implements Closeable { try { webm_track = webm.selectTrack(trackIndex); - track_index = trackIndex; } finally { parsed = true; } @@ -154,8 +148,11 @@ public class OggFromWebMWriter implements Closeable { public void build() throws IOException { float resolution; - int read; - byte[] buffer; + SimpleBlock bloq; + ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255)); + ByteBuffer page = ByteBuffer.allocate(64 * 1024); + + header.order(ByteOrder.LITTLE_ENDIAN); /* step 1: get the amount of frames per seconds */ switch (webm_track.kind) { @@ -176,57 +173,32 @@ public class OggFromWebMWriter implements Closeable { throw new RuntimeException("not implemented"); } - /* step 2a: create packet with code init data */ - ArrayList data_extra = new ArrayList<>(4); - + /* step 2: create packet with code init data */ if (webm_track.codecPrivate != null) { addPacketSegment(webm_track.codecPrivate.length); - ByteBuffer buff = byte_buffer(HEADER_SIZE + segment_table_size + webm_track.codecPrivate.length); - - make_packetHeader(0x00, buff, webm_track.codecPrivate); - data_extra.add(buff.array()); + make_packetHeader(0x00, header, webm_track.codecPrivate); + write(header); + output.write(webm_track.codecPrivate); } - /* step 2b: create packet with metadata */ - buffer = make_metadata(); + /* step 3: create packet with metadata */ + byte[] buffer = make_metadata(); if (buffer != null) { addPacketSegment(buffer.length); - ByteBuffer buff = byte_buffer(HEADER_SIZE + segment_table_size + buffer.length); - - make_packetHeader(0x00, buff, buffer); - data_extra.add(buff.array()); + make_packetHeader(0x00, header, buffer); + write(header); + output.write(buffer); } - - /* step 3: calculate amount of packets */ - SimpleBlock bloq; - int reserve_header = 0; - int headers_amount = 0; - + /* step 4: calculate amount of packets */ while (webm_segment != null) { bloq = getNextBlock(); - if (addPacketSegment(bloq)) { - continue; - } - - reserve_header += HEADER_SIZE + segment_table_size;// header size - clearSegmentTable(); - webm_block = bloq; - headers_amount++; - } - - /* step 4: create packet headers */ - rewind_source(); - - ByteBuffer headers = byte_buffer(reserve_header); - short[] headers_size = new short[headers_amount]; - int header_index = 0; - - while (webm_segment != null) { - bloq = getNextBlock(); - - if (addPacketSegment(bloq)) { + if (bloq != null && addPacketSegment(bloq)) { + int pos = page.position(); + //noinspection ResultOfMethodCallIgnored + bloq.data.read(page.array(), pos, bloq.dataSize); + page.position(pos + bloq.dataSize); continue; } @@ -251,75 +223,21 @@ public class OggFromWebMWriter implements Closeable { elapsed_ns = elapsed_ns / TIME_SCALE_NS; elapsed_ns = Math.ceil(elapsed_ns * resolution); - // create header - headers_size[header_index++] = make_packetHeader((long) elapsed_ns, headers, null); + // create header and calculate page checksum + int checksum = make_packetHeader((long) elapsed_ns, header, null); + checksum = calc_crc32(checksum, page.array(), page.position()); + + header.putInt(HEADER_CHECKSUM_OFFSET, checksum); + + // dump data + write(header); + write(page); + webm_block = bloq; } - - - /* step 5: calculate checksums */ - rewind_source(); - - int offset = 0; - buffer = new byte[BUFFER_SIZE]; - - for (header_index = 0; header_index < headers_size.length; header_index++) { - int checksum_offset = offset + HEADER_CHECKSUM_OFFSET; - int checksum = headers.getInt(checksum_offset); - - while (webm_segment != null) { - bloq = getNextBlock(); - - if (!addPacketSegment(bloq)) { - clearSegmentTable(); - webm_block = bloq; - break; - } - - // calculate page checksum - while ((read = bloq.data.read(buffer)) > 0) { - checksum = calc_crc32(checksum, buffer, 0, read); - } - } - - headers.putInt(checksum_offset, checksum); - offset += headers_size[header_index]; - } - - /* step 6: write extra headers */ - rewind_source(); - - for (byte[] buff : data_extra) { - output.write(buff); - } - - /* step 7: write stream packets */ - byte[] headers_buffers = headers.array(); - offset = 0; - buffer = new byte[BUFFER_SIZE]; - - for (header_index = 0; header_index < headers_size.length; header_index++) { - output.write(headers_buffers, offset, headers_size[header_index]); - offset += headers_size[header_index]; - - while (webm_segment != null) { - bloq = getNextBlock(); - - if (addPacketSegment(bloq)) { - while ((read = bloq.data.read(buffer)) > 0) { - output.write(buffer, 0, read); - } - } else { - clearSegmentTable(); - webm_block = bloq; - break; - } - } - } } - private short make_packetHeader(long gran_pos, ByteBuffer buffer, byte[] immediate_page) { - int offset = buffer.position(); + private int make_packetHeader(long gran_pos, @NonNull ByteBuffer buffer, byte[] immediate_page) { short length = HEADER_SIZE; buffer.putInt(0x5367674f);// "OggS" binary string in little-endian @@ -340,17 +258,15 @@ public class OggFromWebMWriter implements Closeable { clearSegmentTable();// clear segment table for next header - int checksum_crc32 = calc_crc32(0x00, buffer.array(), offset, length); + int checksum_crc32 = calc_crc32(0x00, buffer.array(), length); if (immediate_page != null) { - checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, 0, immediate_page.length); - System.arraycopy(immediate_page, 0, buffer.array(), length, immediate_page.length); + checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length); + buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32); segment_table_next_timestamp -= TIME_SCALE_NS; } - buffer.putInt(offset + HEADER_CHECKSUM_OFFSET, checksum_crc32); - - return length; + return checksum_crc32; } @Nullable @@ -358,7 +274,7 @@ public class OggFromWebMWriter implements Closeable { if ("A_OPUS".equals(webm_track.codecId)) { return new byte[]{ 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string - 0x07, 0x00, 0x00, 0x00,// writing application string size + 0x07, 0x00, 0x00, 0x00,// writting application string size 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags) }; @@ -366,7 +282,7 @@ public class OggFromWebMWriter implements Closeable { return new byte[]{ 0x03,// ???????? 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string - 0x07, 0x00, 0x00, 0x00,// writing application string size + 0x07, 0x00, 0x00, 0x00,// writting application string size 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags) @@ -387,22 +303,9 @@ public class OggFromWebMWriter implements Closeable { return null; } - private void rewind_source() throws IOException { - source.rewind(); - - webm = new WebMReader(source); - webm.parse(); - webm_track = webm.selectTrack(track_index); - webm_segment = webm.getNextSegment(); - webm_cluster = null; - webm_block = null; - webm_block_last_timecode = 0L; - - segment_table_next_timestamp = TIME_SCALE_NS; - } - - private ByteBuffer byte_buffer(int size) { - return ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + private void write(ByteBuffer buffer) throws IOException { + output.write(buffer.array(), 0, buffer.position()); + buffer.position(0); } // @@ -460,41 +363,32 @@ public class OggFromWebMWriter implements Closeable { // private void clearSegmentTable() { - if (packet_flag != FLAG_CONTINUED) { - segment_table_next_timestamp += TIME_SCALE_NS; - packet_flag = FLAG_UNSET; - } + segment_table_next_timestamp += TIME_SCALE_NS; + packet_flag = FLAG_UNSET; segment_table_size = 0; } private boolean addPacketSegment(SimpleBlock block) { - if (block == null) { - return false; - } - long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay; if (timestamp >= segment_table_next_timestamp) { return false; } - boolean result = addPacketSegment((int) block.dataSize); - - if (!result && segment_table_next_timestamp < timestamp) { - // WARNING: ¡¡¡¡ not implemented (lack of documentation) !!!! - packet_flag = FLAG_CONTINUED; - } - - return result; + return addPacketSegment(block.dataSize); } private boolean addPacketSegment(int size) { + if (size > 65025) { + throw new UnsupportedOperationException("page size cannot be larger than 65025"); + } + int available = (segment_table.length - segment_table_size) * 255; - boolean extra = size == 255; + boolean extra = (size % 255) == 0; if (extra) { // add a zero byte entry in the table - // required to indicate the sample size is exactly 255 + // required to indicate the sample size is multiple of 255 available -= 255; } @@ -528,12 +422,10 @@ public class OggFromWebMWriter implements Closeable { } } - private int calc_crc32(int initial_crc, byte[] buffer, int offset, int size) { - size += offset; - - for (; offset < size; offset++) { + private int calc_crc32(int initial_crc, byte[] buffer, int size) { + for (int i = 0; i < size; i++) { int reg = (initial_crc >>> 24) & 0xff; - initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[offset] & 0xff)]; + initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)]; } return initial_crc; diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java index 13c15370d..4cb96d901 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java @@ -368,7 +368,7 @@ public class WebMReader { obj.trackNumber = readEncodedNumber(); obj.relativeTimeCode = stream.readShort(); obj.flags = (byte) stream.read(); - obj.dataSize = (ref.offset + ref.size) - stream.position(); + obj.dataSize = (int) ((ref.offset + ref.size) - stream.position()); obj.createdFromBlock = ref.type == ID_Block; // NOTE: lacing is not implemented, and will be mixed with the stream data @@ -465,7 +465,7 @@ public class WebMReader { public short relativeTimeCode; public long absoluteTimeCodeNs; public byte flags; - public long dataSize; + public int dataSize; private final Element ref; public boolean isKeyframe() { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java index 593feafa7..17a2a7403 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java @@ -14,6 +14,7 @@ import java.nio.channels.ClosedByInterruptException; import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; +import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN; public class DownloadInitializer extends Thread { private final static String TAG = "DownloadInitializer"; @@ -29,9 +30,9 @@ public class DownloadInitializer extends Thread { mConn = null; } - private static void safeClose(HttpURLConnection con) { + private void dispose() { try { - con.getInputStream().close(); + mConn.getInputStream().close(); } catch (Exception e) { // nothing to do } @@ -52,9 +53,9 @@ public class DownloadInitializer extends Thread { long lowestSize = Long.MAX_VALUE; for (int i = 0; i < mMission.urls.length && mMission.running; i++) { - mConn = mMission.openConnection(mMission.urls[i], mId, -1, -1); + mConn = mMission.openConnection(mMission.urls[i], true, -1, -1); mMission.establishConnection(mId, mConn); - safeClose(mConn); + dispose(); if (Thread.interrupted()) return; long length = Utility.getContentLength(mConn); @@ -82,9 +83,9 @@ public class DownloadInitializer extends Thread { } } else { // ask for the current resource length - mConn = mMission.openConnection(mId, -1, -1); + mConn = mMission.openConnection(true, -1, -1); mMission.establishConnection(mId, mConn); - safeClose(mConn); + dispose(); if (!mMission.running || Thread.interrupted()) return; @@ -108,9 +109,9 @@ public class DownloadInitializer extends Thread { } } else { // Open again - mConn = mMission.openConnection(mId, mMission.length - 10, mMission.length); + mConn = mMission.openConnection(true, mMission.length - 10, mMission.length); mMission.establishConnection(mId, mConn); - safeClose(mConn); + dispose(); if (!mMission.running || Thread.interrupted()) return; @@ -171,7 +172,14 @@ public class DownloadInitializer extends Thread { } catch (InterruptedIOException | ClosedByInterruptException e) { return; } catch (Exception e) { - if (!mMission.running) return; + if (!mMission.running || super.isInterrupted()) return; + + if (e instanceof DownloadMission.HttpError && ((DownloadMission.HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { + // for youtube streams. The url has expired + interrupt(); + mMission.doRecover(e); + return; + } if (e instanceof IOException && e.getMessage().contains("Permission denied")) { mMission.notifyError(DownloadMission.ERROR_PERMISSION_DENIED, e); @@ -194,13 +202,6 @@ public class DownloadInitializer extends Thread { @Override public void interrupt() { super.interrupt(); - - if (mConn != null) { - try { - mConn.disconnect(); - } catch (Exception e) { - // nothing to do - } - } + if (mConn != null) dispose(); } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 77b417118..918d6dbea 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -204,22 +204,24 @@ public class DownloadMission extends Mission { /** * Opens a connection * - * @param threadId id of the calling thread, used only for debugging - * @param rangeStart range start - * @param rangeEnd range end + * @param headRequest {@code true} for use {@code HEAD} request method, otherwise, {@code GET} is used + * @param rangeStart range start + * @param rangeEnd range end * @return a {@link java.net.URLConnection URLConnection} linking to the URL. * @throws IOException if an I/O exception occurs. */ - HttpURLConnection openConnection(int threadId, long rangeStart, long rangeEnd) throws IOException { - return openConnection(urls[current], threadId, rangeStart, rangeEnd); + HttpURLConnection openConnection(boolean headRequest, long rangeStart, long rangeEnd) throws IOException { + return openConnection(urls[current], headRequest, rangeStart, rangeEnd); } - HttpURLConnection openConnection(String url, int threadId, long rangeStart, long rangeEnd) throws IOException { + HttpURLConnection openConnection(String url, boolean headRequest, long rangeStart, long rangeEnd) throws IOException { HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setInstanceFollowRedirects(true); conn.setRequestProperty("User-Agent", DownloaderImpl.USER_AGENT); conn.setRequestProperty("Accept", "*/*"); + if (headRequest) conn.setRequestMethod("HEAD"); + // BUG workaround: switching between networks can freeze the download forever conn.setConnectTimeout(30000); conn.setReadTimeout(10000); @@ -229,10 +231,6 @@ public class DownloadMission extends Mission { if (rangeEnd > 0) req += rangeEnd; conn.setRequestProperty("Range", req); - - if (DEBUG) { - Log.d(TAG, threadId + ":" + conn.getRequestProperty("Range")); - } } return conn; @@ -245,13 +243,14 @@ public class DownloadMission extends Mission { * @throws HttpError if the HTTP Status-Code is not satisfiable */ void establishConnection(int threadId, HttpURLConnection conn) throws IOException, HttpError { - conn.connect(); int statusCode = conn.getResponseCode(); if (DEBUG) { + Log.d(TAG, threadId + ":Range=" + conn.getRequestProperty("Range")); Log.d(TAG, threadId + ":Content-Length=" + conn.getContentLength() + " Code:" + statusCode); } + switch (statusCode) { case 204: case 205: @@ -676,6 +675,15 @@ public class DownloadMission extends Mission { return (isPsFailed() || errCode == ERROR_POSTPROCESSING_HOLD) || isFinished(); } + /** + * Indicates if mission urls has expired and there an attempt to renovate them + * + * @return {@code true} if the mission is running a recovery procedure, otherwise, {@code false} + */ + public boolean isRecovering() { + return threads != null && threads.length > 0 && threads[0] instanceof DownloadRunnable && threads[0].isAlive(); + } + private boolean doPostprocessing() { if (psAlgorithm == null || psState == 2) return true; @@ -742,10 +750,8 @@ public class DownloadMission extends Mission { } } - // set the current download url to null in case if the recovery - // process is canceled. Next time start() method is called the - // recovery will be executed, saving time - urls[current] = null; + errCode = ERROR_NOTHING; + errObject = null; if (recoveryInfo[current].attempts >= maxRetry) { recoveryInfo[current].attempts = 0; diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java index 9abd93717..5efbd1153 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -10,10 +10,12 @@ import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; import java.io.IOException; +import java.io.InterruptedIOException; import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; import java.util.List; +import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; public class DownloadMissionRecover extends Thread { @@ -21,14 +23,17 @@ public class DownloadMissionRecover extends Thread { static final int mID = -3; private final DownloadMission mMission; - private final MissionRecoveryInfo mRecovery; private final Exception mFromError; + private final boolean notInitialized; + private HttpURLConnection mConn; + private MissionRecoveryInfo mRecovery; + private StreamExtractor mExtractor; DownloadMissionRecover(DownloadMission mission, Exception originError) { mMission = mission; mFromError = originError; - mRecovery = mission.recoveryInfo[mission.current]; + notInitialized = mission.blocks == null && mission.current == 0; } @Override @@ -38,28 +43,78 @@ public class DownloadMissionRecover extends Thread { return; } + /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { + resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); + return; + }*/ + try { - /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { - resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); - return; - }*/ - StreamingService svr = NewPipe.getServiceByUrl(mMission.source); - - if (svr == null) { - throw new RuntimeException("Unknown source service"); - } - - StreamExtractor extractor = svr.getStreamExtractor(mMission.source); - extractor.fetchPage(); - + mExtractor = svr.getStreamExtractor(mMission.source); + mExtractor.fetchPage(); + } catch (InterruptedIOException | ClosedByInterruptException e) { + return; + } catch (Exception e) { if (!mMission.running || super.isInterrupted()) return; + mMission.notifyError(e); + return; + } + // maybe the following check is redundant + if (!mMission.running || super.isInterrupted()) return; + + if (!notInitialized) { + // set the current download url to null in case if the recovery + // process is canceled. Next time start() method is called the + // recovery will be executed, saving time + mMission.urls[mMission.current] = null; + + mRecovery = mMission.recoveryInfo[mMission.current]; + resolveStream(); + return; + } + + Log.w(TAG, "mission is not fully initialized, this will take a while"); + + try { + for (; mMission.current < mMission.urls.length; mMission.current++) { + mRecovery = mMission.recoveryInfo[mMission.current]; + + if (test()) continue; + if (!mMission.running) return; + + resolveStream(); + if (!mMission.running) return; + + // before continue, check if the current stream was resolved + if (mMission.urls[mMission.current] == null || mMission.errCode != ERROR_NOTHING) { + break; + } + } + } finally { + mMission.current = 0; + } + + mMission.writeThisToFile(); + + if (!mMission.running || super.isInterrupted()) return; + + mMission.running = false; + mMission.start(); + } + + private void resolveStream() { + if (mExtractor.getErrorMessage() != null) { + mMission.notifyError(mFromError); + return; + } + + try { String url = null; - switch (mMission.kind) { + switch (mRecovery.kind) { case 'a': - for (AudioStream audio : extractor.getAudioStreams()) { + for (AudioStream audio : mExtractor.getAudioStreams()) { if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { url = audio.getUrl(); break; @@ -69,9 +124,9 @@ public class DownloadMissionRecover extends Thread { case 'v': List videoStreams; if (mRecovery.desired2) - videoStreams = extractor.getVideoOnlyStreams(); + videoStreams = mExtractor.getVideoOnlyStreams(); else - videoStreams = extractor.getVideoStreams(); + videoStreams = mExtractor.getVideoStreams(); for (VideoStream video : videoStreams) { if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { url = video.getUrl(); @@ -80,7 +135,7 @@ public class DownloadMissionRecover extends Thread { } break; case 's': - for (SubtitlesStream subtitles : extractor.getSubtitles(mRecovery.format)) { + for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.format)) { String tag = subtitles.getLanguageTag(); if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { url = subtitles.getURL(); @@ -114,7 +169,7 @@ public class DownloadMissionRecover extends Thread { ////// Validate the http resource doing a range request ///////////////////// try { - mConn = mMission.openConnection(url, mID, mMission.length - 10, mMission.length); + mConn = mMission.openConnection(url, true, mMission.length - 10, mMission.length); mConn.setRequestProperty("If-Range", mRecovery.validateCondition); mMission.establishConnection(mID, mConn); @@ -140,22 +195,24 @@ public class DownloadMissionRecover extends Thread { if (!mMission.running || e instanceof ClosedByInterruptException) return; throw e; } finally { - this.interrupt(); + disconnect(); } } private void recover(String url, boolean stale) { Log.i(TAG, - String.format("download recovered name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) + String.format("recover() name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) ); + mMission.urls[mMission.current] = url; + mRecovery.attempts = 0; + if (url == null) { mMission.notifyError(ERROR_RESOURCE_GONE, null); return; } - mMission.urls[mMission.current] = url; - mRecovery.attempts = 0; + if (notInitialized) return; if (stale) { mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); @@ -208,15 +265,40 @@ public class DownloadMissionRecover extends Thread { return range; } + private boolean test() { + if (mMission.urls[mMission.current] == null) return false; + + try { + mConn = mMission.openConnection(mMission.urls[mMission.current], true, -1, -1); + mMission.establishConnection(mID, mConn); + + if (mConn.getResponseCode() == 200) return true; + } catch (Exception e) { + // nothing to do + } finally { + disconnect(); + } + + return false; + } + + private void disconnect() { + try { + try { + mConn.getInputStream().close(); + } finally { + mConn.disconnect(); + } + } catch (Exception e) { + // nothing to do + } finally { + mConn = null; + } + } + @Override public void interrupt() { super.interrupt(); - if (mConn != null) { - try { - mConn.disconnect(); - } catch (Exception e) { - // nothing to do - } - } + if (mConn != null) disconnect(); } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index 1d2a4eee7..b0dc793bc 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -80,7 +80,7 @@ public class DownloadRunnable extends Thread { } try { - mConn = mMission.openConnection(mId, start, end); + mConn = mMission.openConnection(false, start, end); mMission.establishConnection(mId, mConn); // check if the download can be resumed diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index b5937c577..e64322b48 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -35,7 +35,11 @@ public class DownloadRunnableFallback extends Thread { private void dispose() { try { - if (mIs != null) mIs.close(); + try { + if (mIs != null) mIs.close(); + } finally { + mConn.disconnect(); + } } catch (IOException e) { // nothing to do } @@ -68,7 +72,13 @@ public class DownloadRunnableFallback extends Thread { long rangeStart = (mMission.unknownLength || start < 1) ? -1 : start; int mId = 1; - mConn = mMission.openConnection(mId, rangeStart, -1); + mConn = mMission.openConnection(false, rangeStart, -1); + + if (mRetryCount == 0 && rangeStart == -1) { + // workaround: bypass android connection pool + mConn.setRequestProperty("Range", "bytes=0-"); + } + mMission.establishConnection(mId, mConn); // check if the download can be resumed @@ -96,6 +106,8 @@ public class DownloadRunnableFallback extends Thread { mMission.notifyProgress(len); } + dispose(); + // if thread goes interrupted check if the last part is written. This avoid re-download the whole file done = len == -1; } catch (Exception e) { @@ -107,8 +119,8 @@ public class DownloadRunnableFallback extends Thread { if (e instanceof HttpError && ((HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { // for youtube streams. The url has expired, recover - mMission.doRecover(e); dispose(); + mMission.doRecover(e); return; } @@ -125,8 +137,6 @@ public class DownloadRunnableFallback extends Thread { return; } - dispose(); - if (done) { mMission.notifyFinished(); } else { diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java index 553ba6d89..bd1d9bc49 100644 --- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java @@ -16,25 +16,28 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { private static final long serialVersionUID = 0L; //public static final String DIRECT_SOURCE = "direct-source://"; - public MediaFormat format; + MediaFormat format; String desired; boolean desired2; int desiredBitrate; + byte kind; + String validateCondition = null; transient int attempts = 0; - String validateCondition = null; - public MissionRecoveryInfo(@NonNull Stream stream) { if (stream instanceof AudioStream) { desiredBitrate = ((AudioStream) stream).average_bitrate; desired2 = false; + kind = 'a'; } else if (stream instanceof VideoStream) { desired = ((VideoStream) stream).getResolution(); desired2 = ((VideoStream) stream).isVideoOnly(); + kind = 'v'; } else if (stream instanceof SubtitlesStream) { desired = ((SubtitlesStream) stream).getLanguageTag(); desired2 = ((SubtitlesStream) stream).isAutoGenerated(); + kind = 's'; } else { throw new RuntimeException("Unknown stream kind"); } @@ -43,6 +46,38 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { if (format == null) throw new NullPointerException("Stream format cannot be null"); } + @NonNull + @Override + public String toString() { + String info; + StringBuilder str = new StringBuilder(); + str.append("type="); + switch (kind) { + case 'a': + str.append("audio"); + info = "bitrate=" + desiredBitrate; + break; + case 'v': + str.append("video"); + info = "quality=" + desired + " videoOnly=" + desired2; + break; + case 's': + str.append("subtitles"); + info = "language=" + desired + " autoGenerated=" + desired2; + break; + default: + info = ""; + str.append("other"); + } + + str.append(" format=") + .append(format.getName()) + .append(' ') + .append(info); + + return str.toString(); + } + @Override public int describeContents() { return 0; @@ -54,6 +89,7 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { parcel.writeString(this.desired); parcel.writeInt(this.desired2 ? 0x01 : 0x00); parcel.writeInt(this.desiredBitrate); + parcel.writeByte(this.kind); parcel.writeString(this.validateCondition); } @@ -62,6 +98,7 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { this.desired = parcel.readString(); this.desired2 = parcel.readInt() != 0x00; this.desiredBitrate = parcel.readInt(); + this.kind = parcel.readByte(); this.validateCondition = parcel.readString(); } diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 6c6198750..78fd7ea9d 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -36,6 +36,7 @@ import android.widget.Toast; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.NavigationHelper; @@ -44,11 +45,11 @@ import java.io.File; import java.lang.ref.WeakReference; import java.net.URI; import java.util.ArrayList; -import java.util.Collections; import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.Mission; +import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.io.StoredFileHelper; import us.shandian.giga.service.DownloadManager; import us.shandian.giga.service.DownloadManagerService; @@ -234,7 +235,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb // hide on error // show if current resource length is not fetched // show if length is unknown - h.progress.setMarquee(!hasError && (!mission.isInitialized() || mission.unknownLength)); + h.progress.setMarquee(mission.isRecovering() || !hasError && (!mission.isInitialized() || mission.unknownLength)); float progress; if (mission.unknownLength) { @@ -463,13 +464,13 @@ public class MissionAdapter extends Adapter implements Handler.Callb break; case ERROR_POSTPROCESSING: case ERROR_POSTPROCESSING_HOLD: - showError(mission.errObject, UserAction.DOWNLOAD_POSTPROCESSING, R.string.error_postprocessing_failed); + showError(mission, UserAction.DOWNLOAD_POSTPROCESSING, R.string.error_postprocessing_failed); return; case ERROR_INSUFFICIENT_STORAGE: msg = R.string.error_insufficient_storage; break; case ERROR_UNKNOWN_EXCEPTION: - showError(mission.errObject, UserAction.DOWNLOAD_FAILED, R.string.general_error); + showError(mission, UserAction.DOWNLOAD_FAILED, R.string.general_error); return; case ERROR_PROGRESS_LOST: msg = R.string.error_progress_lost; @@ -486,7 +487,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb } else if (mission.errObject == null) { msgEx = "(not_decelerated_error_code)"; } else { - showError(mission.errObject, UserAction.DOWNLOAD_FAILED, msg); + showError(mission, UserAction.DOWNLOAD_FAILED, msg); return; } break; @@ -503,7 +504,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb if (mission.errObject != null && (mission.errCode < 100 || mission.errCode >= 600)) { @StringRes final int mMsg = msg; builder.setPositiveButton(R.string.error_report_title, (dialog, which) -> - showError(mission.errObject, UserAction.DOWNLOAD_FAILED, mMsg) + showError(mission, UserAction.DOWNLOAD_FAILED, mMsg) ); } @@ -513,13 +514,30 @@ public class MissionAdapter extends Adapter implements Handler.Callb .show(); } - private void showError(Exception exception, UserAction action, @StringRes int reason) { + private void showError(DownloadMission mission, UserAction action, @StringRes int reason) { + StringBuilder request = new StringBuilder(256); + request.append(mission.source); + + request.append(" ["); + if (mission.recoveryInfo != null) { + for (MissionRecoveryInfo recovery : mission.recoveryInfo) + request.append(" {").append(recovery.toString()).append("} "); + } + request.append("]"); + + String service; + try { + service = NewPipe.getServiceByUrl(mission.source).getServiceInfo().getName(); + } catch (Exception e) { + service = "-"; + } + ErrorActivity.reportError( mContext, - Collections.singletonList(exception), + mission.errObject, null, null, - ErrorActivity.ErrorInfo.make(action, "-", "-", reason) + ErrorActivity.ErrorInfo.make(action, service, request.toString(), reason) ); } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2f69e62cb..b14aab94b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -453,7 +453,7 @@ No hay suficiente espacio disponible en el dispositivo Se perdió el progreso porque el archivo fue eliminado Tiempo de espera excedido - El recurso solicitado ya no esta disponible + No se puede recuperar esta descarga Preguntar dónde descargar Se preguntará dónde guardar cada descarga Se le preguntará dónde guardar cada descarga. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2917fb9fd..f929e0d2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -557,7 +557,7 @@ No space left on device Progress lost, because the file was deleted Connection timeout - The solicited resource is not available anymore + Cannot recover this download Clear finished downloads Are you sure? Continue your %s pending transfers from Downloads From 60d4c8a55df3e28e749e28c578372811bfa5ed77 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Tue, 1 Oct 2019 13:00:16 -0300 Subject: [PATCH 190/270] fallback for pending downloads directory --- .../giga/service/DownloadManager.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index a859a87ca..89c44638d 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -37,6 +37,7 @@ public class DownloadManager { public static final String TAG_AUDIO = "audio"; public static final String TAG_VIDEO = "video"; + private static final String DOWNLOADS_METADATA_FOLDER = "pending_downloads"; private final FinishedMissionStore mFinishedMissionStore; @@ -75,24 +76,33 @@ public class DownloadManager { mPendingMissionsDir = getPendingDir(context); if (!Utility.mkdir(mPendingMissionsDir, false)) { - throw new RuntimeException("failed to create pending_downloads in data directory"); + throw new RuntimeException("failed to create " + DOWNLOADS_METADATA_FOLDER + " directory"); } loadPendingMissions(context); } private static File getPendingDir(@NonNull Context context) { - //File dir = new File(ContextCompat.getDataDir(context), "pending_downloads"); - File dir = context.getExternalFilesDir("pending_downloads"); + File dir = context.getExternalFilesDir(DOWNLOADS_METADATA_FOLDER); + if (testDir(dir)) return dir; - if (dir == null) { - // One of the following paths are not accessible ¿unmounted internal memory? - // /storage/emulated/0/Android/data/org.schabi.newpipe[.debug]/pending_downloads - // /sdcard/Android/data/org.schabi.newpipe[.debug]/pending_downloads - Log.w(TAG, "path to pending downloads are not accessible"); + dir = new File(context.getFilesDir(), DOWNLOADS_METADATA_FOLDER); + if (testDir(dir)) return dir; + + throw new RuntimeException("path to pending downloads are not accessible"); + } + + private static boolean testDir(@Nullable File dir) { + if (dir == null) return false; + + try { + File tmp = new File(dir, ".tmp"); + if (!tmp.createNewFile()) return false; + return tmp.delete();// if the file was created, SHOULD BE deleted too + } catch (Exception e) { + Log.e(TAG, "testDir() failed: " + dir.getAbsolutePath(), e); + return false; } - - return dir; } /** @@ -132,6 +142,7 @@ public class DownloadManager { for (File sub : subs) { if (!sub.isFile()) continue; + if (sub.getName().equals(".tmp")) continue; DownloadMission mis = Utility.readFromFile(sub); if (mis == null || mis.isFinished()) { From da052df106a165aa40774a00a4bfc008a1bd055c Mon Sep 17 00:00:00 2001 From: kapodamy Date: Tue, 1 Oct 2019 15:01:17 -0300 Subject: [PATCH 191/270] update DownloadManager.java * check if the directory pending_downloads was created --- .../java/us/shandian/giga/service/DownloadManager.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index 89c44638d..2d1e9cd00 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -75,10 +75,6 @@ public class DownloadManager { mMissionsFinished = loadFinishedMissions(); mPendingMissionsDir = getPendingDir(context); - if (!Utility.mkdir(mPendingMissionsDir, false)) { - throw new RuntimeException("failed to create " + DOWNLOADS_METADATA_FOLDER + " directory"); - } - loadPendingMissions(context); } @@ -96,6 +92,11 @@ public class DownloadManager { if (dir == null) return false; try { + if (!Utility.mkdir(dir, false)) { + Log.e(TAG, "testDir() cannot create the directory in path: " + dir.getAbsolutePath()); + return false; + } + File tmp = new File(dir, ".tmp"); if (!tmp.createNewFile()) return false; return tmp.delete();// if the file was created, SHOULD BE deleted too From 8a992d4c47180a2fa2f4911990374c94acc9505f Mon Sep 17 00:00:00 2001 From: kapodamy Date: Tue, 1 Oct 2019 16:28:45 -0300 Subject: [PATCH 192/270] update WebMWriter.java fix wrong cue generation --- app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java index 1bf994b1e..8525fabd2 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java @@ -249,7 +249,7 @@ public class WebMWriter implements Closeable { nextCueTime += DEFAULT_CUES_EACH_MS; } keyFrames.add( - new KeyFrame(baseSegmentOffset, currentClusterOffset - 7, written, bTimecode.length, bloq.absoluteTimecode) + new KeyFrame(baseSegmentOffset, currentClusterOffset - 8, written, bTimecode.length, bloq.absoluteTimecode) ); } } From 763995d4c98fe43b3b037e75b4b0ec5c17286ad0 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Wed, 2 Oct 2019 13:31:45 -0300 Subject: [PATCH 193/270] update DownloadDialog.java keep *.opus extension --- .../main/java/org/schabi/newpipe/download/DownloadDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 0006b3c12..60b6192be 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -562,7 +562,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck mainStorage = mainStorageVideo; format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat(); mime = format.mimeType; - filename += format == MediaFormat.OPUS ? "ogg" : format.suffix; + filename += format.suffix; break; case R.id.subtitle_button: mainStorage = mainStorageVideo;// subtitle & video files go together From e6d9d8e26d0660e4e889fd733e2011302360ca6b Mon Sep 17 00:00:00 2001 From: kapodamy Date: Wed, 9 Oct 2019 23:49:23 -0300 Subject: [PATCH 194/270] code cleanup * migrate few annotations to androidx * mission recovery: better error handling (except StreamExtractor.getErrorMessage() method always returns an error) * post-processing: more detailed progress [file specific changes] DownloadMission.java * remove redundant/boilerplate code (again) * make few variables volatile * better file "length" approximation * use "done" variable to count the amount of bytes downloaded (simplify percent calc in UI code) Postprocessing.java * if case of error use "ERROR_POSTPROCESSING" instead of "ERROR_UNKNOWN_EXCEPTION" * simplify source stream init DownloadManager.java * move all "service message sending" code to DownloadMission * remove not implemented method "notifyUserPendingDownloads()" also his unused strings DownloadManagerService.java * use START_STICKY instead of START_NOT_STICKY * simplify addMissionEventListener()/removeMissionEventListener() methods (always are called from the main thread) Deleter.java * better method definition MissionAdapter.java * better method definition * code cleanup * the UI is now refreshed every 750ms * simplify download progress calculation * indicates if the download is actually recovering * smooth download speed measure * show estimated remain time MainFragment.java: * check if viewPager is null (issued by "Apply changes" feature of Android Studio) --- .../newpipe/fragments/MainFragment.java | 9 + .../newpipe/streams/OggFromWebMWriter.java | 2 +- .../giga/get/DownloadInitializer.java | 5 +- .../us/shandian/giga/get/DownloadMission.java | 287 ++++++++--------- .../giga/get/DownloadMissionRecover.java | 160 +++++----- .../shandian/giga/get/DownloadRunnable.java | 5 +- .../giga/get/DownloadRunnableFallback.java | 29 +- .../us/shandian/giga/get/FinishedMission.java | 6 +- .../giga/get/MissionRecoveryInfo.java | 10 +- .../giga/io/ChunkFileInputStream.java | 19 +- .../shandian/giga/io/CircularFileWriter.java | 30 +- .../us/shandian/giga/io/ProgressReport.java | 11 + .../postprocessing/OggFromWebmDemuxer.java | 2 +- .../giga/postprocessing/Postprocessing.java | 54 ++-- .../giga/service/DownloadManager.java | 57 +--- .../giga/service/DownloadManagerService.java | 45 ++- .../giga/ui/adapter/MissionAdapter.java | 302 +++++++++--------- .../us/shandian/giga/ui/common/Deleter.java | 9 +- .../giga/ui/common/ProgressDrawable.java | 5 +- .../giga/ui/fragment/MissionsFragment.java | 45 ++- .../java/us/shandian/giga/util/Utility.java | 50 ++- app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-be/strings.xml | 1 - app/src/main/res/values-cmn/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-da/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-el/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-et/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-he/strings.xml | 1 - app/src/main/res/values-hr/strings.xml | 1 - app/src/main/res/values-id/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ko/strings.xml | 1 - app/src/main/res/values-ms/strings.xml | 1 - app/src/main/res/values-nb-rNO/strings.xml | 1 - app/src/main/res/values-nl-rBE/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pa/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 2 +- 53 files changed, 554 insertions(+), 622 deletions(-) create mode 100644 app/src/main/java/us/shandian/giga/io/ProgressReport.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 720e0f216..70e0d9fb1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -2,6 +2,15 @@ package org.schabi.newpipe.fragments; import android.content.Context; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.tabs.TabLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index e6363e423..37bf9c6d7 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -1,6 +1,6 @@ package org.schabi.newpipe.streams; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.schabi.newpipe.streams.WebMReader.Cluster; import org.schabi.newpipe.streams.WebMReader.Segment; diff --git a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java index 17a2a7403..618200f27 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java @@ -1,9 +1,10 @@ package us.shandian.giga.get; -import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; + import org.schabi.newpipe.streams.io.SharpStream; import java.io.IOException; @@ -177,7 +178,7 @@ public class DownloadInitializer extends Thread { if (e instanceof DownloadMission.HttpError && ((DownloadMission.HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { // for youtube streams. The url has expired interrupt(); - mMission.doRecover(e); + mMission.doRecover(ERROR_HTTP_FORBIDDEN); return; } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 918d6dbea..5ef72162c 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -4,18 +4,21 @@ import android.os.Handler; import android.util.Log; import androidx.annotation.Nullable; +import androidx.annotation.NonNull; import org.schabi.newpipe.DownloaderImpl; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InterruptedIOException; import java.io.Serializable; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; import java.net.UnknownHostException; +import java.nio.channels.ClosedByInterruptException; import javax.net.ssl.SSLException; @@ -27,7 +30,7 @@ import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; public class DownloadMission extends Mission { - private static final long serialVersionUID = 6L;// last bump: 28 september 2019 + private static final long serialVersionUID = 6L;// last bump: 07 october 2019 static final int BUFFER_SIZE = 64 * 1024; static final int BLOCK_SIZE = 512 * 1024; @@ -61,9 +64,9 @@ public class DownloadMission extends Mission { public String[] urls; /** - * Number of bytes downloaded + * Number of bytes downloaded and written */ - public long done; + public volatile long done; /** * Indicates a file generated dynamically on the web server @@ -119,7 +122,7 @@ public class DownloadMission extends Mission { /** * Download/File resume offset in fallback mode (if applicable) {@link DownloadRunnableFallback} */ - long fallbackResumeOffset; + volatile long fallbackResumeOffset; /** * Maximum of download threads running, chosen by the user @@ -132,22 +135,23 @@ public class DownloadMission extends Mission { public MissionRecoveryInfo[] recoveryInfo; private transient int finishCount; - public transient boolean running; + public transient volatile boolean running; public boolean enqueued; public int errCode = ERROR_NOTHING; public Exception errObject = null; public transient Handler mHandler; - private transient boolean mWritingToFile; private transient boolean[] blockAcquired; + private transient long writingToFileNext; + private transient volatile boolean writingToFile; + final Object LOCK = new Lock(); - private transient boolean deleted; - - public transient volatile Thread[] threads = new Thread[0]; - private transient Thread init = null; + @NonNull + public transient Thread[] threads = new Thread[0]; + public transient Thread init = null; public DownloadMission(String[] urls, StoredFileHelper storage, char kind, Postprocessing psInstance) { if (urls == null) throw new NullPointerException("urls is null"); @@ -246,8 +250,10 @@ public class DownloadMission extends Mission { int statusCode = conn.getResponseCode(); if (DEBUG) { - Log.d(TAG, threadId + ":Range=" + conn.getRequestProperty("Range")); - Log.d(TAG, threadId + ":Content-Length=" + conn.getContentLength() + " Code:" + statusCode); + Log.d(TAG, threadId + ":[request] Range=" + conn.getRequestProperty("Range")); + Log.d(TAG, threadId + ":[response] Code=" + statusCode); + Log.d(TAG, threadId + ":[response] Content-Length=" + conn.getContentLength()); + Log.d(TAG, threadId + ":[response] Content-Range=" + conn.getHeaderField("Content-Range")); } @@ -272,24 +278,19 @@ public class DownloadMission extends Mission { } synchronized void notifyProgress(long deltaLen) { - if (!running) return; - if (unknownLength) { length += deltaLen;// Update length before proceeding } done += deltaLen; - if (done > length) { - done = length; - } + if (metadata == null) return; - if (done != length && !deleted && !mWritingToFile) { - mWritingToFile = true; - runAsync(-2, this::writeThisToFile); + if (!writingToFile && (done > writingToFileNext || deltaLen < 0)) { + writingToFile = true; + writingToFileNext = done + BLOCK_SIZE; + writeThisToFileAsync(); } - - notify(DownloadManagerService.MESSAGE_PROGRESS); } synchronized void notifyError(Exception err) { @@ -342,43 +343,42 @@ public class DownloadMission extends Mission { notify(DownloadManagerService.MESSAGE_ERROR); - if (running) { - running = false; - if (threads != null) selfPause(); - } + if (running) pauseThreads(); } synchronized void notifyFinished() { - if (errCode > ERROR_NOTHING) return; - - finishCount++; - - if (blocks.length < 1 || threads == null || finishCount == threads.length) { - if (errCode != ERROR_NOTHING) return; + if (current < urls.length) { + if (++finishCount < threads.length) return; if (DEBUG) { - Log.d(TAG, "onFinish: " + (current + 1) + "/" + urls.length); - } - - if ((current + 1) < urls.length) { - // prepare next sub-mission - long current_offset = offsets[current++]; - offsets[current] = current_offset + length; - initializer(); - return; + Log.d(TAG, "onFinish: downloaded " + (current + 1) + "/" + urls.length); } current++; - unknownLength = false; - - if (!doPostprocessing()) return; - - enqueued = false; - running = false; - deleteThisFromFile(); - - notify(DownloadManagerService.MESSAGE_FINISHED); + if (current < urls.length) { + // prepare next sub-mission + offsets[current] = offsets[current - 1] + length; + initializer(); + return; + } } + + if (psAlgorithm != null && psState == 0) { + threads = new Thread[]{ + runAsync(1, this::doPostprocessing) + }; + return; + } + + + // this mission is fully finished + + unknownLength = false; + enqueued = false; + running = false; + + deleteThisFromFile(); + notify(DownloadManagerService.MESSAGE_FINISHED); } private void notifyPostProcessing(int state) { @@ -396,10 +396,15 @@ public class DownloadMission extends Mission { Log.d(TAG, action + " postprocessing on " + storage.getName()); + if (state == 2) { + psState = state; + return; + } + synchronized (LOCK) { // don't return without fully write the current state psState = state; - Utility.writeToFile(metadata, DownloadMission.this); + writeThisToFile(); } } @@ -411,12 +416,7 @@ public class DownloadMission extends Mission { if (running || isFinished() || urls.length < 1) return; // ensure that the previous state is completely paused. - int maxWait = 10000;// 10 seconds - joinForThread(init, maxWait); - if (threads != null) { - for (Thread thread : threads) joinForThread(thread, maxWait); - threads = null; - } + joinForThreads(10000); running = true; errCode = ERROR_NOTHING; @@ -427,12 +427,14 @@ public class DownloadMission extends Mission { } if (current >= urls.length) { - runAsync(1, this::notifyFinished); + notifyFinished(); return; } + notify(DownloadManagerService.MESSAGE_RUNNING); + if (urls[current] == null) { - doRecover(null); + doRecover(ERROR_RESOURCE_GONE); return; } @@ -446,18 +448,13 @@ public class DownloadMission extends Mission { blockAcquired = new boolean[blocks.length]; if (blocks.length < 1) { - if (unknownLength) { - done = 0; - length = 0; - } - threads = new Thread[]{runAsync(1, new DownloadRunnableFallback(this))}; } else { int remainingBlocks = 0; for (int block : blocks) if (block >= 0) remainingBlocks++; if (remainingBlocks < 1) { - runAsync(1, this::notifyFinished); + notifyFinished(); return; } @@ -483,6 +480,7 @@ public class DownloadMission extends Mission { } running = false; + notify(DownloadManagerService.MESSAGE_PAUSED); if (init != null && init.isAlive()) { // NOTE: if start() method is running ¡will no have effect! @@ -497,29 +495,14 @@ public class DownloadMission extends Mission { Log.w(TAG, "pausing a download that can not be resumed (range requests not allowed by the server)."); } - // check if the calling thread (alias UI thread) is interrupted - if (Thread.currentThread().isInterrupted()) { - writeThisToFile(); - return; - } - - // wait for all threads are suspended before save the state - if (threads != null) runAsync(-1, this::selfPause); + init = null; + pauseThreads(); } - private void selfPause() { - try { - for (Thread thread : threads) { - if (thread.isAlive()) { - thread.interrupt(); - thread.join(5000); - } - } - } catch (Exception e) { - // nothing to do - } finally { - writeThisToFile(); - } + private void pauseThreads() { + running = false; + joinForThreads(-1); + writeThisToFile(); } /** @@ -527,9 +510,10 @@ public class DownloadMission extends Mission { */ @Override public boolean delete() { - deleted = true; if (psAlgorithm != null) psAlgorithm.cleanupTemporalDir(); + notify(DownloadManagerService.MESSAGE_DELETED); + boolean res = deleteThisFromFile(); if (!super.delete()) return false; @@ -544,35 +528,37 @@ public class DownloadMission extends Mission { * @param persistChanges {@code true} to commit changes to the metadata file, otherwise, {@code false} */ public void resetState(boolean rollback, boolean persistChanges, int errorCode) { - done = 0; + length = 0; errCode = errorCode; errObject = null; unknownLength = false; - threads = null; + threads = new Thread[0]; fallbackResumeOffset = 0; blocks = null; blockAcquired = null; if (rollback) current = 0; - - if (persistChanges) - Utility.writeToFile(metadata, DownloadMission.this); + if (persistChanges) writeThisToFile(); } private void initializer() { init = runAsync(DownloadInitializer.mId, new DownloadInitializer(this)); } + private void writeThisToFileAsync() { + runAsync(-2, this::writeThisToFile); + } + /** * Write this {@link DownloadMission} to the meta file asynchronously * if no thread is already running. */ void writeThisToFile() { synchronized (LOCK) { - if (deleted) return; - Utility.writeToFile(metadata, DownloadMission.this); + if (metadata == null) return; + Utility.writeToFile(metadata, this); + writingToFile = false; } - mWritingToFile = false; } /** @@ -625,11 +611,10 @@ public class DownloadMission extends Mission { public long getLength() { long calculated; if (psState == 1 || psState == 3) { - calculated = length; - } else { - calculated = offsets[current < offsets.length ? current : (offsets.length - 1)] + length; + return length; } + calculated = offsets[current < offsets.length ? current : (offsets.length - 1)] + length; calculated -= offsets[0];// don't count reserved space return calculated > nearLength ? calculated : nearLength; @@ -642,7 +627,7 @@ public class DownloadMission extends Mission { */ public void setEnqueued(boolean queue) { enqueued = queue; - runAsync(-2, this::writeThisToFile); + writeThisToFileAsync(); } /** @@ -681,24 +666,19 @@ public class DownloadMission extends Mission { * @return {@code true} if the mission is running a recovery procedure, otherwise, {@code false} */ public boolean isRecovering() { - return threads != null && threads.length > 0 && threads[0] instanceof DownloadRunnable && threads[0].isAlive(); + return threads.length > 0 && threads[0] instanceof DownloadMissionRecover && threads[0].isAlive(); } - private boolean doPostprocessing() { - if (psAlgorithm == null || psState == 2) return true; - + private void doPostprocessing() { + errCode = ERROR_NOTHING; errObject = null; + Thread thread = Thread.currentThread(); notifyPostProcessing(1); - notifyProgress(0); - if (DEBUG) - Thread.currentThread().setName("[" + TAG + "] ps = " + - psAlgorithm.getClass().getSimpleName() + - " filename = " + storage.getName() - ); - - threads = new Thread[]{Thread.currentThread()}; + if (DEBUG) { + thread.setName("[" + TAG + "] ps = " + psAlgorithm + " filename = " + storage.getName()); + } Exception exception = null; @@ -707,6 +687,11 @@ public class DownloadMission extends Mission { } catch (Exception err) { Log.e(TAG, "Post-processing failed. " + psAlgorithm.toString(), err); + if (err instanceof InterruptedIOException || err instanceof ClosedByInterruptException || thread.isInterrupted()) { + notifyError(DownloadMission.ERROR_POSTPROCESSING_STOPPED, null); + return; + } + if (errCode == ERROR_NOTHING) errCode = ERROR_POSTPROCESSING; exception = err; @@ -717,56 +702,38 @@ public class DownloadMission extends Mission { if (errCode != ERROR_NOTHING) { if (exception == null) exception = errObject; notifyError(ERROR_POSTPROCESSING, exception); - - return false; + return; } - return true; + notifyFinished(); } /** * Attempts to recover the download * - * @param fromError exception which require update the url from the source + * @param errorCode error code which trigger the recovery procedure */ - void doRecover(Exception fromError) { + void doRecover(int errorCode) { Log.i(TAG, "Attempting to recover the mission: " + storage.getName()); if (recoveryInfo == null) { - if (fromError == null) - notifyError(ERROR_RESOURCE_GONE, null); - else - notifyError(fromError); - + notifyError(errorCode, null); urls = new String[0];// mark this mission as dead return; } - if (threads != null) { - for (Thread thread : threads) { - if (thread == Thread.currentThread()) continue; - thread.interrupt(); - joinForThread(thread, 0); - } - } - - errCode = ERROR_NOTHING; - errObject = null; - - if (recoveryInfo[current].attempts >= maxRetry) { - recoveryInfo[current].attempts = 0; - notifyError(fromError); - return; - } + joinForThreads(0); threads = new Thread[]{ - runAsync(DownloadMissionRecover.mID, new DownloadMissionRecover(this, fromError)) + runAsync(DownloadMissionRecover.mID, new DownloadMissionRecover(this, errorCode)) }; } private boolean deleteThisFromFile() { synchronized (LOCK) { - return metadata.delete(); + boolean res = metadata.delete(); + metadata = null; + return res; } } @@ -776,8 +743,8 @@ public class DownloadMission extends Mission { * @param id id of new thread (used for debugging only) * @param who the Runnable whose {@code run} method is invoked. */ - private void runAsync(int id, Runnable who) { - runAsync(id, new Thread(who)); + private Thread runAsync(int id, Runnable who) { + return runAsync(id, new Thread(who)); } /** @@ -806,28 +773,44 @@ public class DownloadMission extends Mission { /** * Waits at most {@code millis} milliseconds for the thread to die * - * @param thread the desired thread * @param millis the time to wait in milliseconds */ - private void joinForThread(Thread thread, int millis) { - if (thread == null || !thread.isAlive()) return; - if (thread == Thread.currentThread()) return; + private void joinForThreads(int millis) { + final Thread currentThread = Thread.currentThread(); - if (DEBUG) { - Log.w(TAG, "a thread is !still alive!: " + thread.getName()); + if (init != null && init != currentThread && init.isAlive()) { + init.interrupt(); + + if (millis > 0) { + try { + init.join(millis); + } catch (InterruptedException e) { + Log.w(TAG, "Initializer thread is still running", e); + return; + } + } } - // still alive, this should not happen. - // Possible reasons: + // if a thread is still alive, possible reasons: // slow device // the user is spamming start/pause buttons // start() method called quickly after pause() + for (Thread thread : threads) { + if (!thread.isAlive() || thread == Thread.currentThread()) continue; + thread.interrupt(); + } + try { - thread.join(millis); + for (Thread thread : threads) { + if (!thread.isAlive()) continue; + if (DEBUG) { + Log.w(TAG, "thread alive: " + thread.getName()); + } + if (millis > 0) thread.join(millis); + } } catch (InterruptedException e) { - Log.d(TAG, "timeout on join : " + thread.getName()); - throw new RuntimeException("A thread is still running:\n" + thread.getName()); + throw new RuntimeException("A download thread is still running", e); } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java index 5efbd1153..eb660e564 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -4,6 +4,7 @@ import android.util.Log; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.stream.SubtitlesStream; @@ -15,7 +16,8 @@ import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; import java.util.List; -import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; +import us.shandian.giga.get.DownloadMission.HttpError; + import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; public class DownloadMissionRecover extends Thread { @@ -23,47 +25,67 @@ public class DownloadMissionRecover extends Thread { static final int mID = -3; private final DownloadMission mMission; - private final Exception mFromError; - private final boolean notInitialized; + private final boolean mNotInitialized; + + private final int mErrCode; private HttpURLConnection mConn; private MissionRecoveryInfo mRecovery; private StreamExtractor mExtractor; - DownloadMissionRecover(DownloadMission mission, Exception originError) { + DownloadMissionRecover(DownloadMission mission, int errCode) { mMission = mission; - mFromError = originError; - notInitialized = mission.blocks == null && mission.current == 0; + mNotInitialized = mission.blocks == null && mission.current == 0; + mErrCode = errCode; } @Override public void run() { if (mMission.source == null) { - mMission.notifyError(mFromError); + mMission.notifyError(mErrCode, null); return; } + Exception err = null; + int attempt = 0; + + while (attempt++ < mMission.maxRetry) { + try { + tryRecover(); + return; + } catch (InterruptedIOException | ClosedByInterruptException e) { + return; + } catch (Exception e) { + if (!mMission.running || super.isInterrupted()) return; + err = e; + } + } + + // give up + mMission.notifyError(mErrCode, err); + } + + private void tryRecover() throws ExtractionException, IOException, HttpError { /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); return; }*/ - try { - StreamingService svr = NewPipe.getServiceByUrl(mMission.source); - mExtractor = svr.getStreamExtractor(mMission.source); - mExtractor.fetchPage(); - } catch (InterruptedIOException | ClosedByInterruptException e) { - return; - } catch (Exception e) { - if (!mMission.running || super.isInterrupted()) return; - mMission.notifyError(e); - return; + if (mExtractor == null) { + try { + StreamingService svr = NewPipe.getServiceByUrl(mMission.source); + mExtractor = svr.getStreamExtractor(mMission.source); + mExtractor.fetchPage(); + } catch (ExtractionException e) { + mExtractor = null; + throw e; + } } // maybe the following check is redundant if (!mMission.running || super.isInterrupted()) return; - if (!notInitialized) { + if (!mNotInitialized) { // set the current download url to null in case if the recovery // process is canceled. Next time start() method is called the // recovery will be executed, saving time @@ -87,7 +109,7 @@ public class DownloadMissionRecover extends Thread { if (!mMission.running) return; // before continue, check if the current stream was resolved - if (mMission.urls[mMission.current] == null || mMission.errCode != ERROR_NOTHING) { + if (mMission.urls[mMission.current] == null) { break; } } @@ -103,59 +125,54 @@ public class DownloadMissionRecover extends Thread { mMission.start(); } - private void resolveStream() { - if (mExtractor.getErrorMessage() != null) { - mMission.notifyError(mFromError); + private void resolveStream() throws IOException, ExtractionException, HttpError { + // FIXME: this getErrorMessage() always returns "video is unavailable" + /*if (mExtractor.getErrorMessage() != null) { + mMission.notifyError(mErrCode, new ExtractionException(mExtractor.getErrorMessage())); return; + }*/ + + String url = null; + + switch (mRecovery.kind) { + case 'a': + for (AudioStream audio : mExtractor.getAudioStreams()) { + if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { + url = audio.getUrl(); + break; + } + } + break; + case 'v': + List videoStreams; + if (mRecovery.desired2) + videoStreams = mExtractor.getVideoOnlyStreams(); + else + videoStreams = mExtractor.getVideoStreams(); + for (VideoStream video : videoStreams) { + if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { + url = video.getUrl(); + break; + } + } + break; + case 's': + for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.format)) { + String tag = subtitles.getLanguageTag(); + if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { + url = subtitles.getURL(); + break; + } + } + break; + default: + throw new RuntimeException("Unknown stream type"); } - try { - String url = null; - - switch (mRecovery.kind) { - case 'a': - for (AudioStream audio : mExtractor.getAudioStreams()) { - if (audio.average_bitrate == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) { - url = audio.getUrl(); - break; - } - } - break; - case 'v': - List videoStreams; - if (mRecovery.desired2) - videoStreams = mExtractor.getVideoOnlyStreams(); - else - videoStreams = mExtractor.getVideoStreams(); - for (VideoStream video : videoStreams) { - if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) { - url = video.getUrl(); - break; - } - } - break; - case 's': - for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.format)) { - String tag = subtitles.getLanguageTag(); - if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) { - url = subtitles.getURL(); - break; - } - } - break; - default: - throw new RuntimeException("Unknown stream type"); - } - - resolve(url); - } catch (Exception e) { - if (!mMission.running || e instanceof ClosedByInterruptException) return; - mRecovery.attempts++; - mMission.notifyError(e); - } + resolve(url); } - private void resolve(String url) throws IOException, DownloadMission.HttpError { + private void resolve(String url) throws IOException, HttpError { if (mRecovery.validateCondition == null) { Log.w(TAG, "validation condition not defined, the resource can be stale"); } @@ -190,10 +207,7 @@ public class DownloadMissionRecover extends Thread { return; } - throw new DownloadMission.HttpError(code); - } catch (Exception e) { - if (!mMission.running || e instanceof ClosedByInterruptException) return; - throw e; + throw new HttpError(code); } finally { disconnect(); } @@ -205,14 +219,14 @@ public class DownloadMissionRecover extends Thread { ); mMission.urls[mMission.current] = url; - mRecovery.attempts = 0; if (url == null) { + mMission.urls = new String[0]; mMission.notifyError(ERROR_RESOURCE_GONE, null); return; } - if (notInitialized) return; + if (mNotInitialized) return; if (stale) { mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index b0dc793bc..4aa6e912e 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -87,6 +87,7 @@ public class DownloadRunnable extends Thread { if (mConn.getResponseCode() == 416) { if (block.done > 0) { // try again from the start (of the block) + mMission.notifyProgress(-block.done); block.done = 0; retry = true; mConn.disconnect(); @@ -114,7 +115,7 @@ public class DownloadRunnable extends Thread { int len; // use always start <= end - // fixes a deadlock in DownloadRunnable because youtube is sending one byte alone after downloading 26MiB exactly + // fixes a deadlock because in some videos, youtube is sending one byte alone while (start <= end && mMission.running && (len = is.read(buf, 0, buf.length)) != -1) { f.write(buf, 0, len); start += len; @@ -135,7 +136,7 @@ public class DownloadRunnable extends Thread { if (mId == 1) { // only the first thread will execute the recovery procedure - mMission.doRecover(e); + mMission.doRecover(ERROR_HTTP_FORBIDDEN); } return; } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index e64322b48..9cb40cb32 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -1,8 +1,9 @@ package us.shandian.giga.get; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import org.schabi.newpipe.streams.io.SharpStream; import java.io.IOException; @@ -47,22 +48,10 @@ public class DownloadRunnableFallback extends Thread { if (mF != null) mF.close(); } - private long loadPosition() { - synchronized (mMission.LOCK) { - return mMission.fallbackResumeOffset; - } - } - - private void savePosition(long position) { - synchronized (mMission.LOCK) { - mMission.fallbackResumeOffset = position; - } - } - @Override public void run() { boolean done; - long start = loadPosition(); + long start = mMission.fallbackResumeOffset; if (DEBUG && !mMission.unknownLength && start > 0) { Log.i(TAG, "Resuming a single-thread download at " + start); @@ -83,6 +72,7 @@ public class DownloadRunnableFallback extends Thread { // check if the download can be resumed if (mConn.getResponseCode() == 416 && start > 0) { + mMission.notifyProgress(-start); start = 0; mRetryCount--; throw new DownloadMission.HttpError(416); @@ -92,6 +82,11 @@ public class DownloadRunnableFallback extends Thread { if (!mMission.unknownLength) mMission.unknownLength = Utility.getContentLength(mConn) == -1; + if (mMission.unknownLength || mConn.getResponseCode() == 200) { + // restart amount of bytes downloaded + mMission.done = mMission.offsets[mMission.current] - mMission.offsets[0]; + } + mF = mMission.storage.getStream(); mF.seek(mMission.offsets[mMission.current] + start); @@ -113,14 +108,14 @@ public class DownloadRunnableFallback extends Thread { } catch (Exception e) { dispose(); - savePosition(start); + mMission.fallbackResumeOffset = start; if (!mMission.running || e instanceof ClosedByInterruptException) return; if (e instanceof HttpError && ((HttpError) e).statusCode == ERROR_HTTP_FORBIDDEN) { // for youtube streams. The url has expired, recover dispose(); - mMission.doRecover(e); + mMission.doRecover(ERROR_HTTP_FORBIDDEN); return; } @@ -140,7 +135,7 @@ public class DownloadRunnableFallback extends Thread { if (done) { mMission.notifyFinished(); } else { - savePosition(start); + mMission.fallbackResumeOffset = start; } } diff --git a/app/src/main/java/us/shandian/giga/get/FinishedMission.java b/app/src/main/java/us/shandian/giga/get/FinishedMission.java index b468f3c76..6bc5423b8 100644 --- a/app/src/main/java/us/shandian/giga/get/FinishedMission.java +++ b/app/src/main/java/us/shandian/giga/get/FinishedMission.java @@ -2,17 +2,17 @@ package us.shandian.giga.get; import androidx.annotation.NonNull; -public class FinishedMission extends Mission { +public class FinishedMission extends Mission { public FinishedMission() { } public FinishedMission(@NonNull DownloadMission mission) { source = mission.source; - length = mission.length;// ¿or mission.done? + length = mission.length; timestamp = mission.timestamp; kind = mission.kind; storage = mission.storage; - } + } diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java index bd1d9bc49..f6a3a3984 100644 --- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java @@ -2,7 +2,8 @@ package us.shandian.giga.get; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; + +import androidx.annotation.NonNull; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.AudioStream; @@ -23,8 +24,6 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { byte kind; String validateCondition = null; - transient int attempts = 0; - public MissionRecoveryInfo(@NonNull Stream stream) { if (stream instanceof AudioStream) { desiredBitrate = ((AudioStream) stream).average_bitrate; @@ -51,7 +50,7 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { public String toString() { String info; StringBuilder str = new StringBuilder(); - str.append("type="); + str.append("{type="); switch (kind) { case 'a': str.append("audio"); @@ -73,7 +72,8 @@ public class MissionRecoveryInfo implements Serializable, Parcelable { str.append(" format=") .append(format.getName()) .append(' ') - .append(info); + .append(info) + .append('}'); return str.toString(); } diff --git a/app/src/main/java/us/shandian/giga/io/ChunkFileInputStream.java b/app/src/main/java/us/shandian/giga/io/ChunkFileInputStream.java index 16a90fcee..98015e37e 100644 --- a/app/src/main/java/us/shandian/giga/io/ChunkFileInputStream.java +++ b/app/src/main/java/us/shandian/giga/io/ChunkFileInputStream.java @@ -5,21 +5,23 @@ import org.schabi.newpipe.streams.io.SharpStream; import java.io.IOException; public class ChunkFileInputStream extends SharpStream { + private static final int REPORT_INTERVAL = 256 * 1024; private SharpStream source; private final long offset; private final long length; private long position; - public ChunkFileInputStream(SharpStream target, long start) throws IOException { - this(target, start, target.length()); - } + private long progressReport; + private final ProgressReport onProgress; - public ChunkFileInputStream(SharpStream target, long start, long end) throws IOException { + public ChunkFileInputStream(SharpStream target, long start, long end, ProgressReport callback) throws IOException { source = target; offset = start; length = end - start; position = 0; + onProgress = callback; + progressReport = REPORT_INTERVAL; if (length < 1) { source.close(); @@ -60,12 +62,12 @@ public class ChunkFileInputStream extends SharpStream { } @Override - public int read(byte b[]) throws IOException { + public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override - public int read(byte b[], int off, int len) throws IOException { + public int read(byte[] b, int off, int len) throws IOException { if ((position + len) > length) { len = (int) (length - position); } @@ -76,6 +78,11 @@ public class ChunkFileInputStream extends SharpStream { int res = source.read(b, off, len); position += res; + if (onProgress != null && position > progressReport) { + onProgress.report(position); + progressReport = position + REPORT_INTERVAL; + } + return res; } diff --git a/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java b/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java index e2afb9202..102580570 100644 --- a/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java +++ b/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java @@ -174,12 +174,12 @@ public class CircularFileWriter extends SharpStream { } @Override - public void write(byte b[]) throws IOException { + public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override - public void write(byte b[], int off, int len) throws IOException { + public void write(byte[] b, int off, int len) throws IOException { if (len == 0) { return; } @@ -261,7 +261,7 @@ public class CircularFileWriter extends SharpStream { @Override public void rewind() throws IOException { if (onProgress != null) { - onProgress.report(-out.length - aux.length);// rollback the whole progress + onProgress.report(0);// rollback the whole progress } seek(0); @@ -357,16 +357,6 @@ public class CircularFileWriter extends SharpStream { long check(); } - public interface ProgressReport { - - /** - * Report the size of the new file - * - * @param progress the new size - */ - void report(long progress); - } - public interface WriteErrorHandle { /** @@ -381,10 +371,10 @@ public class CircularFileWriter extends SharpStream { class BufferedFile { - protected final SharpStream target; + final SharpStream target; private long offset; - protected long length; + long length; private byte[] queue = new byte[QUEUE_BUFFER_SIZE]; private int queueSize; @@ -397,16 +387,16 @@ public class CircularFileWriter extends SharpStream { this.target = target; } - protected long getOffset() { + long getOffset() { return offset + queueSize;// absolute offset in the file } - protected void close() { + void close() { queue = null; target.close(); } - protected void write(byte b[], int off, int len) throws IOException { + void write(byte[] b, int off, int len) throws IOException { while (len > 0) { // if the queue is full, the method available() will flush the queue int read = Math.min(available(), len); @@ -436,7 +426,7 @@ public class CircularFileWriter extends SharpStream { target.seek(0); } - protected int available() throws IOException { + int available() throws IOException { if (queueSize >= queue.length) { flush(); return queue.length; @@ -451,7 +441,7 @@ public class CircularFileWriter extends SharpStream { target.seek(0); } - protected void seek(long absoluteOffset) throws IOException { + void seek(long absoluteOffset) throws IOException { if (absoluteOffset == offset) { return;// nothing to do } diff --git a/app/src/main/java/us/shandian/giga/io/ProgressReport.java b/app/src/main/java/us/shandian/giga/io/ProgressReport.java new file mode 100644 index 000000000..14ae9ded9 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/io/ProgressReport.java @@ -0,0 +1,11 @@ +package us.shandian.giga.io; + +public interface ProgressReport { + + /** + * Report the size of the new file + * + * @param progress the new size + */ + void report(long progress); +} \ No newline at end of file diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java index 605c0a88b..04958c495 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java @@ -1,6 +1,6 @@ package us.shandian.giga.postprocessing; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.schabi.newpipe.streams.OggFromWebMWriter; import org.schabi.newpipe.streams.io.SharpStream; diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java index 92510c3df..773ff92d1 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java @@ -1,9 +1,9 @@ package us.shandian.giga.postprocessing; -import android.os.Message; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import org.schabi.newpipe.streams.io.SharpStream; import java.io.File; @@ -14,11 +14,11 @@ import us.shandian.giga.get.DownloadMission; import us.shandian.giga.io.ChunkFileInputStream; import us.shandian.giga.io.CircularFileWriter; import us.shandian.giga.io.CircularFileWriter.OffsetChecker; -import us.shandian.giga.service.DownloadManagerService; +import us.shandian.giga.io.ProgressReport; import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; +import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING; import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_HOLD; -import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION; public abstract class Postprocessing implements Serializable { @@ -63,22 +63,22 @@ public abstract class Postprocessing implements Serializable { * Get a boolean value that indicate if the given algorithm work on the same * file */ - public final boolean worksOnSameFile; + public boolean worksOnSameFile; /** * Indicates whether the selected algorithm needs space reserved at the beginning of the file */ - public final boolean reserveSpace; + public boolean reserveSpace; /** * Gets the given algorithm short name */ - private final String name; + private String name; private String[] args; - protected transient DownloadMission mission; + private transient DownloadMission mission; private File tempFile; @@ -109,16 +109,24 @@ public abstract class Postprocessing implements Serializable { long finalLength = -1; mission.done = 0; - mission.length = mission.storage.length(); + + long length = mission.storage.length() - mission.offsets[0]; + mission.length = length > mission.nearLength ? length : mission.nearLength; + + final ProgressReport readProgress = (long position) -> { + position -= mission.offsets[0]; + if (position > mission.done) mission.done = position; + }; if (worksOnSameFile) { ChunkFileInputStream[] sources = new ChunkFileInputStream[mission.urls.length]; try { - int i = 0; - for (; i < sources.length - 1; i++) { - sources[i] = new ChunkFileInputStream(mission.storage.getStream(), mission.offsets[i], mission.offsets[i + 1]); + for (int i = 0, j = 1; i < sources.length; i++, j++) { + SharpStream source = mission.storage.getStream(); + long end = j < sources.length ? mission.offsets[j] : source.length(); + + sources[i] = new ChunkFileInputStream(source, mission.offsets[i], end, readProgress); } - sources[i] = new ChunkFileInputStream(mission.storage.getStream(), mission.offsets[i]); if (test(sources)) { for (SharpStream source : sources) source.rewind(); @@ -140,7 +148,7 @@ public abstract class Postprocessing implements Serializable { }; out = new CircularFileWriter(mission.storage.getStream(), tempFile, checker); - out.onProgress = this::progressReport; + out.onProgress = (long position) -> mission.done = position; out.onWriteError = (err) -> { mission.psState = 3; @@ -187,11 +195,10 @@ public abstract class Postprocessing implements Serializable { if (result == OK_RESULT) { if (finalLength != -1) { - mission.done = finalLength; mission.length = finalLength; } } else { - mission.errCode = ERROR_UNKNOWN_EXCEPTION; + mission.errCode = ERROR_POSTPROCESSING; mission.errObject = new RuntimeException("post-processing algorithm returned " + result); } @@ -229,23 +236,12 @@ public abstract class Postprocessing implements Serializable { return args[index]; } - private void progressReport(long done) { - mission.done = done; - if (mission.length < mission.done) mission.length = mission.done; - - Message m = new Message(); - m.what = DownloadManagerService.MESSAGE_PROGRESS; - m.obj = mission; - - mission.mHandler.sendMessage(m); - } - @NonNull @Override public String toString() { StringBuilder str = new StringBuilder(); - str.append("name=").append(name).append('['); + str.append("{ name=").append(name).append('['); if (args != null) { for (String arg : args) { @@ -255,6 +251,6 @@ public abstract class Postprocessing implements Serializable { str.delete(0, 1); } - return str.append(']').toString(); + return str.append("] }").toString(); } } diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index 2d1e9cd00..e8bc468e9 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -2,13 +2,11 @@ package us.shandian.giga.service; import android.content.Context; import android.os.Handler; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.DiffUtil; -import android.util.Log; -import android.widget.Toast; - -import org.schabi.newpipe.R; import java.io.File; import java.io.IOException; @@ -152,6 +150,8 @@ public class DownloadManager { continue; } + mis.threads = new Thread[0]; + boolean exists; try { mis.storage = StoredFileHelper.deserialize(mis.storage, ctx); @@ -170,8 +170,6 @@ public class DownloadManager { // is Java IO (avoid showing the "Save as..." dialog) if (exists && mis.storage.isDirect() && !mis.storage.delete()) Log.w(TAG, "Unable to delete incomplete download file: " + sub.getPath()); - - exists = true; } mis.psState = 0; @@ -243,7 +241,6 @@ public class DownloadManager { boolean start = !mPrefQueueLimit || getRunningMissionsCount() < 1; if (canDownloadInCurrentNetwork() && start) { - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS); mission.start(); } } @@ -252,7 +249,6 @@ public class DownloadManager { public void resumeMission(DownloadMission mission) { if (!mission.running) { - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS); mission.start(); } } @@ -261,7 +257,6 @@ public class DownloadManager { if (mission.running) { mission.setEnqueued(false); mission.pause(); - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED); } } @@ -274,7 +269,6 @@ public class DownloadManager { mFinishedMissionStore.deleteMission(mission); } - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_DELETED); mission.delete(); } } @@ -291,7 +285,6 @@ public class DownloadManager { mFinishedMissionStore.deleteMission(mission); } - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_DELETED); mission.storage = null; mission.delete(); } @@ -374,35 +367,29 @@ public class DownloadManager { } public void pauseAllMissions(boolean force) { - boolean flag = false; - synchronized (this) { for (DownloadMission mission : mMissionsPending) { if (!mission.running || mission.isPsRunning() || mission.isFinished()) continue; - if (force) mission.threads = null;// avoid waiting for threads + if (force) { + // avoid waiting for threads + mission.init = null; + mission.threads = new Thread[0]; + } mission.pause(); - flag = true; } } - - if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED); } public void startAllMissions() { - boolean flag = false; - synchronized (this) { for (DownloadMission mission : mMissionsPending) { if (mission.running || mission.isCorrupt()) continue; - flag = true; mission.start(); } } - - if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS); } /** @@ -483,28 +470,18 @@ public class DownloadManager { boolean isMetered = mPrefMeteredDownloads && mLastNetworkStatus == NetworkState.MeteredOperating; - int running = 0; - int paused = 0; synchronized (this) { for (DownloadMission mission : mMissionsPending) { if (mission.isCorrupt() || mission.isPsRunning()) continue; if (mission.running && isMetered) { - paused++; mission.pause(); } else if (!mission.running && !isMetered && mission.enqueued) { - running++; mission.start(); if (mPrefQueueLimit) break; } } } - - if (running > 0) { - mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS); - return; - } - if (paused > 0) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED); } void updateMaximumAttempts() { @@ -513,22 +490,6 @@ public class DownloadManager { } } - /** - * Fast check for pending downloads. If exists, the user will be notified - * TODO: call this method in somewhere - * - * @param context the application context - */ - public static void notifyUserPendingDownloads(Context context) { - int pending = getPendingDir(context).list().length; - if (pending < 1) return; - - Toast.makeText(context, context.getString( - R.string.msg_pending_downloads, - String.valueOf(pending) - ), Toast.LENGTH_LONG).show(); - } - public MissionState checkForExistingMission(StoredFileHelper storage) { synchronized (this) { DownloadMission pending = getPendingMission(storage); diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index ea9029c0b..3da0e75b8 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -25,14 +25,15 @@ import android.os.IBinder; import android.os.Message; import android.os.Parcelable; import android.preference.PreferenceManager; +import android.util.Log; +import android.util.SparseArray; +import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat.Builder; -import android.util.Log; -import android.util.SparseArray; -import android.widget.Toast; import org.schabi.newpipe.R; import org.schabi.newpipe.download.DownloadActivity; @@ -41,8 +42,6 @@ import org.schabi.newpipe.player.helper.LockManager; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.MissionRecoveryInfo; @@ -58,11 +57,11 @@ public class DownloadManagerService extends Service { private static final String TAG = "DownloadManagerService"; + public static final int MESSAGE_RUNNING = 0; public static final int MESSAGE_PAUSED = 1; public static final int MESSAGE_FINISHED = 2; - public static final int MESSAGE_PROGRESS = 3; - public static final int MESSAGE_ERROR = 4; - public static final int MESSAGE_DELETED = 5; + public static final int MESSAGE_ERROR = 3; + public static final int MESSAGE_DELETED = 4; private static final int FOREGROUND_NOTIFICATION_ID = 1000; private static final int DOWNLOADS_NOTIFICATION_ID = 1001; @@ -217,9 +216,11 @@ public class DownloadManagerService extends Service { .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ); } + return START_NOT_STICKY; } } - return START_NOT_STICKY; + + return START_STICKY; } @Override @@ -250,6 +251,7 @@ public class DownloadManagerService extends Service { if (icDownloadFailed != null) icDownloadFailed.recycle(); if (icLauncher != null) icLauncher.recycle(); + mHandler = null; mManager.pauseAllMissions(true); } @@ -274,6 +276,8 @@ public class DownloadManagerService extends Service { } private boolean handleMessage(@NonNull Message msg) { + if (mHandler == null) return true; + DownloadMission mission = (DownloadMission) msg.obj; switch (msg.what) { @@ -284,7 +288,7 @@ public class DownloadManagerService extends Service { handleConnectivityState(false); updateForegroundState(mManager.runMissions()); break; - case MESSAGE_PROGRESS: + case MESSAGE_RUNNING: updateForegroundState(true); break; case MESSAGE_ERROR: @@ -300,11 +304,8 @@ public class DownloadManagerService extends Service { if (msg.what != MESSAGE_ERROR) mFailedDownloads.delete(mFailedDownloads.indexOfValue(mission)); - synchronized (mEchoObservers) { - for (Callback observer : mEchoObservers) { - observer.handleMessage(msg); - } - } + for (Callback observer : mEchoObservers) + observer.handleMessage(msg); return true; } @@ -523,16 +524,6 @@ public class DownloadManagerService extends Service { return PendingIntent.getService(this, intent.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT); } - private void manageObservers(Callback handler, boolean add) { - synchronized (mEchoObservers) { - if (add) { - mEchoObservers.add(handler); - } else { - mEchoObservers.remove(handler); - } - } - } - private void manageLock(boolean acquire) { if (acquire == mLockAcquired) return; @@ -605,11 +596,11 @@ public class DownloadManagerService extends Service { } public void addMissionEventListener(Callback handler) { - manageObservers(handler, true); + mEchoObservers.add(handler); } public void removeMissionEventListener(Callback handler) { - manageObservers(handler, false); + mEchoObservers.remove(handler); } public void clearDownloadNotifications() { diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 78fd7ea9d..e3a7f112a 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -10,16 +10,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.os.Message; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.core.content.FileProvider; -import androidx.core.view.ViewCompat; -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.Log; import android.util.SparseArray; import android.view.HapticFeedbackConstants; @@ -34,6 +24,17 @@ import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.FileProvider; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; + import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; @@ -82,6 +83,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb private static final String TAG = "MissionAdapter"; private static final String UNDEFINED_PROGRESS = "--.-%"; private static final String DEFAULT_MIME_TYPE = "*/*"; + private static final String UNDEFINED_ETA = "--:--"; static { @@ -103,10 +105,11 @@ public class MissionAdapter extends Adapter implements Handler.Callb private View mEmptyMessage; private RecoverHelper mRecover; - public MissionAdapter(Context context, @NonNull DownloadManager downloadManager, View emptyMessage) { + private final Runnable rUpdater = this::updater; + + public MissionAdapter(Context context, @NonNull DownloadManager downloadManager, View emptyMessage, View root) { mContext = context; mDownloadManager = downloadManager; - mDeleter = null; mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mLayout = R.layout.mission_item; @@ -117,7 +120,10 @@ public class MissionAdapter extends Adapter implements Handler.Callb mIterator = downloadManager.getIterator(); + mDeleter = new Deleter(root, mContext, this, mDownloadManager, mIterator, mHandler); + checkEmptyMessageVisibility(); + onResume(); } @Override @@ -142,17 +148,13 @@ public class MissionAdapter extends Adapter implements Handler.Callb if (h.item.mission instanceof DownloadMission) { mPendingDownloadsItems.remove(h); if (mPendingDownloadsItems.size() < 1) { - setAutoRefresh(false); checkMasterButtonsVisibility(); } } h.popupMenu.dismiss(); h.item = null; - h.lastTimeStamp = -1; - h.lastDone = -1; - h.lastCurrent = -1; - h.state = 0; + h.resetSpeedMeasure(); } @Override @@ -191,7 +193,6 @@ public class MissionAdapter extends Adapter implements Handler.Callb h.size.setText(length); h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause); - h.lastCurrent = mission.current; updateProgress(h); mPendingDownloadsItems.add(h); } else { @@ -216,20 +217,10 @@ public class MissionAdapter extends Adapter implements Handler.Callb private void updateProgress(ViewHolderItem h) { if (h == null || h.item == null || h.item.mission instanceof FinishedMission) return; - long now = System.currentTimeMillis(); DownloadMission mission = (DownloadMission) h.item.mission; - - if (h.lastCurrent != mission.current) { - h.lastCurrent = mission.current; - h.lastTimeStamp = now; - h.lastDone = 0; - } else { - if (h.lastTimeStamp == -1) h.lastTimeStamp = now; - if (h.lastDone == -1) h.lastDone = mission.done; - } - - long deltaTime = now - h.lastTimeStamp; - long deltaDone = mission.done - h.lastDone; + double done = mission.done; + long length = mission.getLength(); + long now = System.currentTimeMillis(); boolean hasError = mission.errCode != ERROR_NOTHING; // hide on error @@ -237,19 +228,16 @@ public class MissionAdapter extends Adapter implements Handler.Callb // show if length is unknown h.progress.setMarquee(mission.isRecovering() || !hasError && (!mission.isInitialized() || mission.unknownLength)); - float progress; + double progress; if (mission.unknownLength) { - progress = Float.NaN; + progress = Double.NaN; h.progress.setProgress(0f); } else { - progress = (float) ((double) mission.done / mission.length); - if (mission.urls.length > 1 && mission.current < mission.urls.length) { - progress = (progress / mission.urls.length) + ((float) mission.current / mission.urls.length); - } + progress = done / length; } if (hasError) { - h.progress.setProgress(isNotFinite(progress) ? 1f : progress); + h.progress.setProgress(isNotFinite(progress) ? 1d : progress); h.status.setText(R.string.msg_error); } else if (isNotFinite(progress)) { h.status.setText(UNDEFINED_PROGRESS); @@ -258,59 +246,78 @@ public class MissionAdapter extends Adapter implements Handler.Callb h.progress.setProgress(progress); } - long length = mission.getLength(); + @StringRes int state; + String sizeStr = Utility.formatBytes(length).concat(" "); - int state; if (mission.isPsFailed() || mission.errCode == ERROR_POSTPROCESSING_HOLD) { - state = 0; + h.size.setText(sizeStr); + return; } else if (!mission.running) { - state = mission.enqueued ? 1 : 2; + state = mission.enqueued ? R.string.queued : R.string.paused; } else if (mission.isPsRunning()) { - state = 3; + state = R.string.post_processing; + } else if (mission.isRecovering()) { + state = R.string.recovering; } else { state = 0; } if (state != 0) { // update state without download speed - if (h.state != state) { - String statusStr; - h.state = state; + h.size.setText(sizeStr.concat("(").concat(mContext.getString(state)).concat(")")); + h.resetSpeedMeasure(); + return; + } - switch (state) { - case 1: - statusStr = mContext.getString(R.string.queued); - break; - case 2: - statusStr = mContext.getString(R.string.paused); - break; - case 3: - statusStr = mContext.getString(R.string.post_processing); - break; - default: - statusStr = "?"; - break; - } + if (h.lastTimestamp < 0) { + h.size.setText(sizeStr); + h.lastTimestamp = now; + h.lastDone = done; + return; + } - h.size.setText(Utility.formatBytes(length).concat(" (").concat(statusStr).concat(")")); - } else if (deltaDone > 0) { - h.lastTimeStamp = now; - h.lastDone = mission.done; - } + long deltaTime = now - h.lastTimestamp; + double deltaDone = done - h.lastDone; + if (h.lastDone > done) { + h.lastDone = done; + h.size.setText(sizeStr); return; } if (deltaDone > 0 && deltaTime > 0) { - float speed = (deltaDone * 1000f) / deltaTime; + float speed = (float) ((deltaDone * 1000d) / deltaTime); + float averageSpeed = speed; - String speedStr = Utility.formatSpeed(speed); - String sizeStr = Utility.formatBytes(length); + if (h.lastSpeedIdx < 0) { + for (int i = 0; i < h.lastSpeed.length; i++) { + h.lastSpeed[i] = speed; + } + h.lastSpeedIdx = 0; + } else { + for (int i = 0; i < h.lastSpeed.length; i++) { + averageSpeed += h.lastSpeed[i]; + } + averageSpeed /= h.lastSpeed.length + 1f; + } - h.size.setText(sizeStr.concat(" ").concat(speedStr)); + String speedStr = Utility.formatSpeed(averageSpeed); + String etaStr; - h.lastTimeStamp = now; - h.lastDone = mission.done; + if (mission.unknownLength) { + etaStr = ""; + } else { + long eta = (long) Math.ceil((length - done) / averageSpeed); + etaStr = " @ ".concat(Utility.stringifySeconds(eta)); + } + + h.size.setText(sizeStr.concat(speedStr).concat(etaStr)); + + h.lastTimestamp = now; + h.lastDone = done; + h.lastSpeed[h.lastSpeedIdx++] = speed; + + if (h.lastSpeedIdx >= h.lastSpeed.length) h.lastSpeedIdx = 0; } } @@ -389,6 +396,13 @@ public class MissionAdapter extends Adapter implements Handler.Callb return true; } + private ViewHolderItem getViewHolder(Object mission) { + for (ViewHolderItem h : mPendingDownloadsItems) { + if (h.item.mission == mission) return h; + } + return null; + } + @Override public boolean handleMessage(@NonNull Message msg) { if (mStartButton != null && mPauseButton != null) { @@ -396,33 +410,28 @@ public class MissionAdapter extends Adapter implements Handler.Callb } switch (msg.what) { - case DownloadManagerService.MESSAGE_PROGRESS: case DownloadManagerService.MESSAGE_ERROR: case DownloadManagerService.MESSAGE_FINISHED: + case DownloadManagerService.MESSAGE_DELETED: + case DownloadManagerService.MESSAGE_PAUSED: break; default: return false; } - if (msg.what == DownloadManagerService.MESSAGE_PROGRESS) { - setAutoRefresh(true); - return true; - } + ViewHolderItem h = getViewHolder(msg.obj); + if (h == null) return false; - for (ViewHolderItem h : mPendingDownloadsItems) { - if (h.item.mission != msg.obj) continue; - - if (msg.what == DownloadManagerService.MESSAGE_FINISHED) { + switch (msg.what) { + case DownloadManagerService.MESSAGE_FINISHED: + case DownloadManagerService.MESSAGE_DELETED: // DownloadManager should mark the download as finished applyChanges(); return true; - } - - updateProgress(h); - return true; } - return false; + updateProgress(h); + return true; } private void showError(@NonNull DownloadMission mission) { @@ -470,8 +479,13 @@ public class MissionAdapter extends Adapter implements Handler.Callb msg = R.string.error_insufficient_storage; break; case ERROR_UNKNOWN_EXCEPTION: - showError(mission, UserAction.DOWNLOAD_FAILED, R.string.general_error); - return; + if (mission.errObject != null) { + showError(mission, UserAction.DOWNLOAD_FAILED, R.string.general_error); + return; + } else { + msg = R.string.msg_error; + break; + } case ERROR_PROGRESS_LOST: msg = R.string.error_progress_lost; break; @@ -521,7 +535,9 @@ public class MissionAdapter extends Adapter implements Handler.Callb request.append(" ["); if (mission.recoveryInfo != null) { for (MissionRecoveryInfo recovery : mission.recoveryInfo) - request.append(" {").append(recovery.toString()).append("} "); + request.append(' ') + .append(recovery.toString()) + .append(' '); } request.append("]"); @@ -556,16 +572,10 @@ public class MissionAdapter extends Adapter implements Handler.Callb switch (id) { case R.id.start: h.status.setText(UNDEFINED_PROGRESS); - h.state = -1; - h.size.setText(Utility.formatBytes(mission.getLength())); mDownloadManager.resumeMission(mission); return true; case R.id.pause: - h.state = -1; mDownloadManager.pauseMission(mission); - updateProgress(h); - h.lastTimeStamp = -1; - h.lastDone = -1; return true; case R.id.error_message_view: showError(mission); @@ -598,12 +608,9 @@ public class MissionAdapter extends Adapter implements Handler.Callb shareFile(h.item.mission); return true; case R.id.delete: - if (mDeleter == null) { - mDownloadManager.deleteMission(h.item.mission); - } else { - mDeleter.append(h.item.mission); - } + mDeleter.append(h.item.mission); applyChanges(); + checkMasterButtonsVisibility(); return true; case R.id.md5: case R.id.sha1: @@ -639,7 +646,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb mIterator.end(); for (ViewHolderItem item : mPendingDownloadsItems) { - item.lastTimeStamp = -1; + item.resetSpeedMeasure(); } notifyDataSetChanged(); @@ -672,6 +679,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb public void checkMasterButtonsVisibility() { boolean[] state = mIterator.hasValidPendingMissions(); + Log.d(TAG, "checkMasterButtonsVisibility() running=" + state[0] + " paused=" + state[1]); setButtonVisible(mPauseButton, state[0]); setButtonVisible(mStartButton, state[1]); } @@ -681,86 +689,57 @@ public class MissionAdapter extends Adapter implements Handler.Callb button.setVisible(visible); } - public void ensurePausedMissions() { + public void refreshMissionItems() { for (ViewHolderItem h : mPendingDownloadsItems) { if (((DownloadMission) h.item.mission).running) continue; updateProgress(h); - h.lastTimeStamp = -1; - h.lastDone = -1; + h.resetSpeedMeasure(); } } - public void deleterDispose(boolean commitChanges) { - if (mDeleter != null) mDeleter.dispose(commitChanges); + public void onDestroy() { + mDeleter.dispose(); } - public void deleterLoad(View view) { - if (mDeleter == null) - mDeleter = new Deleter(view, mContext, this, mDownloadManager, mIterator, mHandler); + public void onResume() { + mDeleter.resume(); + mHandler.post(rUpdater); } - public void deleterResume() { - if (mDeleter != null) mDeleter.resume(); - } - - public void recoverMission(DownloadMission mission) { - for (ViewHolderItem h : mPendingDownloadsItems) { - if (mission != h.item.mission) continue; - - mission.errObject = null; - mission.resetState(true, false, DownloadMission.ERROR_NOTHING); - - h.status.setText(UNDEFINED_PROGRESS); - h.state = -1; - h.size.setText(Utility.formatBytes(mission.getLength())); - h.progress.setMarquee(true); - - mDownloadManager.resumeMission(mission); - return; - } - - } - - - private boolean mUpdaterRunning = false; - private final Runnable rUpdater = this::updater; - public void onPaused() { - setAutoRefresh(false); + mDeleter.pause(); + mHandler.removeCallbacks(rUpdater); } - private void setAutoRefresh(boolean enabled) { - if (enabled && !mUpdaterRunning) { - mUpdaterRunning = true; - updater(); - } else if (!enabled && mUpdaterRunning) { - mUpdaterRunning = false; - mHandler.removeCallbacks(rUpdater); - } + + public void recoverMission(DownloadMission mission) { + ViewHolderItem h = getViewHolder(mission); + if (h == null) return; + + mission.errObject = null; + mission.resetState(true, false, DownloadMission.ERROR_NOTHING); + + h.status.setText(UNDEFINED_PROGRESS); + h.size.setText(Utility.formatBytes(mission.getLength())); + h.progress.setMarquee(true); + + mDownloadManager.resumeMission(mission); } private void updater() { - if (!mUpdaterRunning) return; - - boolean running = false; for (ViewHolderItem h : mPendingDownloadsItems) { // check if the mission is running first if (!((DownloadMission) h.item.mission).running) continue; updateProgress(h); - running = true; } - if (running) { - mHandler.postDelayed(rUpdater, 1000); - } else { - mUpdaterRunning = false; - } + mHandler.postDelayed(rUpdater, 1000); } - private boolean isNotFinite(Float value) { - return Float.isNaN(value) || Float.isInfinite(value); + private boolean isNotFinite(double value) { + return Double.isNaN(value) || Double.isInfinite(value); } public void setRecover(@NonNull RecoverHelper callback) { @@ -789,10 +768,11 @@ public class MissionAdapter extends Adapter implements Handler.Callb MenuItem source; MenuItem checksum; - long lastTimeStamp = -1; - long lastDone = -1; - int lastCurrent = -1; - int state = 0; + long lastTimestamp = -1; + double lastDone; + int lastSpeedIdx; + float[] lastSpeed = new float[3]; + String estimatedTimeArrival = UNDEFINED_ETA; ViewHolderItem(View view) { super(view); @@ -902,6 +882,12 @@ public class MissionAdapter extends Adapter implements Handler.Callb return popup; } + + private void resetSpeedMeasure() { + estimatedTimeArrival = UNDEFINED_ETA; + lastTimestamp = -1; + lastSpeedIdx = -1; + } } class ViewHolderHeader extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java index 81b4e33e8..a0828c23d 100644 --- a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java +++ b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java @@ -4,9 +4,10 @@ import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.os.Handler; -import com.google.android.material.snackbar.Snackbar; import android.view.View; +import com.google.android.material.snackbar.Snackbar; + import org.schabi.newpipe.R; import java.util.ArrayList; @@ -113,7 +114,7 @@ public class Deleter { show(); } - private void pause() { + public void pause() { running = false; mHandler.removeCallbacks(rNext); mHandler.removeCallbacks(rShow); @@ -126,13 +127,11 @@ public class Deleter { mHandler.postDelayed(rShow, DELAY_RESUME); } - public void dispose(boolean commitChanges) { + public void dispose() { if (items.size() < 1) return; pause(); - if (!commitChanges) return; - for (Mission mission : items) mDownloadManager.deleteMission(mission); items = null; } diff --git a/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java b/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java index a0ff24aaa..3f638d418 100644 --- a/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java +++ b/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java @@ -9,6 +9,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; + import androidx.annotation.ColorInt; import androidx.annotation.NonNull; @@ -35,8 +36,8 @@ public class ProgressDrawable extends Drawable { mForegroundColor = foreground; } - public void setProgress(float progress) { - mProgress = progress; + public void setProgress(double progress) { + mProgress = (float) progress; invalidateSelf(); } diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 26da47b1f..921eaff5c 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -12,11 +12,6 @@ import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.preference.PreferenceManager; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -24,6 +19,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.R; @@ -72,8 +73,7 @@ public class MissionsFragment extends Fragment { mBinder = (DownloadManagerBinder) binder; mBinder.clearDownloadNotifications(); - mAdapter = new MissionAdapter(mContext, mBinder.getDownloadManager(), mEmpty); - mAdapter.deleterLoad(getView()); + mAdapter = new MissionAdapter(mContext, mBinder.getDownloadManager(), mEmpty, getView()); mAdapter.setRecover(MissionsFragment.this::recoverMission); @@ -132,7 +132,7 @@ public class MissionsFragment extends Fragment { * Added in API level 23. */ @Override - public void onAttach(Context context) { + public void onAttach(@NonNull Context context) { super.onAttach(context); // Bug: in api< 23 this is never called @@ -147,7 +147,7 @@ public class MissionsFragment extends Fragment { */ @SuppressWarnings("deprecation") @Override - public void onAttach(Activity activity) { + public void onAttach(@NonNull Activity activity) { super.onAttach(activity); mContext = activity; @@ -162,7 +162,7 @@ public class MissionsFragment extends Fragment { mBinder.removeMissionEventListener(mAdapter); mBinder.enableNotifications(true); mContext.unbindService(mConnection); - mAdapter.deleterDispose(true); + mAdapter.onDestroy(); mBinder = null; mAdapter = null; @@ -196,13 +196,11 @@ public class MissionsFragment extends Fragment { prompt.create().show(); return true; case R.id.start_downloads: - item.setVisible(false); mBinder.getDownloadManager().startAllMissions(); return true; case R.id.pause_downloads: - item.setVisible(false); mBinder.getDownloadManager().pauseAllMissions(false); - mAdapter.ensurePausedMissions();// update items view + mAdapter.refreshMissionItems();// update items view default: return super.onOptionsItemSelected(item); } @@ -271,23 +269,12 @@ public class MissionsFragment extends Fragment { } } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - - if (mAdapter != null) { - mAdapter.deleterDispose(false); - mForceUpdate = true; - mBinder.removeMissionEventListener(mAdapter); - } - } - @Override public void onResume() { super.onResume(); if (mAdapter != null) { - mAdapter.deleterResume(); + mAdapter.onResume(); if (mForceUpdate) { mForceUpdate = false; @@ -303,7 +290,13 @@ public class MissionsFragment extends Fragment { @Override public void onPause() { super.onPause(); - if (mAdapter != null) mAdapter.onPaused(); + + if (mAdapter != null) { + mForceUpdate = true; + mBinder.removeMissionEventListener(mAdapter); + mAdapter.onPaused(); + } + if (mBinder != null) mBinder.enableNotifications(true); } diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java index 21fdd72ad..46207777a 100644 --- a/app/src/main/java/us/shandian/giga/util/Utility.java +++ b/app/src/main/java/us/shandian/giga/util/Utility.java @@ -4,13 +4,14 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.os.Build; +import android.util.Log; +import android.widget.Toast; + import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import android.util.Log; -import android.widget.Toast; import org.schabi.newpipe.R; import org.schabi.newpipe.streams.io.SharpStream; @@ -26,6 +27,7 @@ import java.io.Serializable; import java.net.HttpURLConnection; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Locale; import us.shandian.giga.io.StoredFileHelper; @@ -39,26 +41,28 @@ public class Utility { } public static String formatBytes(long bytes) { + Locale locale = Locale.getDefault(); if (bytes < 1024) { - return String.format("%d B", bytes); + return String.format(locale, "%d B", bytes); } else if (bytes < 1024 * 1024) { - return String.format("%.2f kB", bytes / 1024d); + return String.format(locale, "%.2f kB", bytes / 1024d); } else if (bytes < 1024 * 1024 * 1024) { - return String.format("%.2f MB", bytes / 1024d / 1024d); + return String.format(locale, "%.2f MB", bytes / 1024d / 1024d); } else { - return String.format("%.2f GB", bytes / 1024d / 1024d / 1024d); + return String.format(locale, "%.2f GB", bytes / 1024d / 1024d / 1024d); } } - public static String formatSpeed(float speed) { + public static String formatSpeed(double speed) { + Locale locale = Locale.getDefault(); if (speed < 1024) { - return String.format("%.2f B/s", speed); + return String.format(locale, "%.2f B/s", speed); } else if (speed < 1024 * 1024) { - return String.format("%.2f kB/s", speed / 1024); + return String.format(locale, "%.2f kB/s", speed / 1024); } else if (speed < 1024 * 1024 * 1024) { - return String.format("%.2f MB/s", speed / 1024 / 1024); + return String.format(locale, "%.2f MB/s", speed / 1024 / 1024); } else { - return String.format("%.2f GB/s", speed / 1024 / 1024 / 1024); + return String.format(locale, "%.2f GB/s", speed / 1024 / 1024 / 1024); } } @@ -188,12 +192,11 @@ public class Utility { switch (type) { case MUSIC: return R.drawable.music; + default: case VIDEO: return R.drawable.video; case SUBTITLE: return R.drawable.subtitle; - default: - return R.drawable.video; } } @@ -274,4 +277,25 @@ public class Utility { return -1; } + + private static String pad(int number) { + return number < 10 ? ("0" + number) : String.valueOf(number); + } + + public static String stringifySeconds(double seconds) { + int h = (int) Math.floor(seconds / 3600); + int m = (int) Math.floor((seconds - (h * 3600)) / 60); + int s = (int) (seconds - (h * 3600) - (m * 60)); + + String str = ""; + + if (h < 1 && m < 1) { + str = "00:"; + } else { + if (h > 0) str = pad(h) + ":"; + if (m > 0) str += pad(m) + ":"; + } + + return str + pad(s); + } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 43b45d15e..86cbbb59a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -471,7 +471,6 @@ غير موجود فشلت المعالجة الاولية حذف التنزيلات المنتهية - "قم بإستكمال %s حيثما يتم التحويل من التنزيلات" توقف أقصى عدد للمحاولات الحد الأقصى لعدد محاولات قبل إلغاء التحميل diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 3c79a96d3..1cf3abd7e 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -458,7 +458,6 @@ Не знойдзена Пасляапрацоўка не ўдалася Ачысціць завершаныя - Аднавіць прыпыненыя загрузкі (%s) Спыніць Максімум спробаў Колькасць спробаў перад адменай загрузкі diff --git a/app/src/main/res/values-cmn/strings.xml b/app/src/main/res/values-cmn/strings.xml index bcb145c16..3ff479bfd 100644 --- a/app/src/main/res/values-cmn/strings.xml +++ b/app/src/main/res/values-cmn/strings.xml @@ -460,7 +460,6 @@ NewPipe 更新可用! 无法创建目标文件夹 服务器不接受多线程下载, 请使用 @string/msg_threads = 1重试 - 继续进行%s个待下载转移 切换至移动数据时有用,尽管一些下载无法被暂停 显示评论 禁用停止显示评论 diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index b741e0d16..9a9cc8654 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -466,7 +466,6 @@ otevření ve vyskakovacím okně Nenalezeno Post-processing selhal Vyčistit dokončená stahování - Pokračovat ve stahování %s souborů, čekajících na stažení Zastavit Maximální počet pokusů o opakování Maximální počet pokusů před zrušením stahování diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 199c2f85d..5e44aab61 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -447,7 +447,6 @@ sat på pause sat i kø Ryd færdige downloads - Fortsæt dine %s ventende overførsler fra Downloads Maksimalt antal genforsøg Maksimalt antal forsøg før downloaden opgives Sæt på pause ved skift til mobildata diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3279e919c..0dc0de8b4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -457,7 +457,6 @@ Nicht gefunden Nachbearbeitung fehlgeschlagen Um fertige Downloads bereinigen - Setze deine %s ausstehenden Übertragungen von Downloads fort Anhalten Maximale Wiederholungen Maximalanzahl der Versuche, bevor der Download abgebrochen wird diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 372cbb1a2..115b8d0b3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -459,7 +459,6 @@ Δεν βρέθηκε Μετεπεξεργασία απέτυχε Εκκαθάριση ολοκληρωμένων λήψεων - Συνέχιση των %s εκκρεμών σας λήψεων Διακοπή Μέγιστες επαναπροσπάθειες Μέγιστος αριθμός προσπαθειών προτού γίνει ακύρωση της λήψης diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b14aab94b..6fcbc9fa7 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -406,6 +406,7 @@ pausado en cola posprocesamiento + recuperando Añadir a cola Acción denegada por el sistema Se eliminó el archivo @@ -424,7 +425,6 @@ Mostrar como grilla Mostrar como lista Limpiar descargas finalizadas - Tienes %s descargas pendientes, ve a Descargas para continuarlas ¿Lo confirma\? Detener Intentos máximos diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 4dfcc3d0e..99dc6cc80 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -460,7 +460,6 @@ Ei leitud Järeltöötlemine nurjus Eemalda lõpetatud allalaadimised - Jätka %s pooleliolevat allalaadimist Stopp Korduskatseid Suurim katsete arv enne allalaadimise tühistamist diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7b636d383..743c6b3fb 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -459,7 +459,6 @@ Ez aurkitua Post-prozesuak huts egin du Garbitu amaitutako deskargak - Berrekin burutzeke dauden %s transferentzia deskargetatik Gelditu Gehienezko saiakerak Deskarga ezeztatu aurretik saiatu beharreko aldi kopurua diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b4388e39f..2091a62fe 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -466,7 +466,6 @@ Nombre maximum de tentatives avant d’annuler le téléchargement Utilisation des onglets par défaut, erreur lors de la lecture des onglets enregistrés Le serveur n’accepte pas les téléchargements multi-fils, veuillez réessayer avec @string/msg_threads = 1 - Continuer vos %s transferts en attente depuis Téléchargement Afficher les commentaires Désactiver pour ne pas afficher les commentaires Lecture automatique diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 5e340d8b3..565f815a1 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -464,7 +464,6 @@ לא נמצא העיבוד המאוחר נכשל פינוי ההורדות שהסתיימו - ניתן להמשיך את %s ההורדות הממתינות שלך דרך ההורדות עצירה מספר הניסיונות החוזרים המרבי מספר הניסיונות החוזרים המרבי בטרם ביטול ההורדה diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index aa4ff9113..a981dcf5e 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -457,7 +457,6 @@ Nije pronađeno Naknadna obrada nije uspjela Obriši završena preuzimanja - Nastavite s prijenosima na čekanju za %s s preuzimanja Stop Maksimalnih ponovnih pokušaja Maksimalni broj pokušaja prije poništavanja preuzimanja diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index d52f5fafa..5fbdcffc1 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -453,7 +453,6 @@ Tidak ditemukan Pengolahan-pasca gagal Hapus unduhan yang sudah selesai - Lanjutkan %s transfer anda yang tertunda dari Unduhan Berhenti Percobaan maksimum Jumlah upaya maksimum sebelum membatalkan unduhan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c92292f99..73633ab03 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -457,7 +457,6 @@ Non trovato Post-processing fallito Pulisci i download completati - Continua i %s trasferimenti in corso dai Download Ferma Tentativi massimi Tentativi massimi prima di cancellare il download diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 58ca2ebff..4c3aeb5c1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -456,7 +456,6 @@ デフォルトのタブを使用します。保存されたタブの読み込みエラーが発生しました メインページに表示されるタブ 新しいバージョンが利用可能なときにアプリの更新を確認する通知を表示します - ダウンロードから %s の保留中の転送を続行します 従量制課金ネットワークの割り込み モバイルデータ通信に切り替える場合に便利ですが、一部のダウンロードは一時停止できません コメントを表示 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index fdc76b04e..39b08347c 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -454,7 +454,6 @@ HTTP 찾을 수 없습니다 후처리 작업이 실패하였습니다 완료된 다운로드 비우기 - 대기중인 %s 다운로드를 지속하세요 멈추기 최대 재시도 횟수 다운로드를 취소하기 전까지 다시 시도할 최대 횟수 diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index daa120ea2..354e7b7de 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -453,7 +453,6 @@ Tidak ditemui Pemprosesan-pasca gagal Hapuskan senarai muat turun yang selesai - Teruskan %s pemindahan anda yang menunggu dari muat turun Berhenti Percubaan maksimum Jumlah percubaan maksimum sebelum membatalkan muat turun diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 6262480b0..e0a08d0a7 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -458,7 +458,6 @@ Ikke funnet Etterbehandling mislyktes Tøm fullførte nedlastinger - Fortsett dine %s ventende overføringer fra Nedlastinger Stopp Maksimalt antall forsøk Maksimalt antall tilkoblingsforsøk før nedlastingen avblåses diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml index f64ff6bf9..5c42bfd23 100644 --- a/app/src/main/res/values-nl-rBE/strings.xml +++ b/app/src/main/res/values-nl-rBE/strings.xml @@ -457,7 +457,6 @@ Niet gevonden Nabewerking mislukt Voltooide downloads wissen - Zet uw %s wachtende downloads verder via Downloads Stoppen Maximaal aantal pogingen Maximaal aantal pogingen vooraleer dat den download wordt geannuleerd diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 6aecc2cd1..b9b86a292 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -457,7 +457,6 @@ Niet gevonden Nabewerking mislukt Voltooide downloads wissen - Zet je %s wachtende downloads voort via Downloads Stop Maximum aantal keer proberen Maximum aantal pogingen voordat de download wordt geannuleerd diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index b57564eba..0e579720a 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -453,7 +453,6 @@ ਨਹੀਂ ਲਭਿਆ Post-processing ਫੇਲ੍ਹ ਮੁਕੰਮਲ ਹੋਈਆਂ ਡਾਊਨਲੋਡ ਸਾਫ਼ ਕਰੋ - ਡਾਉਨਲੋਡਸ ਤੋਂ ਆਪਣੀਆਂ %s ਬਕਾਇਆ ਟ੍ਰਾਂਸਫਰ ਜਾਰੀ ਰੱਖੋ ਰੁੱਕੋ ਵੱਧ ਤੋਂ ਵੱਧ ਕੋਸ਼ਿਸ਼ਾਂ ਡਾਉਨਲੋਡ ਰੱਦ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਵੱਧ ਤੋਂ ਵੱਧ ਕੋਸ਼ਿਸ਼ਾਂ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ca1e52ff2..b7086b34f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -459,7 +459,6 @@ Nie znaleziono Przetwarzanie końcowe nie powiodło się Wyczyść ukończone pobieranie - Kontynuuj %s oczekujące transfery z plików do pobrania Zatrzymaj Maksymalna liczba powtórzeń Maksymalna liczba prób przed anulowaniem pobierania diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0bdf4d006..5de1e6610 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -466,7 +466,6 @@ abrir em modo popup Não encontrado Falha no pós processamento Limpar downloads finalizados - Continuar seus %s downloads pendentes Parar Tentativas Máximas Número máximo de tentativas antes de cancelar o download diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 6d55023d1..88fbb72a6 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -455,7 +455,6 @@ Não encontrado Pós-processamento falhado Limpar transferências concluídas - Continue as suas %s transferências pendentes das Transferências Parar Tentativas máximas Número máximo de tentativas antes de cancelar a transferência diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 51771e1b1..80b587657 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -464,7 +464,6 @@ Загрузка завершена %s загрузок завершено Создать уникальное имя - Возобновить приостановленные загрузки (%s) Максимум попыток Количество попыток перед отменой загрузки Некоторые загрузки не поддерживают докачку и начнутся с начала diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 36c0afd84..cbc201fd5 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -465,7 +465,6 @@ Nenájdené Post-spracovanie zlyhalo Vyčistiť dokončené sťahovania - Pokračujte v preberaní %s zo súborov na prevzatie Stop Maximum opakovaní Maximálny počet pokusov pred zrušením stiahnutia diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 6c9c66f69..1cb6fafd4 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -452,7 +452,6 @@ Bulunamadı İşlem sonrası başarısız Tamamlanan indirmeleri temizle - Beklemedeki %s transferinize İndirmeler\'den devam edin Durdur Azami deneme sayısı İndirmeyi iptal etmeden önce maksimum deneme sayısı diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index fcce99e89..d43b8be66 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -471,7 +471,6 @@ Помилка зчитування збережених вкладок. Використовую типові вкладки. Вкладки, що відображаються на головній сторінці Показувати сповіщення з пропозицією оновити застосунок за наявності нової версії - Продовжити ваші %s відкладених переміщень із Завантажень Корисно під час переходу на мобільні дані, хоча деякі завантаження не можуть бути призупинені Показувати коментарі Вимнути відображення дописів diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index f8860acfd..ab0983e7a 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -452,7 +452,6 @@ Không tìm thấy Xử lý thất bại Dọn các tải về đã hoàn thành - Hãy tiếp tục %s tải về đang chờ Dừng Số lượt thử lại tối đa Số lượt thử lại trước khi hủy tải về diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 310bae3a3..98b9cf381 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -450,7 +450,6 @@ 找不到 後處理失敗 清除已結束的下載 - 繼續從您所擱置中的下載 %s 傳輸 停止 最大重試次數 在取消下載前的最大嘗試數 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f929e0d2b..c2d8d70f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -526,6 +526,7 @@ paused queued post-processing + recovering Queue Action denied by the system @@ -560,7 +561,6 @@ Cannot recover this download Clear finished downloads Are you sure? - Continue your %s pending transfers from Downloads Stop Maximum retries Maximum number of attempts before canceling the download From 3ca461413ed3ae200852d5a19fb0f7ed67e2ff86 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Sun, 24 Nov 2019 14:00:22 -0300 Subject: [PATCH 195/270] Merge branch 'dev' into dl-last-features --- .../us/shandian/giga/get/DownloadMission.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 5ef72162c..917a0a148 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -1,6 +1,9 @@ package us.shandian.giga.get; +import android.os.Build; import android.os.Handler; +import android.system.ErrnoException; +import android.system.OsConstants; import android.util.Log; import androidx.annotation.Nullable; @@ -35,9 +38,6 @@ public class DownloadMission extends Mission { static final int BUFFER_SIZE = 64 * 1024; static final int BLOCK_SIZE = 512 * 1024; - @SuppressWarnings("SpellCheckingInspection") - private static final String INSUFFICIENT_STORAGE = "ENOSPC"; - private static final String TAG = "DownloadMission"; public static final int ERROR_NOTHING = -1; @@ -315,13 +315,29 @@ public class DownloadMission extends Mission { public synchronized void notifyError(int code, Exception err) { Log.e(TAG, "notifyError() code = " + code, err); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (err.getCause() instanceof ErrnoException) { + int errno = ((ErrnoException) err.getCause()).errno; + if (errno == OsConstants.ENOSPC) { + code = ERROR_INSUFFICIENT_STORAGE; + err = null; + } else if (errno == OsConstants.EACCES) { + code = ERROR_PERMISSION_DENIED; + err = null; + } + } + } + if (err instanceof IOException) { - if (!storage.canWrite() || err.getMessage().contains("Permission denied")) { + if (err.getMessage().contains("Permission denied")) { code = ERROR_PERMISSION_DENIED; err = null; - } else if (err.getMessage().contains(INSUFFICIENT_STORAGE)) { + } else if (err.getMessage().contains("ENOSPC")) { code = ERROR_INSUFFICIENT_STORAGE; err = null; + } else if (!storage.canWrite()) { + code = ERROR_FILE_CREATION; + err = null; } } From 84ec320df4f314cde85847e9f7d1373eb34740dc Mon Sep 17 00:00:00 2001 From: kapodamy Date: Tue, 26 Nov 2019 13:41:16 -0300 Subject: [PATCH 196/270] commit * rebase fixup, add null check * better ETA string * drop connection read timeout, for HSDPA networks * bump NPE version --- app/build.gradle | 2 +- .../main/java/org/schabi/newpipe/download/DownloadDialog.java | 2 +- app/src/main/java/us/shandian/giga/get/DownloadMission.java | 3 +-- .../main/java/us/shandian/giga/ui/adapter/MissionAdapter.java | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a1afd63a2..7e4707f99 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.TeamNewPipe:NewPipeExtractor:5c420340ceb39' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:b6d3252' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 60b6192be..29208b0e0 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -780,7 +780,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck if (selectedStream.getFormat() == MediaFormat.M4A) { psName = Postprocessing.ALGORITHM_M4A_NO_DASH; - } else if (selectedStream.getFormat() == MediaFormat.OPUS) { + } else if (selectedStream.getFormat() == MediaFormat.WEBMA_OPUS) { psName = Postprocessing.ALGORITHM_OGG_FROM_WEBM_DEMUXER; } break; diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 917a0a148..c0f85b321 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -228,7 +228,6 @@ public class DownloadMission extends Mission { // BUG workaround: switching between networks can freeze the download forever conn.setConnectTimeout(30000); - conn.setReadTimeout(10000); if (rangeStart >= 0) { String req = "bytes=" + rangeStart + "-"; @@ -316,7 +315,7 @@ public class DownloadMission extends Mission { public synchronized void notifyError(int code, Exception err) { Log.e(TAG, "notifyError() code = " + code, err); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (err.getCause() instanceof ErrnoException) { + if (err != null && err.getCause() instanceof ErrnoException) { int errno = ((ErrnoException) err.getCause()).errno; if (errno == OsConstants.ENOSPC) { code = ERROR_INSUFFICIENT_STORAGE; diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index e3a7f112a..8420e343b 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -308,10 +308,10 @@ public class MissionAdapter extends Adapter implements Handler.Callb etaStr = ""; } else { long eta = (long) Math.ceil((length - done) / averageSpeed); - etaStr = " @ ".concat(Utility.stringifySeconds(eta)); + etaStr = Utility.formatBytes((long) done) + "/" + Utility.stringifySeconds(eta) + " "; } - h.size.setText(sizeStr.concat(speedStr).concat(etaStr)); + h.size.setText(sizeStr.concat(etaStr).concat(speedStr)); h.lastTimestamp = now; h.lastDone = done; From baee238a2cd80044dc49c0541ecb784565436dc4 Mon Sep 17 00:00:00 2001 From: Geoflly Adonias Date: Thu, 28 Nov 2019 11:43:53 +0000 Subject: [PATCH 197/270] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-pt-rBR/strings.xml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index aaac4fd4c..7b0de8630 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -205,8 +205,8 @@ abrir em modo popup Nenhum vídeo - %s vídeo - %s vídeos + %s Vídeo + %s Vídeos Item excluído Player @@ -214,7 +214,7 @@ abrir em modo popup Deseja apagar este item do seu histórico de pesquisas\? Conteúdo da página principal Página em branco - Página de banca + Página do Quiosque Página de inscrição Página de feed Página de canais @@ -516,4 +516,16 @@ abrir em modo popup Mude as pastas de download para surtir efeito Alterar serviço, selecionados: Quiosque Padrão + Ninguém está assistindo + + %s assistindo + %s estão assistindo + + Ninguém está ouvindo + + %s ouvinte + %s ouvintes + + O idioma será atualizado assim que o aplicativo for reiniciado. + Quiosque padrão \ No newline at end of file From e37a86efc2ad0ea329f2e1ffddd07e979660fc5e Mon Sep 17 00:00:00 2001 From: ssantos Date: Sun, 24 Nov 2019 07:31:33 +0000 Subject: [PATCH 198/270] Translated using Weblate (German) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9bc04ccad..23df02887 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -519,4 +519,5 @@ %s Zuhörer Die Sprache ändert sich, sobald die App neu gestartet wird. + Standard-Kiosk \ No newline at end of file From 87418562340b66b406aab535e19facb30ab2fce8 Mon Sep 17 00:00:00 2001 From: naofum Date: Sat, 23 Nov 2019 22:12:07 +0000 Subject: [PATCH 199/270] Translated using Weblate (Japanese) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-ja/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index cb83d7253..83cb09562 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -512,4 +512,5 @@ %s リスナー アプリを再起動すると、言語が変更されます。 + デフォルトのキオスク \ No newline at end of file From 2ad0792581d8182c346e55c6994b8ef02818b5a6 Mon Sep 17 00:00:00 2001 From: BennyBeat Date: Thu, 28 Nov 2019 08:11:59 +0000 Subject: [PATCH 200/270] Translated using Weblate (Catalan) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-ca/strings.xml | 235 ++++++++++++++----------- 1 file changed, 132 insertions(+), 103 deletions(-) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 33e3cd73a..33f5f89cb 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -7,8 +7,8 @@ Baixa Cerca Paràmetres - Tria un navegador - Subscriu-t\'hi + Trieu un navegador + Subscripció Subscrit Mostra la informació Subscripcions @@ -37,7 +37,7 @@ Depuració Contingut Desactiva les restriccions per edat - Mostra el vídeo restringit per edat. Pots permetre aquesta mena de continguts des dels paràmetres. + Mostra el vídeo restringit per edat. Podeu permetre aquesta mena de continguts des dels paràmetres. EN DIRECTE Baixades Baixades @@ -46,7 +46,7 @@ Llista de reproducció Desactivat - Esborra + Neteja Millor resolució Desfés Sempre @@ -80,27 +80,27 @@ Col·labora-hi Lloc web Llicència del NewPipe - Llegeix la llicència + Llegiu la llicència Historial L\'historial està desactivat Historial L\'historial és buit S\'ha esborrat l\'historial S\'ha eliminat l\'element - Vols eliminar aquest element de l\'historial de cerca\? - Vols eliminar aquest element de l\'historial de reproduccions\? - Segur que vols eliminar tots els elements de l\'historial\? + Voleu eliminar aquest element de l\'historial de cerca\? + Voleu eliminar aquest element de l\'historial de reproduccions\? + Segur que voleu eliminar tots els elements de l\'historial\? Contingut de la pàgina principal Pàgina en blanc Pàgina de subscripcions - Tria un canal + Trieu un canal S\'ha completat l\'exportació S\'ha completat la importació Elimina Detalls Paràmetres d\'àudio Reproductor de vídeo - Reproductor en segon pla + Reproductor en rerefons Reproductor emergent Demana-ho sempre Crea una llista de reproducció @@ -117,41 +117,41 @@ Per defecte %1$s reproduccions Publicat el %1$s - No s\'ha trobat un reproductor de fluxos. Vols instal·lar el VLC\? - No s\'ha trobat cap reproductor de fluxos (pots instal·lar el VLC per reproduir-lo). + No s\'ha trobat cap reproductor de fluxos. Voleu instal·lar el VLC\? + No s\'ha trobat cap reproductor de fluxos (podeu instal·lar el VLC per reproduir-ho). Obre en mode emergent Baixa el fitxer de vídeo - Volies dir: %1$s\? + Volíeu dir: %1$s\? Comparteix-ho amb rotació Reproductor de vídeo extern Mode emergent del NewPipe - Has eliminat la subscripció d\'aquest canal + Heu eliminat la subscripció a aquest canal No s\'ha pogut modificar la subscripció No s\'ha pogut actualitzar la subscripció Principal - Segon pla + Rerefons Emergent Afegeix a - Els fitxers de vídeo baixats s\'emmagatzemen aquí - Tria la carpeta de baixades per als fitxers de vídeo + Els fitxers de vídeo baixats es desen aquí + Trieu la carpeta de baixades per als fitxers de vídeo Els fitxers d\'àudio baixats es desen aquí - Tria la carpeta de baixada per als fitxers d\'àudio - Reprodueix un vídeo quan el NewPipe s\'executa des d\'una altra aplicació + Trieu la carpeta de baixada per als fitxers d\'àudio + Reprodueix un vídeo quan el NewPipe s\'executa des d\'altra aplicació Resolució per defecte del mode emergent Mostra resolucions superiors - Només alguns dispositius són compatibles amb la reproducció de vídeos en 2K/4K - Reprodueix amb Kodi - No s\'ha trobat l\'aplicació Kodi. Vols instal·lar-la\? - Activa «Reprodueix amb Kodi» + No tots els dispositius són compatibles amb la reproducció de vídeos en 2K/4K + Reprodueix amb el Kodi + No s\'ha trobat l\'aplicació Kodi. Voleu instal·lar-la\? + Mostra «Reprodueix amb el Kodi» Mostra una opció per reproduir un vídeo amb el centre multimèdia Kodi Reproductor emergent intel·ligent Recorda la darrera mida i posició del reproductor emergent Cerca ràpida poc precisa La cerca poc precisa permet que el reproductor cerqui una posició més ràpidament amb menys precisió Carrega les miniatures - S\'ha esborrat la memòria cau d\'imatges - Esborra les metadades de la memòria cau + S\'ha eliminat la memòria cau d\'imatges + Elimina les metadades de la memòria cau S\'ha esborrat la memòria cau de metadades Afegeix vídeos relacionats a la cua Control per gestos del reproductor @@ -165,15 +165,15 @@ País per defecte dels continguts Llengua per defecte dels continguts Emergent - S\'està reproduint en segon pla + S\'està reproduint en rerefons S\'està reproduint en mode emergent - Afegit a la cua del reproductor en segon pla - Afegit a la cua del reproductor emergent + S\'ha afegit a la cua del reproductor en rerefons + S\'ha afegit a la cua del reproductor emergent Reprodueix Notificació del NewPipe - Notificacions dels reproductors en segon pla o emergents del NewPipe + Notificacions dels reproductors en rerefons o emergents del NewPipe No s\'han pogut carregar totes les miniatures - No s\'ha pogut desencriptar la signatura de l\'URL del vídeo + No s\'ha pogut desxifrar la signatura de l\'URL del vídeo No s\'ha pogut processar el lloc web No s\'ha pogut processar del tot el lloc web Contingut no disponible @@ -190,38 +190,38 @@ No s\'ha trobat cap flux d\'àudio La carpeta no existeix El fitxer o la font de contingut no existeix - El fitxer no existeix o no teniu permisos per llegir-lo o escriure-hi + El fitxer no existeix o bé no teniu permisos de lectura/escriptura El nom del fitxer no pot estar en blanc S\'ha produït un error: %1$s - Informa de l\'error per correu electrònic + Informeu de l\'error per correu electrònic S\'han produït alguns errors. - INFORMA\'N + INFORME Informació: Què ha passat: Comentari (en anglès): Detalls: Miniatura de previsualització del vídeo - Miniatura de previsualització del vídeo + Reprodueix el vídeo, duració: Miniatura de l\'avatar del propietari M\'agrada No m\'agrada Fes servir el Tor (En proves) Força el trànsit de baixada a través del Tor per a més privadesa (no compatible encara amb les emissions de vídeo). - Informa sobre un error + Notifiqueu un error Informe de l\'usuari Cap resultat No hi ha res aquí No s\'ha pogut crear el directori de baixades «%1$s» S\'ha creat el directori de baixades «%1$s» - Torna a intentar-ho + Torna a provar S\'ha denegat el permís d\'accés a l\'emmagatzematge - Sense subscriptors - Sense reproduccions + Cap subscripció + Cap reproducció %s reproducció %s reproduccions - Sense vídeos + Cap vídeo Vídeo Vídeos @@ -236,10 +236,10 @@ Tanca Canvia el nom Fils - Servidor incompatible + Servidor no compatible El fitxer ja existeix Baixada del NewPipe activa - Espera… + Un moment… S\'ha copiat al porta-retalls Caràcters permesos als noms de fitxer Lletres i dígits @@ -247,43 +247,43 @@ © %1$s per %2$s sota %3$s Reprodueix transmissions de manera lliure i lleugera a l\'Android. Visualitza a GitHub - Fes una donació - Per a més informació i notícies, visita el nostre lloc web. + Feu una donació + Per a més informació i notícies, visiteu el nostre web. Últimes reproduccions Més reproduïts - Pàgina d\'un quiosc + Tendències Pàgina de novetats Pàgina d\'un canal - Tria un quiosc + Trieu un quiosc El fitxer no té un format ZIP vàlid Avís: No s\'han pogut importar tots els fitxers. Això sobreescriurà els paràmetres actuals. Quiosc Tendències Els millors 50 - Reproductor en segon pla + Reproductor en rerefons Reproductor emergent - Afegeix a la cua de reproducció en segon pla + Afegeix a la cua de reproducció en rerefons Afegeix a la cua de reproducció emergent Reprodueix aquí Obre el calaix Tanca el calaix S\'està obtenint la informació… S\'està carregant el contingut seleccionat - Vols eliminar aquesta llista de reproducció\? + Voleu eliminar aquesta llista de reproducció\? No s\'ha pogut eliminar la llista de reproducció. Importació i exportació Controls de la velocitat de reproducció Tempo To - Toca el botó de cerca per començar + Feu un toc al botó de cerca per començar Elimina l\'àudio en algunes resolucions Reproductor d\'àudio extern - Desactiva-ho per evitar que es carreguin les miniatures i estalviar dades i memòria. Si canvies aquesta opció, s\'esborrarà la memòria cau d\'imatges tant de la memòria com de l\'emmagatzematge. + Desactiveu-ho per no generar miniatures i estalviar dades i memòria. Canviant aquesta opció, s\'eliminarà la memòria cau d\'imatges tant de la memòria com de l\'emmagatzematge. Emmagatzema les cerques localment - Registra els vídeos visualitzats + Crea un historial de vídeos visualitzats Reprèn automàticament - Aquesta URL no és compatible + Aquest URL no és compatible Informe d\'error Més tard Filtra @@ -291,20 +291,20 @@ S\'està redimensionant Reprodueix-ho tot Canvia l\'orientació - Canvia al mode en segon pla + Canvia al mode en rerefons Canvia al mode emergent Canvia al mode principal Sobreescriu l\'historial i les subscripcions actuals S\'està recuperant el reproductor després de l\'error - Ho sentim, això no hauria d\'haver ocorregut. - Arrossega per a reordenar la llista + Bé, és lamentable. + Arrossegueu per reordenar la llista mil milions mil milions Inicia Nova missió - L\'URL té un format incorrecte o no hi ha connexió a internet - Toca aquí per a més detalls + L\'URL té un format no vàlid o no hi ha connexió a Internet + Feu un toc aquí per a més detalls Defineix una carpeta de baixades més endavant als paràmetres Es necessita aquest permís per a obrir el mode emergent Camp reCAPTCHA @@ -312,15 +312,15 @@ Se substituiran els caràcters no vàlids amb aquest valor Caràcter de substitució Principals caràcters especials - Ja siguin idees, traduccions, canvis en el disseny, una neteja del codi o canvis importants de programació, la teva ajuda sempre és benvinguda. Com més feina feta hi hagi, millor! - El NewPipe està desenvolupat per voluntaris que fan servir el seu temps lliure per a oferir-te la millor experiència possible. Fes una aportació per assegurar que els nostres desenvolupadors puguin millorar encara més el NewPipe mentre fan un cafè. - Fes la teva aportació + Idees, traduccions, canvis en el disseny, neteja del codi, canvis importants de programació... La vostra ajuda sempre és benvinguda. Com més feina feta hi hagi, millor! + El NewPipe està desenvolupat per voluntaris que fan servir el seu temps lliure per oferir-vos la millor experiència possible. Feu una aportació per assegurar que els nostres desenvolupadors puguin millorar encara més el NewPipe mentre fan un cafè. + Feu la vostra aportació Cerques Reproduccions - Encara no t\'has subscrit a cap canal + Encara no us heu subscrit a cap canal Novetats - Mantén premut per afegir a la cua - Comença a reproduir en segon pla + Manteniu premut per afegir a la cua + Comença a reproduir en rerefons Comença a reproduir en mode emergent Defineix com a miniatura de la llista de reproducció Afegeix la llista de reproducció a les adreces d\'interès @@ -346,45 +346,45 @@ Aviat hi haurà novetats aquí ;D Acció d\'obertura preferida Acció per defecte en obrir continguts — %s - "La supervisió de fugues de memòria pot fer que l\'aplicació deixi de respondre mentre es bolca la memòria " + La supervisió de fugues de memòria pot fer que l\'aplicació deixi de respondre mentre es bolca la memòria Informa d\'errors fora del cicle de vida Força l\'informe d\'excepcions Rx que no es puguin transmetre que tinguin lloc fora del cicle de vida d\'un fragment o activitat després de disposar-los - Importa les teves subscripcions de YouTube mitjançant el fitxer d\'exportació: + Importeu les vostres subscripcions de YouTube mitjançant el fitxer d\'exportació: \n -\n1. Vés a aquesta URL: %1$s -\n2. Inicia la sessió quan se\'t demani +\n1. Aneu a : %1$s +\n2. Inicieu la sessió quan si us demani \n3. S\'hauria d\'iniciar una baixada (el fitxer d\'exportació) - Importa un perfil de SoundCloud mitjançant l\'URL o l\'identificador del teu perfil: + Importeu un perfil del SoundCloud mitjançant l\'URL o l\'identificador del vostre perfil: \n -\n1. Activa el «Mode d\'ordinador» en un navegador (el lloc web no està disponible per a dispositius mòbils) -\n2. Vés a aquesta URL: %1$s -\n3. Inicia la sessió al teu compte quan se\'t demani -\n4. Copia l\'URL de la pàgina on se\'t redireccioni +\n1. Activeu el «Mode d\'ordinador» en un navegador (el lloc web no està disponible per a dispositius mòbils) +\n2. Aneu a: %1$s +\n3. Inicieu la sessió al vostre compte quan si us demani +\n4. Copieu l\'URL on si us ha redirigit. identificador, soundcloud.com/identificador - Tingues en compte que això pot comportar un ús intensiu de la xarxa. + Tingueu en compte que això pot comportar un ús intensiu de la xarxa. \n -\nVols continuar\? +\nVoleu continuar\? No hi ha vídeos que es puguin baixar Subtítols Modifica la mida del text i el fons dels subtítols. Cal reiniciar l\'aplicació per aplicar els canvis. No s\'ha trobat cap aplicació que pugui reproduir aquest fitxer - Esborra l\'historial de reproduccions - Esborra l\'historial dels vídeos reproduïts i les posicions de reproducció - Vols esborrar tot l\'historial de reproduccions\? - S\'ha esborrat l\'historial de reproduccions. - Esborra l\'historial de cerca - Esborra l\'historial de paraules cercades - Vols esborrar tot l\'historial de cerca\? - S\'ha esborrat l\'historial de cerca. + Neteja l\'historial de reproduccions + Neteja l\'historial dels vídeos reproduïts i les posicions de reproducció + Voleu suprimir tot l\'historial de reproduccions\? + S\'ha netejat l\'historial de reproduccions. + Neteja l\'historial de cerca + Neteja l\'historial de paraules cercades + Voleu suprimir tot l\'historial de cerca\? + S\'ha netejat l\'historial de cerca. S\'ha esborrat 1 element. - NewPipe és programari lliure sota llicència copyleft: pots fer-lo servir, estudiar-lo, compartir-lo i millorar-lo al teu gust. En concret, pots redistribuir-lo i/o modificar-lo d\'acord amb els termes de la llicència GNU GPL publicada per la Free Software Foundation, ja sigui la versió 3 o (segons vulguis) qualsevol altra versió posterior. - Vols importar també els paràmetres\? - Política de privacitat del NewPipe - El projecte NewPipe es pren molt seriosament la teva privacitat. Per aquesta raó, l\'aplicació no emmagatzema cap mena de dades sense el teu consentiment. -\nLa política de privacitat del NewPipe descriu detalladament quines dades s\'envien i s\'emmagatzemen quan envies un informe d\'error. - Llegeix la política de privacitat - Per tal de complir amb el Reglament General de Protecció de Dades europeu (GDPR), et demanem que posis atenció a la política de privacitat del NewPipe. Llegeix-la detingudament. -\nSi vols enviar-nos un informe d\'error, l\'hauràs d\'acceptar. + El NewPipe és programari lliure sota llicència copyleft: el podeu fer servir, estudiar, compartir i millorar com vulgueu. Concretament, el podeu redistribuir i/o modificar d\'acord amb els termes de la llicència GNU GPL publicada per la Free Software Foundation, versió 3 o qualsevol altra versió posterior. + Voleu importar també els paràmetres\? + Política de privadesa del NewPipe + El projecte NewPipe es pren molt seriosament la vostra privadesa. Per aquesta raó, l\'aplicació no emmagatzema cap dada sense el vostre consentiment. +\nLa política de privadesa del NewPipe descriu detalladament quines dades s\'envien i s\'emmagatzemen quan envieu un informe d\'error. + Llegiu la política de privadesa + Per complir amb el Reglament General de Protecció de Dades europeu (GDPR), us demanem que pareu atenció a la política de privadesa del NewPipe. Llegiu-la detingudament. +\nSi voleu informar d\'un error, l\'haureu d\'acceptar. Accepta Rebutja Sense restriccions @@ -396,13 +396,13 @@ Minimitza al reproductor emergent Avança ràpid durant el silenci Pas - Reinicialitza + Reinicia Canals Llistes de reproducció Pistes Usuaris Pestanya nova - Tria una pestanya + Trieu una pestanya Control de volum per gestos Fes servir gestos per controlar el volum del reproductor Control de brillantor per gestos @@ -410,15 +410,15 @@ Actualitzacions S\'ha eliminat el fitxer L\'emmagatzematge extern no està disponible - Reinicialitza els valors per defecte - Vols reinicialitzar els valors per defecte\? + Reinicia als valors per defecte + Voleu reiniciar als valors per defecte\? Selecció Actualitzacions Llista Quadrícula Automàtic Canvia la vista - Està disponible una nova actualització del NewPipe! + Està disponible una actualització del NewPipe! Pendent en pausa a la cua @@ -456,14 +456,14 @@ Sobreescriu No s\'ha trobat Mostra els comentaris - Desactiva-ho per deixar de mostrar els comentaris + Desactiveu-ho per no mostrar els comentaris Reproducció automàtica - No hi ha comentaris + Cap comentari No s\'han pogut carregar els comentaris Tanca - S\'estan utilitzant les pestanyes per defecte, s\'ha produït un error en llegir les pestanyes desades - Mostra una notificació per demanar l\'actualització de l\'aplicació si hi ha una nova versió disponible - Toca per baixar + S\'ha produït un error en llegir les pestanyes desades; s\'estan utilitzant les pestanyes per defecte + Mostra una notificació per demanar l\'actualització de l\'aplicació si hi ha una versió nova disponible + Toqueu per baixar El servidor no està enviant dades Comentaris @@ -480,13 +480,42 @@ Limita la cua de baixades Inicia les baixades Pausa les baixades - Se us demanarà la ubicació de cada baixada + Si us demanarà la ubicació de cada baixada Posicions a les llistes Mostra els indicadors de posició de reproducció a les llistes Neteja les dades El sistema ha denegat l\'acció - Reprèn les teves %s baixades pendents des de Baixades + Reprèn les %s baixades pendents des de Baixades S\'ha tancat el NewPipe mentre es treballava en el fitxer - Pregunta on baixar + Demana on baixar Canvia les carpetes de baixada perquè tingui efecte + No es pot desar a la targeta externa. Voleu restablir la carpeta de baixades\? + Permís denegat pel sistema + El servidor no accepta baixades simultànies. Proveu amb @string/msg_threads = 1 + No es pot satisfer el rang seleccionat + Restaura la darrera posició de la reproducció + S\'ha suprimit les posicions de reproducció. + El fitxer s\'ha mogut o suprimit + Només una baixada alhora + Si us demanarà la ubicació de cada baixada. +\nTrieu SAF si voleu desar el contingut en una memòria externa + Utilitza SAF + El SAF (Storage Access Framework; estructura d\'accés a l\'emmagatzematge) us permet realitzar baixades a una memòria externa com una targeta SD. +\nNota: No és compatible en tots els dispositius + Esborra les posicions de reproducció + Esborra totes les posicions de reproducció + Voleu suprimir tots els punts de reproducció\? + In/Habilita el servei; selecció actual: + Cap visualització + + %s visualització + %s visualitzacions + + Cap reproducció + + %s escoltant + %s escoltant + + Es canviarà la llengua en reiniciar l\'aplicació. + Tendències \ No newline at end of file From c4707978c4113a860b9134c16e8c3d369ab23ba0 Mon Sep 17 00:00:00 2001 From: zeritti Date: Sun, 24 Nov 2019 11:25:15 +0000 Subject: [PATCH 201/270] Translated using Weblate (Czech) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-cs/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index d539923fe..9fc230e70 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -515,4 +515,18 @@ otevření ve vyskakovacím okně Smazat všechny pozice playbacku\? Změnit adresář pro stažené soubory Přepnout službu, právě vybráno: + Nikdo nesleduje + + %s sleduje + %s sledují + %s sleduje + + Nikdo neposlouchá + + %s posluchač + %s posluchači + %s posluchačů + + Ke změně jazyka dojde po restartu aplikace. + Výchozí kiosek \ No newline at end of file From 27f38f329f1c5cb11f8b661e5f2c4574a334edfa Mon Sep 17 00:00:00 2001 From: WaldiS Date: Sun, 24 Nov 2019 18:29:31 +0000 Subject: [PATCH 202/270] Translated using Weblate (Polish) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-pl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index e7766e199..8be07a4e1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -525,4 +525,5 @@ %s słuchaczy Język zmieni się po ponownym uruchomieniu aplikacji. + Domyślny Kiosk \ No newline at end of file From 0df5d7a93442e7e895ba40417b006c644f5ba790 Mon Sep 17 00:00:00 2001 From: Stefano Panzeri Date: Tue, 26 Nov 2019 14:38:43 +0000 Subject: [PATCH 203/270] Translated using Weblate (Italian) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-it/strings.xml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 35fdebeda..999b35de0 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -102,7 +102,7 @@ Tocca per maggiori dettagli Attendi… Copiato negli appunti - Seleziona una cartella per i downloads + Seleziona dopo nelle impostazioni una cartella per i downloads Impossibile caricare l\'immagine L\'app/UI si è interrotta Cosa:\\nRichiesta:\\nLingua contenuto:\\nServizio:\\nOrario GMT:\\nPacchetto:\\nVersione:\\nVersione SO: @@ -111,9 +111,9 @@ Nero Tutto Canale - K + k M - B + Mrd È richiesta la risoluzione del reCAPTCHA Più tardi @@ -435,7 +435,7 @@ in pausa in coda post-processo - Coda + Accoda Azione negata dal sistema Download fallito Download finito @@ -507,4 +507,16 @@ Eliminare tutte le posizioni di riproduzione\? Cambia le cartelle di download per renderlo effettivo Attiva/disattiva il servizio, attualmente selezionato: + Nessuno sta guardando + + %s sta guardando + %s stanno guardando + + Nessuno sta ascoltando + + %s ascoltatore + %s ascoltatori + + La lingua verrà cambiata al riavvio dell\'applicazione. + Pagina predefinita \ No newline at end of file From da82e3f5d15343db14952478684ac325d48767f4 Mon Sep 17 00:00:00 2001 From: narayaan Date: Mon, 25 Nov 2019 14:44:26 +0000 Subject: [PATCH 204/270] Translated using Weblate (Dutch) Currently translated at 90.7% (461 of 508 strings) --- app/src/main/res/values-nl/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index dcd8ba182..a2ae2f1a6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -37,7 +37,7 @@ Gebruikersafbeelding van uploader Vind-ik-niet-leuks Downloadfolder voor audio - Gedownloade muziek is hier opgeslagen + Gedownloade muziek wordt hier opgeslagen Voer downloadlocatie voor audiobestanden in Thema Donker @@ -480,4 +480,6 @@ Het Storage Acces Framework laat downloads naar een externe SD kaart toe. \n \nNota: niet alle toestellen zijn compatibel + Wis data + Verander de downloadmappen om effect te bekomen \ No newline at end of file From 3bce9a8eada929c1a652549ed1b78ffc8e4da9d9 Mon Sep 17 00:00:00 2001 From: JoC Date: Wed, 27 Nov 2019 13:58:34 +0000 Subject: [PATCH 205/270] Translated using Weblate (Spanish) Currently translated at 99.6% (506 of 508 strings) --- app/src/main/res/values-es/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3aa0bac66..a58ce720b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -507,4 +507,15 @@ ¿Quiere eliminar todas las posiciones de reproducción\? Activar/desactivar servicio, seleccionados actualmente: kiosco predeterminado + Nadie está viendo + + %s viendo + %s viendo + + Nadie está escuchando + + %s escucha + %s escuchas + + El idioma cambiará luego de que la app sea reiniciada. \ No newline at end of file From 7dd7ea1a32392cec8c4d60f2cca7e04895e47cff Mon Sep 17 00:00:00 2001 From: Lucas Galello Date: Thu, 28 Nov 2019 18:45:31 +0000 Subject: [PATCH 206/270] Translated using Weblate (Spanish) Currently translated at 99.6% (506 of 508 strings) --- app/src/main/res/values-es/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a58ce720b..c3d3e16b0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -490,8 +490,8 @@ Desactívela para ocultar los comentarios Reproducción automática - Comentarios - + Comentario + Comentarios Sin comentarios No se pudo cargar comentarios From 6c0f5bef21c654a769f3d7356b322d6517f1769e Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 24 Nov 2019 15:20:32 +0000 Subject: [PATCH 207/270] Translated using Weblate (French) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-fr/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f10e57510..d40a6f284 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -205,8 +205,8 @@ Aucune vidéo - %s vidéo - %s vidéos + Vidéo + Vidéos Caractères spéciaux Élément supprimé @@ -519,4 +519,5 @@ %s auditeurs La langue changera lors du redémarrage de l\'application. + Kiosque par défaut \ No newline at end of file From 8039055a872e7f69d02fc0bfdadfaabb0a68e839 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 25 Nov 2019 08:33:18 +0000 Subject: [PATCH 208/270] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 125eb1828..1e28f7c1d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -501,18 +501,19 @@ 切換服務,目前已選取: 影片 - + 預設 Kiosk 沒有人在看 %s 個觀眾 - + 沒有人正在聽 %s 個聽眾 - + 語言將會在重新啟動應用程式後變更。 + 預設 Kiosk \ No newline at end of file From 77d3a1ef45dbb1597dabcada9a3c4dd56303d1a7 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Sat, 30 Nov 2019 06:17:35 +0000 Subject: [PATCH 209/270] Translated using Weblate (Arabic) Currently translated at 99.4% (505 of 508 strings) --- app/src/main/res/values-ar/strings.xml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 7156d08ba..75f688b56 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -20,7 +20,7 @@ يتم تخزين ملفات الفيديو التي تم تنزيلها هنا مجلد تحميل الفيديو "لا يمكن إنشاء مجلد للتنزيلات في '%1$s'" - إنشاء دليل التنزيل \'%1$s\' + دليل التنزيل الذي تم إنشاؤه \'%1$s\' تثبيت تطبيق Kore غير موجود. هل تريد تثبيته ؟ مضيء @@ -42,7 +42,7 @@ مشاركة مشاركة بواسطة عرض مقاطع الفيديو \"التالية\" و \"المشابهة\" - عرض خيار تشغيل الفيديو عبر مركز وسائط كودي + عرض خيارات تشغيل الفيديو من خلال مركز كودي ميديا عرض خيار التشغيل بواسطة كودي السمة تم النشر يوم %1$s @@ -200,7 +200,7 @@ فتح الموقع المساهمون التراخيص - مجاني خفيف الوزن بث حي على أندرويد. + تطبيق مجاني خفيف الوزن وبث حي على نظام أندرويد. ساهم إذا كانت لديك أفكار؛ أو ترجمة، أو تغييرات تخص التصميم، أو تنظيف و تحسين الشفرة البرمجية ، أو تعديلات عميقة عليها، فتذكر أنّ مساعدتك دائما موضع ترحيب. وكلما أتممنا شيئا كلما كان ذلك أفضل ! عرض على GitHub @@ -449,7 +449,7 @@ متوقف في قائمة الانتظار قيد المعالجة - قائمة الانتظار + قائمه انتظار تم رفضها من قبل النظام فشل التنزيل تم الانتهاء من التحميل @@ -523,4 +523,8 @@ تغيير مجلدات التنزيل إلى حيز التنفيذ‮‮‮ تبديل الخدمة ، المحدد حاليًا: الكشك الافتراضي + لاتوجد مشاهدة + لا أحد يستمع + ستتغير اللغة بمجرد إعادة تشغيل التطبيق. + الكشك الافتراضي \ No newline at end of file From 890d1cb50b5764659be870c5cbff2732666e828f Mon Sep 17 00:00:00 2001 From: yausername Date: Tue, 3 Dec 2019 01:50:23 +0530 Subject: [PATCH 210/270] update extractor, kiosk names and icons --- app/build.gradle | 2 +- .../java/org/schabi/newpipe/util/KioskTranslator.java | 8 ++++++++ app/src/main/res/values/strings.xml | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a128d8841..0abaf44f7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.yausername:NewPipeExtractor:6a7680c' + implementation 'com.github.yausername:NewPipeExtractor:00c2368' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java index 1f32383d0..18c95e394 100644 --- a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java +++ b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java @@ -31,6 +31,12 @@ public class KioskTranslator { return c.getString(R.string.top_50); case "New & hot": return c.getString(R.string.new_and_hot); + case "Local": + return c.getString(R.string.local); + case "Recently added": + return c.getString(R.string.recently_added); + case "Most liked": + return c.getString(R.string.most_liked); case "conferences": return c.getString(R.string.conferences); default: @@ -50,6 +56,8 @@ public class KioskTranslator { return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local); case "Recently added": return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent); + case "Most liked": + return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.thumbs_up); case "conferences": return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); default: diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c652b7f65..772421a53 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -405,6 +405,9 @@ Trending Top 50 New & hot + Local + Recently added + Most liked Conferences %1$s/%2$s From 17c5e73994e6f68418712d8d0785a649208da1fd Mon Sep 17 00:00:00 2001 From: yausername Date: Tue, 3 Dec 2019 02:04:52 +0530 Subject: [PATCH 211/270] null check on share --- .../newpipe/fragments/list/channel/ChannelFragment.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index ade96bdc4..c20ff0fc2 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -179,10 +179,14 @@ public class ChannelFragment extends BaseListInfoFragment { openRssFeed(); break; case R.id.menu_item_openInBrowser: - ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl()); + if (currentInfo != null) { + ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl()); + } break; case R.id.menu_item_share: - ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl()); + if (currentInfo != null) { + ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl()); + } break; default: return super.onOptionsItemSelected(item); From 0c40a45075dc2bb0811c5ba23dfcae8ed56c4f03 Mon Sep 17 00:00:00 2001 From: yausername Date: Tue, 3 Dec 2019 02:08:59 +0530 Subject: [PATCH 212/270] use plurals --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 772421a53..bcd4aedcc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,7 +111,7 @@ Default content language PeerTube instances Set your favorite peertube instances - Find the instance that best suits you on https://instances.joinpeertube.org + Find the instances that best suit you on https://instances.joinpeertube.org Add instance enter instance url Player From 9e290ce91af255e7138dec373a8705920dbf03a1 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sun, 1 Dec 2019 06:34:55 +0000 Subject: [PATCH 213/270] Translated using Weblate (Basque) Currently translated at 98.2% (499 of 508 strings) --- app/src/main/res/values-eu/strings.xml | 35 +++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7da39393e..0716d55dc 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -12,9 +12,9 @@ Partekatu honekin Nabigatzailea aukeratu biratzea - Bideoak deskargatzeko kokapena - Deskargatutako bideoak gordetzeko bide-izena - Zehaztu bideoak deskargatzeko kokapena + Bideoak deskargatzeko karpeta + Deskargatutako bideoak hemen gordeko dira + Hautatu bideoak deskargatzeko karpeta Lehenetsitako bereizmena Jo Kodirekin Ez da aurkitu Kore aplikazioa. Kore instalatu? @@ -30,7 +30,7 @@ Bideoa eta Audioa Erreproduzitu Bideoaren aurreikuspen argazkitxoa - Bideoaren aurreikuspen argazkitxoa + Erreproduzitu bideoa, iraupena: Igotzailearen abatarraren iruditxoa Ez dute gustoko Gustoko dute @@ -43,8 +43,8 @@ Atzeko planoan erreproduzitzen Ukitu bilaketa hasteko Audioa deskargatzeko karpeta - Zehaztu audioa deskargatzeko bide-izena - Deskargatutako audioa hemen gordetzen da + Hautatu audio fitxategiak deskargatzeko karpeta + Deskargatutako audio fitxategiak hemen gordetzen dira Erreprodukzio automatikoa Bideoa abiatzen du NewPipe beste aplikazio batek deitu badu Iluna @@ -116,7 +116,7 @@ Audioa Saiatu berriro Biltegia atzitzeko baimena ukatu da - K + k M MM Hasi @@ -199,8 +199,8 @@ Bideorik ez - %s Bideoa - %s Bideoak + Bideoa + Bideoak Historiala Bilatuta @@ -288,7 +288,7 @@ Zure uneko historiala eta harpidetzak gainidazten ditu Esportatu historiala, harpidetzak eta erreprodukzio-zerrendak Garbitu ikusitakoaren historiala - Jotako jarioen historiala ezabatzen du + Jotako jarioen historiala eta erreprodukzio puntuak ezabatzen ditu Ezabatu ikusitakoaren historia osoa\? Ikusitakoaren historiala ezabatuta. Garbitu bilaketa historiala @@ -497,6 +497,17 @@ Pausatu deskargak Galdetu non deskargatu Non gorde galdetuko zaizu deskarga bakoitzean - Non gorde galdetuko zaizu deskarga bakoitzean. -\nGaitu aukera hau kanpo SD txartelean gorde nahi baduzu + Non gorde galdetuko zaizu deskarga bakoitzean. +\nHautatu SAF kanpo SD txartelean gorde nahi baduzu + Aldatu deskargen karpetak indarrean jartzeko + Ez dago inor ikusten + + %s ikusten + %s ikusten + + Ez dago inor entzuten + + %s entzuten + %s entzuten + \ No newline at end of file From f587d79cd8b818e31a81f4c0752770aac4294751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sun, 1 Dec 2019 23:24:02 +0000 Subject: [PATCH 214/270] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 98.8% (502 of 508 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index c993c2a40..9265adadf 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -518,4 +518,5 @@ %s lyttere Språk vil ikke bli endret før programmet startes på ny. + Forvalgt kiosk \ No newline at end of file From d32ad36f3d13fe3212019fac0cc1f3e5b3c23473 Mon Sep 17 00:00:00 2001 From: yausername Date: Tue, 3 Dec 2019 07:16:25 +0530 Subject: [PATCH 215/270] reorder peertube settings entry --- app/src/main/res/xml/content_settings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index 0d579ba35..4044e92d8 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -12,13 +12,6 @@ android:summary="%s" android:title="@string/content_language_title"/> - - + + Date: Tue, 3 Dec 2019 07:22:14 +0530 Subject: [PATCH 216/270] updated extractor --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0abaf44f7..3b0b8fe6c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.yausername:NewPipeExtractor:00c2368' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:43b54cc' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' From d2a59ecc62bf3fe97b6515ce6701ff5761b73495 Mon Sep 17 00:00:00 2001 From: yausername Date: Thu, 5 Dec 2019 05:11:05 +0530 Subject: [PATCH 217/270] grammar fix --- .../schabi/newpipe/settings/PeertubeInstanceListFragment.java | 2 +- app/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index 097d96d20..cfb3fe8a4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -231,7 +231,7 @@ public class PeertubeInstanceListFragment extends Fragment { add(instance); }, e -> { progressBar.setVisibility(View.GONE); - Toast.makeText(getActivity(), "failed to validate instance", Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.peertube_instance_add_fail, Toast.LENGTH_SHORT).show(); }); disposables.add(disposable); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bcd4aedcc..ecffa0db2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -114,6 +114,7 @@ Find the instances that best suit you on https://instances.joinpeertube.org Add instance enter instance url + Failed to validate instance Player Behavior Video & audio From 0ccd30b12ef97c61570bf3960876757e71566ba8 Mon Sep 17 00:00:00 2001 From: Daniele Lira Mereb Date: Wed, 4 Dec 2019 04:35:11 +0000 Subject: [PATCH 218/270] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 7b0de8630..ec441aa10 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -288,7 +288,7 @@ abrir em modo popup Dispensar Renomear Deseja apagar este item do seu histórico de assistidos\? - Tem certeza que deseja apagar todos itens do histórico? + Tem certeza de que deseja apagar todos itens do histórico\? Reproduzido anteriormente Mais reproduzido Sempre perguntar From c15cead9e201306a29eb4d808858cb826994beb6 Mon Sep 17 00:00:00 2001 From: ozyc Date: Tue, 3 Dec 2019 15:10:06 +0000 Subject: [PATCH 219/270] Translated using Weblate (Esperanto) Currently translated at 100.0% (508 of 508 strings) --- app/src/main/res/values-eo/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 25fa34e06..8ff03d6b6 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -518,4 +518,5 @@ %s aŭskultantoj La lingvo ŝanĝos kiam la apo restartos. + Defaŭlta Kiosko \ No newline at end of file From aae8865bdd1a57379e748dd8ff4ed870ff3f7392 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Thu, 5 Dec 2019 14:04:48 -0300 Subject: [PATCH 220/270] remove unused imports --- .../java/org/schabi/newpipe/fragments/MainFragment.java | 9 --------- .../us/shandian/giga/get/DownloadMissionRecover.java | 5 ----- .../java/us/shandian/giga/get/MissionRecoveryInfo.java | 1 - 3 files changed, 15 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 70e0d9fb1..720e0f216 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -2,15 +2,6 @@ package org.schabi.newpipe.fragments; import android.content.Context; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.material.tabs.TabLayout; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java index eb660e564..14ac392a0 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -66,11 +66,6 @@ public class DownloadMissionRecover extends Thread { } private void tryRecover() throws ExtractionException, IOException, HttpError { - /*if (mMission.source.startsWith(MissionRecoveryInfo.DIRECT_SOURCE)) { - resolve(mMission.source.substring(MissionRecoveryInfo.DIRECT_SOURCE.length())); - return; - }*/ - if (mExtractor == null) { try { StreamingService svr = NewPipe.getServiceByUrl(mMission.source); diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java index f6a3a3984..e52f35cc6 100644 --- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java @@ -15,7 +15,6 @@ import java.io.Serializable; public class MissionRecoveryInfo implements Serializable, Parcelable { private static final long serialVersionUID = 0L; - //public static final String DIRECT_SOURCE = "direct-source://"; MediaFormat format; String desired; From c6cd2dd8543e03aa04e194f7596f70ea7e01afe1 Mon Sep 17 00:00:00 2001 From: dgarciabad Date: Thu, 5 Dec 2019 13:11:44 +0000 Subject: [PATCH 221/270] Translated using Weblate (Basque) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-eu/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 0716d55dc..7c2c5da03 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -510,4 +510,13 @@ %s entzuten %s entzuten + SAF erabili + Biltegian Sartzeko Armazoiak kanpoko SD txartel betera jaitsierak egitea ahalbidetzen du. +\nOharra: gailu batzuk ez dira bateragarriak + Ezabatu erreprodukziorako kokapenak + Erreprodukziorako kokapen guztiak ezabatzen ditu + Ezabatu erreprodukziorako kokapen guztiak\? + Aktibatu zerbitzua, orain hautatua: + Hizkuntza aldatuko da aplikazioa berrabiarazterakoan. + Kiosko Lehenetsia \ No newline at end of file From eb15bc97a733011e2f91b824a7fe136cebba9a52 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Thu, 5 Dec 2019 17:37:43 +0000 Subject: [PATCH 222/270] Translated using Weblate (Arabic) Currently translated at 99.4% (505 of 508 strings) --- app/src/main/res/values-ar/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 75f688b56..7d8f37ff7 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -234,7 +234,7 @@ التفاصيل الإعدادات الصوتية تشغيل هنا - تشغيل في وضع نافذة منبثقة + بدأ التشغيل في نافذة منبثقة جديدة تحدي الكابتشا ضغط مطول للإدراج الى قائمة الانتظار @@ -425,10 +425,10 @@ تتبيه تحديث التطبيق إيماءة التحكم بالصوت الأحداث - إخطارات NewPipe جديدة  الإصدار - ذاكرة التخزين الخارجية غير متوفرة + "تنبيه عند تواجد إصدار جديد newpipe " + وحدة التخزين الخارجية غير متاحة "التنزيل على بطاقة SD الخارجية غير ممكن. إعادة تعيين موقع مجلد التحميل؟" - استخدام خطأ علامات التبويب الافتراضية, أثناء قراءة علامات التبويب المحفوظة + باستخدام علامات التبويب الافتراضية ، خطأ أثناء قراءة علامات التبويب المحفوظة استعادة الضبط الافتراضي هل تريد استعادة الإعدادات الافتراضية؟ عدد المشتركين غير متاح From 3ff2da3b20a35be7a612187ac674b7abf6924f24 Mon Sep 17 00:00:00 2001 From: dgarciabad Date: Thu, 5 Dec 2019 13:16:59 +0000 Subject: [PATCH 223/270] Translated using Weblate (Spanish) Currently translated at 99.8% (507 of 508 strings) --- app/src/main/res/values-es/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c3d3e16b0..aaf1e276d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -518,4 +518,5 @@ %s escuchas El idioma cambiará luego de que la app sea reiniciada. + Kiosko por defecto \ No newline at end of file From 7d80d04f343263fcc2408aae7ea78e9b6035e06d Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Fri, 6 Dec 2019 08:32:45 -0700 Subject: [PATCH 224/270] Remove unused code pt1 --- .../newpipe/fragments/list/playlist/PlaylistFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 38ae88efa..21ff73d49 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -275,7 +275,6 @@ public class PlaylistFragment extends BaseListInfoFragment { }); } } else { // Else : hide the uploader section - //headerUploaderLayout.setVisibility(View.INVISIBLE); headerUploaderName.setText(R.string.playlist_no_uploader); } @@ -448,4 +447,4 @@ public class PlaylistFragment extends BaseListInfoFragment { playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr)); playlistBookmarkButton.setTitle(titleRes); } -} \ No newline at end of file +} From c05633979c7abffbae9f85c31be1f7520c2393d2 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Fri, 6 Dec 2019 08:35:14 -0700 Subject: [PATCH 225/270] Update app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java Co-Authored-By: Redirion --- .../newpipe/local/holder/RemotePlaylistItemHolder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index b30a6230d..e73d1000b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -31,7 +31,10 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { // Here is where the uploader name is set in the bookmarked playlists library itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), NewPipe.getNameOfService(item.getServiceId()))); - if (item.getUploader() == null) { + if (item.getUploader() != null) { + itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), + NewPipe.getNameOfService(item.getServiceId()))); + } else { itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId())); } From 693756bdd6b5cfead25c286d16b12b1fb3aa7835 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Fri, 6 Dec 2019 08:36:57 -0700 Subject: [PATCH 226/270] Removed redundant. Related to last merge --- .../schabi/newpipe/local/holder/RemotePlaylistItemHolder.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index e73d1000b..105eb6a68 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -29,8 +29,6 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { itemTitleView.setText(item.getName()); itemStreamCountView.setText(String.valueOf(item.getStreamCount())); // Here is where the uploader name is set in the bookmarked playlists library - itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), - NewPipe.getNameOfService(item.getServiceId()))); if (item.getUploader() != null) { itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), NewPipe.getNameOfService(item.getServiceId()))); From ae88b4c697e5bcb24f9dbe5095b10b61bacfd84b Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Fri, 6 Dec 2019 08:38:15 -0700 Subject: [PATCH 227/270] remove unused code pt2 --- .../newpipe/fragments/list/playlist/PlaylistFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 21ff73d49..6941741af 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -260,7 +260,6 @@ public class PlaylistFragment extends BaseListInfoFragment { animateView(headerUploaderLayout, true, 300); headerUploaderLayout.setOnClickListener(null); if (!TextUtils.isEmpty(result.getUploaderName())) { // If we have an uploader : Put them into the ui - //headerUploaderLayout.setVisibility(View.VISIBLE); headerUploaderName.setText(result.getUploaderName()); if (!TextUtils.isEmpty(result.getUploaderUrl())) { headerUploaderLayout.setOnClickListener(v -> { @@ -274,7 +273,7 @@ public class PlaylistFragment extends BaseListInfoFragment { } }); } - } else { // Else : hide the uploader section + } else { // Else : say we have no uploader headerUploaderName.setText(R.string.playlist_no_uploader); } From 5a2cd93d13d08915857712e7a693ec370d34c5e4 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Fri, 6 Dec 2019 16:30:07 -0300 Subject: [PATCH 228/270] remove netbeans editor-fold comments --- .../schabi/newpipe/streams/Mp4DashReader.java | 13 +++++-------- .../newpipe/streams/Mp4FromDashWriter.java | 16 ++++++++-------- .../newpipe/streams/OggFromWebMWriter.java | 9 +++------ .../org/schabi/newpipe/streams/WebMReader.java | 16 ++++++++-------- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java index c52ebf3aa..0cfd856e1 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java @@ -15,7 +15,6 @@ import java.util.NoSuchElementException; */ public class Mp4DashReader { - // private static final int ATOM_MOOF = 0x6D6F6F66; private static final int ATOM_MFHD = 0x6D666864; private static final int ATOM_TRAF = 0x74726166; @@ -50,7 +49,7 @@ public class Mp4DashReader { private static final int HANDLER_VIDE = 0x76696465; private static final int HANDLER_SOUN = 0x736F756E; private static final int HANDLER_SUBT = 0x73756274; - // + private final DataReader stream; @@ -293,7 +292,8 @@ public class Mp4DashReader { return null; } - // + + private long readUint() throws IOException { return stream.readInt() & 0xffffffffL; } @@ -392,9 +392,7 @@ public class Mp4DashReader { return readBox(); } - // - // private Moof parse_moof(Box ref, int trackId) throws IOException { Moof obj = new Moof(); @@ -795,9 +793,8 @@ public class Mp4DashReader { return readFullBox(b); } - // - // + class Box { int type; @@ -1013,5 +1010,5 @@ public class Mp4DashReader { public TrunEntry info; public byte[] data; } -// + } diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 420f77955..818f6148e 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -161,7 +161,7 @@ public class Mp4FromDashWriter { boolean singleChunk = tracks.length == 1 && tracks[0].kind == TrackKind.Audio; - // + for (int i = 0; i < readers.length; i++) { int samplesSize = 0; int sampleSizeChanges = 0; @@ -255,7 +255,7 @@ public class Mp4FromDashWriter { tracks[i].trak.tkhd.duration = sampleExtra[i];// this never should happen } } - // + boolean is64 = read > THRESHOLD_FOR_CO64; @@ -426,7 +426,7 @@ public class Mp4FromDashWriter { } } - // + private int writeEntry64(int offset, long value) throws IOException { outBackup(); @@ -469,9 +469,9 @@ public class Mp4FromDashWriter { lastWriteOffset = -1; } } - // - // + + private void outWrite(byte[] buffer) throws IOException { outWrite(buffer, buffer.length); } @@ -581,9 +581,9 @@ public class Mp4FromDashWriter { private int auxOffset() { return auxBuffer == null ? (int) writeOffset : auxBuffer.position(); } - // - // + + private int make_ftyp() throws IOException { byte[] buffer = new byte[]{ 0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70,// ftyp @@ -815,7 +815,7 @@ public class Mp4FromDashWriter { return buffer.array(); } - // + class TablesInfo { diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index 37bf9c6d7..20e88c4c7 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -308,7 +308,8 @@ public class OggFromWebMWriter implements Closeable { buffer.position(0); } - // + + @Nullable private SimpleBlock getNextBlock() throws IOException { SimpleBlock res; @@ -359,9 +360,7 @@ public class OggFromWebMWriter implements Closeable { return 0f; } - // - // private void clearSegmentTable() { segment_table_next_timestamp += TIME_SCALE_NS; packet_flag = FLAG_UNSET; @@ -407,9 +406,7 @@ public class OggFromWebMWriter implements Closeable { return true; } - // - // private void populate_crc32_table() { for (int i = 0; i < 0x100; i++) { int crc = i << 24; @@ -430,5 +427,5 @@ public class OggFromWebMWriter implements Closeable { return initial_crc; } - // + } diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java index 4cb96d901..42875c364 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java @@ -15,7 +15,6 @@ import java.util.NoSuchElementException; */ public class WebMReader { - // private final static int ID_EMBL = 0x0A45DFA3; private final static int ID_EMBLReadVersion = 0x02F7; private final static int ID_EMBLDocType = 0x0282; @@ -44,7 +43,7 @@ public class WebMReader { private final static int ID_SimpleBlock = 0x23; private final static int ID_Block = 0x21; private final static int ID_GroupBlock = 0x20; -// + public enum TrackKind { Audio/*2*/, Video/*1*/, Other @@ -110,7 +109,8 @@ public class WebMReader { return segment; } - // + + private long readNumber(Element parent) throws IOException { int length = (int) parent.contentSize; long value = 0; @@ -225,9 +225,9 @@ public class WebMReader { stream.skipBytes(skip); } -// - // + + private boolean readEbml(Element ref, int minReadVersion, int minDocTypeVersion) throws IOException { Element elem = untilElement(ref, ID_EMBLReadVersion); if (elem == null) { @@ -389,9 +389,9 @@ public class WebMReader { return obj; } -// - // + + class Element { int type; @@ -536,5 +536,5 @@ public class WebMReader { } } -// + } From 03939555ace804af32113f50d061ebc7889d82fe Mon Sep 17 00:00:00 2001 From: kapodamy Date: Sat, 7 Dec 2019 00:16:01 -0300 Subject: [PATCH 229/270] add missing change after updating NPE use +webm_opus instead of +opus --- .../java/org/schabi/newpipe/util/SecondaryStreamHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java index d2ebcd9f8..ab58bc917 100644 --- a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java @@ -52,10 +52,12 @@ public class SecondaryStreamHelper { } } + if (m4v) return null; + // retry, but this time in reverse order for (int i = audioStreams.size() - 1; i >= 0; i--) { AudioStream audio = audioStreams.get(i); - if (audio.getFormat() == (m4v ? MediaFormat.MP3 : MediaFormat.OPUS)) { + if (audio.getFormat() == MediaFormat.WEBMA_OPUS) { return audio; } } From a0151f2a68592ee53df8fd10db1a022eac034394 Mon Sep 17 00:00:00 2001 From: yausername Date: Tue, 10 Dec 2019 12:36:56 +0530 Subject: [PATCH 230/270] more grammar fix --- .../newpipe/settings/PeertubeInstanceListFragment.java | 9 ++++----- app/src/main/res/values/strings.xml | 6 ++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index cfb3fe8a4..d8c36e5cb 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -36,7 +36,6 @@ import com.grack.nanojson.JsonStringWriter; import com.grack.nanojson.JsonWriter; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.PeertubeHelper; @@ -219,7 +218,7 @@ public class PeertubeInstanceListFragment extends Fragment { } private void addInstance(String url) { - String cleanUrl = verifyUrl(url); + String cleanUrl = cleanUrl(url); if(null == cleanUrl) return; progressBar.setVisibility(View.VISIBLE); Disposable disposable = Single.fromCallable(() -> { @@ -237,7 +236,7 @@ public class PeertubeInstanceListFragment extends Fragment { } @Nullable - private String verifyUrl(String url){ + private String cleanUrl(String url){ // if protocol not present, add https if(!url.startsWith("http")){ url = "https://" + url; @@ -246,13 +245,13 @@ public class PeertubeInstanceListFragment extends Fragment { url = url.replaceAll("/$", ""); // only allow https if (!url.startsWith("https://")) { - Toast.makeText(getActivity(), "instance url should start with https://", Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.peertube_instance_add_https_only, Toast.LENGTH_SHORT).show(); return null; } // only allow if not already exists for (PeertubeInstance instance : instanceList) { if (instance.getUrl().equals(url)) { - Toast.makeText(getActivity(), "instance already exists", Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.peertube_instance_add_exists, Toast.LENGTH_SHORT).show(); return null; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18b11db02..5e47f875c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,10 +111,12 @@ Default content language PeerTube instances Set your favorite peertube instances - Find the instances that best suit you on https://instances.joinpeertube.org + Find the instances that best suit you on https://joinpeertube.org/instances#instances-list Add instance - enter instance url + Enter instance url Failed to validate instance + Only https urls are supported + Instance already exists Player Behavior Video & audio From 054279d5535ed4e086ca56531d5b458743f2ae26 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 10 Dec 2019 09:37:57 -0700 Subject: [PATCH 231/270] Update app/src/main/res/values/strings.xml Co-Authored-By: Stypox --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 23b8f9c36..a0e84974c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -444,7 +444,7 @@ Playlisted Playlist thumbnail changed. Could not delete playlist. - Auto-Generated (no uploader found) + Auto-generated (no uploader found) No Captions Fit From 7844547e4f44ad4a76703887b4df98132b94e717 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 10 Dec 2019 09:44:02 -0700 Subject: [PATCH 232/270] not used --- .../layout/playlist_header_no_uploader.xml | 94 ------------------- 1 file changed, 94 deletions(-) delete mode 100644 app/src/main/res/layout/playlist_header_no_uploader.xml diff --git a/app/src/main/res/layout/playlist_header_no_uploader.xml b/app/src/main/res/layout/playlist_header_no_uploader.xml deleted file mode 100644 index 0f692c690..000000000 --- a/app/src/main/res/layout/playlist_header_no_uploader.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 68a14073141829017b82ec73b17b9a1a2365e3e3 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 10 Dec 2019 09:48:16 -0700 Subject: [PATCH 233/270] Dont update this --- app/src/main/res/layout/playlist_header.xml | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml index 93fde3b8f..c599a53a5 100644 --- a/app/src/main/res/layout/playlist_header.xml +++ b/app/src/main/res/layout/playlist_header.xml @@ -33,6 +33,8 @@ android:layout_marginLeft="4dp" android:layout_marginRight="6dp" android:layout_marginTop="6dp" + android:layout_toLeftOf="@+id/playlist_stream_count" + android:layout_toStartOf="@+id/playlist_stream_count" android:background="?attr/selectableItemBackground" android:gravity="left|center_vertical" android:padding="2dp" @@ -63,24 +65,22 @@ tools:ignore="RtlHardcoded" tools:text="Typical uploader name"/> - - + + - \ No newline at end of file + From ea70a1f334cf90550d16e90575a59dd4dd4ac882 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 10 Dec 2019 09:50:33 -0700 Subject: [PATCH 234/270] no change --- app/src/main/res/layout/playlist_header.xml | 34 ++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml index 93fde3b8f..f49ca295d 100644 --- a/app/src/main/res/layout/playlist_header.xml +++ b/app/src/main/res/layout/playlist_header.xml @@ -33,6 +33,8 @@ android:layout_marginLeft="4dp" android:layout_marginRight="6dp" android:layout_marginTop="6dp" + android:layout_toLeftOf="@+id/playlist_stream_count" + android:layout_toStartOf="@+id/playlist_stream_count" android:background="?attr/selectableItemBackground" android:gravity="left|center_vertical" android:padding="2dp" @@ -63,24 +65,22 @@ tools:ignore="RtlHardcoded" tools:text="Typical uploader name"/> - - + + Date: Tue, 10 Dec 2019 09:50:53 -0700 Subject: [PATCH 235/270] Revert "Merge branch 'dev' of https://github.com/PeterHindes/NewPipe into dev" This reverts commit 23ee22566daa27b6021081a8916db349aa3ec43d, reversing changes made to ea70a1f334cf90550d16e90575a59dd4dd4ac882. --- app/src/main/res/layout/playlist_header.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml index c599a53a5..f49ca295d 100644 --- a/app/src/main/res/layout/playlist_header.xml +++ b/app/src/main/res/layout/playlist_header.xml @@ -90,4 +90,4 @@ - + \ No newline at end of file From 19fb8cfbfe705dec2315ab90c6fa8bcfcb2730b7 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 10 Dec 2019 12:13:04 -0700 Subject: [PATCH 236/270] Update app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java Co-Authored-By: Redirion --- .../schabi/newpipe/local/holder/RemotePlaylistItemHolder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index 105eb6a68..6dacee4d0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -29,7 +29,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { itemTitleView.setText(item.getName()); itemStreamCountView.setText(String.valueOf(item.getStreamCount())); // Here is where the uploader name is set in the bookmarked playlists library - if (item.getUploader() != null) { + if (!TextUtils.isEmpty(item.getUploader)) { itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), NewPipe.getNameOfService(item.getServiceId()))); } else { From b365973ac62007fa1c6d22ecfbc927cf00278490 Mon Sep 17 00:00:00 2001 From: Peter Hindes Date: Tue, 10 Dec 2019 12:18:49 -0700 Subject: [PATCH 237/270] fix last recomendation. syntax and imports --- .../schabi/newpipe/local/holder/RemotePlaylistItemHolder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index 6dacee4d0..8bb16c318 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -10,6 +10,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; +import android.text.TextUtils; + import java.text.DateFormat; public class RemotePlaylistItemHolder extends PlaylistItemHolder { @@ -29,7 +31,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { itemTitleView.setText(item.getName()); itemStreamCountView.setText(String.valueOf(item.getStreamCount())); // Here is where the uploader name is set in the bookmarked playlists library - if (!TextUtils.isEmpty(item.getUploader)) { + if (!TextUtils.isEmpty(item.getUploader())) { itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), NewPipe.getNameOfService(item.getServiceId()))); } else { From 59c19614ea36a5f7701ac4ae3cf778185944c985 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 10 Dec 2019 22:19:54 +0100 Subject: [PATCH 238/270] Add PeerTube to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d4952b2b8..f78725338 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc * YouTube * SoundCloud \[beta\] * media.ccc.de \[beta\] +* PeerTube instances \[beta\] ## Updates When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can: From 06af26f1f2b5c4f6da66060f74c6f0ae4c827f7c Mon Sep 17 00:00:00 2001 From: Marco vR Date: Tue, 27 Aug 2019 22:43:59 +0200 Subject: [PATCH 239/270] Black navigation bar for black theme See: https://github.com/TeamNewPipe/NewPipe/issues/1494 --- app/src/main/res/values-v27/styles.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 app/src/main/res/values-v27/styles.xml diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml new file mode 100644 index 000000000..50843937e --- /dev/null +++ b/app/src/main/res/values-v27/styles.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file From 7e78197b37ccd8e13566185e7cc92a06440f3e13 Mon Sep 17 00:00:00 2001 From: Marco vR Date: Tue, 15 Oct 2019 14:23:55 +0200 Subject: [PATCH 240/270] Avoid duplicated code & dark navbar for DarkTheme --- app/src/main/res/values-v27/styles.xml | 17 +++++++++-------- app/src/main/res/values/styles.xml | 8 ++++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml index 50843937e..09e827ecd 100644 --- a/app/src/main/res/values-v27/styles.xml +++ b/app/src/main/res/values-v27/styles.xml @@ -1,13 +1,14 @@ - - \ No newline at end of file + + - - - -