From 2339f51ad4707367a98c844840dd4f5f7295675f Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Fri, 14 Feb 2025 21:14:42 -0300 Subject: [PATCH 01/50] [#11930] Share as YouTube temporary playlist Initial commit. --- .../local/playlist/LocalPlaylistFragment.java | 100 +++++++++++++----- .../local/playlist/PlayListShareMode.java | 8 ++ .../playlist/LocalPlaylistFragmentTest.java | 55 ++++++++++ 3 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java create mode 100644 app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index c87d9cccc..3e99b01c4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -2,6 +2,9 @@ package org.schabi.newpipe.local.playlist; import static org.schabi.newpipe.error.ErrorUtil.showUiErrorSnackbar; import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS; +import static org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES; +import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout; import android.content.Context; @@ -64,12 +67,14 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import java.util.stream.Stream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; +import okhttp3.HttpUrl; public class LocalPlaylistFragment extends BaseLocalListFragment, Void> implements PlaylistControlViewHolder, DebounceSavable { @@ -385,34 +390,76 @@ public class LocalPlaylistFragment extends BaseLocalListFragment Single.just(playlist.stream() - .map(PlaylistStreamEntry::getStreamEntity) - .map(streamEntity -> { - if (shouldSharePlaylistDetails) { - return context.getString(R.string.video_details_list_item, - streamEntity.getTitle(), streamEntity.getUrl()); - } else { - return streamEntity.getUrl(); - } - }) - .collect(Collectors.joining("\n")))) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(urlsText -> ShareUtils.shareText( - context, name, shouldSharePlaylistDetails - ? context.getString(R.string.share_playlist_content_details, - name, urlsText) : urlsText), - throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable))); + .flatMapSingle(playlist -> Single.just(export( shareMode + , playlist.stream().map(PlaylistStreamEntry::getStreamEntity) + , context + ) + )) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( urlsText -> ShareUtils.shareText( context + , name + , shareMode == JUST_URLS ? urlsText + : context.getString(R.string.share_playlist_content_details, name, urlsText)) + , throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable)) + ); + } + + static String export(PlayListShareMode shareMode, Stream entityStream, Context context) { + + return switch(shareMode) { + + case WITH_TITLES -> exportWithTitles(entityStream, context); + case JUST_URLS -> exportJustUrls(entityStream); + case YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(entityStream); + }; + } + + static String exportWithTitles(Stream entityStream, Context context) { + + return entityStream + .map(entity -> context.getString(R.string.video_details_list_item, entity.getTitle(), entity.getUrl())) + .collect(Collectors.joining("\n")); + } + + static String exportJustUrls(Stream entityStream) { + + return entityStream + .map(StreamEntity::getUrl) + .collect(Collectors.joining("\n")); + } + + static String exportAsYoutubeTempPlaylist(Stream entityStream) { + + String videoIDs = entityStream + .map(entity -> getYouTubeId(entity.getUrl())) + .collect(Collectors.joining(",")); + + return "http://www.youtube.com/watch_videos?video_ids=" + videoIDs; + } + + /** + * Gets the video id from a YouTube URL + */ + static String getYouTubeId(String url) { + + HttpUrl httpUrl = HttpUrl.parse(url); + + return httpUrl == null ? null + : httpUrl.queryParameter("v") + ; } public void removeWatchedStreams(final boolean removePartiallyWatched) { @@ -875,10 +922,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment - sharePlaylist(/* shouldSharePlaylistDetails= */ true) + sharePlaylist(WITH_TITLES) + ) + .setNeutralButton("Share as YouTube temporary playlist", (dialog, which) -> // TODO R.string.share_playlist_as_YouTube_temporary_playlist + sharePlaylist(YOUTUBE_TEMP_PLAYLIST) ) .setNegativeButton(R.string.share_playlist_with_list, (dialog, which) -> - sharePlaylist(/* shouldSharePlaylistDetails= */ false) + sharePlaylist(JUST_URLS) ) .show(); } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java b/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java new file mode 100644 index 000000000..3de1effc9 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java @@ -0,0 +1,8 @@ +package org.schabi.newpipe.local.playlist; + +public enum PlayListShareMode { + + JUST_URLS + ,WITH_TITLES + ,YOUTUBE_TEMP_PLAYLIST +} diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java new file mode 100644 index 000000000..41e1f6091 --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java @@ -0,0 +1,55 @@ +package org.schabi.newpipe.local.playlist; + +import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; + +import androidx.annotation.NonNull; + +import org.junit.Assert; +import org.junit.Test; +import org.schabi.newpipe.database.stream.model.StreamEntity; +import org.schabi.newpipe.extractor.stream.StreamType; + +import java.util.List; +import java.util.stream.Stream; + +public class LocalPlaylistFragmentTest { + + @Test + public void youTubeTempPlaylist() { + + Stream entityStream = List.of( + + "https://www.youtube.com/watch?v=1" + ,"https://www.youtube.com/watch?v=2" + ,"https://www.youtube.com/watch?v=3" + ) + .stream() + .map(LocalPlaylistFragmentTest::newStreamEntity) + ; + + String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, entityStream, null); + + Assert.assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url); + } + + @NonNull + static StreamEntity newStreamEntity(String url) { + + return new StreamEntity( + + 0 + , 1 + , url + , "Title" + , StreamType.VIDEO_STREAM + , 100 + , "Uploader" + , null + , null + , null + , null + , null + , null + ); + } +} From 430b4eb916765fd5cdbf3398e2d15f636865785a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 15 Feb 2025 13:08:00 +0100 Subject: [PATCH 02/50] Translated using Weblate (Persian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 92.7% (686 of 740 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Georgian) Currently translated at 83.3% (70 of 84 strings) Translated using Weblate (Estonian) Currently translated at 16.6% (14 of 84 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Mainfränkisch) Currently translated at 1.0% (8 of 740 strings) Translated using Weblate (Bavarian) Currently translated at 3.9% (29 of 740 strings) Translated using Weblate (German) Currently translated at 100.0% (84 of 84 strings) Added translation using Weblate (Mainfränkisch) Translated using Weblate (Thai) Currently translated at 36.6% (271 of 740 strings) Translated using Weblate (Armenian) Currently translated at 28.2% (209 of 740 strings) Translated using Weblate (Georgian) Currently translated at 85.7% (72 of 84 strings) Translated using Weblate (Thai) Currently translated at 34.3% (254 of 740 strings) Translated using Weblate (Gujarati) Currently translated at 11.3% (84 of 740 strings) Translated using Weblate (Hungarian) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Slovak) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Nepali) Currently translated at 1.1% (1 of 84 strings) Translated using Weblate (Slovak) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (French) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Gujarati) Currently translated at 11.0% (82 of 740 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (740 of 740 strings) Co-authored-by: Alex25820 Co-authored-by: Bruno Fragoso Co-authored-by: Davit Mayilyan Co-authored-by: Emin Tufan Çetin Co-authored-by: Garfield2150 Co-authored-by: Ghost of Sparta Co-authored-by: Goudarz Jafari Co-authored-by: Hosted Weblate Co-authored-by: Kchenik Poudel Co-authored-by: Kuko Co-authored-by: Paul Sibila Co-authored-by: Priit Jõerüüt Co-authored-by: Rex_sa Co-authored-by: Temuri Doghonadze Co-authored-by: freddyLovesUs Co-authored-by: રાજ ભાતેલીઆ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/et/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ka/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ne/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/ Translation: NewPipe/Metadata --- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-bar/strings.xml | 4 +- app/src/main/res/values-et/strings.xml | 55 +++++------- app/src/main/res/values-fa/strings.xml | 2 + app/src/main/res/values-gu/strings.xml | 12 +-- app/src/main/res/values-hy/strings.xml | 8 +- app/src/main/res/values-pt/strings.xml | 4 +- app/src/main/res/values-th/strings.xml | 86 ++++++++++++++----- app/src/main/res/values-tr/strings.xml | 6 +- app/src/main/res/values-vmf/strings.xml | 10 +++ .../metadata/android/de/changelogs/1003.txt | 10 ++- .../metadata/android/et/changelogs/1003.txt | 10 ++- .../metadata/android/et/changelogs/998.txt | 4 + .../metadata/android/et/changelogs/999.txt | 12 +++ .../metadata/android/et/full_description.txt | 2 +- .../metadata/android/et/short_description.txt | 2 +- .../metadata/android/fr/changelogs/1002.txt | 5 +- .../metadata/android/fr/changelogs/1003.txt | 7 +- .../metadata/android/hu/changelogs/1002.txt | 4 +- .../metadata/android/hu/changelogs/1003.txt | 10 ++- .../metadata/android/ka/changelogs/1002.txt | 1 - .../metadata/android/ka/changelogs/1003.txt | 1 - .../metadata/android/ka/changelogs/65.txt | 26 ------ .../metadata/android/ka/changelogs/68.txt | 31 ------- .../metadata/android/ka/changelogs/69.txt | 19 ---- .../metadata/android/ne/short_description.txt | 1 + .../metadata/android/sk/changelogs/1000.txt | 2 +- .../metadata/android/sk/changelogs/1002.txt | 5 +- .../metadata/android/sk/full_description.txt | 2 +- .../metadata/android/sk/short_description.txt | 2 +- .../metadata/android/sv/changelogs/1003.txt | 10 ++- 31 files changed, 179 insertions(+), 176 deletions(-) create mode 100644 app/src/main/res/values-vmf/strings.xml create mode 100644 fastlane/metadata/android/et/changelogs/998.txt create mode 100644 fastlane/metadata/android/et/changelogs/999.txt delete mode 100644 fastlane/metadata/android/ka/changelogs/1002.txt delete mode 100644 fastlane/metadata/android/ka/changelogs/1003.txt delete mode 100644 fastlane/metadata/android/ka/changelogs/65.txt delete mode 100644 fastlane/metadata/android/ka/changelogs/68.txt delete mode 100644 fastlane/metadata/android/ka/changelogs/69.txt create mode 100644 fastlane/metadata/android/ne/short_description.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 5946b6b16..3b70c9d8c 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -18,7 +18,7 @@ اختر مجلد التنزيل لملفات الفيديو يتم تخزين ملفات الفيديو التي تم تنزيلها هنا مجلد تحميل الفيديو - ثبت + ثبيت تطبيق Kore غير موجود. هل تريد تثبيته؟ فاتح خطأ في الشبكة diff --git a/app/src/main/res/values-bar/strings.xml b/app/src/main/res/values-bar/strings.xml index 1949e1efa..63ae52c9a 100644 --- a/app/src/main/res/values-bar/strings.xml +++ b/app/src/main/res/values-bar/strings.xml @@ -26,4 +26,6 @@ Duad bei manchen Auflösungen d\'Tonspur weggad Im Pop-up Modus aufmacha Drug auf\'d Lubn zum ofanga. - \ No newline at end of file + Bassd scho + naa + diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index a002c9a0d..cb1034b14 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -99,8 +99,8 @@ Alati Üks kord Fail - NewPipe teavitus - Teavitused NewPipe pleierile + NewPipe\'i teavitus + NewPipe\'i meediaesitaja teavitused [Tundmatu] Lülita taustale Lülita hüpikpleierile @@ -183,7 +183,7 @@ Failinimi Lõimed Viga - NewPipe allalaadimine + NewPipe\'i on allalaadimisel Üksikasjade nägemiseks toksa Palun oota… Kopeeriti lõikepuhvrisse @@ -197,18 +197,18 @@ Asendustähemärk Tähed ja numbrid Erimärgid - NewPipe rakendusest + Rakenduse teave: NewPipe Kolmanda osapoole litsentsid Rakenduse teave ja KKK Litsentsid Panusta Vaata GitHubis Anneta - Veebileht - Enama info saamiseks külasta NewPipe veebilehte. - NewPipe privaatsuspoliitika + Veebisait + Täiendava info ja uudiste lugemiseks külasta NewPipe\'i veebisaiti. + NewPipe\'i privaatsuspoliitika Loe privaatsuspoliitikat - NewPipe litsents + NewPipe\'i litsents Loe litsentsi Ajalugu Ajalugu @@ -303,11 +303,10 @@ © %1$s %2$s %3$s alla Vaba ja lihtne voogesitus Androidis. Kui sul on ideid kujunduse muutmisest, koodi puhastamisest või suurtest koodi muudatustest - abi on alati teretulnud. Mida rohkem tehtud, seda paremaks läheb! - NewPipe arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutamise kogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi. + NewPipe\'i arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutuskogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi. Anneta - NewPipe võtab privaatsust väga tõsiselt. Seetõttu ei kogu rakendus ilma nõusolekuta mingeid andmeid. -\nNewPipe privaatsuspoliitika selgitab üksikasjalikult, milliseid andmeid saadetakse ja kogutakse veateate saatmisel. - NewPipe vaba avatud koodiga tarkvara. Seada võid kasutada, uurida, jagada ja parandada nii, nagu õigemaks pead. Täpsemalt - seda võid levitada ja/või muuta vastavalt Vaba Tarkvara Sihtasutuse avaldatud GNU Üldise Avaliku Litsentsi v.3 (või sinu valikul hilisema versiooni) tingimustele. + NewPipe võtab privaatsust väga tõsiselt. Seetõttu ei kogu rakendus ilma nõusolekuta mingeid andmeid. \nNewPipe\'i privaatsuspoliitika selgitab üksikasjalikult, milliseid andmeid saadetakse ja kogutakse veateate saatmisel. + NewPipe on vaba ja avatud lähtekoodiga tarkvara. Seada võid kasutada, uurida, jagada ja parandada nii, nagu õigemaks pead. Täpsemalt - seda võid levitada ja/või muuta vastavalt Vaba Tarkvara Sihtasutuse avaldatud GNU Üldise Avaliku Litsentsi v.3 (või sinu valikul hilisema versiooni) tingimustele. Teavita elutsüklist väljas vigadest Impordi SoundCloudi profiil trükkides URL või oma ID: \n @@ -321,8 +320,7 @@ Keri helitu koht edasi Samm Lähtesta - Selleks, et täita Euroopa Üldist Andmekaitse Määrust (GDPR), juhime tähelepanu NewPipe\'i privaatsuspoliitikale. Palun lugege seda hoolikalt. -\nMeile veateate saatmiseks pead sellega nõustuma. + Selleks, et täita Euroopa Üldist Andmekaitse Määrust (GDPR), juhime tähelepanu NewPipe\'i privaatsuspoliitikale. Palun loe seda hoolikalt. \nMeile veateate saatmiseks pead sellega nõustuma. Minimeeri, kui kasutad teisi rakendusi Tegevus lülitusel peamiselt videopleierilt teisele rakendusele — %s Pole @@ -335,7 +333,7 @@ Sündmused Fail kustutati Rakenduse värskenduse teavitus - Teavitus NewPipe uuetest versioonidest + Teavitus NewPipe\'i uuetest versioonidest Väline andmekandja pole saadaval Allalaadimine välisele SD-kaardile ei ole võimalik. Kas lähtestada allalaadimiste kataloogi asukoht\? Tõrge salvestatud vahekaaride lugemisel; kasutatakse vaikeväärtusi @@ -350,7 +348,7 @@ Nimekiri Võrgustik Auto - NewPipe värskendus on saadaval! + NewPipe\'i värskendus on saadaval! Lõpetatud Ootel peatatud @@ -556,7 +554,7 @@ Luba korraga vaid üks allalaadimine Piira allalaadimiste järjekorda Faili kustutamisega läks ka tööjärg kautsi - Faili töötlemisel NewPipe lõpetas töö + NewPipe lõpetas faili töötlemisel töö Lülita meedia tunneldamine välja juhul, kui esitamisel tekib must ekraan või pildi kuvamine on katkendlik. Lülita meedia tunneldamine välja Vaheta teenust, hetkel on kasutusel: @@ -609,9 +607,7 @@ Luba kiire režiim Hangi võimalusel spetsiaalsest voost Kiirvoo režiim ei paku selle kohta täiendavat teavet. - Autori konto on lõpetatud. -\nTulevikus ei saa NewPipe seda voogu laadida. -\nKas soovid tühistada selle kanali tellimuse\? + Autori konto on suletud. \nTulevikus ei saa NewPipe seda meediavoogu laadida. \nKas soovid tühistada selle kanali tellimuse? Voo \'%s\' laadimine nurjus. Via voo laadimisel Värskenda alati @@ -622,17 +618,7 @@ Android 10st alates on toetatud ainult salvestusjuurdepääsu raamistik \'Storage Access Framework\' Sinult küsitakse iga kord, kuhu alla laadimine salvestada Südamlik autor - Kas sinu meelest on voo laadimine aeglane\? Sel juhul proovi lubada kiire laadimine (seda saad muuta seadetes või vajutades allolevat nuppu). -\n -\nNewPipe pakub kahte voo laadimise strateegiat: -\n• Tellitud kanali täielik, kuid aeglane hankimine. -\n• Teenuse spetsiaalse lõpp-punkti kasutamine, mis on kiire, kuid tavaliselt mittetäielik. -\n -\nErinevus nende kahe vahel seisneb selles, et kiirel puudub tavaliselt teave, näiteks üksuse pikkus või tüüp (ei saa eristada reaalajas videoid tavalistest) ja see võib tagastada vähem üksusi. -\n -\nYouTube on näide teenusest, mis pakub seda kiirmeetodit oma RSS-vooga. -\n -\nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave. + Kas sinu meelest on voo laadimine aeglane? Sel juhul proovi lubada kiire laadimine (seda saad muuta seadetes või vajutades allolevat nuppu). \n \nNewPipe pakub kahte voo laadimise strateegiat: \n• Tellitud kanali täielik, kuid aeglane hankimine. \n• Teenuse spetsiaalse otspunkti kasutamine, mis on kiire, kuid tavaliselt mittetäielik. \n \nErinevus nende kahe vahel seisneb selles, et kiirel puudub tavaliselt teave, näiteks üksuse pikkus või tüüp (ei saa eristada reaalajas videoid tavalistest) ja see võib tagastada vähem üksusi. \n \nYouTube on näide teenusest, mis pakub seda kiirmeetodit oma RSS-vooga. \n \nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave. Märgi vaadatuks Näita piltide kohal Picasso värvides riba, mis märgib pildi allikat: punane tähistab võrku, sinine kohalikku andmekandjat ja roheline kohalikku mälu Näita piltide allikat @@ -658,7 +644,7 @@ Uued andmevoo kirjed Näita „Jooksuta meediamängija kokku“ nupukest\\ Näitab valikut meediamängija kokkujooksutamiseks - NewPipe töös tekkis viga, sellest teavitamiseks toksa + NewPipe\'i töös tekkis viga, sellest teavitamiseks toksa Jooksuta meediamängija kokku Näita veateate akent Teavitus vigadest @@ -814,8 +800,7 @@ Näita vähem Muuda iga teavituse tegevust sellel toksates. Kolm esimest tegevust (esita/peata esitus, eelmine video, järgmine video) on süsteemsed ja neid ei saa muuta. Varundus ja taastamine - NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada. -\nKas sa soovid sellist võimalust kasuutada? + NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada. \nKas sa soovid sellist võimalust kasutada? Lähtesta seadistused Lähtesta kõik seadistused nende vaikimisi väärtusteks Seadmes pole enam piisavalt vaba ruumi @@ -824,6 +809,6 @@ \nKas sa soovid jätkata? Jah Ei - Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam luua ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning edaspidi loo ekspordifailid NewPipe versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada. + Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam kasutada ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning eelista ekspordifaile, mis on loodud NewPipe\'i versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe\'i uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada. täiendav diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 4bba29c0e..b5e5de31c 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -764,4 +764,6 @@ مدّت پسروی ؟ + پشتیبان‌گیری و بازیابی + بدون جریان زنده diff --git a/app/src/main/res/values-gu/strings.xml b/app/src/main/res/values-gu/strings.xml index cdeac0b82..637fc9bf8 100644 --- a/app/src/main/res/values-gu/strings.xml +++ b/app/src/main/res/values-gu/strings.xml @@ -23,7 +23,7 @@ પ્રથમ ક્રિયા બટન સૂચનામાં બતાવેલ વિડિઓ થંબનેલને ૧૬:૯ થી ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો થંબનેલને ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો - કોડી મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો + કોડિ મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો અનુપસ્થિત Kore અનુપ્રયોગ સ્થાપિત કરીએ? ફક્ત થોડા ઉપકરણો 2K / 4K વિડિઓઝ ચલાવી શકે છે ઉચ્ચ રીઝોલ્યુશન બતાવો @@ -40,7 +40,7 @@ પૃષ્ઠભૂમિ ટેબ પસંદ કરો બુકમાર્ક કરેલ પ્લેલિસ્ટ્સ - સબ્સ્ક્રિપ્શન્સ + લવાજમઓ માહિતી બતાવો સબ્સ્ક્રિપ્શન અપડેટ કરી શકાયું નથી સબ્સ્ક્રિપ્શન બદલી શકાયું નહીં @@ -72,13 +72,15 @@ ઠીક છે હા ના - વલણમાં છે + વલણમાંનાં આપોઆપ કતારબદ્ધતા પ્લેયરને ક્રેશ કરો ઇતિહાસ કોટિથી ચલાવો - કોટિથી ચલાવવાનો વિકલ્પ દેખાટો + કોડિથી ચલાવવાનો વિકલ્પ દેખાડો ડાઉનલોડ કરો આપમેળે ચલાવો નવું શું છે - \ No newline at end of file + ડાઉનલોડ્સ + ડાઉનલોડ્સ + diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index 365bfe9ea..3be44ba73 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -1,6 +1,6 @@ - Սեղմեք որոնման կոճակը որ սկսել + Սեղմեք խոշորացույցը որ սկսեք Որոնել Բեռնված Բեռնված @@ -228,7 +228,7 @@ Դասավորել Գամված մեկնաբանություն Հաշիվը կասեցված է - + Ալբոմներ Այո Ոչ @@ -241,4 +241,6 @@ Ալիքներ Ուղիղ Անհայտ - \ No newline at end of file + Նկատի ունե՞ս «%1$s» + Բարձրություն + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index bd98dc7d8..b5ebcbd2e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -281,7 +281,7 @@ Limpar histórico de visualizações Continuar (sem repetição) a fila de reprodução anexando um vídeo relacionado Mostrar dica \"Toque longo para colocar na fila\" - Mostrar dica ao premir em segundo plano ou no botão \"Detalhes\" da janela popup + Mostrar dica ao premir em segundo plano ou no botão \"Detalhes\" da janela popup:\\ Canais Listas de reprodução Faixas @@ -528,7 +528,7 @@ Remover visualizados Os textos originais dos serviços serão visíveis nos itens do vídeo Mostrar antiguidade nos itens - Ativar \"Modo restrito\" do YouTube + Ativar \"Modo restrito\\ do YouTube Por %s Criado por %s Miniatura do avatar do canal diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 1816fa212..bcbbca0a4 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -1,6 +1,6 @@ - แตะที่ปุ่ม \"ค้นหา\" เพื่อเริ่มต้น + แตะที่สัญลักษณ์แว่นขยายเพื่อเริ่มต้น เผยแพร่เมื่อ %1$s ไม่พบแอปที่สามารถสตรีมสื่อวีดีโอได้ คุณต้องการติดตั้ง VLC หรือไม่\? ไม่พบแอปที่สามารถสตรีมสื่อวีดีโอได้ (คุณสามารถติดตั้ง VLC เพื่อดูวีดีโอ) @@ -12,33 +12,33 @@ ดาวน์โหลด ดาวน์โหลดไฟล์สตรีม ค้นหา - ตั้งค่า + การตั้งค่า หรือคุณหมายถึง \"%1$s\"\? แชร์ด้วย ใช้เครื่องเล่นวีดิโอภายนอก - ใช้แอปเล่นเสียงภายนอก + ใช้เครื่องเล่นเสียงภายนอก ติดตาม ติดตามแล้ว ยกเลิกการติดตาม ยกเลิกการติดตามช่องแล้ว ไม่สามารถเปลี่ยนสถานะการติดตามได้ - ไม่สามารถอัปเดตการติดตาม + ไม่สามารถอัพเดทการติดตาม แสดงข้อมูล การติดตาม เพลย์ลิสต์ที่เก็บไว้ เลือกแท็บ มีอะไรใหม่ - พื้นหลัง - ป๊อปอัพ + เล่นในพื้นหลัง + พ็อปอัพ เพิ่มไปยัง - เส้นทางการดาวน์โหลดวิดีโอ - เส้นทางในการจัดเก็บวิดีโอที่ดาวน์โหลดมา - เลือกเส้นทางการดาวน์โหลดสำหรับไฟล์วิดีโอ - โฟลเดอร์ที่ดาวน์โหลดเสียง + โฟลเดอร์ดาวน์โหลดของวิดีโอ + ไฟล์วิดีโอที่ดาวน์โหลดไว้จะถูกเก็บที่นี่ + เลือกโฟลเดอร์ดาวน์โหลดสำหรับไฟล์วิดีโอ + โฟลเดอร์ดาวน์โหลดของเสียง ไฟล์เสียงที่ดาวน์โหลดไว้จะถูกเก็บไว้ที่นี่ - เลือกเส้นทางการดาวน์โหลดสำหรับไฟล์เสียง + เลือกโฟลเดอร์ดาวน์โหลดสำหรับไฟล์เสียง ความละเอียดเริ่มต้น - ความละเอียดเริ่มต้นในโหมดป๊อปอัพ + ความละเอียดเริ่มต้นในโหมดพ็อปอัพ แสดงความละเอียดที่สูงขึ้น เฉพาะบางอุปกรณ์ที่รองรับการเล่นวิดีโอ 2K/4K เปิดด้วย Kodi @@ -349,7 +349,7 @@ หยุดชั่วคราวเมื่อเปลี่ยนเป็นข้อมูลมือถือ การดาวน์โหลดที่ไม่สามารถหยุดพักได้จะเริ่มต้นใหม่ ปิด - บางความละเอียดอาจไม่มีเสียง + ลบเสียงสำหรับบางความละเอียดหน้าจอ แคช metadate ถูกลบแล้ว เล่นต่อหลังจากการขัดจังหวะ เล่นต่อ @@ -361,13 +361,55 @@ เปิดด้วย ทำเครื่องหมายว่าดูแล้ว ตกลง - ปุ่มการกระทำที่สี่ - ปุ่มการกระทำแรก - ปุ่มการกระทำที่สาม - ปุ่มการกระทำที่ห้า - แก้ไขการกระทำของการแต่การแจ้งเตือนด้วยการแตะไปที่มัน เลือกสามรายการที่จะแสดงในการแจ้งเตือนในการแจ้งเตือนแบบกระทัดรัดโดยใช้ปุ่มกาเครื่องหมายทางขวา - ครอบตัดตัวอย่างภาพเป็นอัตราส่วน 1:1 - ครอบตัดตัวอย่างภาพที่แสดงในการแจ้งเตือนจากอัตราส่วน 16:9 เป็น 1:1 - ทำเครื่องเล่นพัง - ปุ่มการกระทำรอง + ปุ่มคำสั่งที่สี่ + ปุ่มคำสั่งแรก + ปุ่มคำสั่งที่สาม + ปุ่มคำสั่งที่ห้า + แก้ไขคำสั่งของการแต่การแจ้งเตือนด้วยการแตะไปที่มัน เลือกสามรายการที่จะแสดงในการแจ้งเตือนในการแจ้งเตือนแบบกระทัดรัดโดยใช้ปุ่มกาเครื่องหมายทางขวา + ตัดหน้าปกวิดีโอเป็นอัตราส่วน 1:1 + ตัดหน้าปกวิดีโอที่แสดงในการแจ้งเตือนจากอัตราส่วน 16:9 เป็น 1:1 + เครื่องเล่นวิดีโอแครช + ปุ่มคำสั่งที่สอง + โหมดกลางคืน + เลือกเสียงต้นฉบับโดยไม่คำนึงถึงภาษาที่ใช้ + ปิดเพื่อซ่อนคำอธิบายของวิดีโอและข้อมูลเพิ่มเติมอื่นๆ + ชอบเสียงต้นฉบับมากกว่า + ชอบเสียงแบบบรรยายมากกว่า + คำสั่งสัมผัสฝั่งซ้าย + ไม่มี + ใส่ที่อยู่ URL ของอินสแตนซ์ + มีอินสแตนซ์นี้อยู่แล้ว + สลับ + เปลี่ยนสีการแจ้งเตือน + เปลี่ยนจากเครื่องเล่นหนึ่งไปอีกเครื่องเล่นหนึ่งอาจแทนที่คิวของคุณ + มีประโยชน์อย่างมากเมื่อใส่หูฟังที่ปุ่มกดพัง + เลือกเสียงแบบบรรยายสำหรับผู้มีความบกพร่องทางการมองเห็น ถ้าเกิดมีตัวเลือกนี้ + แนะนำการค้นหาที่อยู่ในท้องที่ + เลือกคำสั่งสัมผัสสำหรับฝั่งซ้ายของหน้าจอเครื่องเล่น + การแจ้งเตือนจากเครื่องเล่น + วนซ้ำ + เร่งความเร็วไปข้างหน้า/ย้อนกลับหาช่วงเวลา + เริ่มเครื่องเล่นหลักแบบเต็มหน้าจอ + อย่าเริ่มวิดีโอในเครื่องเล่นเล็ก แต่เปลี่ยนเป็นเต็มหน้าจอโดยตรง ถ้าการหมุนหน้าจออัตโนมัติล็อคไว้ คุณยังสามารถเข้าถึงเครื่องเล่นเล็กโดยการออกจากโหมดเต็มหน้าจอ + ไม่สามารถระบุที่อยู่ URL ได้ ลองเปิดด้วยแอปอื่น + ถามก่อนการเคลียร์คิว + คิวในเครื่องเล่นที่ใช้งานอยู่จะถูกแทนที่ + แสดงคำอธิบาย + แสดง meta info + ล้าง cached metadata + เข้าคิวอัตโนมัติ + เลือกคำสั่งสัมผัสฝั่งขวาของหน้าจอเครื่องเล่น + คำสั่งสัมผัสฝั่งขวา + ความสว่าง + ระดับเสียง + แนะนำการค้นหาที่อยู่ไกลขึ้น + PeerTube อินสแตนซ์ + เลือก PeerTube อินสแตนซ์โปรดของคุณ + หาอินสแตนซ์ที่คุณชอบอยู่ใน %s + เพิ่มอินสแตนซ์ + ไม่สามารถตรวจสอบอินสแตนซ์ได้ + รับรองเฉพาะที่อยู่ URL แบบ HTTPS + ไม่มี + ไม่ + ใช่/ตกลง diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a87dac75e..1e29f67a5 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -157,7 +157,7 @@ Geçmiş Bu ögeyi arama geçmişinden silmek istiyor musunuz\? \"Basılı tutarak kuyruğa ekle\" ipucunu göster - Video \"Ayrıntılar:\" sayfasında arka plan veya açılır pencere düğmesine basıldığında ipucu göster + Şurada arka plan ya da açılır pencere düğmesine basıldığında ipucu göster: Video \"Ayrıntılar:\\ Tümünü Oynat [Bilinmeyen] Bu akış oynatılamadı @@ -517,7 +517,7 @@ İzleneni kaldır Akış ögelerinde hizmetlerden alınan özgün metinler görünecektir Ögelerde özgün \'… önce\'yi göster - YouTube\'un \"Kısıtlı Kip\"ini aç + Aç: YouTube \"Kısıtlı Kip\\ %s tarafından %s tarafından oluşturuldu Kanalın avatar küçük resmi @@ -658,7 +658,7 @@ Hata raporlama bildirimi NewPipe hatayla karşılaştı, bildirmek için dokun Hata oluştu, bildirime bakın - \"Oynatıcıyı çöktür\"ü göster + Göster: \"Oynatıcıyı çöktür\\ Hata bildirimi oluştur Hata balonu göster Bu eyleme uygun dosya yönetici yok. diff --git a/app/src/main/res/values-vmf/strings.xml b/app/src/main/res/values-vmf/strings.xml new file mode 100644 index 000000000..b1cb4e774 --- /dev/null +++ b/app/src/main/res/values-vmf/strings.xml @@ -0,0 +1,10 @@ + + + im brüscher öffn + passd scho + passd scho + stoarnieren + iser + net + tealn + diff --git a/fastlane/metadata/android/de/changelogs/1003.txt b/fastlane/metadata/android/de/changelogs/1003.txt index 6c6dc761a..76f798a72 100644 --- a/fastlane/metadata/android/de/changelogs/1003.txt +++ b/fastlane/metadata/android/de/changelogs/1003.txt @@ -1,4 +1,6 @@ -Behoben: YouTube spielt keinen Stream ab. - -Diese Version behebt nur den dringendsten Fehler, der das Laden von YouTube-Videodetails verhindert. -Wir sind uns bewusst, dass es andere Probleme gibt, und wir werden bald eine separate Version erstellen, um sie zu lösen. +Dies ist eine Hotfix-Version, die YouTube-Fehler behebt: +• [Youtube] Behebung, dass keine Videoinformationen geladen werden, Behebung von HTTP 403-Fehlern beim Abspielen von Videos und Wiederherstellung der Wiedergabe einiger altersbeschränkter Videos +• Die Größe der Untertitel wird nicht mehr geändert +• Behebung des doppelten Herunterladens von Informationen beim Öffnen eines Streams +• [Soundcloud] Entfernen von nicht abspielbaren DRM-geschützten Streams +• Aktualisierte Übersetzungen diff --git a/fastlane/metadata/android/et/changelogs/1003.txt b/fastlane/metadata/android/et/changelogs/1003.txt index c4c747f5b..53c967eea 100644 --- a/fastlane/metadata/android/et/changelogs/1003.txt +++ b/fastlane/metadata/android/et/changelogs/1003.txt @@ -1,4 +1,6 @@ -Parandasime vea, kus ühtegi YouTube'i meediavoogu ei õnnestunud esitada. - -See versioon parandab vaid hetkel kõige olulisema vea, kus YouTube'i video andmeid ei õnnestunud laadida. -Me oleme teadlikud ka muudest vigadest ning nendega tegeleme hiljem. +See on kiirparandus, mis teeb korda need YouTube'i vead: +• [YouTube] Parandasime vea, kus ühtegi meediavoogu ei õnnestunud esitada, parandasime HTTP 403-tüüpi vead videote esitamisel ja taastasime mõnede vanusepiirangutega videote esitamise +• Parandasime vea, kus subtiitrite suurus ei muutunud +• Parandasime vea, kus meediavoo avamisel laadisime tema teabe kaks korda alla +• [Soundcloud] eemaldasime mitteesitatavad DRM-kaitsega meediavood +• Uuendasime tõlkeid diff --git a/fastlane/metadata/android/et/changelogs/998.txt b/fastlane/metadata/android/et/changelogs/998.txt new file mode 100644 index 000000000..a1bb7399c --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/998.txt @@ -0,0 +1,4 @@ +Parandasime vea, kus ühtegi YouTube'i meediavoogu ei õnnestunud esitada ja HTTP olekuteade oli 403. + +Juhuslikud HTTP 403 veas esituse keskel pole veel parandatud. +Me oleme teadlikud ka sellest veast ja kiirparandus lisandub niipea, kui võimalik. diff --git a/fastlane/metadata/android/et/changelogs/999.txt b/fastlane/metadata/android/et/changelogs/999.txt new file mode 100644 index 000000000..3f3aa24e5 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/999.txt @@ -0,0 +1,12 @@ +See kiirparandus teeb korda vea, kus YouTube'i video esituse keskel esitus katkeb HTTP olekuteatega 403. + +Uus +• [SoundCloud] Lisandus on.soundcloud.com võrguaadresside tugi + +Täiendused +• [Bandcamp] Näitame lisateavet raadiokioski vaates + +Parandused +• [YouTube] Parandasime juhuslikud HTTP 403 vead videoesituse keskel +• [YouTube] Tuvastame tunnuspildi ja päisepildi enamatest kanalipäisete tüüpidest +• [Bandcamp] Mitmed veaparandused ja nüüdsest alati kasutame HTTPSi diff --git a/fastlane/metadata/android/et/full_description.txt b/fastlane/metadata/android/et/full_description.txt index 1a8cb9cac..e5192d070 100644 --- a/fastlane/metadata/android/et/full_description.txt +++ b/fastlane/metadata/android/et/full_description.txt @@ -1 +1 @@ -NewPipe ei kasuta Google-i raamistiku teeke ega YouTube APIt. See ainult sõelub veebilehelt vajamineva info. Seega saab rakendust kasutada ka seadmes, millesse ei ole paigaldatud Google teenuseid (Google Services). Lisaks ei ole NewPipe kasutamisel vaja YouTube'i kontot ja ta on FLOSS. +NewPipe ei kasuta Google'i raamistiku teeke ega YouTube'i APIt. Ta ainult sõelub veebilehelt vajamineva info. Seega saab rakendust kasutada ka seadmes, millesse ei ole paigaldatud Google'i teenuseid (Google Services). Lisaks ei ole NewPipe'i kasutamisel vaja YouTube'i kontot ja tegemist on avatud lähtekoodil põhineva tasuta ja vaba tarkvaraga. diff --git a/fastlane/metadata/android/et/short_description.txt b/fastlane/metadata/android/et/short_description.txt index 5d0d820e2..ee606a574 100644 --- a/fastlane/metadata/android/et/short_description.txt +++ b/fastlane/metadata/android/et/short_description.txt @@ -1 +1 @@ -Tasuta ja lihtne rakendus YouTube vaatamiseks. +Tasuta ja lihtne rakendus YouTube'i vaatamiseks. diff --git a/fastlane/metadata/android/fr/changelogs/1002.txt b/fastlane/metadata/android/fr/changelogs/1002.txt index 3ad3bf279..1b6846169 100644 --- a/fastlane/metadata/android/fr/changelogs/1002.txt +++ b/fastlane/metadata/android/fr/changelogs/1002.txt @@ -1 +1,4 @@ -Correction de YouTube qui ne lisait aucun média +Correction d'un problème empêchant YouTube de lire les vidéos en streaming. + +Cette version résout uniquement l'erreur la plus urgente qui empêche le chargement des détails des vidéos YouTube. +Nous sommes conscients qu'il existe d'autres problèmes et nous publierons bientôt une version séparée pour les résoudre. diff --git a/fastlane/metadata/android/fr/changelogs/1003.txt b/fastlane/metadata/android/fr/changelogs/1003.txt index 3ad3bf279..6b969c81a 100644 --- a/fastlane/metadata/android/fr/changelogs/1003.txt +++ b/fastlane/metadata/android/fr/changelogs/1003.txt @@ -1 +1,6 @@ -Correction de YouTube qui ne lisait aucun média +Il s'agit d'une version de correction qui résout les erreurs de YouTube : +• [YouTube] Correction du non-chargement des informations des vidéos, correction des erreurs HTTP 403 lors de la lecture des vidéos et restauration de la lecture de certaines vidéos restreintes par l'âge +• Correction des tailles de sous-titres qui ne changent pas +• Correction du téléchargement des informations deux fois lors de l'ouverture d'un flux +• [Soundcloud] Suppression des flux protégés par DRM non lisibles +• Traductions mises à jour diff --git a/fastlane/metadata/android/hu/changelogs/1002.txt b/fastlane/metadata/android/hu/changelogs/1002.txt index 5eebb2174..87454df24 100644 --- a/fastlane/metadata/android/hu/changelogs/1002.txt +++ b/fastlane/metadata/android/hu/changelogs/1002.txt @@ -1,4 +1,4 @@ -Javítva: a YouTube-ról nem játszik le semmilyen streamet. +Javítva a YouTube-ról nem játszik le semmilyen streamet. -Ez a kiadás csak a legsürgősebb hibát kezeli, ami megakadályozza a YouTube-videó részleteinek betöltését. +Ez a kiadás csak a legsürgősebb hibát kezeli, ami megakadályozza a YouTube videó részleteinek betöltését. Tisztában vagyunk azzal, hogy vannak más problémák is, és hamarosan külön kiadást készítünk ezek megoldására. diff --git a/fastlane/metadata/android/hu/changelogs/1003.txt b/fastlane/metadata/android/hu/changelogs/1003.txt index 5eebb2174..b33f5725b 100644 --- a/fastlane/metadata/android/hu/changelogs/1003.txt +++ b/fastlane/metadata/android/hu/changelogs/1003.txt @@ -1,4 +1,6 @@ -Javítva: a YouTube-ról nem játszik le semmilyen streamet. - -Ez a kiadás csak a legsürgősebb hibát kezeli, ami megakadályozza a YouTube-videó részleteinek betöltését. -Tisztában vagyunk azzal, hogy vannak más problémák is, és hamarosan külön kiadást készítünk ezek megoldására. +Ez egy azonnali kiadás, amely a YouTube hibáit javítja: +• [YouTube] A videóinformációk betöltésének elmaradása, a videók lejátszása közben fellépő HTTP 403 hibák javítása és néhány korhatáros videó lejátszásának visszaállítása +• A feliratméret váltásának javítása +• Az információ kétszeri letöltésének javítása a stream megnyitásakor +• [Soundcloud] A lejátszhatatlan DRM-védett streamek eltávolítása +• Frissített fordítások diff --git a/fastlane/metadata/android/ka/changelogs/1002.txt b/fastlane/metadata/android/ka/changelogs/1002.txt deleted file mode 100644 index d20512f17..000000000 --- a/fastlane/metadata/android/ka/changelogs/1002.txt +++ /dev/null @@ -1 +0,0 @@ -გაასწორა YouTube არ უკრავს არცერთ ნაკადს diff --git a/fastlane/metadata/android/ka/changelogs/1003.txt b/fastlane/metadata/android/ka/changelogs/1003.txt deleted file mode 100644 index d20512f17..000000000 --- a/fastlane/metadata/android/ka/changelogs/1003.txt +++ /dev/null @@ -1 +0,0 @@ -გაასწორა YouTube არ უკრავს არცერთ ნაკადს diff --git a/fastlane/metadata/android/ka/changelogs/65.txt b/fastlane/metadata/android/ka/changelogs/65.txt deleted file mode 100644 index e2ca8059d..000000000 --- a/fastlane/metadata/android/ka/changelogs/65.txt +++ /dev/null @@ -1,26 +0,0 @@ -### გაუმჯობესებები - - - გამორთეთ ბურგერმენუს ხატის ანიმაცია #1486 - - ჩამოტვირთვების წაშლის გაუქმება #1472 - - ჩამოტვირთვის ვარიანტი გაზიარების მენიუში #1498 - - დამატებულია გაზიარების ვარიანტი გრძელი შეხების მენიუში #1454 - - მთავარი მოთამაშის მინიმიზაცია #1354 გასასვლელზე - - ბიბლიოთეკის ვერსიის განახლება და მონაცემთა ბაზის სარეზერვო დაფიქსირება #1510 - - ExoPlayer 2.8.2 განახლება #1392 - - გადამუშავდა დაკვრის სიჩქარის კონტროლის დიალოგი, რათა მხარი დაუჭიროს სხვადასხვა ნაბიჯების ზომას უფრო სწრაფი სიჩქარის ცვლილებისთვის. - - დამატებულია გადართვა სწრაფი წინსვლისთვის დუმილის დროს დაკვრის სიჩქარის კონტროლში. ეს გამოსადეგი უნდა იყოს აუდიო წიგნებისთვის და გარკვეული მუსიკის ჟანრებისთვის და შეუძლია ნამდვილი უწყვეტი გამოცდილების მოტანა (და შეიძლება დაარღვიოს სიმღერა მრავალი დუმილით =\\). - - რეფაქტორირებული მედია წყაროს გარჩევადობა, რათა მეტამონაცემების გადაცემა მედიასთან ერთად შიგადაშიგ პლეერში, ვიდრე ხელით. ახლა ჩვენ გვაქვს მეტამონაცემების ერთი წყარო და პირდაპირ ხელმისაწვდომია დაკვრის დაწყებისას. - - დაფიქსირდა დისტანციური დასაკრავი სიის მეტამონაცემები არ განახლდება, როდესაც ახალი მეტამონაცემები ხელმისაწვდომია დასაკრავი სიის ფრაგმენტის გახსნისას. - - სხვადასხვა ინტერფეისის შესწორებები: #1383, ფონური მოთამაშის შეტყობინებების კონტროლი ახლა ყოველთვის თეთრია, უფრო ადვილია ამომხტარი მოთამაშის გამორთვა ფლანგით - - გამოიყენეთ ახალი ექსტრაქტორი რეფაქტორირებული არქიტექტურით მულტისერვისისთვის - - ### ასწორებს - - - დააფიქსირეთ #1440 გატეხილი ვიდეო ინფორმაციის განლაგება #1491 - - ნახეთ ისტორიის შესწორება #1497 - - #1495, მეტამონაცემების (მინიატურების, სათაურის და ვიდეოების რაოდენობა) განახლებით, როგორც კი მომხმარებელი წვდება დასაკრავ სიას. - - #1475, მონაცემთა ბაზაში ხედის დარეგისტრირებით, როდესაც მომხმარებელი იწყებს ვიდეოს გარე პლეერზე დეტალურ ფრაგმენტზე. - - დააფიქსირეთ ეკრანის დროის ამოწურვა ამომხტარი რეჟიმის შემთხვევაში. #1463 (დასწორებულია #640) - - მთავარი ვიდეო პლეერის დაფიქსირება #1509 - - [#1412] დაფიქსირდა გამეორების რეჟიმი, რომელიც იწვევს მოთამაშის NPE-ს, როდესაც მიიღება ახალი განზრახვა, როდესაც მოთამაშის აქტივობა ფონზეა. - - დაფიქსირებული მინიმიზაცია მოთამაშის ამომხტარ ფანჯარაში არ ანადგურებს მოთამაშეს, როდესაც ამომხტარი ნებართვა არ არის მინიჭებული. diff --git a/fastlane/metadata/android/ka/changelogs/68.txt b/fastlane/metadata/android/ka/changelogs/68.txt deleted file mode 100644 index 1c6967bcb..000000000 --- a/fastlane/metadata/android/ka/changelogs/68.txt +++ /dev/null @@ -1,31 +0,0 @@ -v0.14.1-ის ცვლილებები - - ### გამოსწორდა - - დაფიქსირდა ვიდეო url #1659-ის გაშიფვრა ვერ მოხერხდა - - დაფიქსირდა აღწერილობის ბმული კარგად არ არის ამონაწერი #1657 - - v0.14.0-ის # ცვლილებები - - ### ახალი - - ახალი უჯრის დიზაინი #1461 - - ახალი კონფიგურირებადი წინა გვერდი #1461 - - ### გაუმჯობესებები - - გადამუშავებული ჟესტების კონტროლი #1604 - - ახალი გზა ამომხტარი პლეერის დახურვის #1597 - - ### გამოსწორდა - - შეცდომის გამოსწორება, როდესაც ხელმოწერების რაოდენობა მიუწვდომელია. იხურება #1649. - - აჩვენეთ "აბონენტთა რაოდენობა მიუწვდომელია" ამ შემთხვევებში - - შეასწორეთ NPE, როდესაც YouTube დასაკრავი სია ცარიელია - - სწრაფი შესწორება კიოსკებისთვის SoundCloud-ში - - Refactor და bugfix #1623 - - დააფიქსირეთ ციკლური ძიების შედეგი #1562 - - შეასწორეთ ძიების ზოლი, რომელიც არ არის სტატიკურად განლაგებული - - გაასწორეთ YT Premium ვიდეო არ არის სწორად დაბლოკილი - - დააფიქსირეთ ვიდეოები, რომლებიც ზოგჯერ არ იტვირთება (DASH ანალიზების გამო) - - დააფიქსირეთ ბმულები ვიდეოს აღწერაში - - გაფრთხილების ჩვენება, როდესაც ვინმე ცდილობს გარე sdcard-ზე ჩამოტვირთვას - - დააფიქსირეთ არაფერი ნაჩვენები გამონაკლისის გამომწვევი ანგარიში - - ესკიზი არ არის ნაჩვენები ანდროიდ 8.1-ის ფონურ პლეერში [იხილეთ აქ](https://github.com/TeamNewPipe/NewPipe/issues/943) - - სამაუწყებლო მიმღების რეგისტრაციის დაფიქსირება. იხურება #1641. diff --git a/fastlane/metadata/android/ka/changelogs/69.txt b/fastlane/metadata/android/ka/changelogs/69.txt deleted file mode 100644 index 4bc9f3c03..000000000 --- a/fastlane/metadata/android/ka/changelogs/69.txt +++ /dev/null @@ -1,19 +0,0 @@ -### ახალი - - დიდხანს შეეხეთ წაშლას და გააზიარეთ გამოწერებში #1516 - - ტაბლეტის ინტერფეისი და ბადის სიის განლაგება #1617 - - ### გაუმჯობესებები - - შეინახეთ და გადატვირთეთ ბოლო გამოყენებული ასპექტის თანაფარდობა #1748 - - ჩართეთ ხაზოვანი განლაგება ჩამოტვირთვების აქტივობაში ვიდეოს სრული სახელებით #1771 - - წაშალეთ და გააზიარეთ ხელმოწერები პირდაპირ გამოწერების ჩანართიდან #1516 - - ახლა რიგში დაყენება იწვევს ვიდეოს დაკვრას, თუ დაკვრის რიგი უკვე დასრულდა #1783 - - ცალკე პარამეტრები მოცულობისა და სიკაშკაშის ჟესტებისთვის #1644 - - დაამატეთ მხარდაჭერა ლოკალიზაციის #1792-ისთვის - - ### ასწორებს - - დააფიქსირეთ დროის ანალიზი . ფორმატში, ამიტომ NewPipe შეიძლება გამოყენებულ იქნას ფინეთში - - შეასწორეთ გამოწერების რაოდენობა - - დაამატეთ წინა პლანზე სერვისის ნებართვა API 28+ მოწყობილობებისთვის #1830 - - ### ცნობილი შეცდომები - - დაკვრის მდგომარეობის შენახვა შეუძლებელია Android P-ზე diff --git a/fastlane/metadata/android/ne/short_description.txt b/fastlane/metadata/android/ne/short_description.txt new file mode 100644 index 000000000..383f78457 --- /dev/null +++ b/fastlane/metadata/android/ne/short_description.txt @@ -0,0 +1 @@ +एन्ड्रोइडका लागि निशुल्क, हलुका युट्युब फ्रन्टइन्ड । diff --git a/fastlane/metadata/android/sk/changelogs/1000.txt b/fastlane/metadata/android/sk/changelogs/1000.txt index 36b9aeae1..61cabd89d 100644 --- a/fastlane/metadata/android/sk/changelogs/1000.txt +++ b/fastlane/metadata/android/sk/changelogs/1000.txt @@ -1,4 +1,4 @@ -Vylepšenie +Vylepšené - Umožnené kliknutie na popis playlistu, aby sa zobrazilo viac/menej obsahu - [PeerTube] Automatické spracovanie odkazov inštancie `subscribeto.me` - Spustenie prehrávania iba jednej položky v histórii diff --git a/fastlane/metadata/android/sk/changelogs/1002.txt b/fastlane/metadata/android/sk/changelogs/1002.txt index 2f96b8dc5..789b1caef 100644 --- a/fastlane/metadata/android/sk/changelogs/1002.txt +++ b/fastlane/metadata/android/sk/changelogs/1002.txt @@ -1 +1,4 @@ -Fixed YouTube not playing any stream +Opravené prehrávanie videí. + +Toto vydanie rieši len najpálčivejšiu chybu, ktorá zabraňuje načítať detaily s videom. +Sme si vedomí aj ďalších chýb a čoskoro vydáme ďalšie vydanie, ktoré ich bude riešiť. diff --git a/fastlane/metadata/android/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt index abda185e2..9de86c677 100644 --- a/fastlane/metadata/android/sk/full_description.txt +++ b/fastlane/metadata/android/sk/full_description.txt @@ -1 +1 @@ -NewPipe nepoužíva žiadne Google knižnice ani YouTube rozhranie. Analyzuje iba webovú stránku aby získala potrebné informácie. Preto je možné túto aplikáciu používať na zariadeniach bez nainštalovaných služieb Google. Na použitie aplikácie NewPipe tiež nepotrebujete účet na YouTube, je to bezproblémové. +NewPipe nepoužíva žiadne Google framework knižnice, ani YouTube API rozhranie. Len analyzuje web, aby získal potrebné informácie. Preto je možné túto aplikáciu používať na zariadeniach bez nainštalovaných Google služieb. Taktiež nepotrebujete účet na YouTube. Appka je FLOSS. diff --git a/fastlane/metadata/android/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt index 1fe84348f..b2351c660 100644 --- a/fastlane/metadata/android/sk/short_description.txt +++ b/fastlane/metadata/android/sk/short_description.txt @@ -1 +1 @@ -Jednoduchý a bezplatný YouTube prehrávač pre Android. +Slobodný a nenáročný YouTube prehrávač pre Android. diff --git a/fastlane/metadata/android/sv/changelogs/1003.txt b/fastlane/metadata/android/sv/changelogs/1003.txt index b25a7e652..3b4532ca0 100644 --- a/fastlane/metadata/android/sv/changelogs/1003.txt +++ b/fastlane/metadata/android/sv/changelogs/1003.txt @@ -1,4 +1,6 @@ -Åtgärdat att YouTube inte spelar någon stream. - -Den här versionen fixar enbart det mest brådskande felet som förhindrar att YouTube-videoinformation laddas. -Vi är medvetna om att det finns andra problem, och vi kommer snart att göra en separat version för att lösa dem. +Detta är en snabbkorrigeringsversion som fixar YouTube-fel: +• [YouTube] Fixat att ingen videoinformation laddades, fixat HTTP 403 när videor spelas och återställde uppspelningen av några åldersbegränsade videor +• Fixade att undertexts storleken inte ändrades +• Fixade att information laddades ner två gånger vid öppning av en ström +• [Soundcloud] Tog bort ospelbara DRM-skyddade strömmar +• Uppdaterade översättningar From b764ad33c44e80371e79f3b64edcb3f819e0adfa Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 15 Feb 2025 17:48:13 +0100 Subject: [PATCH 03/50] Drop some assumptions on how PlayerService is started and reused Read the comments in the lines changed to understand more --- .../java/org/schabi/newpipe/ktx/Bundle.kt | 13 ++++++ .../schabi/newpipe/player/PlayerService.java | 46 +++++++++++-------- .../newpipe/player/helper/PlayerHolder.java | 4 +- .../schabi/newpipe/util/NavigationHelper.java | 1 + 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt b/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt index 61721d546..e32376960 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt @@ -7,3 +7,16 @@ import androidx.core.os.BundleCompat inline fun Bundle.parcelableArrayList(key: String?): ArrayList? { return BundleCompat.getParcelableArrayList(this, key, T::class.java) } + +fun Bundle?.toDebugString(): String { + if (this == null) { + return "null" + } + val string = StringBuilder("Bundle{") + for (key in this.keySet()) { + @Suppress("DEPRECATION") // we want this[key] to return items of any type + string.append(" ").append(key).append(" => ").append(this[key]).append(";") + } + string.append(" }") + return string.toString() +} diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index e7abf4320..61eb3f733 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -28,6 +28,7 @@ import android.os.Binder; import android.os.IBinder; import android.util.Log; +import org.schabi.newpipe.ktx.BundleKt; import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -41,6 +42,7 @@ import java.lang.ref.WeakReference; public final class PlayerService extends Service { private static final String TAG = PlayerService.class.getSimpleName(); private static final boolean DEBUG = Player.DEBUG; + public static final String SHOULD_START_FOREGROUND_EXTRA = "should_start_foreground_extra"; private Player player; @@ -59,35 +61,39 @@ public final class PlayerService extends Service { assureCorrectAppLanguage(this); ThemeHelper.setTheme(this); - player = new Player(this); - /* - Create the player notification and start immediately the service in foreground, - otherwise if nothing is played or initializing the player and its components (especially - loading stream metadata) takes a lot of time, the app would crash on Android 8+ as the - service would never be put in the foreground while we said to the system we would do so - */ - player.UIs().get(NotificationPlayerUi.class) - .ifPresent(NotificationPlayerUi::createNotificationAndStartForeground); + // Note: you might be tempted to create the player instance and call startForeground here, + // but be aware that the Android system might start the service just to perform media + // queries. In those cases creating a player instance is a waste of resources, and calling + // startForeground means creating a useless empty notification. In case it's really needed + // the player instance can be created here, but startForeground() should definitely not be + // called here unless the service is actually starting in the foreground, to avoid the + // useless notification. } @Override public int onStartCommand(final Intent intent, final int flags, final int startId) { if (DEBUG) { Log.d(TAG, "onStartCommand() called with: intent = [" + intent + + "], extras = [" + BundleKt.toDebugString(intent.getExtras()) + "], flags = [" + flags + "], startId = [" + startId + "]"); } - /* - Be sure that the player notification is set and the service is started in foreground, - otherwise, the app may crash on Android 8+ as the service would never be put in the - foreground while we said to the system we would do so - The service is always requested to be started in foreground, so always creating a - notification if there is no one already and starting the service in foreground should - not create any issues - If the service is already started in foreground, requesting it to be started shouldn't - do anything - */ - if (player != null) { + // All internal NewPipe intents used to interact with the player, that are sent to the + // PlayerService using startForegroundService(), will have SHOULD_START_FOREGROUND_EXTRA, + // to ensure startForeground() is called (otherwise Android will force-crash the app). + if (intent.getBooleanExtra(SHOULD_START_FOREGROUND_EXTRA, false)) { + if (player == null) { + // make sure the player exists, in case the service was resumed + player = new Player(this); + } + + // Be sure that the player notification is set and the service is started in foreground, + // otherwise, the app may crash on Android 8+ as the service would never be put in the + // foreground while we said to the system we would do so. The service is always + // requested to be started in foreground, so always creating a notification if there is + // no one already and starting the service in foreground should not create any issues. + // If the service is already started in foreground, requesting it to be started + // shouldn't do anything. player.UIs().get(NotificationPlayerUi.class) .ifPresent(NotificationPlayerUi::createNotificationAndStartForeground); } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index b55a6547a..11b7379b3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -130,7 +130,9 @@ public final class PlayerHolder { // and NullPointerExceptions inside the service because the service will be // bound twice. Prevent it with unbinding first unbind(context); - ContextCompat.startForegroundService(context, new Intent(context, PlayerService.class)); + final Intent intent = new Intent(context, PlayerService.class); + intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true); + ContextCompat.startForegroundService(context, intent); serviceConnection.doPlayAfterConnect(playAfterConnect); bind(context); } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index e4cb46f94..e1d296297 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -96,6 +96,7 @@ public final class NavigationHelper { } intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent()); intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback); + intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true); return intent; } From cfb6e114d6918602c5208e3cc5ae35ffc46b91b9 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 10:31:42 +0100 Subject: [PATCH 04/50] Disable logs about view animations by default --- app/src/main/java/org/schabi/newpipe/ktx/View.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt index bf0dcb201..b781335e1 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -17,8 +17,10 @@ import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.interpolator.view.animation.FastOutSlowInInterpolator -import org.schabi.newpipe.MainActivity +// logs in this class are disabled by default since it's usually not useful, +// you can enable them by setting this flag to MainActivity.DEBUG +private const val DEBUG = false private const val TAG = "ViewUtils" /** @@ -38,7 +40,7 @@ fun View.animate( delay: Long = 0, execOnEnd: Runnable? = null ) { - if (MainActivity.DEBUG) { + if (DEBUG) { val id = try { resources.getResourceEntryName(id) } catch (e: Exception) { @@ -51,7 +53,7 @@ fun View.animate( Log.d(TAG, "animate(): $msg") } if (isVisible && enterOrExit) { - if (MainActivity.DEBUG) { + if (DEBUG) { Log.d(TAG, "animate(): view was already visible > view = [$this]") } animate().setListener(null).cancel() @@ -60,7 +62,7 @@ fun View.animate( execOnEnd?.run() return } else if ((isGone || isInvisible) && !enterOrExit) { - if (MainActivity.DEBUG) { + if (DEBUG) { Log.d(TAG, "animate(): view was already gone > view = [$this]") } animate().setListener(null).cancel() @@ -89,7 +91,7 @@ fun View.animate( * @param colorEnd the background color to end with */ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) { - if (MainActivity.DEBUG) { + if (DEBUG) { Log.d( TAG, "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + @@ -109,7 +111,7 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo } fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator { - if (MainActivity.DEBUG) { + if (DEBUG) { Log.d(TAG, "animateHeight: duration = [$duration], from $height to → $targetHeight in: $this") } val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat()) @@ -127,7 +129,7 @@ fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator { } fun View.animateRotation(duration: Long, targetRotation: Int) { - if (MainActivity.DEBUG) { + if (DEBUG) { Log.d(TAG, "animateRotation: duration = [$duration], from $rotation to → $targetRotation in: $this") } animate().setListener(null).cancel() From 5819546ea9083359d389ad2df4c4f2199725762f Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 15 Feb 2025 18:26:10 +0100 Subject: [PATCH 05/50] Have PlayerService implement MediaBrowserServiceCompat Co-authored-by: Haggai Eran --- app/src/main/AndroidManifest.xml | 8 +++ .../newpipe/player/PlayQueueActivity.java | 3 ++ .../schabi/newpipe/player/PlayerService.java | 49 +++++++++++++++++-- .../newpipe/player/helper/PlayerHolder.java | 1 + app/src/main/res/xml/automotive_app_desc.xml | 3 ++ 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/xml/automotive_app_desc.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d11de9f47..e52dded5e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -64,6 +64,9 @@ + + + + + + diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index 195baecbd..f989a68d0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -183,7 +183,10 @@ public final class PlayQueueActivity extends AppCompatActivity //////////////////////////////////////////////////////////////////////////// private void bind() { + // Note: this code should not really exist, and PlayerHolder should be used instead, but + // it will be rewritten when NewPlayer will replace the current player. final Intent bindIntent = new Intent(this, PlayerService.class); + bindIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION); final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE); if (!success) { unbindService(serviceConnection); diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index 61eb3f733..7b9b76cfb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -21,28 +21,36 @@ package org.schabi.newpipe.player; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; -import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; +import android.support.v4.media.MediaBrowserCompat; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media.MediaBrowserServiceCompat; + import org.schabi.newpipe.ktx.BundleKt; import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.util.ThemeHelper; import java.lang.ref.WeakReference; +import java.util.List; /** * One service for all players. */ -public final class PlayerService extends Service { +public final class PlayerService extends MediaBrowserServiceCompat { private static final String TAG = PlayerService.class.getSimpleName(); private static final boolean DEBUG = Player.DEBUG; + public static final String SHOULD_START_FOREGROUND_EXTRA = "should_start_foreground_extra"; + public static final String BIND_PLAYER_HOLDER_ACTION = "bind_player_holder_action"; private Player player; @@ -55,6 +63,8 @@ public final class PlayerService extends Service { @Override public void onCreate() { + super.onCreate(); + if (DEBUG) { Log.d(TAG, "onCreate() called"); } @@ -148,6 +158,7 @@ public final class PlayerService extends Service { if (DEBUG) { Log.d(TAG, "destroy() called"); } + super.onDestroy(); cleanup(); } @@ -170,7 +181,25 @@ public final class PlayerService extends Service { @Override public IBinder onBind(final Intent intent) { - return mBinder; + if (DEBUG) { + Log.d(TAG, "onBind() called with: intent = [" + intent + + "], extras = [" + BundleKt.toDebugString(intent.getExtras()) + "]"); + } + + if (BIND_PLAYER_HOLDER_ACTION.equals(intent.getAction())) { + // Note that this binder might be reused multiple times while the service is alive, even + // after unbind() has been called: https://stackoverflow.com/a/8794930 . + return mBinder; + + } else if (MediaBrowserServiceCompat.SERVICE_INTERFACE.equals(intent.getAction())) { + // MediaBrowserService also uses its own binder, so for actions related to the media + // browser service, pass the onBind to the superclass. + return super.onBind(intent); + + } else { + // This is an unknown request, avoid returning any binder to not leak objects. + return null; + } } public static class LocalBinder extends Binder { @@ -188,4 +217,18 @@ public final class PlayerService extends Service { return playerService.get().player; } } + + @Nullable + @Override + public BrowserRoot onGetRoot(@NonNull final String clientPackageName, + final int clientUid, + @Nullable final Bundle rootHints) { + return null; + } + + @Override + public void onLoadChildren(@NonNull final String parentId, + @NonNull final Result> result) { + + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index 11b7379b3..30cdd5582 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -183,6 +183,7 @@ public final class PlayerHolder { } final Intent serviceIntent = new Intent(context, PlayerService.class); + serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION); bound = context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); if (!bound) { diff --git a/app/src/main/res/xml/automotive_app_desc.xml b/app/src/main/res/xml/automotive_app_desc.xml new file mode 100644 index 000000000..90e6f30ef --- /dev/null +++ b/app/src/main/res/xml/automotive_app_desc.xml @@ -0,0 +1,3 @@ + + + From 7d17468266b62ed9689340d6550efbf2540ac560 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 09:11:35 +0100 Subject: [PATCH 06/50] Instantiate media session and connector in PlayerService This changes significantly how the MediaSessionCompat and MediaSessionConnector objects are used: - now they are tied to the service and not to the player, and so they might be reused with multiple players (which should be allowed) - now they can exist even if there is no player (which is fundamental to be able to answer media browser queries) --- .../org/schabi/newpipe/player/Player.java | 15 ++++++- .../schabi/newpipe/player/PlayerService.java | 21 ++++++++- .../mediasession/MediaSessionPlayerUi.java | 43 ++++++++----------- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 920435a7e..41705ffb2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -55,6 +55,7 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.media.AudioManager; +import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; import android.view.LayoutInflater; @@ -71,6 +72,7 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player.PositionInfo; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -269,7 +271,16 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Constructor - public Player(@NonNull final PlayerService service) { + /** + * @param service the service this player resides in + * @param mediaSession used to build the {@link MediaSessionPlayerUi}, lives in the service and + * could possibly be reused with multiple player instances + * @param sessionConnector used to build the {@link MediaSessionPlayerUi}, lives in the service + * and could possibly be reused with multiple player instances + */ + public Player(@NonNull final PlayerService service, + @NonNull final MediaSessionCompat mediaSession, + @NonNull final MediaSessionConnector sessionConnector) { this.service = service; context = service; prefs = PreferenceManager.getDefaultSharedPreferences(context); @@ -302,7 +313,7 @@ public final class Player implements PlaybackListener, Listener { // notification ui in the UIs list, since the notification depends on the media session in // PlayerUi#initPlayer(), and UIs.call() guarantees UI order is preserved. UIs = new PlayerUiList( - new MediaSessionPlayerUi(this), + new MediaSessionPlayerUi(this, mediaSession, sessionConnector), new NotificationPlayerUi(this) ); } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index 7b9b76cfb..ee8585c9c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -27,12 +27,15 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.support.v4.media.MediaBrowserCompat; +import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media.MediaBrowserServiceCompat; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; + import org.schabi.newpipe.ktx.BundleKt; import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi; @@ -52,6 +55,12 @@ public final class PlayerService extends MediaBrowserServiceCompat { public static final String SHOULD_START_FOREGROUND_EXTRA = "should_start_foreground_extra"; public static final String BIND_PLAYER_HOLDER_ACTION = "bind_player_holder_action"; + // these are instantiated in onCreate() as per + // https://developer.android.com/training/cars/media#browser_workflow + private MediaSessionCompat mediaSession; + private MediaSessionConnector sessionConnector; + + @Nullable private Player player; private final IBinder mBinder = new PlayerService.LocalBinder(this); @@ -71,6 +80,12 @@ public final class PlayerService extends MediaBrowserServiceCompat { assureCorrectAppLanguage(this); ThemeHelper.setTheme(this); + // see https://developer.android.com/training/cars/media#browser_workflow + mediaSession = new MediaSessionCompat(this, "MediaSessionPlayerServ"); + setSessionToken(mediaSession.getSessionToken()); + sessionConnector = new MediaSessionConnector(mediaSession); + sessionConnector.setMetadataDeduplicationEnabled(true); + // Note: you might be tempted to create the player instance and call startForeground here, // but be aware that the Android system might start the service just to perform media // queries. In those cases creating a player instance is a waste of resources, and calling @@ -94,7 +109,7 @@ public final class PlayerService extends MediaBrowserServiceCompat { if (intent.getBooleanExtra(SHOULD_START_FOREGROUND_EXTRA, false)) { if (player == null) { // make sure the player exists, in case the service was resumed - player = new Player(this); + player = new Player(this, mediaSession, sessionConnector); } // Be sure that the player notification is set and the service is started in foreground, @@ -159,7 +174,11 @@ public final class PlayerService extends MediaBrowserServiceCompat { Log.d(TAG, "destroy() called"); } super.onDestroy(); + cleanup(); + + mediaSession.setActive(false); + mediaSession.release(); } private void cleanup() { diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index c673e688c..fe884834b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -38,10 +38,10 @@ public class MediaSessionPlayerUi extends PlayerUi implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = "MediaSessUi"; - @Nullable - private MediaSessionCompat mediaSession; - @Nullable - private MediaSessionConnector sessionConnector; + @NonNull + private final MediaSessionCompat mediaSession; + @NonNull + private final MediaSessionConnector sessionConnector; private final String ignoreHardwareMediaButtonsKey; private boolean shouldIgnoreHardwareMediaButtons = false; @@ -50,9 +50,13 @@ public class MediaSessionPlayerUi extends PlayerUi private List prevNotificationActions = List.of(); - public MediaSessionPlayerUi(@NonNull final Player player) { + public MediaSessionPlayerUi(@NonNull final Player player, + @NonNull final MediaSessionCompat mediaSession, + @NonNull final MediaSessionConnector sessionConnector) { super(player); - ignoreHardwareMediaButtonsKey = + this.mediaSession = mediaSession; + this.sessionConnector = sessionConnector; + this.ignoreHardwareMediaButtonsKey = context.getString(R.string.ignore_hardware_media_buttons_key); } @@ -61,10 +65,8 @@ public class MediaSessionPlayerUi extends PlayerUi super.initPlayer(); destroyPlayer(); // release previously used resources - mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); - sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); sessionConnector.setPlayer(getForwardingPlayer()); @@ -89,27 +91,18 @@ public class MediaSessionPlayerUi extends PlayerUi public void destroyPlayer() { super.destroyPlayer(); player.getPrefs().unregisterOnSharedPreferenceChangeListener(this); - if (sessionConnector != null) { - sessionConnector.setMediaButtonEventHandler(null); - sessionConnector.setPlayer(null); - sessionConnector.setQueueNavigator(null); - sessionConnector = null; - } - if (mediaSession != null) { - mediaSession.setActive(false); - mediaSession.release(); - mediaSession = null; - } + sessionConnector.setMediaButtonEventHandler(null); + sessionConnector.setPlayer(null); + sessionConnector.setQueueNavigator(null); + mediaSession.setActive(false); prevNotificationActions = List.of(); } @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - if (sessionConnector != null) { - // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update - sessionConnector.invalidateMediaSessionMetadata(); - } + // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update + sessionConnector.invalidateMediaSessionMetadata(); } @@ -200,8 +193,8 @@ public class MediaSessionPlayerUi extends PlayerUi return; } - if (sessionConnector == null) { - // sessionConnector will be null after destroyPlayer is called + if (!mediaSession.isActive()) { + // mediaSession will be inactive after destroyPlayer is called return; } From 1e08cc8c8f665712c4cfc3849ad96a892a9f3b29 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 08:39:24 +0100 Subject: [PATCH 07/50] Add MediaBrowserCommon with info item's and pages' IDs Co-authored-by: Haggai Eran --- .../player/mediabrowser/MediaBrowserCommon.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserCommon.kt diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserCommon.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserCommon.kt new file mode 100644 index 000000000..12d69a163 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserCommon.kt @@ -0,0 +1,40 @@ +package org.schabi.newpipe.player.mediabrowser + +import org.schabi.newpipe.BuildConfig +import org.schabi.newpipe.extractor.InfoItem.InfoType +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException + +internal const val ID_AUTHORITY = BuildConfig.APPLICATION_ID +internal const val ID_ROOT = "//$ID_AUTHORITY" +internal const val ID_BOOKMARKS = "playlists" +internal const val ID_HISTORY = "history" +internal const val ID_INFO_ITEM = "item" + +internal const val ID_LOCAL = "local" +internal const val ID_REMOTE = "remote" +internal const val ID_URL = "url" +internal const val ID_STREAM = "stream" +internal const val ID_PLAYLIST = "playlist" +internal const val ID_CHANNEL = "channel" + +internal fun infoItemTypeToString(type: InfoType): String { + return when (type) { + InfoType.STREAM -> ID_STREAM + InfoType.PLAYLIST -> ID_PLAYLIST + InfoType.CHANNEL -> ID_CHANNEL + else -> throw IllegalStateException("Unexpected value: $type") + } +} + +internal fun infoItemTypeFromString(type: String): InfoType { + return when (type) { + ID_STREAM -> InfoType.STREAM + ID_PLAYLIST -> InfoType.PLAYLIST + ID_CHANNEL -> InfoType.CHANNEL + else -> throw IllegalStateException("Unexpected value: $type") + } +} + +internal fun parseError(mediaId: String): ContentNotAvailableException { + return ContentNotAvailableException("Failed to parse media ID $mediaId") +} From 9bb2c0b48453f94b1c59b41fd3038767c8a59257 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 08:42:15 +0100 Subject: [PATCH 08/50] Add getPlaylist(id) to RemotePlaylistManager Co-authored-by: Haggai Eran --- .../newpipe/database/playlist/dao/PlaylistRemoteDAO.java | 2 +- .../schabi/newpipe/local/playlist/RemotePlaylistManager.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java index 8ab8a2afd..ef77d5ade 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java @@ -34,7 +34,7 @@ public interface PlaylistRemoteDAO extends BasicDAO { @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId") - Flowable> getPlaylist(long playlistId); + Flowable getPlaylist(long playlistId); @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java index 4cc51f752..08b203a7e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java @@ -26,6 +26,10 @@ public class RemotePlaylistManager { return playlistRemoteTable.getPlaylists().subscribeOn(Schedulers.io()); } + public Flowable getPlaylist(final long playlistId) { + return playlistRemoteTable.getPlaylist(playlistId).subscribeOn(Schedulers.io()); + } + public Flowable> getPlaylist(final PlaylistInfo info) { return playlistRemoteTable.getPlaylist(info.getServiceId(), info.getUrl()) .subscribeOn(Schedulers.io()); From 690b40d0c4b6c27c42e8ac001152a2842d2855f6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 08:55:51 +0100 Subject: [PATCH 09/50] Allow creating PlayQueue from ListInfo and index --- .../newpipe/player/playqueue/AbstractInfoPlayQueue.java | 6 +++++- .../schabi/newpipe/player/playqueue/PlaylistPlayQueue.java | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java index 33ec390a5..dbfac5cca 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java @@ -28,13 +28,17 @@ abstract class AbstractInfoPlayQueue> private transient Disposable fetchReactor; protected AbstractInfoPlayQueue(final T info) { + this(info, 0); + } + + protected AbstractInfoPlayQueue(final T info, final int index) { this(info.getServiceId(), info.getUrl(), info.getNextPage(), info.getRelatedItems() .stream() .filter(StreamInfoItem.class::isInstance) .map(StreamInfoItem.class::cast) .collect(Collectors.toList()), - 0); + index); } protected AbstractInfoPlayQueue(final int serviceId, diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java index 01883d7d9..32316f393 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java @@ -16,6 +16,10 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue super(info); } + public PlaylistPlayQueue(final PlaylistInfo info, final int index) { + super(info, index); + } + public PlaylistPlayQueue(final int serviceId, final String url, final Page nextPage, From 5eabcb52b5e7b3d3445a8cd59ff06b975995123f Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 08:56:38 +0100 Subject: [PATCH 10/50] Add getThumbnailUrl() to PlaylistLocalItem interface Co-authored-by: Haggai Eran --- .../newpipe/database/playlist/PlaylistLocalItem.java | 5 +++++ .../newpipe/database/playlist/PlaylistMetadataEntry.java | 8 ++++++++ .../database/playlist/model/PlaylistRemoteEntity.java | 3 +++ 3 files changed, 16 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java index 072c49e2c..91f4622e9 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.database.playlist; +import androidx.annotation.Nullable; + import org.schabi.newpipe.database.LocalItem; public interface PlaylistLocalItem extends LocalItem { @@ -10,4 +12,7 @@ public interface PlaylistLocalItem extends LocalItem { long getUid(); void setDisplayIndex(long displayIndex); + + @Nullable + String getThumbnailUrl(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java index 03a1e1e30..8fbadb020 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java @@ -9,6 +9,8 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL; +import androidx.annotation.Nullable; + public class PlaylistMetadataEntry implements PlaylistLocalItem { public static final String PLAYLIST_STREAM_COUNT = "streamCount"; @@ -71,4 +73,10 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem { public void setDisplayIndex(final long displayIndex) { this.displayIndex = displayIndex; } + + @Nullable + @Override + public String getThumbnailUrl() { + return thumbnailUrl; + } } 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 60027a057..0b0e3605e 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 @@ -2,6 +2,7 @@ package org.schabi.newpipe.database.playlist.model; import android.text.TextUtils; +import androidx.annotation.Nullable; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Ignore; @@ -134,6 +135,8 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { this.name = name; } + @Nullable + @Override public String getThumbnailUrl() { return thumbnailUrl; } From 6cedd117fe6e1e36054e4389aa0296f4ae6cde18 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 08:57:01 +0100 Subject: [PATCH 11/50] Add StreamHistoryEntry.toStreamInfoItem() Co-authored-by: Haggai Eran --- .../database/history/model/StreamHistoryEntry.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt index a93ba1652..27fc429f1 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt @@ -3,6 +3,8 @@ package org.schabi.newpipe.database.history.model import androidx.room.ColumnInfo import androidx.room.Embedded import org.schabi.newpipe.database.stream.model.StreamEntity +import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.util.image.ImageStrategy import java.time.OffsetDateTime data class StreamHistoryEntry( @@ -27,4 +29,17 @@ data class StreamHistoryEntry( return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId && accessDate.isEqual(other.accessDate) } + + fun toStreamInfoItem(): StreamInfoItem = + StreamInfoItem( + streamEntity.serviceId, + streamEntity.url, + streamEntity.title, + streamEntity.streamType, + ).apply { + duration = streamEntity.duration + uploaderName = streamEntity.uploader + uploaderUrl = streamEntity.uploaderUrl + thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl) + } } From 3fcac10e7ffd49a6f8bab78a4349ef717430ccee Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 09:01:31 +0100 Subject: [PATCH 12/50] Add MediaBrowserPlaybackPreparer This class will receive the media URLs generated by [MediaBrowserImpl] and will start playback of the corresponding streams or playlists. Co-authored-by: Haggai Eran Co-authored-by: Profpatsch --- .../MediaBrowserPlaybackPreparer.kt | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt new file mode 100644 index 000000000..9d77ae8b9 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt @@ -0,0 +1,258 @@ +package org.schabi.newpipe.player.mediabrowser + +import android.content.Context +import android.net.Uri +import android.os.Bundle +import android.os.ResultReceiver +import android.support.v4.media.session.PlaybackStateCompat +import android.util.Log +import com.google.android.exoplayer2.Player +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector.PlaybackPreparer +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.NewPipeDatabase +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.InfoItem.InfoType +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler +import org.schabi.newpipe.local.playlist.LocalPlaylistManager +import org.schabi.newpipe.local.playlist.RemotePlaylistManager +import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue +import org.schabi.newpipe.player.playqueue.PlayQueue +import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue +import org.schabi.newpipe.player.playqueue.SinglePlayQueue +import org.schabi.newpipe.util.ChannelTabHelper +import org.schabi.newpipe.util.ExtractorHelper +import org.schabi.newpipe.util.NavigationHelper +import java.util.function.BiConsumer + +/** + * This class is used to cleanly separate the Service implementation (in + * [org.schabi.newpipe.player.PlayerService]) and the playback preparer implementation (in this + * file). We currently use the playback preparer only in conjunction with the media browser: the + * playback preparer will receive the media URLs generated by [MediaBrowserImpl] and will start + * playback of the corresponding streams or playlists. + * + * @param setMediaSessionError takes an error String and an error code from [PlaybackStateCompat], + * calls `sessionConnector.setCustomErrorMessage(errorString, errorCode)` + * @param clearMediaSessionError calls `sessionConnector.setCustomErrorMessage(null)` + */ +class MediaBrowserPlaybackPreparer( + private val context: Context, + private val setMediaSessionError: BiConsumer, // error string, error code + private val clearMediaSessionError: Runnable, +) : PlaybackPreparer { + private val database = NewPipeDatabase.getInstance(context) + private var disposable: Disposable? = null + + fun dispose() { + disposable?.dispose() + } + + //region Overrides + override fun getSupportedPrepareActions(): Long { + return PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID + } + + override fun onPrepare(playWhenReady: Boolean) { + // TODO handle onPrepare + } + + override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) { + if (MainActivity.DEBUG) { + Log.d(TAG, "onPrepareFromMediaId($mediaId, $playWhenReady, $extras)") + } + + disposable?.dispose() + disposable = extractPlayQueueFromMediaId(mediaId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { playQueue -> + clearMediaSessionError.run() + NavigationHelper.playOnBackgroundPlayer(context, playQueue, playWhenReady) + }, + { throwable -> + Log.e(TAG, "Failed to start playback of media ID [$mediaId]", throwable) + onPrepareError() + } + ) + } + + override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) { + onUnsupportedError() + } + + override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) { + onUnsupportedError() + } + + override fun onCommand( + player: Player, + command: String, + extras: Bundle?, + cb: ResultReceiver? + ): Boolean { + return false + } + //endregion + + //region Errors + private fun onUnsupportedError() { + setMediaSessionError.accept( + context.getString(R.string.content_not_supported), + PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED + ) + } + + private fun onPrepareError() { + setMediaSessionError.accept( + context.getString(R.string.error_snackbar_message), + PlaybackStateCompat.ERROR_CODE_APP_ERROR + ) + } + //endregion + + //region Building play queues from playlists and history + private fun extractLocalPlayQueue(playlistId: Long, index: Int): Single { + return LocalPlaylistManager(database).getPlaylistStreams(playlistId).firstOrError() + .map { items -> SinglePlayQueue(items.map { it.toStreamInfoItem() }, index) } + } + + private fun extractRemotePlayQueue(playlistId: Long, index: Int): Single { + return RemotePlaylistManager(database).getPlaylist(playlistId).firstOrError() + .flatMap { ExtractorHelper.getPlaylistInfo(it.serviceId, it.url, false) } + .flatMap { info -> + info.errors.firstOrNull { it !is ContentNotSupportedException }?.let { + return@flatMap Single.error(it) + } + Single.just(PlaylistPlayQueue(info, index)) + } + } + + private fun extractPlayQueueFromMediaId(mediaId: String): Single { + try { + val mediaIdUri = Uri.parse(mediaId) + val path = ArrayList(mediaIdUri.pathSegments) + if (path.isEmpty()) { + throw parseError(mediaId) + } + + return when (/*val uriType = */path.removeAt(0)) { + ID_BOOKMARKS -> extractPlayQueueFromPlaylistMediaId( + mediaId, + path, + mediaIdUri.getQueryParameter(ID_URL) ?: throw parseError(mediaId) + ) + + ID_HISTORY -> extractPlayQueueFromHistoryMediaId(mediaId, path) + + ID_INFO_ITEM -> extractPlayQueueFromInfoItemMediaId( + mediaId, + path, + mediaIdUri.getQueryParameter(ID_URL) ?: throw parseError(mediaId) + ) + + else -> throw parseError(mediaId) + } + } catch (e: ContentNotAvailableException) { + return Single.error(e) + } + } + + @Throws(ContentNotAvailableException::class) + private fun extractPlayQueueFromPlaylistMediaId( + mediaId: String, + path: MutableList, + url: String, + ): Single { + if (path.isEmpty()) { + throw parseError(mediaId) + } + + when (val playlistType = path.removeAt(0)) { + ID_LOCAL, ID_REMOTE -> { + if (path.size != 2) { + throw parseError(mediaId) + } + val playlistId = path[0].toLong() + val index = path[1].toInt() + return if (playlistType == ID_LOCAL) + extractLocalPlayQueue(playlistId, index) + else + extractRemotePlayQueue(playlistId, index) + } + + ID_URL -> { + if (path.size != 1) { + throw parseError(mediaId) + } + + val serviceId = path[0].toInt() + return ExtractorHelper.getPlaylistInfo(serviceId, url, false) + .map { PlaylistPlayQueue(it) } + } + + else -> throw parseError(mediaId) + } + } + + @Throws(ContentNotAvailableException::class) + private fun extractPlayQueueFromHistoryMediaId( + mediaId: String, + path: List, + ): Single { + if (path.size != 1) { + throw parseError(mediaId) + } + + val streamId = path[0].toLong() + return database.streamHistoryDAO().getHistory() + .firstOrError() + .map { items -> + val infoItems = items + .filter { it.streamId == streamId } + .map { it.toStreamInfoItem() } + SinglePlayQueue(infoItems, 0) + } + } + + @Throws(ContentNotAvailableException::class) + private fun extractPlayQueueFromInfoItemMediaId( + mediaId: String, + path: List, + url: String, + ): Single { + if (path.size != 2) { + throw parseError(mediaId) + } + + val serviceId = path[1].toInt() + return when (/*val infoItemType = */infoItemTypeFromString(path[0])) { + InfoType.STREAM -> ExtractorHelper.getStreamInfo(serviceId, url, false) + .map { SinglePlayQueue(it) } + + InfoType.PLAYLIST -> ExtractorHelper.getPlaylistInfo(serviceId, url, false) + .map { PlaylistPlayQueue(it) } + + InfoType.CHANNEL -> ExtractorHelper.getChannelInfo(serviceId, url, false) + .map { info -> + val playableTab = info.tabs + .firstOrNull { ChannelTabHelper.isStreamsTab(it) } + ?: throw ContentNotAvailableException("No streams tab found") + return@map ChannelTabPlayQueue(serviceId, ListLinkHandler(playableTab)) + } + + else -> throw parseError(mediaId) + } + } + //endregion + + companion object { + private val TAG = MediaBrowserPlaybackPreparer::class.simpleName + } +} From 4c88a193bd68739a3f44a9f6e3a23a71b7c638fa Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 09:03:12 +0100 Subject: [PATCH 13/50] Add MediaBrowserImpl This class implements the media browser service interface as a standalone class for clearer separation of concerns (otherwise everything would need to go in PlayerService, since PlayerService overrides MediaBrowserServiceCompat) Co-authored-by: Haggai Eran Co-authored-by: Profpatsch --- .../player/mediabrowser/MediaBrowserImpl.kt | 413 ++++++++++++++++++ .../main/res/drawable/ic_bookmark_white.xml | 10 + .../main/res/drawable/ic_history_white.xml | 10 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 434 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt create mode 100644 app/src/main/res/drawable/ic_bookmark_white.xml create mode 100644 app/src/main/res/drawable/ic_history_white.xml diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt new file mode 100644 index 000000000..2cecd928f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt @@ -0,0 +1,413 @@ +package org.schabi.newpipe.player.mediabrowser + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.os.Bundle +import android.support.v4.media.MediaBrowserCompat +import android.support.v4.media.MediaDescriptionCompat +import android.util.Log +import androidx.annotation.DrawableRes +import androidx.media.MediaBrowserServiceCompat +import androidx.media.MediaBrowserServiceCompat.Result +import androidx.media.utils.MediaConstants +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.core.SingleSource +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.MainActivity.DEBUG +import org.schabi.newpipe.NewPipeDatabase +import org.schabi.newpipe.R +import org.schabi.newpipe.database.history.model.StreamHistoryEntry +import org.schabi.newpipe.database.playlist.PlaylistLocalItem +import org.schabi.newpipe.database.playlist.PlaylistStreamEntry +import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity +import org.schabi.newpipe.extractor.InfoItem +import org.schabi.newpipe.extractor.InfoItem.InfoType +import org.schabi.newpipe.extractor.ListInfo +import org.schabi.newpipe.extractor.channel.ChannelInfoItem +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem +import org.schabi.newpipe.extractor.search.SearchInfo +import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.local.bookmark.MergedPlaylistManager +import org.schabi.newpipe.local.playlist.LocalPlaylistManager +import org.schabi.newpipe.local.playlist.RemotePlaylistManager +import org.schabi.newpipe.util.ExtractorHelper +import org.schabi.newpipe.util.ServiceHelper +import org.schabi.newpipe.util.image.ImageStrategy +import java.util.function.Consumer + +/** + * This class is used to cleanly separate the Service implementation (in + * [org.schabi.newpipe.player.PlayerService]) and the media browser implementation (in this file). + * + * @param notifyChildrenChanged takes the parent id of the children that changed + */ +class MediaBrowserImpl( + private val context: Context, + notifyChildrenChanged: Consumer, // parentId +) { + private val database = NewPipeDatabase.getInstance(context) + private var disposables = CompositeDisposable() + + init { + // this will listen to changes in the bookmarks until this MediaBrowserImpl is dispose()d + disposables.add( + getMergedPlaylists().subscribe { notifyChildrenChanged.accept(ID_BOOKMARKS) } + ) + } + + //region Cleanup + fun dispose() { + disposables.dispose() + } + //endregion + + //region onGetRoot + fun onGetRoot( + clientPackageName: String, + clientUid: Int, + rootHints: Bundle? + ): MediaBrowserServiceCompat.BrowserRoot { + if (DEBUG) { + Log.d(TAG, "onGetRoot($clientPackageName, $clientUid, $rootHints)") + } + + val extras = Bundle() + extras.putBoolean( + MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true + ) + return MediaBrowserServiceCompat.BrowserRoot(ID_ROOT, extras) + } + //endregion + + //region onLoadChildren + fun onLoadChildren(parentId: String, result: Result>) { + result.detach() // allows sendResult() to happen later + disposables.add( + onLoadChildren(parentId) + .subscribe( + { result.sendResult(it) }, + { throwable -> + // null indicates an error, see the docs of MediaSessionCompat.onSearch() + result.sendResult(null) + Log.e(TAG, "onLoadChildren error for parentId=$parentId: $throwable") + } + ) + ) + } + + private fun onLoadChildren(parentId: String): Single> { + if (DEBUG) { + Log.d(TAG, "onLoadChildren($parentId)") + } + + try { + val parentIdUri = Uri.parse(parentId) + val path = ArrayList(parentIdUri.pathSegments) + + if (path.isEmpty()) { + return Single.just( + listOf( + createRootMediaItem( + ID_BOOKMARKS, + context.resources.getString(R.string.tab_bookmarks_short), + R.drawable.ic_bookmark_white + ), + createRootMediaItem( + ID_HISTORY, + context.resources.getString(R.string.action_history), + R.drawable.ic_history_white + ) + ) + ) + } + + when (/*val uriType = */path.removeAt(0)) { + ID_BOOKMARKS -> { + if (path.isEmpty()) { + return populateBookmarks() + } + if (path.size == 2) { + val localOrRemote = path[0] + val playlistId = path[1].toLong() + if (localOrRemote == ID_LOCAL) { + return populateLocalPlaylist(playlistId) + } else if (localOrRemote == ID_REMOTE) { + return populateRemotePlaylist(playlistId) + } + } + Log.w(TAG, "Unknown playlist URI: $parentId") + throw parseError(parentId) + } + + ID_HISTORY -> return populateHistory() + + else -> throw parseError(parentId) + } + } catch (e: ContentNotAvailableException) { + return Single.error(e) + } + } + + private fun createRootMediaItem( + mediaId: String?, + folderName: String?, + @DrawableRes iconResId: Int + ): MediaBrowserCompat.MediaItem { + val builder = MediaDescriptionCompat.Builder() + builder.setMediaId(mediaId) + builder.setTitle(folderName) + val resources = context.resources + builder.setIconUri( + Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(resources.getResourcePackageName(iconResId)) + .appendPath(resources.getResourceTypeName(iconResId)) + .appendPath(resources.getResourceEntryName(iconResId)) + .build() + ) + + val extras = Bundle() + extras.putString( + MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, + context.getString(R.string.app_name) + ) + builder.setExtras(extras) + return MediaBrowserCompat.MediaItem( + builder.build(), + MediaBrowserCompat.MediaItem.FLAG_BROWSABLE + ) + } + + private fun createPlaylistMediaItem(playlist: PlaylistLocalItem): MediaBrowserCompat.MediaItem { + val builder = MediaDescriptionCompat.Builder() + builder + .setMediaId(createMediaIdForInfoItem(playlist is PlaylistRemoteEntity, playlist.uid)) + .setTitle(playlist.orderingName) + .setIconUri(playlist.thumbnailUrl?.let { Uri.parse(it) }) + + val extras = Bundle() + extras.putString( + MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, + context.resources.getString(R.string.tab_bookmarks), + ) + builder.setExtras(extras) + return MediaBrowserCompat.MediaItem( + builder.build(), + MediaBrowserCompat.MediaItem.FLAG_BROWSABLE, + ) + } + + private fun createInfoItemMediaItem(item: InfoItem): MediaBrowserCompat.MediaItem? { + val builder = MediaDescriptionCompat.Builder() + builder.setMediaId(createMediaIdForInfoItem(item)) + .setTitle(item.name) + + when (item.infoType) { + InfoType.STREAM -> builder.setSubtitle((item as StreamInfoItem).uploaderName) + InfoType.PLAYLIST -> builder.setSubtitle((item as PlaylistInfoItem).uploaderName) + InfoType.CHANNEL -> builder.setSubtitle((item as ChannelInfoItem).description) + else -> return null + } + + ImageStrategy.choosePreferredImage(item.thumbnails)?.let { + builder.setIconUri(Uri.parse(it)) + } + + return MediaBrowserCompat.MediaItem( + builder.build(), + MediaBrowserCompat.MediaItem.FLAG_PLAYABLE + ) + } + + private fun buildMediaId(): Uri.Builder { + return Uri.Builder().authority(ID_AUTHORITY) + } + + private fun buildPlaylistMediaId(playlistType: String?): Uri.Builder { + return buildMediaId() + .appendPath(ID_BOOKMARKS) + .appendPath(playlistType) + } + + private fun buildLocalPlaylistItemMediaId(isRemote: Boolean, playlistId: Long): Uri.Builder { + return buildPlaylistMediaId(if (isRemote) ID_REMOTE else ID_LOCAL) + .appendPath(playlistId.toString()) + } + + private fun buildInfoItemMediaId(item: InfoItem): Uri.Builder { + return buildMediaId() + .appendPath(ID_INFO_ITEM) + .appendPath(infoItemTypeToString(item.infoType)) + .appendPath(item.serviceId.toString()) + .appendQueryParameter(ID_URL, item.url) + } + + private fun createMediaIdForInfoItem(isRemote: Boolean, playlistId: Long): String { + return buildLocalPlaylistItemMediaId(isRemote, playlistId) + .build().toString() + } + + private fun createLocalPlaylistStreamMediaItem( + playlistId: Long, + item: PlaylistStreamEntry, + index: Int, + ): MediaBrowserCompat.MediaItem { + val builder = MediaDescriptionCompat.Builder() + builder.setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index)) + .setTitle(item.streamEntity.title) + .setSubtitle(item.streamEntity.uploader) + .setIconUri(Uri.parse(item.streamEntity.thumbnailUrl)) + + return MediaBrowserCompat.MediaItem( + builder.build(), + MediaBrowserCompat.MediaItem.FLAG_PLAYABLE + ) + } + + private fun createRemotePlaylistStreamMediaItem( + playlistId: Long, + item: StreamInfoItem, + index: Int, + ): MediaBrowserCompat.MediaItem { + val builder = MediaDescriptionCompat.Builder() + builder.setMediaId(createMediaIdForPlaylistIndex(true, playlistId, index)) + .setTitle(item.name) + .setSubtitle(item.uploaderName) + + ImageStrategy.choosePreferredImage(item.thumbnails)?.let { + builder.setIconUri(Uri.parse(it)) + } + + return MediaBrowserCompat.MediaItem( + builder.build(), + MediaBrowserCompat.MediaItem.FLAG_PLAYABLE + ) + } + + private fun createMediaIdForPlaylistIndex( + isRemote: Boolean, + playlistId: Long, + index: Int, + ): String { + return buildLocalPlaylistItemMediaId(isRemote, playlistId) + .appendPath(index.toString()) + .build().toString() + } + + private fun createMediaIdForInfoItem(item: InfoItem): String { + return buildInfoItemMediaId(item).build().toString() + } + + private fun populateHistory(): Single> { + val history = database.streamHistoryDAO().getHistory().firstOrError() + return history.map { items -> + items.map { this.createHistoryMediaItem(it) } + } + } + + private fun createHistoryMediaItem(streamHistoryEntry: StreamHistoryEntry): MediaBrowserCompat.MediaItem { + val builder = MediaDescriptionCompat.Builder() + val mediaId = buildMediaId() + .appendPath(ID_HISTORY) + .appendPath(streamHistoryEntry.streamId.toString()) + .build().toString() + builder.setMediaId(mediaId) + .setTitle(streamHistoryEntry.streamEntity.title) + .setSubtitle(streamHistoryEntry.streamEntity.uploader) + .setIconUri(Uri.parse(streamHistoryEntry.streamEntity.thumbnailUrl)) + + return MediaBrowserCompat.MediaItem( + builder.build(), + MediaBrowserCompat.MediaItem.FLAG_PLAYABLE + ) + } + + private fun getMergedPlaylists(): Flowable> { + return MergedPlaylistManager.getMergedOrderedPlaylists( + LocalPlaylistManager(database), + RemotePlaylistManager(database) + ) + } + + private fun populateBookmarks(): Single> { + val playlists = getMergedPlaylists().firstOrError() + return playlists.map { playlist -> + playlist.map { this.createPlaylistMediaItem(it) } + } + } + + private fun populateLocalPlaylist(playlistId: Long): Single> { + val playlist = LocalPlaylistManager(database).getPlaylistStreams(playlistId).firstOrError() + return playlist.map { items -> + items.mapIndexed { index, item -> + createLocalPlaylistStreamMediaItem(playlistId, item, index) + } + } + } + + private fun populateRemotePlaylist(playlistId: Long): Single> { + return RemotePlaylistManager(database).getPlaylist(playlistId).firstOrError() + .flatMap { ExtractorHelper.getPlaylistInfo(it.serviceId, it.url, false) } + .flatMap { info -> + info.errors.firstOrNull { it !is ContentNotSupportedException }?.let { + return@flatMap Single.error(it) + } + Single.just( + info.relatedItems.mapIndexed { index, item -> + createRemotePlaylistStreamMediaItem(playlistId, item, index) + } + ) + } + } + //endregion + + //region Search + fun onSearch( + query: String, + result: Result> + ) { + result.detach() // allows sendResult() to happen later + disposables.add( + searchMusicBySongTitle(query) + .flatMap { this.mediaItemsFromInfoItemList(it) } + .subscribeOn(Schedulers.io()) + .subscribe( + { result.sendResult(it) }, + { throwable -> + // null indicates an error, see the docs of MediaSessionCompat.onSearch() + result.sendResult(null) + Log.e(TAG, "Search error for query=\"$query\": $throwable") + } + ) + ) + } + + private fun searchMusicBySongTitle(query: String?): Single { + val serviceId = ServiceHelper.getSelectedServiceId(context) + return ExtractorHelper.searchFor(serviceId, query, listOf(), "") + } + + private fun mediaItemsFromInfoItemList( + result: ListInfo + ): SingleSource> { + result.errors.firstOrNull()?.let { return@mediaItemsFromInfoItemList Single.error(it) } + + return try { + Single.just( + result.relatedItems.mapNotNull { item -> this.createInfoItemMediaItem(item) } + ) + } catch (e: Exception) { + Single.error(e) + } + } + //endregion + + companion object { + private val TAG: String = MediaBrowserImpl::class.java.getSimpleName() + } +} diff --git a/app/src/main/res/drawable/ic_bookmark_white.xml b/app/src/main/res/drawable/ic_bookmark_white.xml new file mode 100644 index 000000000..a04ed256e --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_white.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_history_white.xml b/app/src/main/res/drawable/ic_history_white.xml new file mode 100644 index 000000000..585285b89 --- /dev/null +++ b/app/src/main/res/drawable/ic_history_white.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c27e6cbb..529ef0d9d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,6 +33,7 @@ Show info Subscriptions Bookmarked Playlists + Playlists Choose Tab Background Popup From 064e1d39c73c07e496e7a3ddb2226eb46f59479e Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 09:14:19 +0100 Subject: [PATCH 14/50] Use the media browser implementation in PlayerService Now the media browser queries are replied to by MediaBrowserImpl Co-authored-by: Haggai Eran --- .../schabi/newpipe/player/PlayerService.java | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index ee8585c9c..cbad4220c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -37,6 +37,8 @@ import androidx.media.MediaBrowserServiceCompat; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.ktx.BundleKt; +import org.schabi.newpipe.player.mediabrowser.MediaBrowserImpl; +import org.schabi.newpipe.player.mediabrowser.MediaBrowserPlaybackPreparer; import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -55,6 +57,12 @@ public final class PlayerService extends MediaBrowserServiceCompat { public static final String SHOULD_START_FOREGROUND_EXTRA = "should_start_foreground_extra"; public static final String BIND_PLAYER_HOLDER_ACTION = "bind_player_holder_action"; + // These objects are used to cleanly separate the Service implementation (in this file) and the + // media browser and playback preparer implementations. At the moment the playback preparer is + // only used in conjunction with the media browser. + private MediaBrowserImpl mediaBrowserImpl; + private MediaBrowserPlaybackPreparer mediaBrowserPlaybackPreparer; + // these are instantiated in onCreate() as per // https://developer.android.com/training/cars/media#browser_workflow private MediaSessionCompat mediaSession; @@ -66,10 +74,7 @@ public final class PlayerService extends MediaBrowserServiceCompat { private final IBinder mBinder = new PlayerService.LocalBinder(this); - /*////////////////////////////////////////////////////////////////////////// - // Service's LifeCycle - //////////////////////////////////////////////////////////////////////////*/ - + //region Service lifecycle @Override public void onCreate() { super.onCreate(); @@ -80,12 +85,21 @@ public final class PlayerService extends MediaBrowserServiceCompat { assureCorrectAppLanguage(this); ThemeHelper.setTheme(this); + mediaBrowserImpl = new MediaBrowserImpl(this, this::notifyChildrenChanged); + // see https://developer.android.com/training/cars/media#browser_workflow mediaSession = new MediaSessionCompat(this, "MediaSessionPlayerServ"); setSessionToken(mediaSession.getSessionToken()); sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector.setMetadataDeduplicationEnabled(true); + mediaBrowserPlaybackPreparer = new MediaBrowserPlaybackPreparer( + this, + sessionConnector::setCustomErrorMessage, + () -> sessionConnector.setCustomErrorMessage(null) + ); + sessionConnector.setPlaybackPreparer(mediaBrowserPlaybackPreparer); + // Note: you might be tempted to create the player instance and call startForeground here, // but be aware that the Android system might start the service just to perform media // queries. In those cases creating a player instance is a waste of resources, and calling @@ -177,8 +191,10 @@ public final class PlayerService extends MediaBrowserServiceCompat { cleanup(); + mediaBrowserPlaybackPreparer.dispose(); mediaSession.setActive(false); mediaSession.release(); + mediaBrowserImpl.dispose(); } private void cleanup() { @@ -197,7 +213,9 @@ public final class PlayerService extends MediaBrowserServiceCompat { protected void attachBaseContext(final Context base) { super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base)); } + //endregion + //region Bind @Override public IBinder onBind(final Intent intent) { if (DEBUG) { @@ -236,18 +254,28 @@ public final class PlayerService extends MediaBrowserServiceCompat { return playerService.get().player; } } + //endregion - @Nullable + //region Media browser @Override public BrowserRoot onGetRoot(@NonNull final String clientPackageName, final int clientUid, @Nullable final Bundle rootHints) { - return null; + // TODO check if the accessing package has permission to view data + return mediaBrowserImpl.onGetRoot(clientPackageName, clientUid, rootHints); } @Override public void onLoadChildren(@NonNull final String parentId, @NonNull final Result> result) { - + mediaBrowserImpl.onLoadChildren(parentId, result); } + + @Override + public void onSearch(@NonNull final String query, + final Bundle extras, + @NonNull final Result> result) { + mediaBrowserImpl.onSearch(query, result); + } + //endregion } From ec6612dd71163578f4cd45500202ecf7a3f8f0b9 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 09:14:26 +0100 Subject: [PATCH 15/50] Call exoPlayer.prepare() on PlaybackPreparer.onPrepare() If a playbackPreparer is set, then instead of calling `player.prepare()`, the MediaSessionConnector will call `playbackPreparer.onPrepare(true)` instead, as seen below. This commit makes it so that playbackPreparer.onPrepare(true) restores the original behavior of just calling player.prepare(). From MediaSessionConnector -> MediaSessionCompat.Callback implementation: ```java @Override public void onPlay() { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PLAY)) { if (player.getPlaybackState() == Player.STATE_IDLE) { if (playbackPreparer != null) { playbackPreparer.onPrepare(/* playWhenReady= */ true); } else { player.prepare(); } } else if (player.getPlaybackState() == Player.STATE_ENDED) { seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET); } Assertions.checkNotNull(player).play(); } } ``` --- .../main/java/org/schabi/newpipe/player/Player.java | 13 +++++++++++++ .../org/schabi/newpipe/player/PlayerService.java | 7 ++++++- .../mediabrowser/MediaBrowserPlaybackPreparer.kt | 7 ++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 41705ffb2..b86d11e5c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1386,6 +1386,19 @@ public final class Player implements PlaybackListener, Listener { public void onCues(@NonNull final CueGroup cueGroup) { UIs.call(playerUi -> playerUi.onCues(cueGroup.cues)); } + + /** + * To be called when the {@code PlaybackPreparer} set in the {@link MediaSessionConnector} + * receives an {@code onPrepare()} call. This function allows restoring the default behavior + * that would happen if there was no playback preparer set, i.e. to just call + * {@code player.prepare()}. You can find the default behavior in `onPlay()` inside the + * {@link MediaSessionConnector} file. + */ + public void onPrepare() { + if (!exoPlayerIsNull()) { + simpleExoPlayer.prepare(); + } + } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index cbad4220c..fa8dda526 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -96,7 +96,12 @@ public final class PlayerService extends MediaBrowserServiceCompat { mediaBrowserPlaybackPreparer = new MediaBrowserPlaybackPreparer( this, sessionConnector::setCustomErrorMessage, - () -> sessionConnector.setCustomErrorMessage(null) + () -> sessionConnector.setCustomErrorMessage(null), + (playWhenReady) -> { + if (player != null) { + player.onPrepare(); + } + } ); sessionConnector.setPlaybackPreparer(mediaBrowserPlaybackPreparer); diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt index 9d77ae8b9..d059bbdde 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt @@ -29,6 +29,7 @@ import org.schabi.newpipe.util.ChannelTabHelper import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.NavigationHelper import java.util.function.BiConsumer +import java.util.function.Consumer /** * This class is used to cleanly separate the Service implementation (in @@ -40,11 +41,15 @@ import java.util.function.BiConsumer * @param setMediaSessionError takes an error String and an error code from [PlaybackStateCompat], * calls `sessionConnector.setCustomErrorMessage(errorString, errorCode)` * @param clearMediaSessionError calls `sessionConnector.setCustomErrorMessage(null)` + * @param onPrepare takes playWhenReady, calls `player.prepare()`; this is needed because + * `MediaSessionConnector`'s `onPlay()` method calls this class' [onPrepare] instead of + * `player.prepare()` if the playback preparer is not null, but we want the original behavior */ class MediaBrowserPlaybackPreparer( private val context: Context, private val setMediaSessionError: BiConsumer, // error string, error code private val clearMediaSessionError: Runnable, + private val onPrepare: Consumer, ) : PlaybackPreparer { private val database = NewPipeDatabase.getInstance(context) private var disposable: Disposable? = null @@ -59,7 +64,7 @@ class MediaBrowserPlaybackPreparer( } override fun onPrepare(playWhenReady: Boolean) { - // TODO handle onPrepare + onPrepare.accept(playWhenReady) } override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) { From dc62d211f5f468526880f295c030d54c8b2caa24 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 10:29:52 +0100 Subject: [PATCH 16/50] Properly stop PlayerService This commit is a consequence of the commit "Drop some assumptions on how PlayerService is started and reused". Since the assumptions on how the PlayerService is started and reused have changed, we also need to adapt the way it is stopped. This means allowing the service to remain alive even after the player is destroyed, in case the system is still accessing PlayerService e.g. through the media browser interface. The foreground service needs to be stopped and the notification removed in any case. --- .../org/schabi/newpipe/player/Player.java | 4 +-- .../schabi/newpipe/player/PlayerService.java | 33 ++++++++++++++++--- .../newpipe/player/helper/PlayerHolder.java | 8 +++++ .../newpipe/player/ui/PopupPlayerUi.java | 2 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index b86d11e5c..040f0dc99 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -657,7 +657,7 @@ public final class Player implements PlaybackListener, Listener { Log.d(TAG, "onPlaybackShutdown() called"); } // destroys the service, which in turn will destroy the player - service.stopService(); + service.destroyPlayerAndStopService(); } public void smoothStopForImmediateReusing() { @@ -729,7 +729,7 @@ public final class Player implements PlaybackListener, Listener { pause(); break; case ACTION_CLOSE: - service.stopService(); + service.destroyPlayerAndStopService(); break; case ACTION_PLAY_PAUSE: playPause(); diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index fa8dda526..596cdef36 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -32,6 +32,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.app.ServiceCompat; import androidx.media.MediaBrowserServiceCompat; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; @@ -150,7 +151,7 @@ public final class PlayerService extends MediaBrowserServiceCompat { Stop the service in this case, which will be removed from the foreground and its notification cancelled in its destruction */ - stopSelf(); + destroyPlayerAndStopService(); return START_NOT_STICKY; } @@ -197,7 +198,6 @@ public final class PlayerService extends MediaBrowserServiceCompat { cleanup(); mediaBrowserPlaybackPreparer.dispose(); - mediaSession.setActive(false); mediaSession.release(); mediaBrowserImpl.dispose(); } @@ -207,11 +207,36 @@ public final class PlayerService extends MediaBrowserServiceCompat { player.destroy(); player = null; } + + // Should already be handled by MediaSessionPlayerUi, but just to be sure. + mediaSession.setActive(false); + + // Should already be handled by NotificationUtil.cancelNotificationAndStopForeground() in + // NotificationPlayerUi, but let's make sure that the foreground service is stopped. + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE); } - public void stopService() { + /** + * Destroys the player and allows the player instance to be garbage collected. Sets the media + * session to inactive. Stops the foreground service and removes the player notification + * associated with it. Tries to stop the {@link PlayerService} completely, but this step will + * have no effect in case some service connection still uses the service (e.g. the Android Auto + * system accesses the media browser even when no player is running). + */ + public void destroyPlayerAndStopService() { + if (DEBUG) { + Log.d(TAG, "destroyPlayerAndStopService() called"); + } + cleanup(); - stopSelf(); + + // This only really stops the service if there are no other service connections (see docs): + // for example the (Android Auto) media browser binder will block stopService(). + // This is why we also stopForeground() above, to make sure the notification is removed. + // If we were to call stopSelf(), then the service would be surely stopped (regardless of + // other service connections), but this would be a waste of resources since the service + // would be immediately restarted by those same connections to perform the queries. + stopService(new Intent(this, PlayerService.class)); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index 30cdd5582..7263afccd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -138,8 +138,16 @@ public final class PlayerHolder { } public void stopService() { + if (DEBUG) { + Log.d(TAG, "stopService() called"); + } + if (playerService != null) { + playerService.destroyPlayerAndStopService(); + } final Context context = getCommonContext(); unbind(context); + // destroyPlayerAndStopService() already runs the next line of code, but run it again just + // to make sure to stop the service even if playerService is null by any chance. context.stopService(new Intent(context, PlayerService.class)); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 02f7c07b0..6c98ab0fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -382,7 +382,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { private void end() { windowManager.removeView(closeOverlayBinding.getRoot()); closeOverlayBinding = null; - player.getService().stopService(); + player.getService().destroyPlayerAndStopService(); } }).start(); } From e5458bcb144363e0ee3b7414695014c5658fc371 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 10:45:02 +0100 Subject: [PATCH 17/50] Properly handle item errors during media browser loading Non-item errors, i.e. critical parsing errors of the page, are still handled properly. --- .../player/mediabrowser/MediaBrowserImpl.kt | 46 +++++++------------ .../MediaBrowserPlaybackPreparer.kt | 10 ++-- 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt index 2cecd928f..3108da80f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt @@ -13,7 +13,6 @@ import androidx.media.MediaBrowserServiceCompat.Result import androidx.media.utils.MediaConstants import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.core.SingleSource import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.MainActivity.DEBUG @@ -25,10 +24,8 @@ import org.schabi.newpipe.database.playlist.PlaylistStreamEntry import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity import org.schabi.newpipe.extractor.InfoItem import org.schabi.newpipe.extractor.InfoItem.InfoType -import org.schabi.newpipe.extractor.ListInfo import org.schabi.newpipe.extractor.channel.ChannelInfoItem import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException -import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem import org.schabi.newpipe.extractor.search.SearchInfo import org.schabi.newpipe.extractor.stream.StreamInfoItem @@ -86,6 +83,10 @@ class MediaBrowserImpl( //region onLoadChildren fun onLoadChildren(parentId: String, result: Result>) { + if (DEBUG) { + Log.d(TAG, "onLoadChildren($parentId)") + } + result.detach() // allows sendResult() to happen later disposables.add( onLoadChildren(parentId) @@ -101,10 +102,6 @@ class MediaBrowserImpl( } private fun onLoadChildren(parentId: String): Single> { - if (DEBUG) { - Log.d(TAG, "onLoadChildren($parentId)") - } - try { val parentIdUri = Uri.parse(parentId) val path = ArrayList(parentIdUri.pathSegments) @@ -353,15 +350,12 @@ class MediaBrowserImpl( private fun populateRemotePlaylist(playlistId: Long): Single> { return RemotePlaylistManager(database).getPlaylist(playlistId).firstOrError() .flatMap { ExtractorHelper.getPlaylistInfo(it.serviceId, it.url, false) } - .flatMap { info -> - info.errors.firstOrNull { it !is ContentNotSupportedException }?.let { - return@flatMap Single.error(it) + .map { + // ignore it.errors, i.e. ignore errors about specific items, since there would + // be no way to show the error properly in Android Auto anyway + it.relatedItems.mapIndexed { index, item -> + createRemotePlaylistStreamMediaItem(playlistId, item, index) } - Single.just( - info.relatedItems.mapIndexed { index, item -> - createRemotePlaylistStreamMediaItem(playlistId, item, index) - } - ) } } //endregion @@ -371,10 +365,16 @@ class MediaBrowserImpl( query: String, result: Result> ) { + if (DEBUG) { + Log.d(TAG, "onSearch($query)") + } + result.detach() // allows sendResult() to happen later disposables.add( searchMusicBySongTitle(query) - .flatMap { this.mediaItemsFromInfoItemList(it) } + // ignore it.errors, i.e. ignore errors about specific items, since there would + // be no way to show the error properly in Android Auto anyway + .map { it.relatedItems.mapNotNull(this::createInfoItemMediaItem) } .subscribeOn(Schedulers.io()) .subscribe( { result.sendResult(it) }, @@ -391,20 +391,6 @@ class MediaBrowserImpl( val serviceId = ServiceHelper.getSelectedServiceId(context) return ExtractorHelper.searchFor(serviceId, query, listOf(), "") } - - private fun mediaItemsFromInfoItemList( - result: ListInfo - ): SingleSource> { - result.errors.firstOrNull()?.let { return@mediaItemsFromInfoItemList Single.error(it) } - - return try { - Single.just( - result.relatedItems.mapNotNull { item -> this.createInfoItemMediaItem(item) } - ) - } catch (e: Exception) { - Single.error(e) - } - } //endregion companion object { diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt index d059bbdde..ae6a9865a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt @@ -17,7 +17,6 @@ import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.R import org.schabi.newpipe.extractor.InfoItem.InfoType import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException -import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler import org.schabi.newpipe.local.playlist.LocalPlaylistManager import org.schabi.newpipe.local.playlist.RemotePlaylistManager @@ -131,12 +130,9 @@ class MediaBrowserPlaybackPreparer( private fun extractRemotePlayQueue(playlistId: Long, index: Int): Single { return RemotePlaylistManager(database).getPlaylist(playlistId).firstOrError() .flatMap { ExtractorHelper.getPlaylistInfo(it.serviceId, it.url, false) } - .flatMap { info -> - info.errors.firstOrNull { it !is ContentNotSupportedException }?.let { - return@flatMap Single.error(it) - } - Single.just(PlaylistPlayQueue(info, index)) - } + // ignore info.errors, i.e. ignore errors about specific items, since there would + // be no way to show the error properly in Android Auto anyway + .map { info -> PlaylistPlayQueue(info, index) } } private fun extractPlayQueueFromMediaId(mediaId: String): Single { From 1d98518bfa56b522c7fb74d2ddbe5ef7506755e4 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 16 Feb 2025 10:49:20 +0100 Subject: [PATCH 18/50] Fix loading remote playlists in media browser --- .../player/mediabrowser/MediaBrowserPlaybackPreparer.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt index ae6a9865a..f34677a29 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt @@ -147,7 +147,7 @@ class MediaBrowserPlaybackPreparer( ID_BOOKMARKS -> extractPlayQueueFromPlaylistMediaId( mediaId, path, - mediaIdUri.getQueryParameter(ID_URL) ?: throw parseError(mediaId) + mediaIdUri.getQueryParameter(ID_URL) ) ID_HISTORY -> extractPlayQueueFromHistoryMediaId(mediaId, path) @@ -169,7 +169,7 @@ class MediaBrowserPlaybackPreparer( private fun extractPlayQueueFromPlaylistMediaId( mediaId: String, path: MutableList, - url: String, + url: String?, ): Single { if (path.isEmpty()) { throw parseError(mediaId) @@ -189,7 +189,7 @@ class MediaBrowserPlaybackPreparer( } ID_URL -> { - if (path.size != 1) { + if (path.size != 1 || url == null) { throw parseError(mediaId) } From 6558794d265ba8f20958c1e3e1644bcfce22773f Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 18 Feb 2025 17:36:43 +0100 Subject: [PATCH 19/50] Try to bind to PlayerService when MainActivity starts Fixes mini-player not appearing on app start if the player service is already playing something. The PlayerService (and the player) may be started from an external intent that does not involve the MainActivity (e.g. RouterActivity or Android Auto's media browser interface). This PR tries to bind to the PlayerService as soon as the MainActivity starts, but only does so in a passive way, i.e. if the service is not already running it is not started. Once the connection between PlayerHolder and PlayerService is setup, the ACTION_PLAYER_STARTED broadcast is sent to MainActivity so that it can setup the bottom mini-player. Another important thing this commit does is to check whether the player is open before actually adding the mini-player view, since the PlayerService could be bound even without a running player (e.g. Android Auto's media browser is being used). This is a consequence of commit "Drop some assumptions on how PlayerService is started and reused". --- .../java/org/schabi/newpipe/MainActivity.java | 7 ++++- .../newpipe/player/helper/PlayerHolder.java | 30 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 354e06587..bbfc98f61 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -848,7 +848,8 @@ public class MainActivity extends AppCompatActivity { @Override public void onReceive(final Context context, final Intent intent) { if (Objects.equals(intent.getAction(), - VideoDetailFragment.ACTION_PLAYER_STARTED)) { + VideoDetailFragment.ACTION_PLAYER_STARTED) + && PlayerHolder.getInstance().isPlayerOpen()) { openMiniPlayerIfMissing(); // At this point the player is added 100%, we can unregister. Other actions // are useless since the fragment will not be removed after that. @@ -860,6 +861,10 @@ public class MainActivity extends AppCompatActivity { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED); registerReceiver(broadcastReceiver, intentFilter); + + // If the PlayerHolder is not bound yet, but the service is running, try to bind to it. + // Once the connection is established, the ACTION_PLAYER_STARTED will be sent. + PlayerHolder.getInstance().tryBindIfNeeded(this); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index 7263afccd..b9afaa7c7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -22,6 +22,7 @@ import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.util.NavigationHelper; public final class PlayerHolder { @@ -121,6 +122,9 @@ public final class PlayerHolder { public void startService(final boolean playAfterConnect, final PlayerServiceExtendedEventListener newListener) { + if (DEBUG) { + Log.d(TAG, "startService() called with playAfterConnect=" + playAfterConnect); + } final Context context = getCommonContext(); setListener(newListener); if (bound) { @@ -182,6 +186,10 @@ public final class PlayerHolder { listener.onServiceConnected(player, playerService, playAfterConnect); } startPlayerListener(); + + // notify the main activity that binding the service has completed, so that it can + // open the bottom mini-player + NavigationHelper.sendPlayerStartedEvent(localBinder.getService()); } } @@ -189,16 +197,28 @@ public final class PlayerHolder { if (DEBUG) { Log.d(TAG, "bind() called"); } - - final Intent serviceIntent = new Intent(context, PlayerService.class); - serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION); - bound = context.bindService(serviceIntent, serviceConnection, - Context.BIND_AUTO_CREATE); + // BIND_AUTO_CREATE starts the service if it's not already running + bound = bind(context, Context.BIND_AUTO_CREATE); if (!bound) { context.unbindService(serviceConnection); } } + public void tryBindIfNeeded(final Context context) { + if (!bound) { + // flags=0 means the service will not be started if it does not already exist. In this + // case the return value is not useful, as a value of "true" does not really indicate + // that the service is going to be bound. + bind(context, 0); + } + } + + private boolean bind(final Context context, final int flags) { + final Intent serviceIntent = new Intent(context, PlayerService.class); + serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION); + return context.bindService(serviceIntent, serviceConnection, flags); + } + private void unbind(final Context context) { if (DEBUG) { Log.d(TAG, "unbind() called"); From 126f4b0e30e321646537951b21f67a562ec9d6db Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 18 Feb 2025 18:03:10 +0100 Subject: [PATCH 20/50] Fix crash when closing video detail fragment This bug started appearing because the way to close the player is now unified in PlayerHolder.stopService(), which causes the player to reach back to the video detail fragment with a notification of the shutdown (i.e. onServiceStopped() is called). This is fixed by adding a nullability check on the binding. --- .../fragments/detail/VideoDetailFragment.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 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 40a22103b..fa2360247 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 @@ -1848,13 +1848,16 @@ public final class VideoDetailFragment @Override public void onServiceStopped() { - setOverlayPlayPauseImage(false); - if (currentInfo != null) { - updateOverlayData(currentInfo.getName(), - currentInfo.getUploaderName(), - currentInfo.getThumbnails()); + // the binding could be null at this point, if the app is finishing + if (binding != null) { + setOverlayPlayPauseImage(false); + if (currentInfo != null) { + updateOverlayData(currentInfo.getName(), + currentInfo.getUploaderName(), + currentInfo.getThumbnails()); + } + updateOverlayPlayQueueButtonVisibility(); } - updateOverlayPlayQueueButtonVisibility(); } @Override From a7a7dc53631579ea71baed0f097c0ef8638ae07b Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 18 Feb 2025 19:27:42 +0100 Subject: [PATCH 21/50] Handle player and player service separately This is, again, a consequence of the commit "Drop some assumptions on how PlayerService is started and reused". This commit notified VideoDetailFragment of player starting and stopping independently of the player. Read the comments in the code changes for more information. --- .../fragments/detail/VideoDetailFragment.java | 22 +++-- .../newpipe/player/PlayQueueActivity.java | 2 +- .../schabi/newpipe/player/PlayerService.java | 48 ++++++++++- .../PlayerServiceExtendedEventListener.java | 43 +++++++++- .../newpipe/player/helper/PlayerHolder.java | 85 ++++++++++++------- 5 files changed, 158 insertions(+), 42 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 fa2360247..083d1fe05 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 @@ -236,11 +236,14 @@ public final class VideoDetailFragment // Service management //////////////////////////////////////////////////////////////////////////*/ @Override - public void onServiceConnected(final Player connectedPlayer, - final PlayerService connectedPlayerService, - final boolean playAfterConnect) { - player = connectedPlayer; + public void onServiceConnected(@NonNull final PlayerService connectedPlayerService) { playerService = connectedPlayerService; + } + + @Override + public void onPlayerConnected(@NonNull final Player connectedPlayer, + final boolean playAfterConnect) { + player = connectedPlayer; // It will do nothing if the player is not in fullscreen mode hideSystemUiIfNeeded(); @@ -272,11 +275,18 @@ public final class VideoDetailFragment updateOverlayPlayQueueButtonVisibility(); } + @Override + public void onPlayerDisconnected() { + player = null; + // the binding could be null at this point, if the app is finishing + if (binding != null) { + restoreDefaultBrightness(); + } + } + @Override public void onServiceDisconnected() { playerService = null; - player = null; - restoreDefaultBrightness(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index f989a68d0..49aff657a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -224,7 +224,7 @@ public final class PlayQueueActivity extends AppCompatActivity Log.d(TAG, "Player service is connected"); if (service instanceof PlayerService.LocalBinder) { - player = ((PlayerService.LocalBinder) service).getPlayer(); + player = ((PlayerService.LocalBinder) service).getService().getPlayer(); } if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) { diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index 596cdef36..af6cf2467 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -46,6 +46,7 @@ import org.schabi.newpipe.util.ThemeHelper; import java.lang.ref.WeakReference; import java.util.List; +import java.util.function.Consumer; /** @@ -74,6 +75,13 @@ public final class PlayerService extends MediaBrowserServiceCompat { private final IBinder mBinder = new PlayerService.LocalBinder(this); + /** + * The parameter taken by this {@link Consumer} can be null to indicate the player is being + * stopped. + */ + @Nullable + private Consumer onPlayerStartedOrStopped = null; + //region Service lifecycle @Override @@ -127,7 +135,8 @@ public final class PlayerService extends MediaBrowserServiceCompat { // PlayerService using startForegroundService(), will have SHOULD_START_FOREGROUND_EXTRA, // to ensure startForeground() is called (otherwise Android will force-crash the app). if (intent.getBooleanExtra(SHOULD_START_FOREGROUND_EXTRA, false)) { - if (player == null) { + final boolean playerWasNull = (player == null); + if (playerWasNull) { // make sure the player exists, in case the service was resumed player = new Player(this, mediaSession, sessionConnector); } @@ -141,6 +150,13 @@ public final class PlayerService extends MediaBrowserServiceCompat { // shouldn't do anything. player.UIs().get(NotificationPlayerUi.class) .ifPresent(NotificationPlayerUi::createNotificationAndStartForeground); + + if (playerWasNull && onPlayerStartedOrStopped != null) { + // notify that a new player was created (but do it after creating the foreground + // notification just to make sure we don't incur, due to slowness, in + // "Context.startForegroundService() did not then call Service.startForeground()") + onPlayerStartedOrStopped.accept(player); + } } if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) @@ -204,6 +220,10 @@ public final class PlayerService extends MediaBrowserServiceCompat { private void cleanup() { if (player != null) { + if (onPlayerStartedOrStopped != null) { + // notify that the player is being destroyed + onPlayerStartedOrStopped.accept(null); + } player.destroy(); player = null; } @@ -279,9 +299,31 @@ public final class PlayerService extends MediaBrowserServiceCompat { public PlayerService getService() { return playerService.get(); } + } - public Player getPlayer() { - return playerService.get().player; + /** + * @return the current active player instance. May be null, since the player service can outlive + * the player e.g. to respond to Android Auto media browser queries. + */ + @Nullable + public Player getPlayer() { + return player; + } + + /** + * Sets the listener that will be called when the player is started or stopped. If a + * {@code null} listener is passed, then the current listener will be unset. The parameter taken + * by the {@link Consumer} can be null to indicate that the player is stopping. + * @param listener the listener to set or unset + */ + public void setPlayerListener(@Nullable final Consumer listener) { + this.onPlayerStartedOrStopped = listener; + if (listener != null) { + if (player == null) { + listener.accept(null); + } else { + listener.accept(player); + } } } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java index 8effe2f0e..549abc952 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java @@ -1,11 +1,48 @@ package org.schabi.newpipe.player.event; +import androidx.annotation.NonNull; + import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; +/** + * In addition to {@link PlayerServiceEventListener}, provides callbacks for service and player + * connections and disconnections. "Connected" here means that the service (resp. the + * player) is running and is bound to {@link org.schabi.newpipe.player.helper.PlayerHolder}. + * "Disconnected" means that either the service (resp. the player) was stopped completely, or that + * {@link org.schabi.newpipe.player.helper.PlayerHolder} is not bound. + */ public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener { - void onServiceConnected(Player player, - PlayerService playerService, - boolean playAfterConnect); + /** + * The player service just connected to {@link org.schabi.newpipe.player.helper.PlayerHolder}, + * but the player may not be active at this moment, e.g. in case the service is running to + * respond to Android Auto media browser queries without playing anything. + * {@link #onPlayerConnected(Player, boolean)} will be called right after this function if there + * is a player. + * + * @param playerService the newly connected player service + */ + void onServiceConnected(@NonNull PlayerService playerService); + + /** + * The player service is already connected and the player was just started. + * + * @param player the newly connected or started player + * @param playAfterConnect whether to open the video player in the video details fragment + */ + void onPlayerConnected(@NonNull Player player, boolean playAfterConnect); + + /** + * The player got disconnected, for one of these reasons: the player is getting closed while + * leaving the service open for future media browser queries, the service is stopping + * completely, or {@link org.schabi.newpipe.player.helper.PlayerHolder} is unbinding. + */ + void onPlayerDisconnected(); + + /** + * The service got disconnected from {@link org.schabi.newpipe.player.helper.PlayerHolder}, + * either because {@link org.schabi.newpipe.player.helper.PlayerHolder} is unbinding or because + * the service is stopping completely. + */ void onServiceDisconnected(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index b9afaa7c7..20a0f3766 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -24,6 +24,9 @@ import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.NavigationHelper; +import java.util.Optional; +import java.util.function.Consumer; + public final class PlayerHolder { private PlayerHolder() { @@ -45,7 +48,16 @@ public final class PlayerHolder { private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection(); private boolean bound; @Nullable private PlayerService playerService; - @Nullable private Player player; + + private Optional getPlayer() { + return Optional.ofNullable(playerService) + .flatMap(s -> Optional.ofNullable(s.getPlayer())); + } + + private Optional getPlayQueue() { + // player play queue might be null e.g. while player is starting + return getPlayer().flatMap(p -> Optional.ofNullable(p.getPlayQueue())); + } /** * Returns the current {@link PlayerType} of the {@link PlayerService} service, @@ -55,21 +67,15 @@ public final class PlayerHolder { */ @Nullable public PlayerType getType() { - if (player == null) { - return null; - } - return player.getPlayerType(); + return getPlayer().map(Player::getPlayerType).orElse(null); } public boolean isPlaying() { - if (player == null) { - return false; - } - return player.isPlaying(); + return getPlayer().map(Player::isPlaying).orElse(false); } public boolean isPlayerOpen() { - return player != null; + return getPlayer().isPresent(); } /** @@ -78,7 +84,7 @@ public final class PlayerHolder { * @return true only if the player is open and its play queue is ready (i.e. it is not null) */ public boolean isPlayQueueReady() { - return player != null && player.getPlayQueue() != null; + return getPlayQueue().isPresent(); } public boolean isBound() { @@ -86,18 +92,11 @@ public final class PlayerHolder { } public int getQueueSize() { - if (player == null || player.getPlayQueue() == null) { - // player play queue might be null e.g. while player is starting - return 0; - } - return player.getPlayQueue().size(); + return getPlayQueue().map(PlayQueue::size).orElse(0); } public int getQueuePosition() { - if (player == null || player.getPlayQueue() == null) { - return 0; - } - return player.getPlayQueue().getIndex(); + return getPlayQueue().map(PlayQueue::getIndex).orElse(0); } public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) { @@ -108,9 +107,10 @@ public final class PlayerHolder { } // Force reload data from service - if (player != null) { - listener.onServiceConnected(player, playerService, false); + if (playerService != null) { + listener.onServiceConnected(playerService); startPlayerListener(); + // ^ will call listener.onPlayerConnected() down the line if there is an active player } } @@ -181,11 +181,12 @@ public final class PlayerHolder { final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service; playerService = localBinder.getService(); - player = localBinder.getPlayer(); if (listener != null) { - listener.onServiceConnected(player, playerService, playAfterConnect); + listener.onServiceConnected(playerService); + getPlayer().ifPresent(p -> listener.onPlayerConnected(p, playAfterConnect)); } startPlayerListener(); + // ^ will call listener.onPlayerConnected() down the line if there is an active player // notify the main activity that binding the service has completed, so that it can // open the bottom mini-player @@ -229,25 +230,32 @@ public final class PlayerHolder { bound = false; stopPlayerListener(); playerService = null; - player = null; if (listener != null) { + listener.onPlayerDisconnected(); listener.onServiceDisconnected(); } } } private void startPlayerListener() { - if (player != null) { - player.setFragmentListener(internalListener); + if (playerService != null) { + // setting the player listener will take care of calling relevant callbacks if the + // player in the service is (not) already active, also see playerStateListener below + playerService.setPlayerListener(playerStateListener); } + getPlayer().ifPresent(p -> p.setFragmentListener(internalListener)); } private void stopPlayerListener() { - if (player != null) { - player.removeFragmentListener(internalListener); + if (playerService != null) { + playerService.setPlayerListener(null); } + getPlayer().ifPresent(p -> p.removeFragmentListener(internalListener)); } + /** + * This listener will be held by the players created by {@link PlayerService}. + */ private final PlayerServiceEventListener internalListener = new PlayerServiceEventListener() { @Override @@ -334,4 +342,23 @@ public final class PlayerHolder { unbind(getCommonContext()); } }; + + /** + * This listener will be held by bound {@link PlayerService}s to notify of the player starting + * or stopping. This is necessary since the service outlives the player e.g. to answer Android + * Auto media browser queries. + */ + private final Consumer playerStateListener = (@Nullable final Player player) -> { + if (listener != null) { + if (player == null) { + // player.fragmentListener=null is already done by player.stopActivityBinding(), + // which is called by player.destroy(), which is in turn called by PlayerService + // before setting its player to null + listener.onPlayerDisconnected(); + } else { + listener.onPlayerConnected(player, serviceConnection.playAfterConnect); + player.setFragmentListener(internalListener); + } + } + }; } From 94d4c21cc7ec018d86525c839525d9c2aad6fbca Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Tue, 18 Feb 2025 17:47:22 -0300 Subject: [PATCH 22/50] [#11930] @Test export_justUrls() --- .../playlist/LocalPlaylistFragmentTest.java | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java index 41e1f6091..12242ad1d 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.local.playlist; +import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS; import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; import androidx.annotation.NonNull; @@ -15,23 +16,45 @@ import java.util.stream.Stream; public class LocalPlaylistFragmentTest { @Test - public void youTubeTempPlaylist() { + public void export_asYouTubeTempPlaylist() { - Stream entityStream = List.of( + Stream entityStream = asStreamEntityStream( "https://www.youtube.com/watch?v=1" ,"https://www.youtube.com/watch?v=2" ,"https://www.youtube.com/watch?v=3" - ) - .stream() - .map(LocalPlaylistFragmentTest::newStreamEntity) - ; + ); String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, entityStream, null); Assert.assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url); } + @Test + public void export_justUrls() { + + Stream entityStream = asStreamEntityStream( + + "https://www.youtube.com/watch?v=1" + ,"https://www.youtube.com/watch?v=2" + ,"https://www.youtube.com/watch?v=3" + ); + + String exported = LocalPlaylistFragment.export(JUST_URLS, entityStream, null); + + Assert.assertEquals(""" + https://www.youtube.com/watch?v=1 + https://www.youtube.com/watch?v=2 + https://www.youtube.com/watch?v=3""", exported); + } + + @NonNull + private static Stream asStreamEntityStream(String... urls) { + + return Stream.of(urls) + .map(LocalPlaylistFragmentTest::newStreamEntity); + } + @NonNull static StreamEntity newStreamEntity(String url) { From c6b87cd3169056b1feb301d26763918ea0caa2e7 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Tue, 18 Feb 2025 20:59:13 -0300 Subject: [PATCH 23/50] [#11930] Making CheckStyle happy --- .../local/playlist/LocalPlaylistFragment.java | 69 ++++++++++++------- .../local/playlist/PlayListShareMode.java | 6 +- .../playlist/LocalPlaylistFragmentTest.java | 55 ++++++++------- 3 files changed, 74 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 3e99b01c4..354985b85 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -399,27 +399,39 @@ public class LocalPlaylistFragment extends BaseLocalListFragment Single.just(export( shareMode - , playlist.stream().map(PlaylistStreamEntry::getStreamEntity) - , context - ) - )) + .flatMapSingle(playlist -> Single.just(export( + + shareMode, + playlist.stream().map(PlaylistStreamEntry::getStreamEntity), + context + ))) .observeOn(AndroidSchedulers.mainThread()) - .subscribe( urlsText -> ShareUtils.shareText( context - , name - , shareMode == JUST_URLS ? urlsText - : context.getString(R.string.share_playlist_content_details, name, urlsText)) - , throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable)) - ); + .subscribe( + urlsText -> { + + final String content = shareMode == JUST_URLS + ? urlsText + : context.getString(R.string.share_playlist_content_details, + name, + urlsText + ); + + ShareUtils.shareText(context, name, content); + }, + throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable) + ) + ); } - static String export(PlayListShareMode shareMode, Stream entityStream, Context context) { + static String export(final PlayListShareMode shareMode, + final Stream entityStream, + final Context context) { - return switch(shareMode) { + return switch (shareMode) { case WITH_TITLES -> exportWithTitles(entityStream, context); case JUST_URLS -> exportJustUrls(entityStream); @@ -427,23 +439,27 @@ public class LocalPlaylistFragment extends BaseLocalListFragment entityStream, Context context) { + static String exportWithTitles(final Stream entityStream, final Context context) { return entityStream - .map(entity -> context.getString(R.string.video_details_list_item, entity.getTitle(), entity.getUrl())) + .map(entity -> context.getString(R.string.video_details_list_item, + entity.getTitle(), + entity.getUrl() + ) + ) .collect(Collectors.joining("\n")); } - static String exportJustUrls(Stream entityStream) { + static String exportJustUrls(final Stream entityStream) { return entityStream .map(StreamEntity::getUrl) .collect(Collectors.joining("\n")); } - static String exportAsYoutubeTempPlaylist(Stream entityStream) { + static String exportAsYoutubeTempPlaylist(final Stream entityStream) { - String videoIDs = entityStream + final String videoIDs = entityStream .map(entity -> getYouTubeId(entity.getUrl())) .collect(Collectors.joining(",")); @@ -451,15 +467,17 @@ public class LocalPlaylistFragment extends BaseLocalListFragment sharePlaylist(WITH_TITLES) ) - .setNeutralButton("Share as YouTube temporary playlist", (dialog, which) -> // TODO R.string.share_playlist_as_YouTube_temporary_playlist + // TODO R.string.share_playlist_as_YouTube_temporary_playlist + .setNeutralButton("Share as YouTube temporary playlist", (dialog, which) -> sharePlaylist(YOUTUBE_TEMP_PLAYLIST) ) .setNegativeButton(R.string.share_playlist_with_list, (dialog, which) -> diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java b/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java index 3de1effc9..f0433aba8 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java @@ -2,7 +2,7 @@ package org.schabi.newpipe.local.playlist; public enum PlayListShareMode { - JUST_URLS - ,WITH_TITLES - ,YOUTUBE_TEMP_PLAYLIST + JUST_URLS, + WITH_TITLES, + YOUTUBE_TEMP_PLAYLIST } diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java index 12242ad1d..170193155 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java @@ -10,37 +10,36 @@ import org.junit.Test; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.extractor.stream.StreamType; -import java.util.List; import java.util.stream.Stream; public class LocalPlaylistFragmentTest { @Test - public void export_asYouTubeTempPlaylist() { + public void exportAsYouTubeTempPlaylist() { - Stream entityStream = asStreamEntityStream( + final Stream entityStream = asStreamEntityStream( - "https://www.youtube.com/watch?v=1" - ,"https://www.youtube.com/watch?v=2" - ,"https://www.youtube.com/watch?v=3" + "https://www.youtube.com/watch?v=1", + "https://www.youtube.com/watch?v=2", + "https://www.youtube.com/watch?v=3" ); - String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, entityStream, null); + final String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, entityStream, null); Assert.assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url); } @Test - public void export_justUrls() { + public void exportJustUrls() { - Stream entityStream = asStreamEntityStream( + final Stream entityStream = asStreamEntityStream( - "https://www.youtube.com/watch?v=1" - ,"https://www.youtube.com/watch?v=2" - ,"https://www.youtube.com/watch?v=3" + "https://www.youtube.com/watch?v=1", + "https://www.youtube.com/watch?v=2", + "https://www.youtube.com/watch?v=3" ); - String exported = LocalPlaylistFragment.export(JUST_URLS, entityStream, null); + final String exported = LocalPlaylistFragment.export(JUST_URLS, entityStream, null); Assert.assertEquals(""" https://www.youtube.com/watch?v=1 @@ -49,30 +48,30 @@ public class LocalPlaylistFragmentTest { } @NonNull - private static Stream asStreamEntityStream(String... urls) { + private static Stream asStreamEntityStream(final String... urls) { return Stream.of(urls) .map(LocalPlaylistFragmentTest::newStreamEntity); } @NonNull - static StreamEntity newStreamEntity(String url) { + static StreamEntity newStreamEntity(final String url) { return new StreamEntity( - 0 - , 1 - , url - , "Title" - , StreamType.VIDEO_STREAM - , 100 - , "Uploader" - , null - , null - , null - , null - , null - , null + 0, + 1, + url, + "Title", + StreamType.VIDEO_STREAM, + 100, + "Uploader", + null, + null, + null, + null, + null, + null ); } } From acac50a1d1a66a4bd12e87573478d4532ed7ab50 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Wed, 19 Feb 2025 16:29:34 -0300 Subject: [PATCH 24/50] [#11930] Non-Youtube URLs should be ignored --- .../schabi/newpipe/local/playlist/LocalPlaylistFragment.java | 2 ++ .../newpipe/local/playlist/LocalPlaylistFragmentTest.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 354985b85..cb47c2199 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -65,6 +65,7 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -461,6 +462,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment getYouTubeId(entity.getUrl())) + .filter(Objects::nonNull) .collect(Collectors.joining(",")); return "http://www.youtube.com/watch_videos?video_ids=" + videoIDs; diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java index 170193155..fb93a9e86 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java @@ -20,7 +20,8 @@ public class LocalPlaylistFragmentTest { final Stream entityStream = asStreamEntityStream( "https://www.youtube.com/watch?v=1", - "https://www.youtube.com/watch?v=2", + "https://soundcloud.com/cautious-clayofficial/cold-war-2", // non-Youtube URLs should be + "https://www.youtube.com/watch?v=2", // ignored "https://www.youtube.com/watch?v=3" ); From b1f995a78c6ed792296d35969afcd118c97584a1 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Thu, 20 Feb 2025 16:26:03 -0300 Subject: [PATCH 25/50] [#11930] Playlist with more than 50 items --- app/build.gradle | 3 ++ .../local/playlist/LocalPlaylistFragment.java | 14 +++++-- .../playlist/LocalPlaylistFragmentTest.java | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d03bd64e3..2cdc952af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -255,6 +255,9 @@ dependencies { // HTTP client implementation "com.squareup.okhttp3:okhttp:4.12.0" + // Apache Commons Collections + implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.4' + // Media player implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}" implementation "com.google.android.exoplayer:exoplayer-dash:${exoPlayerVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index cb47c2199..008dc4a9c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -31,6 +31,7 @@ import androidx.viewbinding.ViewBinding; import com.evernote.android.state.State; +import org.apache.commons.collections4.queue.CircularFifoQueue; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.NewPipeDatabase; @@ -460,10 +461,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment entityStream) { - final String videoIDs = entityStream - .map(entity -> getYouTubeId(entity.getUrl())) - .filter(Objects::nonNull) - .collect(Collectors.joining(",")); + final CircularFifoQueue last50 = new CircularFifoQueue<>(50); + + entityStream + .map(entity -> getYouTubeId(entity.getUrl())) + .filter(Objects::nonNull) + .forEachOrdered(last50::add); + + final String videoIDs = last50.stream() + .collect(Collectors.joining(",")); return "http://www.youtube.com/watch_videos?video_ids=" + videoIDs; } diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java index fb93a9e86..eca798a24 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java @@ -10,6 +10,7 @@ import org.junit.Test; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.extractor.stream.StreamType; +import java.util.List; import java.util.stream.Stream; public class LocalPlaylistFragmentTest { @@ -30,6 +31,42 @@ public class LocalPlaylistFragmentTest { Assert.assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url); } + @Test + public void exportMoreThan50Items() { + /* + * Playlist has more than 50 items => take the last 50 + * (YouTube limitation) + */ + + final List ids = List.of( + + -1, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 + ); + + final Stream entityStream = ids.stream() + .map(id -> "https://www.youtube.com/watch?v=" + id) + .map(LocalPlaylistFragmentTest::newStreamEntity); + + final String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, entityStream, null); + + Assert.assertEquals( + + "http://www.youtube.com/watch_videos?video_ids=" + + "1,2,3,4,5,6,7,8,9,10," + + "11,12,13,14,15,16,17,18,19,20," + + "21,22,23,24,25,26,27,28,29,30," + + "31,32,33,34,35,36,37,38,39,40," + + "41,42,43,44,45,46,47,48,49,50", + + url + ); + } + @Test public void exportJustUrls() { From c9ec257a5e3d80f753aa112e8f71d15c287a8eb9 Mon Sep 17 00:00:00 2001 From: Thompson3142 <115718208+Thompson3142@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:38:58 +0100 Subject: [PATCH 26/50] Ugly fix for broken text colors in dark mode (#12035) * Ugly fix for broken text colors in dark mode * Add comment for clarification * Added error prevention * Update app/src/main/java/org/schabi/newpipe/MainActivity.java --------- Co-authored-by: Stypox --- .../main/java/org/schabi/newpipe/MainActivity.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 354e06587..b9592085b 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -38,6 +38,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.webkit.WebView; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.FrameLayout; @@ -140,6 +141,19 @@ public class MainActivity extends AppCompatActivity { ThemeHelper.setDayNightMode(this); ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); + // Fixes text color turning black in dark/black mode: + // https://github.com/TeamNewPipe/NewPipe/issues/12016 + // For further reference see: https://issuetracker.google.com/issues/37124582 + if (DeviceUtils.supportsWebView()) { + try { + new WebView(this); + } catch (final Throwable e) { + if (DEBUG) { + Log.e(TAG, "Failed to create WebView", e); + } + } + } + assureCorrectAppLanguage(this); super.onCreate(savedInstanceState); From 49b71942ad9b7027a54434a6b1d72887ebaf0975 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 24 Feb 2025 14:21:05 +0100 Subject: [PATCH 27/50] Fix style and add comment about null player --- .../main/java/org/schabi/newpipe/player/PlayerService.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index af6cf2467..1888bce01 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -319,11 +319,8 @@ public final class PlayerService extends MediaBrowserServiceCompat { public void setPlayerListener(@Nullable final Consumer listener) { this.onPlayerStartedOrStopped = listener; if (listener != null) { - if (player == null) { - listener.accept(null); - } else { - listener.accept(player); - } + // if there is no player, then `null` will be sent here, to ensure the state is synced + listener.accept(player); } } //endregion From 24bb71a23f0df82866f7525f5def7e638ae6f6cd Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Mon, 24 Feb 2025 19:22:36 -0300 Subject: [PATCH 28/50] [#11930] Making it more efficient: Reverse iteration + limit(50) + reverse --- .../local/playlist/LocalPlaylistFragment.java | 45 ++++++++++++------- .../playlist/LocalPlaylistFragmentTest.java | 38 +++++++++++----- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 008dc4a9c..e8d8573a1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.local.playlist; +import static com.google.common.collect.Streams.stream; +import static org.apache.commons.collections4.IterableUtils.reversedIterable; import static org.schabi.newpipe.error.ErrorUtil.showUiErrorSnackbar; import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS; @@ -7,6 +9,8 @@ import static org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES; import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout; +import static java.util.Collections.reverse; + import android.content.Context; import android.os.Bundle; import android.os.Parcelable; @@ -30,7 +34,9 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.viewbinding.ViewBinding; import com.evernote.android.state.State; +import com.google.common.collect.Streams; +import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.queue.CircularFifoQueue; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; @@ -408,7 +414,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment Single.just(export( shareMode, - playlist.stream().map(PlaylistStreamEntry::getStreamEntity), + playlist, context ))) .observeOn(AndroidSchedulers.mainThread()) @@ -430,20 +436,21 @@ public class LocalPlaylistFragment extends BaseLocalListFragment entityStream, + final List playlist, final Context context) { return switch (shareMode) { - case WITH_TITLES -> exportWithTitles(entityStream, context); - case JUST_URLS -> exportJustUrls(entityStream); - case YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(entityStream); + case WITH_TITLES -> exportWithTitles(playlist, context); + case JUST_URLS -> exportJustUrls(playlist); + case YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(playlist); }; } - static String exportWithTitles(final Stream entityStream, final Context context) { + static String exportWithTitles(final List playlist, final Context context) { - return entityStream + return playlist.stream() + .map(PlaylistStreamEntry::getStreamEntity) .map(entity -> context.getString(R.string.video_details_list_item, entity.getTitle(), entity.getUrl() @@ -452,26 +459,30 @@ public class LocalPlaylistFragment extends BaseLocalListFragment entityStream) { + static String exportJustUrls(final List playlist) { - return entityStream + return playlist.stream() + .map(PlaylistStreamEntry::getStreamEntity) .map(StreamEntity::getUrl) .collect(Collectors.joining("\n")); } - static String exportAsYoutubeTempPlaylist(final Stream entityStream) { + static String exportAsYoutubeTempPlaylist(final List playlist) { - final CircularFifoQueue last50 = new CircularFifoQueue<>(50); - - entityStream + final List videoIDs = + stream(reversedIterable(playlist)) + .map(PlaylistStreamEntry::getStreamEntity) .map(entity -> getYouTubeId(entity.getUrl())) .filter(Objects::nonNull) - .forEachOrdered(last50::add); + .limit(50) + .collect(Collectors.toList()); - final String videoIDs = last50.stream() - .collect(Collectors.joining(",")); + reverse(videoIDs); - return "http://www.youtube.com/watch_videos?video_ids=" + videoIDs; + final String commaSeparatedVideoIDs = videoIDs.stream() + .collect(Collectors.joining(",")); + + return "http://www.youtube.com/watch_videos?video_ids=" + commaSeparatedVideoIDs; } /** diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java index eca798a24..1d5d4a6a0 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import org.junit.Assert; import org.junit.Test; +import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.extractor.stream.StreamType; @@ -18,7 +19,7 @@ public class LocalPlaylistFragmentTest { @Test public void exportAsYouTubeTempPlaylist() { - final Stream entityStream = asStreamEntityStream( + final List playlist = asPlaylist( "https://www.youtube.com/watch?v=1", "https://soundcloud.com/cautious-clayofficial/cold-war-2", // non-Youtube URLs should be @@ -26,7 +27,7 @@ public class LocalPlaylistFragmentTest { "https://www.youtube.com/watch?v=3" ); - final String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, entityStream, null); + final String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, playlist, null); Assert.assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url); } @@ -48,11 +49,13 @@ public class LocalPlaylistFragmentTest { 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 ); - final Stream entityStream = ids.stream() - .map(id -> "https://www.youtube.com/watch?v=" + id) - .map(LocalPlaylistFragmentTest::newStreamEntity); + final List playlist = asPlaylist( - final String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, entityStream, null); + ids.stream() + .map(id -> "https://www.youtube.com/watch?v=" + id) + ); + + final String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, playlist, null); Assert.assertEquals( @@ -70,14 +73,14 @@ public class LocalPlaylistFragmentTest { @Test public void exportJustUrls() { - final Stream entityStream = asStreamEntityStream( + final List playlist = asPlaylist( "https://www.youtube.com/watch?v=1", "https://www.youtube.com/watch?v=2", "https://www.youtube.com/watch?v=3" ); - final String exported = LocalPlaylistFragment.export(JUST_URLS, entityStream, null); + final String exported = LocalPlaylistFragment.export(JUST_URLS, playlist, null); Assert.assertEquals(""" https://www.youtube.com/watch?v=1 @@ -86,10 +89,23 @@ public class LocalPlaylistFragmentTest { } @NonNull - private static Stream asStreamEntityStream(final String... urls) { + static List asPlaylist(final String... urls) { - return Stream.of(urls) - .map(LocalPlaylistFragmentTest::newStreamEntity); + return asPlaylist(Stream.of(urls)); + } + + @NonNull + static List asPlaylist(final Stream urls) { + + return urls + .map(LocalPlaylistFragmentTest::newPlaylistStreamEntry) + .toList(); + } + + @NonNull + private static PlaylistStreamEntry newPlaylistStreamEntry(final String url) { + + return new PlaylistStreamEntry(newStreamEntity(url), 0, 0, 0); } @NonNull From 76a02d5858d99e72a410431e54ad3a0e055c7285 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Mon, 24 Feb 2025 20:16:40 -0300 Subject: [PATCH 29/50] [#11930] Extracting to a separate file --- .../local/playlist/ExportPlaylist.java | 90 +++++++++++++++++++ .../local/playlist/LocalPlaylistFragment.java | 75 +--------------- ...gmentTest.java => ExportPlaylistTest.java} | 11 +-- 3 files changed, 97 insertions(+), 79 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.java rename app/src/test/java/org/schabi/newpipe/local/playlist/{LocalPlaylistFragmentTest.java => ExportPlaylistTest.java} (89%) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.java b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.java new file mode 100644 index 000000000..94002dd5b --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.java @@ -0,0 +1,90 @@ +package org.schabi.newpipe.local.playlist; + +import static com.google.common.collect.Streams.stream; +import static org.apache.commons.collections4.IterableUtils.reversedIterable; +import static java.util.Collections.reverse; + +import android.content.Context; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; +import org.schabi.newpipe.database.stream.model.StreamEntity; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import okhttp3.HttpUrl; + + + +final class ExportPlaylist { + + private ExportPlaylist() { + } + + static String export(final PlayListShareMode shareMode, + final List playlist, + final Context context) { + + return switch (shareMode) { + + case WITH_TITLES -> exportWithTitles(playlist, context); + case JUST_URLS -> exportJustUrls(playlist); + case YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(playlist); + }; + } + + static String exportWithTitles(final List playlist, + final Context context) { + + return playlist.stream() + .map(PlaylistStreamEntry::getStreamEntity) + .map(entity -> context.getString(R.string.video_details_list_item, + entity.getTitle(), + entity.getUrl() + ) + ) + .collect(Collectors.joining("\n")); + } + + static String exportJustUrls(final List playlist) { + + return playlist.stream() + .map(PlaylistStreamEntry::getStreamEntity) + .map(StreamEntity::getUrl) + .collect(Collectors.joining("\n")); + } + + static String exportAsYoutubeTempPlaylist(final List playlist) { + + final List videoIDs = + stream(reversedIterable(playlist)) + .map(PlaylistStreamEntry::getStreamEntity) + .map(entity -> getYouTubeId(entity.getUrl())) + .filter(Objects::nonNull) + .limit(50) + .collect(Collectors.toList()); + + reverse(videoIDs); + + final String commaSeparatedVideoIDs = videoIDs.stream() + .collect(Collectors.joining(",")); + + return "http://www.youtube.com/watch_videos?video_ids=" + commaSeparatedVideoIDs; + } + + /** + * Gets the video id from a YouTube URL. + * + * @param url YouTube URL + * @return the video id + */ + static String getYouTubeId(final String url) { + + final HttpUrl httpUrl = HttpUrl.parse(url); + + return httpUrl == null ? null + : httpUrl.queryParameter("v"); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index e8d8573a1..812d9fb38 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -1,15 +1,13 @@ package org.schabi.newpipe.local.playlist; -import static com.google.common.collect.Streams.stream; -import static org.apache.commons.collections4.IterableUtils.reversedIterable; import static org.schabi.newpipe.error.ErrorUtil.showUiErrorSnackbar; import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.local.playlist.ExportPlaylist.export; import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS; import static org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES; import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout; -import static java.util.Collections.reverse; import android.content.Context; import android.os.Bundle; @@ -34,10 +32,6 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.viewbinding.ViewBinding; import com.evernote.android.state.State; -import com.google.common.collect.Streams; - -import org.apache.commons.collections4.IterableUtils; -import org.apache.commons.collections4.queue.CircularFifoQueue; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.NewPipeDatabase; @@ -72,17 +66,14 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import java.util.stream.Stream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; -import okhttp3.HttpUrl; public class LocalPlaylistFragment extends BaseLocalListFragment, Void> implements PlaylistControlViewHolder, DebounceSavable { @@ -435,70 +426,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist, - final Context context) { - - return switch (shareMode) { - - case WITH_TITLES -> exportWithTitles(playlist, context); - case JUST_URLS -> exportJustUrls(playlist); - case YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(playlist); - }; - } - - static String exportWithTitles(final List playlist, final Context context) { - - return playlist.stream() - .map(PlaylistStreamEntry::getStreamEntity) - .map(entity -> context.getString(R.string.video_details_list_item, - entity.getTitle(), - entity.getUrl() - ) - ) - .collect(Collectors.joining("\n")); - } - - static String exportJustUrls(final List playlist) { - - return playlist.stream() - .map(PlaylistStreamEntry::getStreamEntity) - .map(StreamEntity::getUrl) - .collect(Collectors.joining("\n")); - } - - static String exportAsYoutubeTempPlaylist(final List playlist) { - - final List videoIDs = - stream(reversedIterable(playlist)) - .map(PlaylistStreamEntry::getStreamEntity) - .map(entity -> getYouTubeId(entity.getUrl())) - .filter(Objects::nonNull) - .limit(50) - .collect(Collectors.toList()); - - reverse(videoIDs); - - final String commaSeparatedVideoIDs = videoIDs.stream() - .collect(Collectors.joining(",")); - - return "http://www.youtube.com/watch_videos?video_ids=" + commaSeparatedVideoIDs; - } - - /** - * Gets the video id from a YouTube URL. - * - * @param url YouTube URL - * @return the video id - */ - static String getYouTubeId(final String url) { - - final HttpUrl httpUrl = HttpUrl.parse(url); - - return httpUrl == null ? null - : httpUrl.queryParameter("v"); - } - public void removeWatchedStreams(final boolean removePartiallyWatched) { if (isRewritingPlaylist) { return; diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.java similarity index 89% rename from app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java rename to app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.java index 1d5d4a6a0..f90fd78bb 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.local.playlist; +import static org.schabi.newpipe.local.playlist.ExportPlaylist.export; import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS; import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; @@ -14,7 +15,7 @@ import org.schabi.newpipe.extractor.stream.StreamType; import java.util.List; import java.util.stream.Stream; -public class LocalPlaylistFragmentTest { +public class ExportPlaylistTest { @Test public void exportAsYouTubeTempPlaylist() { @@ -27,7 +28,7 @@ public class LocalPlaylistFragmentTest { "https://www.youtube.com/watch?v=3" ); - final String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, playlist, null); + final String url = export(YOUTUBE_TEMP_PLAYLIST, playlist, null); Assert.assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url); } @@ -55,7 +56,7 @@ public class LocalPlaylistFragmentTest { .map(id -> "https://www.youtube.com/watch?v=" + id) ); - final String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, playlist, null); + final String url = export(YOUTUBE_TEMP_PLAYLIST, playlist, null); Assert.assertEquals( @@ -80,7 +81,7 @@ public class LocalPlaylistFragmentTest { "https://www.youtube.com/watch?v=3" ); - final String exported = LocalPlaylistFragment.export(JUST_URLS, playlist, null); + final String exported = export(JUST_URLS, playlist, null); Assert.assertEquals(""" https://www.youtube.com/watch?v=1 @@ -98,7 +99,7 @@ public class LocalPlaylistFragmentTest { static List asPlaylist(final Stream urls) { return urls - .map(LocalPlaylistFragmentTest::newPlaylistStreamEntry) + .map(ExportPlaylistTest::newPlaylistStreamEntry) .toList(); } From 998d84de6c0f1ea707f9efda1ba1ac3a8bab4229 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Mon, 24 Feb 2025 20:57:06 -0300 Subject: [PATCH 30/50] [#11930] Converting to Kotlin --- .../local/playlist/ExportPlaylist.java | 90 ------------ .../newpipe/local/playlist/ExportPlaylist.kt | 72 ++++++++++ .../local/playlist/LocalPlaylistFragment.java | 2 +- .../local/playlist/ExportPlaylistTest.java | 132 ------------------ .../local/playlist/ExportPlaylistTest.kt | 110 +++++++++++++++ 5 files changed, 183 insertions(+), 223 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.java create mode 100644 app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt delete mode 100644 app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.java create mode 100644 app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.java b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.java deleted file mode 100644 index 94002dd5b..000000000 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.schabi.newpipe.local.playlist; - -import static com.google.common.collect.Streams.stream; -import static org.apache.commons.collections4.IterableUtils.reversedIterable; -import static java.util.Collections.reverse; - -import android.content.Context; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; -import org.schabi.newpipe.database.stream.model.StreamEntity; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import okhttp3.HttpUrl; - - - -final class ExportPlaylist { - - private ExportPlaylist() { - } - - static String export(final PlayListShareMode shareMode, - final List playlist, - final Context context) { - - return switch (shareMode) { - - case WITH_TITLES -> exportWithTitles(playlist, context); - case JUST_URLS -> exportJustUrls(playlist); - case YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(playlist); - }; - } - - static String exportWithTitles(final List playlist, - final Context context) { - - return playlist.stream() - .map(PlaylistStreamEntry::getStreamEntity) - .map(entity -> context.getString(R.string.video_details_list_item, - entity.getTitle(), - entity.getUrl() - ) - ) - .collect(Collectors.joining("\n")); - } - - static String exportJustUrls(final List playlist) { - - return playlist.stream() - .map(PlaylistStreamEntry::getStreamEntity) - .map(StreamEntity::getUrl) - .collect(Collectors.joining("\n")); - } - - static String exportAsYoutubeTempPlaylist(final List playlist) { - - final List videoIDs = - stream(reversedIterable(playlist)) - .map(PlaylistStreamEntry::getStreamEntity) - .map(entity -> getYouTubeId(entity.getUrl())) - .filter(Objects::nonNull) - .limit(50) - .collect(Collectors.toList()); - - reverse(videoIDs); - - final String commaSeparatedVideoIDs = videoIDs.stream() - .collect(Collectors.joining(",")); - - return "http://www.youtube.com/watch_videos?video_ids=" + commaSeparatedVideoIDs; - } - - /** - * Gets the video id from a YouTube URL. - * - * @param url YouTube URL - * @return the video id - */ - static String getYouTubeId(final String url) { - - final HttpUrl httpUrl = HttpUrl.parse(url); - - return httpUrl == null ? null - : httpUrl.queryParameter("v"); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt new file mode 100644 index 000000000..953cb7d17 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt @@ -0,0 +1,72 @@ +package org.schabi.newpipe.local.playlist + +import android.content.Context +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.schabi.newpipe.R +import org.schabi.newpipe.database.playlist.PlaylistStreamEntry +import org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS +import org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES +import org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST +import java.util.Objects.nonNull + +fun export( + shareMode: PlayListShareMode, + playlist: List, + context: Context +): String { + return when (shareMode) { + WITH_TITLES -> exportWithTitles(playlist, context) + JUST_URLS -> exportJustUrls(playlist) + YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(playlist) + } +} + +fun exportWithTitles( + playlist: List, + context: Context +): String { + + return playlist.asSequence() + .map { it.streamEntity } + .map { entity -> + context.getString( + R.string.video_details_list_item, + entity.title, + entity.url + ) + } + .joinToString(separator = "\n") +} + +fun exportJustUrls(playlist: List): String { + + return playlist.asSequence() + .map { it.streamEntity.url } + .joinToString(separator = "\n") +} + +fun exportAsYoutubeTempPlaylist(playlist: List): String { + + val videoIDs = playlist.asReversed().asSequence() + .map { it.streamEntity } + .map { getYouTubeId(it.url) } + .filter(::nonNull) + .take(50) + .toList() + .asReversed() + .joinToString(separator = ",") + + return "http://www.youtube.com/watch_videos?video_ids=$videoIDs" +} + +/** + * Gets the video id from a YouTube URL. + * + * @param url YouTube URL + * @return the video id + */ +fun getYouTubeId(url: String): String? { + val httpUrl = url.toHttpUrlOrNull() + + return httpUrl?.queryParameter("v") +} diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 812d9fb38..61c12bfd4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -2,7 +2,7 @@ package org.schabi.newpipe.local.playlist; import static org.schabi.newpipe.error.ErrorUtil.showUiErrorSnackbar; import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.local.playlist.ExportPlaylist.export; +import static org.schabi.newpipe.local.playlist.ExportPlaylistKt.export; import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS; import static org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES; import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.java deleted file mode 100644 index f90fd78bb..000000000 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.schabi.newpipe.local.playlist; - -import static org.schabi.newpipe.local.playlist.ExportPlaylist.export; -import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS; -import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; - -import androidx.annotation.NonNull; - -import org.junit.Assert; -import org.junit.Test; -import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; -import org.schabi.newpipe.database.stream.model.StreamEntity; -import org.schabi.newpipe.extractor.stream.StreamType; - -import java.util.List; -import java.util.stream.Stream; - -public class ExportPlaylistTest { - - @Test - public void exportAsYouTubeTempPlaylist() { - - final List playlist = asPlaylist( - - "https://www.youtube.com/watch?v=1", - "https://soundcloud.com/cautious-clayofficial/cold-war-2", // non-Youtube URLs should be - "https://www.youtube.com/watch?v=2", // ignored - "https://www.youtube.com/watch?v=3" - ); - - final String url = export(YOUTUBE_TEMP_PLAYLIST, playlist, null); - - Assert.assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url); - } - - @Test - public void exportMoreThan50Items() { - /* - * Playlist has more than 50 items => take the last 50 - * (YouTube limitation) - */ - - final List ids = List.of( - - -1, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 - ); - - final List playlist = asPlaylist( - - ids.stream() - .map(id -> "https://www.youtube.com/watch?v=" + id) - ); - - final String url = export(YOUTUBE_TEMP_PLAYLIST, playlist, null); - - Assert.assertEquals( - - "http://www.youtube.com/watch_videos?video_ids=" - + "1,2,3,4,5,6,7,8,9,10," - + "11,12,13,14,15,16,17,18,19,20," - + "21,22,23,24,25,26,27,28,29,30," - + "31,32,33,34,35,36,37,38,39,40," - + "41,42,43,44,45,46,47,48,49,50", - - url - ); - } - - @Test - public void exportJustUrls() { - - final List playlist = asPlaylist( - - "https://www.youtube.com/watch?v=1", - "https://www.youtube.com/watch?v=2", - "https://www.youtube.com/watch?v=3" - ); - - final String exported = export(JUST_URLS, playlist, null); - - Assert.assertEquals(""" - https://www.youtube.com/watch?v=1 - https://www.youtube.com/watch?v=2 - https://www.youtube.com/watch?v=3""", exported); - } - - @NonNull - static List asPlaylist(final String... urls) { - - return asPlaylist(Stream.of(urls)); - } - - @NonNull - static List asPlaylist(final Stream urls) { - - return urls - .map(ExportPlaylistTest::newPlaylistStreamEntry) - .toList(); - } - - @NonNull - private static PlaylistStreamEntry newPlaylistStreamEntry(final String url) { - - return new PlaylistStreamEntry(newStreamEntity(url), 0, 0, 0); - } - - @NonNull - static StreamEntity newStreamEntity(final String url) { - - return new StreamEntity( - - 0, - 1, - url, - "Title", - StreamType.VIDEO_STREAM, - 100, - "Uploader", - null, - null, - null, - null, - null, - null - ); - } -} diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt new file mode 100644 index 000000000..42622fe9c --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt @@ -0,0 +1,110 @@ +package org.schabi.newpipe.local.playlist + +import android.content.Context +import org.junit.Assert.assertEquals +import org.junit.Test +import org.mockito.Mockito.mock +import org.schabi.newpipe.database.playlist.PlaylistStreamEntry +import org.schabi.newpipe.database.stream.model.StreamEntity +import org.schabi.newpipe.extractor.stream.StreamType +import org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS +import org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST +import java.util.stream.Stream + +class ExportPlaylistTest { + + @Test + fun exportAsYouTubeTempPlaylist() { + val playlist = asPlaylist( + "https://www.youtube.com/watch?v=1", + "https://soundcloud.com/cautious-clayofficial/cold-war-2", // non-Youtube URLs should be ignored + "https://www.youtube.com/watch?v=2", + "https://www.youtube.com/watch?v=3" + ) + + val url = export(YOUTUBE_TEMP_PLAYLIST, playlist, mock(Context::class.java)) + + assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url) + } + + @Test + fun exportMoreThan50Items() { + /* + * Playlist has more than 50 items => take the last 50 + * (YouTube limitation) + */ + + val ids = listOf( + -1, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 + ) + + val playlist = asPlaylist( + ids.stream() + .map { id: Int -> "https://www.youtube.com/watch?v=$id" } + ) + + val url = export(YOUTUBE_TEMP_PLAYLIST, playlist, mock(Context::class.java)) + + assertEquals( + "http://www.youtube.com/watch_videos?video_ids=" + + "1,2,3,4,5,6,7,8,9,10," + + "11,12,13,14,15,16,17,18,19,20," + + "21,22,23,24,25,26,27,28,29,30," + + "31,32,33,34,35,36,37,38,39,40," + + "41,42,43,44,45,46,47,48,49,50", + + url + ) + } + + @Test + fun exportJustUrls() { + val playlist = asPlaylist( + "https://www.youtube.com/watch?v=1", + "https://www.youtube.com/watch?v=2", + "https://www.youtube.com/watch?v=3" + ) + + val exported = export(JUST_URLS, playlist, mock(Context::class.java)) + + assertEquals( + """ + https://www.youtube.com/watch?v=1 + https://www.youtube.com/watch?v=2 + https://www.youtube.com/watch?v=3 + """.trimIndent(), + exported + ) + } +} + +fun asPlaylist(vararg urls: String): List { + return asPlaylist(Stream.of(*urls)) +} + +fun asPlaylist(urls: Stream): List { + return urls + .map { url: String -> newPlaylistStreamEntry(url) } + .toList() +} + +fun newPlaylistStreamEntry(url: String): PlaylistStreamEntry { + return PlaylistStreamEntry(newStreamEntity(url), 0, 0, 0) +} + +fun newStreamEntity(url: String): StreamEntity { + return StreamEntity( + 0, + 1, + url, + "Title", + StreamType.VIDEO_STREAM, + 100, + "Uploader" + ) +} From 3c7b026d7d9afbb90ea41fd99bd093396d0e1e91 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Tue, 25 Feb 2025 20:23:07 -0300 Subject: [PATCH 31/50] [#11930] Updating javadoc --- .../local/playlist/LocalPlaylistFragment.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 61c12bfd4..de1aabb9c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -388,16 +388,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment Date: Wed, 26 Feb 2025 21:25:39 -0300 Subject: [PATCH 32/50] [#11930] Removing Apache Commons Collections It's no longer needed after the conversion to Kotlin. --- app/build.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2cdc952af..d03bd64e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -255,9 +255,6 @@ dependencies { // HTTP client implementation "com.squareup.okhttp3:okhttp:4.12.0" - // Apache Commons Collections - implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.4' - // Media player implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}" implementation "com.google.android.exoplayer:exoplayer-dash:${exoPlayerVersion}" From f0c89494dd597a65bfb1d7846c940f0f02e7d085 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 27 Feb 2025 09:09:08 +0530 Subject: [PATCH 33/50] Fix stream notification grouping --- .../local/feed/notifications/NotificationHelper.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 8ea89368d..646596884 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -88,7 +88,7 @@ class NotificationHelper(val context: Context) { // Show individual stream notifications, set channel icon only if there is actually // one - showStreamNotifications(newStreams, data.serviceId, bitmap) + showStreamNotifications(newStreams, data.serviceId, data.url, bitmap) // Show summary notification manager.notify(data.pseudoId, summaryBuilder.build()) @@ -97,7 +97,7 @@ class NotificationHelper(val context: Context) { override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { // Show individual stream notifications - showStreamNotifications(newStreams, data.serviceId, null) + showStreamNotifications(newStreams, data.serviceId, data.url, null) // Show summary notification manager.notify(data.pseudoId, summaryBuilder.build()) iconLoadingTargets.remove(this) // allow it to be garbage-collected @@ -118,10 +118,11 @@ class NotificationHelper(val context: Context) { private fun showStreamNotifications( newStreams: List, serviceId: Int, + channelUrl: String, channelIcon: Bitmap? ) { for (stream in newStreams) { - val notification = createStreamNotification(stream, serviceId, channelIcon) + val notification = createStreamNotification(stream, serviceId, channelUrl, channelIcon) manager.notify(stream.url.hashCode(), notification) } } @@ -129,6 +130,7 @@ class NotificationHelper(val context: Context) { private fun createStreamNotification( item: StreamInfoItem, serviceId: Int, + channelUrl: String, channelIcon: Bitmap? ): Notification { return NotificationCompat.Builder( @@ -139,7 +141,7 @@ class NotificationHelper(val context: Context) { .setLargeIcon(channelIcon) .setContentTitle(item.name) .setContentText(item.uploaderName) - .setGroup(item.uploaderUrl) + .setGroup(channelUrl) .setColor(ContextCompat.getColor(context, R.color.ic_launcher_background)) .setColorized(true) .setAutoCancel(true) From d81244e77ce6f311520b4db63ba1984c59290a3c Mon Sep 17 00:00:00 2001 From: tfga Date: Mon, 10 Mar 2025 19:11:20 -0300 Subject: [PATCH 34/50] YT temp playlist URL: http => https Co-authored-by: Stypox --- .../java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt index 953cb7d17..226d4a8b0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt @@ -56,7 +56,7 @@ fun exportAsYoutubeTempPlaylist(playlist: List): String { .asReversed() .joinToString(separator = ",") - return "http://www.youtube.com/watch_videos?video_ids=$videoIDs" + return "https://www.youtube.com/watch_videos?video_ids=$videoIDs" } /** From 10110397fd3bf3cd97ce8c037e194428f021a9c8 Mon Sep 17 00:00:00 2001 From: Miles Krell Date: Mon, 10 Mar 2025 22:01:09 -0400 Subject: [PATCH 35/50] Use display name instead of only the language --- app/src/main/java/org/schabi/newpipe/util/Localization.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8f8ba596f..6830e390b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -308,7 +308,7 @@ public final class Localization { *
    *
  • English (original)
  • *
  • English (descriptive)
  • - *
  • Spanish (dubbed)
  • + *
  • Spanish (Spain) (dubbed)
  • *
* * @param context the context used to get the app language @@ -318,7 +318,7 @@ public final class Localization { public static String audioTrackName(@NonNull final Context context, final AudioStream track) { final String name; if (track.getAudioLocale() != null) { - name = track.getAudioLocale().getDisplayLanguage(getAppLocale(context)); + name = track.getAudioLocale().getDisplayName(); } else if (track.getAudioTrackName() != null) { name = track.getAudioTrackName(); } else { From c28478ae53a0380f0f053440c187769dd11219d2 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Tue, 11 Mar 2025 19:44:04 -0300 Subject: [PATCH 36/50] getYouTubeId(): Changing implementation to use YoutubeStreamLinkHandler (PR review from @Stypox) --- .../newpipe/local/playlist/ExportPlaylist.kt | 14 +++---- .../local/playlist/ExportPlaylistTest.kt | 38 ++++++++----------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt index 226d4a8b0..72540d9d2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt @@ -1,13 +1,13 @@ package org.schabi.newpipe.local.playlist import android.content.Context -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.schabi.newpipe.R import org.schabi.newpipe.database.playlist.PlaylistStreamEntry +import org.schabi.newpipe.extractor.exceptions.ParsingException +import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory import org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS import org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES import org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST -import java.util.Objects.nonNull fun export( shareMode: PlayListShareMode, @@ -48,9 +48,8 @@ fun exportJustUrls(playlist: List): String { fun exportAsYoutubeTempPlaylist(playlist: List): String { val videoIDs = playlist.asReversed().asSequence() - .map { it.streamEntity } - .map { getYouTubeId(it.url) } - .filter(::nonNull) + .map { it.streamEntity.url } + .mapNotNull(::getYouTubeId) .take(50) .toList() .asReversed() @@ -59,6 +58,8 @@ fun exportAsYoutubeTempPlaylist(playlist: List): String { return "https://www.youtube.com/watch_videos?video_ids=$videoIDs" } +val linkHandler: YoutubeStreamLinkHandlerFactory = YoutubeStreamLinkHandlerFactory.getInstance() + /** * Gets the video id from a YouTube URL. * @@ -66,7 +67,6 @@ fun exportAsYoutubeTempPlaylist(playlist: List): String { * @return the video id */ fun getYouTubeId(url: String): String? { - val httpUrl = url.toHttpUrlOrNull() - return httpUrl?.queryParameter("v") + return try { linkHandler.getId(url) } catch (e: ParsingException) { null } } diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt index 42622fe9c..50db490bd 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt @@ -16,15 +16,21 @@ class ExportPlaylistTest { @Test fun exportAsYouTubeTempPlaylist() { val playlist = asPlaylist( - "https://www.youtube.com/watch?v=1", + "https://www.youtube.com/watch?v=10000000000", "https://soundcloud.com/cautious-clayofficial/cold-war-2", // non-Youtube URLs should be ignored - "https://www.youtube.com/watch?v=2", - "https://www.youtube.com/watch?v=3" + "https://www.youtube.com/watch?v=20000000000", + "https://www.youtube.com/watch?v=30000000000" ) val url = export(YOUTUBE_TEMP_PLAYLIST, playlist, mock(Context::class.java)) - assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url) + assertEquals( + "https://www.youtube.com/watch_videos?video_ids=" + + "10000000000," + + "20000000000," + + "30000000000", + url + ) } @Test @@ -34,30 +40,18 @@ class ExportPlaylistTest { * (YouTube limitation) */ - val ids = listOf( - -1, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 - ) - val playlist = asPlaylist( - ids.stream() - .map { id: Int -> "https://www.youtube.com/watch?v=$id" } + (10..70) + .map { id -> "https://www.youtube.com/watch?v=aaaaaaaaa$id" } // YouTube video IDs are 11 characters long + .stream() ) val url = export(YOUTUBE_TEMP_PLAYLIST, playlist, mock(Context::class.java)) - assertEquals( - "http://www.youtube.com/watch_videos?video_ids=" + - "1,2,3,4,5,6,7,8,9,10," + - "11,12,13,14,15,16,17,18,19,20," + - "21,22,23,24,25,26,27,28,29,30," + - "31,32,33,34,35,36,37,38,39,40," + - "41,42,43,44,45,46,47,48,49,50", + val videoIDs = (21..70).map { id -> "aaaaaaaaa$id" }.joinToString(",") + assertEquals( + "https://www.youtube.com/watch_videos?video_ids=$videoIDs", url ) } From f96b8f7b2a68ef0a17de762487e06c29fb315554 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Tue, 11 Mar 2025 20:19:54 -0300 Subject: [PATCH 37/50] Comment: maximum length of 50 items (PR review from @Stypox) --- .../java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt index 72540d9d2..cb619c7cc 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt @@ -50,7 +50,7 @@ fun exportAsYoutubeTempPlaylist(playlist: List): String { val videoIDs = playlist.asReversed().asSequence() .map { it.streamEntity.url } .mapNotNull(::getYouTubeId) - .take(50) + .take(50) // YouTube limitation: temp playlists can't have more than 50 items .toList() .asReversed() .joinToString(separator = ",") From 8830e87242367124d2618db9a56376761ffe20e1 Mon Sep 17 00:00:00 2001 From: tfga Date: Tue, 11 Mar 2025 20:35:18 -0300 Subject: [PATCH 38/50] YouTube video IDs are 11 characters long Co-authored-by: Stypox --- .../org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt index 50db490bd..41577742d 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt @@ -59,9 +59,9 @@ class ExportPlaylistTest { @Test fun exportJustUrls() { val playlist = asPlaylist( - "https://www.youtube.com/watch?v=1", - "https://www.youtube.com/watch?v=2", - "https://www.youtube.com/watch?v=3" + "https://www.youtube.com/watch?v=10000000000", + "https://www.youtube.com/watch?v=20000000000", + "https://www.youtube.com/watch?v=30000000000" ) val exported = export(JUST_URLS, playlist, mock(Context::class.java)) From 587df093ea66e413cecdcf5143d2453e1e3a9cbe Mon Sep 17 00:00:00 2001 From: tfga Date: Tue, 11 Mar 2025 20:35:41 -0300 Subject: [PATCH 39/50] YouTube video IDs are 11 characters long Co-authored-by: Stypox --- .../org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt index 41577742d..d9be2271e 100644 --- a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt @@ -68,9 +68,9 @@ class ExportPlaylistTest { assertEquals( """ - https://www.youtube.com/watch?v=1 - https://www.youtube.com/watch?v=2 - https://www.youtube.com/watch?v=3 + https://www.youtube.com/watch?v=10000000000 + https://www.youtube.com/watch?v=20000000000 + https://www.youtube.com/watch?v=30000000000 """.trimIndent(), exported ) From 599d86151a80da945c2f4ebd68c96ab04c2334e6 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Tue, 11 Mar 2025 21:26:58 -0300 Subject: [PATCH 40/50] Making ktLint happy --- .../java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt index cb619c7cc..0d4dcbfd0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt @@ -50,7 +50,7 @@ fun exportAsYoutubeTempPlaylist(playlist: List): String { val videoIDs = playlist.asReversed().asSequence() .map { it.streamEntity.url } .mapNotNull(::getYouTubeId) - .take(50) // YouTube limitation: temp playlists can't have more than 50 items + .take(50) // YouTube limitation: temp playlists can't have more than 50 items .toList() .asReversed() .joinToString(separator = ",") From f3b3d5c3e7e546e9885e0fb836674c97b67a4d14 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Wed, 12 Mar 2025 19:08:09 -0300 Subject: [PATCH 41/50] R.string.share_playlist_as_youtube_temporary_playlist --- .../schabi/newpipe/local/playlist/LocalPlaylistFragment.java | 5 ++--- app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index de1aabb9c..0cdc74abb 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -887,9 +887,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment sharePlaylist(WITH_TITLES) ) - // TODO R.string.share_playlist_as_YouTube_temporary_playlist - .setNeutralButton("Share as YouTube temporary playlist", (dialog, which) -> - sharePlaylist(YOUTUBE_TEMP_PLAYLIST) + .setNeutralButton(R.string.share_playlist_as_youtube_temporary_playlist, + (dialog, which) -> sharePlaylist(YOUTUBE_TEMP_PLAYLIST) ) .setNegativeButton(R.string.share_playlist_with_list, (dialog, which) -> sharePlaylist(JUST_URLS) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c27e6cbb..7c9637f31 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -849,6 +849,7 @@ Share playlist with details such as playlist name and video titles or as a simple list of video URLs Share with Titles Share URL list + Share as YouTube temporary playlist - %1$s: %2$s %1$s\n%2$s From eb0568044ae4bdc76061281bc4351877c2cb55af Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Wed, 12 Mar 2025 19:09:31 -0300 Subject: [PATCH 42/50] R.string.share_playlist_as_youtube_temporary_playlist: pt-BR + Minor fixes to related translations --- app/src/main/res/values-pt-rBR/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 9f464a11c..d7942aa99 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -791,8 +791,9 @@ Ao vivo Qualidade da imagem \? - Compartilhar URL - Compartilhar com título + Compartilhar URLs + Compartilhar com títulos + Compartilhar como playlist temporária do YouTube %1$s \n%2$s Alternar orientação da tela From 098f60d59301efebdaa014446091a4fd4970fc71 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Thu, 13 Mar 2025 18:16:09 -0300 Subject: [PATCH 43/50] Don't add the title when sharing as YouTube temp playlist --- .../newpipe/local/playlist/LocalPlaylistFragment.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 0cdc74abb..1d08bef81 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -411,12 +411,12 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { - final String content = shareMode == JUST_URLS - ? urlsText - : context.getString(R.string.share_playlist_content_details, + final String content = shareMode == WITH_TITLES + ? context.getString(R.string.share_playlist_content_details, name, urlsText - ); + ) + : urlsText; ShareUtils.shareText(context, name, content); }, From be097f26c8bfdec006376c22dfc679cad66b3866 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Thu, 13 Mar 2025 19:10:26 -0300 Subject: [PATCH 44/50] Deleting the "explanatory text" bellow the title Share playlist with details such as playlist name and video titles or as a simple list of video URLs Share playlist with details such as playlist name and video titles or as a simple list of video URLs
(Discussion: https://github.com/TeamNewPipe/NewPipe/pull/12065#discussion_r1994349485) --- .../org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java | 1 - app/src/main/res/values-ar-rLY/strings.xml | 1 - app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-az/strings.xml | 1 - app/src/main/res/values-be/strings.xml | 1 - app/src/main/res/values-bg/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-eo/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 1 - app/src/main/res/values-et/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fi/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-gl/strings.xml | 1 - app/src/main/res/values-he/strings.xml | 1 - app/src/main/res/values-hi/strings.xml | 1 - app/src/main/res/values-hr/strings.xml | 1 - app/src/main/res/values-hu/strings.xml | 1 - app/src/main/res/values-in/strings.xml | 1 - app/src/main/res/values-is/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-lt/strings.xml | 1 - app/src/main/res/values-lv/strings.xml | 1 - app/src/main/res/values-mk/strings.xml | 1 - app/src/main/res/values-ms/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-or/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-rPT/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ro/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-ryu/strings.xml | 1 - app/src/main/res/values-sat/strings.xml | 1 - app/src/main/res/values-sc/strings.xml | 1 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-sr/strings.xml | 1 - app/src/main/res/values-sv/strings.xml | 1 - app/src/main/res/values-ta/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-rCN/strings.xml | 1 - app/src/main/res/values-zh-rHK/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 53 files changed, 53 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 1d08bef81..f40e6672c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -882,7 +882,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment sharePlaylist(WITH_TITLES) diff --git a/app/src/main/res/values-ar-rLY/strings.xml b/app/src/main/res/values-ar-rLY/strings.xml index 26c1c8b47..fa19afe78 100644 --- a/app/src/main/res/values-ar-rLY/strings.xml +++ b/app/src/main/res/values-ar-rLY/strings.xml @@ -856,6 +856,5 @@ %1$s \n%2$s شارِك قائمة التشغيل - شارِك قائمة التشغيل بتفاصيليها مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين تشعّبيّة للفيديوهات - %1$s: %2$s diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 5946b6b16..8df5771d7 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -856,7 +856,6 @@ %1$s \n%2$s مشاركة قائمة التشغيل - شارك تفاصيل قائمة التشغيل مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين URL للفيديو - %1$s: %2$s رد %s diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index f9be4bf46..7fe5bf335 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -787,7 +787,6 @@ Yüksək keyfiyyət \? Oynatma siyahısın paylaş - Pleylist adı və video başlıqları kimi təfsilatlar və ya video URL-lərin sadə siyahısı olaraq pleylist paylaş Başlıqlarla paylaşın - %1$s: %2$s %1$s diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index b15060e36..132bfc954 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -781,7 +781,6 @@ Пераматаць назад Паўтарыць Атрыманыя ўкладкі пры абнаўленні стужкі. Гэты параметр не прымяняецца, калі канал абнаўляецца ў хуткім рэжыме. - Абагуліць плэйліст, перадаецца назва плэйліста і назвы відэа або просты спіс URL-адрасоў відэа Сярэдняя якасць Загрузнік аватараў Банеры diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index fe0376cda..c91d00327 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -714,7 +714,6 @@ Повторение Превъртане назад Напред - Споделете плейлист с подробности, като име на плейлист и заглавия на видеоклипове или като обикновен списък с URL адреси на видеоклипове Споделяне на списък с URL Изтрии всички позиции на възпроизвеждане? Позициите за възпроизвеждане са изтрити diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index e0c92119e..c9d2e1e94 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -806,7 +806,6 @@ Alba Přetočení zpět Znovu přehrát - Sdílejte playlist s podrobnostmi jako je jeho název a názvy videí, nebo jako jednoduchý seznam adres videí Střední kvalita Bannery Playlisty diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index b7925f3a4..f1afafa5f 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -772,7 +772,6 @@ Indlæs ikke billeder Lav kvalitet Del Playliste - Del playliste med detajler såsom playlistenavn og videotitler eller som en simpel liste over video-URL\'er Del med Titler Del URL-liste diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index d246233e3..a7c3f044d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -804,7 +804,6 @@ %1$s \n%2$s Wiedergabeliste teilen - Teile die Wiedergabeliste mit Details wie dem Namen der Wiedergabeliste und den Videotiteln oder als einfache Liste von Video-URLs - %1$s: %2$s %s Antwort diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 5d89408df..66c16c0b9 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -804,7 +804,6 @@ %1$s \n%2$s Κοινοποίηση λίστας - Μοιραστείτε τη λίστα αναπαραγωγής με λεπτομέρειες όπως το όνομα της λίστας αναπαραγωγής και τους τίτλους βίντεο ή ως μια απλή λίστα διευθύνσεων URL βίντεο - %1$s: %2$s %s απάντηση diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 28bc57358..66e2c4d10 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -616,7 +616,6 @@ Neniu filmofluo ludeblas por ekstera ludilo Filmetoj Filmetoj kiuj spektiĝis antaŭ aŭ post sia aldoniĝo al la ludlisto foriĝus.. \nĈu vi certas? Ĉi tio nemalfareblus! - Kunhavigus ludliston inkluzivante informojn kiel la nomoj de listeroj, aŭ kiel simpla listo de ligiloj Restarigi implicitajn agordojn Jes, kaj ankaŭ parte spektitajn filmetojn diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 5cceeff41..8e0de7864 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -818,7 +818,6 @@ %1$s \n%2$s Compartir la lista de reproducción - Compartir las listas de reproducción con los detalles como el nombre de la lista y los títulos de los vídeos o como una simple lista de una dirección URL con los vídeos - %1$s: %2$s %s respuesta diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index a002c9a0d..1758092c2 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -804,7 +804,6 @@ %1$s \n%2$s Jaga esitusloendit - Jaga esitusloendit kas väga detailse teabega palade kohta või lihtsa url\'ide loendina - %1$s: %2$s Näita veel diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index f38181f64..b1c393db7 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -774,7 +774,6 @@ Editatu beheko jakinarazpen ekintza bakoitza gainean sakatuz. Lehen hiru ekintzak (erreproduzitu/pausatu, aurrekoa eta hurrengoa) sistemarengatik ezarrita daude eta ezin dira pertsonalizatu. Atzera egin Irudiaren kalitatea - Partekatu erreprodukzio-zerrenda xehetasunekin, esate baterako, erreprodukzio-zerrendaren izena eta bideo-izenburuak edo bideo-URLen zerrenda soil gisa Aukera gehiago Iraupena Aurrera egin diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 889b51152..3aae66640 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -759,7 +759,6 @@ Kelaa taaksepäin Noudettavat välilehdet syötettä päivitettäessä. Tällä valinnalla ei ole vaikutusta, jos kanava päivitetään käyttämällä nopeaa tilaa. Poistetaanko kaikki ladatut tiedostot levyltä\? - Jaa soittolista, jossa on tietoja, kuten soittolistan nimi ja videon nimi, tai yksinkertainen luettelo videoiden URL-osoitteista Keskilaatu Lataajan avatarit Prosentti diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a2ab6db1b..9c8a36557 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -817,7 +817,6 @@ Avancer Rembobiner Rejouer - Partager la liste de lecture avec des détails tel que son nom et le titre de ses vidéos ou simplement la liste des URLs des vidéos Avatars du téléverseur Sélectionnez la qualité des images et si les images doivent être chargées, pour réduire l\'utilisation de la mémoire et de données. Les modifications vident à la fois le cache des images en mémoire et sur le disque — %s Lire diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 40482eaea..84bbbb338 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -815,7 +815,6 @@ Encabezados Lapelas a mostrar nas páxinas das canles Escolla da calidade das imaxes e se cargar as imaxes na súa totalidade, para reducir o uso de datos e memoria. Os cambios limpan a caché das imaxes na memoria e no disco - %s - Compartir a lista de reprodución con detalles como o nome da lista e os títulos dos videos ou como unha lista sinxela cos enlaces URL dos videos Compartir lista de URLs A configuración da exportación a ser importada emprega un formato vulnerable que fica obsoleto dende NewPipe 0.27.0. Comprobe que a exportación que está a importar proveña dunha fonte fiable e preferibelmente empregue exportacións de NewPipe 0.27.0 ou posterior. A compatibilidade coa importación deste formato vulnerable será eliminada por completo próximamente e as versión antigas de NewPipe non poderán importar configuracións de exportacións dende novas versións. Pistas diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 45f1d0b50..def09a450 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -830,7 +830,6 @@ %1$s \n%2$s שיתוף רשימת נגינה - שיתוף רשימת נגינה עם פרטים כגון שם רשימת נגינה וכותרות סרטונים או כרשימה פשוטה של כתובות סרטונים - %1$s: %2$s להציג עוד להציג פחות diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 4a629bbe4..4640629a4 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -804,7 +804,6 @@ %1$s \n%2$s प्लेलिस्ट साझा करें - प्लेलिस्ट को प्लेलिस्ट नाम और वीडियो शीर्षक जैसे विवरण के साथ या वीडियो यूआरएल की एक सरल सूची के रूप में साझा करें - %1$s: %2$s %s जवाब diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 9be48f4fb..582421659 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -800,7 +800,6 @@ Srednja kvaliteta Visoka kvaliteta \? - Dijeli playlistu s detaljima kao što su ime playliste i naslovi videa ili kao jednostavan popis URL-ova videa Dijeli s naslovima Dijeli popis URL-ova – %1$s: %2$s diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 71a35db4c..216e6d733 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -796,7 +796,6 @@ Magas minőségű \? Lejátszási lista megosztása - Lejátszási lista megosztása olyan részletekkel, mint például a lejátszási lista neve és a videó címe, vagy a videó webcímek egyszerű listájaként Megosztás címekkel %1$s \n%2$s diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 3183f3014..5120a20de 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -790,7 +790,6 @@ %1$s \n%2$s Bagikan Daftar Putar - Bagikan daftar putar dengan detail seperti nama daftar putar dan judul video atau sebagai daftar video URL yang sederhana Panji - %1$s: %2$s Sentuh untuk menyunting tindakan notifikasi di bawah. Tiga tindakan pertama (mainkan/jeda, sebelumnya dan selanjutnya) disetel oleh sistem dan tidak bisa dikustomisasi. diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index edeeec214..148181861 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -792,7 +792,6 @@ Losa varanlega smámynd Breyttu hverri tilkynningu hér fyrir neðan með því að ýta á hana. Fyrstu þrjár aðgerðirnar (spila/bíða, fyrra og næsta) eru skilgreindar af kerfinu og er því ekki hægt að sérsníða. Flipar sem á að sækja við uppfærslu þessa streymis. Þetta hefur engin áhrif ef rás er uppfærð með hraðstreymisham. - Deildu spilunarlista með atriðum eins og heiti spilunarlistans og titlum myndskeiða eða sem einföldum lista yfir slóðir á myndskeið Nota varaeiginleika ExoPlayer-afkóðarans Vegna takmarkana í ExoPlayer-spilaranum var tímalengd hoppa sett á %d sekúndur Margmiðlunargöng (media tunneling) voru gerð óvirk á tækinu þínu þar sem þessi gerð tækja er þekkt fyrir að styðja ekki þennan eiginleika. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a6a560fb4..542040d26 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -817,7 +817,6 @@ %1$s \n%2$s Condividi playlist - Condividi la playlist con dettagli come il suo nome e i titoli video o come un semplice elenco di URL video - %1$s: %2$s %s risposta diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index eeac136fc..04e54348d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -791,7 +791,6 @@ %1$s \n%2$s プレイリストを共有 - プレイリスト名やビデオタイトルなどの詳細を含むプレイリスト、またはビデオURLのみのシンプルなリストとしてプレイリストを共有します - %1$s: %2$s %sの返信 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 39114997a..283adc7f2 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -776,7 +776,6 @@ 되감기 다시 재생 피드를 업데이트할 때 가져올 탭입니다. 빠른 모드를 사용하여 채널을 업데이트하는 경우 이 옵션은 효과가 없습니다. - 재생목록 이름, 동영상 제목 등의 세부정보 또는 간단한 동영상 URL 목록으로 재생목록을 공유하세요 중간 품질 업로더 아바타 배너 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 455f0e8c7..23476bf36 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -825,7 +825,6 @@ Vaizdo įrašai Takeliai Pasirinkite paveikslėlių kokybę ir ar apskritai įkelti paveikslėlius, kad sumažintumėte duomenų ir atminties naudojimą. Pakeitimai išvalo atmintyje ir diske esančių vaizdų talpyklą - %s - Dalintis grojaraščiu su tokia informacija kaip grojaraščio pavadinimas ir vaizdo įrašo pavadinimas arba paprastas vaizdo įrašų nuorodų sąrašas Dalintis su pavadinimais Dalintis grojaraščiu Dalintis nuorodų sąrašu diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 0698f89d3..2f528a346 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -824,7 +824,6 @@ %1$s %2$s Skaņdarbi Īsie video - Kopīgot atskaņošanas saraksta nosaukumu un to video nosaukumus vai tikai atskaņošanas sarakstā iekļauto video URL saites Kopīgot atskaņošanas sarakstu Kopīgot nosaukumus Importētā eksporta iestatījumi izmanto ievainojamo formātu, kas tika pārtraukts kopš NewPipe 0.27.0 versijas. Pārliecinieties, ka importētie dati ir no uzticama avota, un turpmāk ir vēlams izmantot tikai datus, kas veikti NewPipe 0.27.0 vai jaunākās versijās. Iestatījumu importēšanas atbalsts šajā neaizsargātajā formātā drīzumā tiks pilnībā aizvākts, un tad vecās NewPipe versijas vairs nevarēs importēt iestatījumus, kas veikti jaunajās versijās. diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 92d265758..ba41b730b 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -713,7 +713,6 @@ Инстанцата не може да биде потврдена Изберете го квалитетот на сликите и дали воопшто да се вчитуваат слики, за да го намалите користењето на интернет и меморија. Промените го чистат кешот на сликите (анг. image cache), како и во меморијата, така и на дискот — %s - %1$s: %2$s - Споделете ја плејлистата со подробности (детали), како името на плејлистата и насловите на видеата или како едноставен список од линковите на видеата Ништо Поставките во извезениот фајл кој се увезува користат ранлив формат кој повеќе не е поддржан од NewPipe 0.27.0. Уверете се дека извезениот фајл кој се увезува е од доверлив извор и претпочитајте во иднина да користите само износи добиени од NewPipe 0.27.0 или понова верзија. Поддршката за увезување поставки од овој ранлив формат наскоро ќе биде целосно укината и тогаш старите верзии на NewPipe повеќе нема да можат да увезуваат поставки од износи од новите верзии. Побарај потврда пред чистење на редоследот diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 9864051d8..bb0527655 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -503,7 +503,6 @@ Mutu rendah Mutu sederhana Mutu tinggi - Kongsikan senarai main dengan butiran seperti nama senarai main dan tajuk video atau sebagai senarai ringkas URL video Kongsi dengan Tajuk Kongsi senarai URL Tunjukkan lagi diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a478ba8c3..33ed4d26d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -804,7 +804,6 @@ %1$s \n%2$s Afspeellijst delen - Deel afspeellijst met details zoals afspeellijstnaam en videotitels of als een eenvoudige lijst met video-URL\'s - %1$s: %2$s %s reactie diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 91c7f1425..cae53566e 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -804,7 +804,6 @@ %1$s \n%2$s ଖେଳ ତାଲିକା ସହଭାଗ କରନ୍ତୁ - ପ୍ଲେ-ଲିଷ୍ଟ ନାମ ଏବଂ ଭିଡିଓ ଶୀର୍ଷକ କିମ୍ବା ଭିଡିଓ URLଗୁଡ଼ିକର ଏକ ସରଳ ତାଲିକା ଭାବରେ ବିବରଣୀ ସହିତ ପ୍ଲେ-ଲିଷ୍ଟ ଅଂଶୀଦାର କରନ୍ତୁ - %1$s: %2$s ଅଧିକ ଦର୍ଶାନ୍ତୁ ଏହା ଉପରେ ଟ୍ୟାପ କରି ନିମ୍ନରେ ଦିଆଯାଇଥିବା ପ୍ରତ୍ୟେକ ବିଜ୍ଞପ୍ତି କାର୍ଯ୍ୟକୁ ସମ୍ପାଦନ କରନ୍ତୁ । ପ୍ରଥମ ତିନୋଟି କାର୍ଯ୍ୟ (ଖେଳ/ବିରତି, ପୂର୍ବବର୍ତ୍ତୀ ଏବଂ ପରବର୍ତ୍ତୀ) ତନ୍ତ୍ର ଦ୍ୱାରା ସେଟ କରାଯାଇଥାଏ ଏବଂ ଏହାକୁ ଇଚ୍ଛାରୂପଣ କରାଯାଇପାରିବ ନାହିଁ । diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index bfb51a738..0da6cdd09 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -804,7 +804,6 @@ %1$s \n%2$s ਪਲੇਲਿਸਟ ਸਾਂਝੀ ਕਰੋ - ਪਲੇਲਿਸਟ ਨੂੰ ਪਲੇਲਿਸਟ ਨਾਮ ਅਤੇ ਵੀਡੀਓ ਸਿਰਲੇਖ ਜਿਹੇ ਵੇਰਵਿਆਂ ਸਮੇਤ ਜਾਂ ਵੀਡੀਓ URL ਦੀ ਇੱਕ ਸਰਲ ਸੂਚੀ ਦੇ ਰੂਪ ਵਿੱਚ ਸਾਂਝਾ ਕਰੋ - %1$s: %2$s %s ਜਵਾਬ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b3a43f403..63768acb5 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -825,7 +825,6 @@ %1$s \n%2$s Udostępnij playlistę - Udostępnij playlistę ze szczegółami, takimi jak nazwa playlisty i tytuły wideo, lub jako prostą listę adresów URL wideo. – %1$s: %2$s %s odpowiedź diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d7942aa99..e0b58da6a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -809,7 +809,6 @@ Avançar Retroceder Repetir - Compartilhar playlist com detalhes como o nome da playlist e títulos de vídeo ou como uma lista simples dos URL de vídeos Qualidade média Fotos de perfil do autor - %1$s: %2$s diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 59c77daca..85e951b8e 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -804,7 +804,6 @@ Recuar Repetição Separadores a obter ao atualizar o feed. Esta opção não tem efeito se um canal for atualizado utilizando o modo rápido. - Partilhe a lista de reprodução com detalhes como o nome da lista de reprodução e os títulos dos vídeos ou como uma simples lista de URLs de vídeos Média qualidade Avatar dos publicadores Bandeiras diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index bd98dc7d8..1326e0ca7 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -817,7 +817,6 @@ Canais Vídeo anterior Direto - Partilhe a lista de reprodução com detalhes como o nome da lista de reprodução e os títulos dos vídeos ou como uma simples lista de URLs de vídeos Escolha a qualidade das imagens e se pretende carregar imagens, para reduzir a utilização de dados e de memória. As alterações limpam a cache de imagens na memória e no disco - %s Mostrar mais diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 3cc83967f..fc62b5d1e 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -791,7 +791,6 @@ File ce vor fi preluate când se actualizează fluxul. Această opțiune nu are niciun efect dacă un canal este actualizat folosind modul rapid. Selectați o coloană sonoră cu descrieri pentru persoane cu deficiențe vizuale, dacă este disponibilă Acțiunea gestului din stânga - Distribuiți playlistul cu detalii precum numele playlistului și titlurile videourilor sau ca o simplă listă de URL-uri a videourilor Calitate medie Preferați audioul descriptiv Modificați dimensiunea intervalului de încărcare pentru conținuturi progresive (în prezent %s). O valoare mai mică poate accelera încărcarea lor inițială diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 35fdb8263..71dcdaea4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -808,7 +808,6 @@ Перемотать назад Повторить Получаемые вкладки при обновлении ленты. Эта функция не применяется, если канал обновляется с помощью быстрого режима. - Поделиться подборкой с подробностями, такими как название подборки и названия видео, или просто списком URL видео Среднее качество Загрузчик аватаров Баннеры diff --git a/app/src/main/res/values-ryu/strings.xml b/app/src/main/res/values-ryu/strings.xml index c0195057a..0396245e1 100644 --- a/app/src/main/res/values-ryu/strings.xml +++ b/app/src/main/res/values-ryu/strings.xml @@ -804,7 +804,6 @@ %1$s \n%2$s プレイリストちゅーゆーいん - プレイリストめいてぃがろービデオタイトルんでーぬしょうさいくくむるプレイリスト、あらんでぃビデオURLぬみぬシンプルやるリストとぅしてぃプレイリストちゅーゆーいんさびーん - %1$s: %2$s %sぬへんしん diff --git a/app/src/main/res/values-sat/strings.xml b/app/src/main/res/values-sat/strings.xml index 751a0cde7..8b44cd5e0 100644 --- a/app/src/main/res/values-sat/strings.xml +++ b/app/src/main/res/values-sat/strings.xml @@ -678,7 +678,6 @@ ᱞᱟᱯᱷᱟᱝ ᱥᱤᱠᱷᱱᱟ. ᱛᱟᱞᱢᱟ ᱥᱤᱠᱷᱱᱟᱹᱛ ᱩᱥᱩᱞ ᱥᱤᱠᱷᱱᱟᱹᱛ - ᱯᱷᱟᱭᱞᱤᱥᱴ ᱧᱩᱛᱩᱢ ᱟᱨ ᱵᱷᱤᱰᱤᱭᱳ ᱧᱩᱛᱩᱢ ᱞᱮᱠᱟᱛᱮ ᱟᱨᱵᱟᱝ ᱵᱷᱤᱰᱤᱭᱳ URL ᱨᱮᱱᱟᱜ ᱢᱤᱫ ᱞᱮᱠᱟᱱ ᱞᱤᱥᱴᱤ ᱞᱮᱠᱟᱛᱮ ᱴᱷᱟᱶ ᱮᱢ ᱢᱮ URL ᱛᱟᱹᱞᱠᱟᱹ ᱥᱟᱯᱲᱟᱣ - %1$s: %2$s ᱱᱚᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ ᱨᱮ ᱑᱖:᱙ ᱠᱷᱚᱱ ᱑:᱑ ᱟᱥᱯᱮᱠᱴ ᱚᱱᱩᱯᱟᱹᱛ ᱨᱮ ᱵᱷᱤᱰᱤᱭᱳ ᱛᱷᱚᱢᱵᱱᱮᱞ ᱜᱮᱫᱽ ᱢᱮ diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 9b6fb5863..d92532c31 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -788,7 +788,6 @@ Torra in segus Torra a reprodùere Ischedas de recuperare cando agiornas sa fonte. Custa optzione non tenet efetu si unu canale benit agiornadu impreende sa modalidade lestra. - Cumpartzi s\'iscalita cun detàllios che a su nùmene de s\'iscalita e sos tìtulos de sos vìdeos o che a una lista simpre de URL de vìdeos Calidade mesana Avatars de su carrigadore Insignas diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 0677feb95..dee7b1264 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -823,7 +823,6 @@ %s odpovede %s odpovedí - Zdieľajte playlist s podrobnosťami, ako je jeho názov a názvy videí, alebo ako jednoduchý zoznam URL adries videí - %1$s: %2$s %1$s \n%2$s diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index a837451ba..5c8c28a57 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -817,7 +817,6 @@ %1$s \n%2$s Дели плејлисту - Делите плејлисту са детаљима, као што су назив плејлисте и наслови видео снимака или као једноставна листа URL адреса видео снимака -%1$s: %2$s %s одговор diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 740143d0b..c85d17220 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -804,7 +804,6 @@ Uppladdarens visningsbilder Banderoller - %1$s: %2$s - Dela spellistan med detaljer så som spellistans namn och video-titlarna eller som en enkel lista med URL till videorna Välj bildkvalitet och om bilder överhuvudtaget ska laddas för att minska data och minnesanvändningen. Ändringar rensar både i minnet och bildcache på disk – %s Visa mer diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 958df593e..0056f7416 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -470,7 +470,6 @@ முன்னோக்கி பட தகுதி உயர் தகுதி - பிளேலிச்ட் பெயர் மற்றும் வீடியோ தலைப்புகள் போன்ற விவரங்களுடன் அல்லது வீடியோ முகவரி களின் எளிய பட்டியலாக பிளேலிச்ட்டைப் பகிரவும் மேலும் விருப்பங்கள் காலம் முன்னாடி diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a87dac75e..ccd3b3b62 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -791,7 +791,6 @@ Geri sar Yeniden oynat Besleme güncellenirken alınacak sekmeler. Hızlı kip kullanılırken kanal güncelleniyorsa bu seçeneğin etkisi yoktur. - Oynatma listesini, oynatma listesi adı ve video başlıkları gibi ayrıntılarla ya da video adreslerinin basit listesi olarak paylaş Orta nitelik Yükleyen avatarları Afişler diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 39fdddd67..22d9eb478 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -821,7 +821,6 @@ %1$s \n%2$s Поділитися добіркою - Поділитися добіркою з подробицями, такими як назва добірки та назви відео, або просто списком URL-адрес відео - %1$s: %2$s Показати більше Відредагуйте кожну дію сповіщення, натиснувши на неї. Перші три дії (відтворення/пауза, попередній і наступний) встановлюються системою і не можуть бути змінені. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 23a0c96dc..22bc03c2c 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -787,7 +787,6 @@ Tua đi Album Tua lại - Chia sẻ danh sách phát với các thông tin chi tiết như tên danh sách phát và tiêu đề video hoặc dưới dạng danh sách URL video đơn giản Chất lượng trung bình - %1$s: %2$s Chọn chất lượng hình ảnh và chọn có tải chất lượng ảnh hay không, để giảm mức sử dụng dữ liệu và bộ nhớ. Thay đổi xoá cache ảnh cho cả trong bộ nhớ lẫn ổ cứng - %s diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 5efb32d4f..e8d19cb8b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -791,7 +791,6 @@ %1$s \n%2$s 分享播放列表 - 分享详细的播放列表(带名称和视频标题等信息)或只分享视频网址列表 - %1$s: %2$s %s 条回复 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 0a862a48d..c804c53c0 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -775,7 +775,6 @@ 跳後 重播 更新摘要嘅時候要攞邊啲分頁返嚟。若果頻道用快速模式更新,就橫豎都無相干嘞。 - 分享播放清單要詳細包含播放清單個名同埋入面啲片名,定簡單得啲影片嘅 URL 一般畫質 上載者嘅頭像 橫額 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 30d66f124..030c23f60 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -791,7 +791,6 @@ %1$s \n%2$s 分享播放清單 - 分享包含播放清單名稱與影片標題等詳細資訊的播放清單,或是僅作為簡單的影片網址清單 - %1$s:%2$s %s 個回覆 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7c9637f31..729dae48c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -846,7 +846,6 @@ High quality \? Share Playlist - Share playlist with details such as playlist name and video titles or as a simple list of video URLs Share with Titles Share URL list Share as YouTube temporary playlist From 2ceb70236ef27b070eebbda25cbcca778db2c8e2 Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Fri, 14 Mar 2025 21:56:42 -0300 Subject: [PATCH 45/50] sharePlaylist(): converting javadoc from Markdown back to "classic javadoc" (request from @Stypox) --- .../local/playlist/LocalPlaylistFragment.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index f40e6672c..f5562549c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -388,15 +388,16 @@ public class LocalPlaylistFragment extends BaseLocalListFragment + *
  • {@code JUST_URLS}: shares the URLs only.
  • + *
  • {@code WITH_TITLES}: each entry in the list is accompanied by its title.
  • + *
  • {@code YOUTUBE_TEMP_PLAYLIST}: shares as a YouTube temporary playlist.
  • + * + * + * @param shareMode The way the playlist should be shared. + */ private void sharePlaylist(final PlayListShareMode shareMode) { final Context context = requireContext(); From d321e57620c4260bc2a776fb04334dca3a5c88f2 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 15 Mar 2025 00:12:09 +0100 Subject: [PATCH 46/50] Translated using Weblate (Czech) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Catalan) Currently translated at 88.2% (653 of 740 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 90.4% (76 of 84 strings) Translated using Weblate (Belarusian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Hindi) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Punjabi) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Belarusian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Punjabi) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Hindi) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (German) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (German) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Croatian) Currently translated at 99.7% (738 of 740 strings) Translated using Weblate (Russian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Belarusian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Belarusian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Belarusian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Italian) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Czech) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Portuguese) Currently translated at 99.8% (739 of 740 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Czech) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Russian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Hungarian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (French) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (84 of 84 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Belarusian) Currently translated at 99.5% (737 of 740 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Icelandic) Currently translated at 99.4% (736 of 740 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Greek) Currently translated at 25.0% (21 of 84 strings) Translated using Weblate (Greek) Currently translated at 23.8% (20 of 84 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (740 of 740 strings) Translated using Weblate (French) Currently translated at 99.5% (737 of 740 strings) Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439JBYL80IGQTF25UXNR0X1BG@users.noreply.hosted.weblate.org> Co-authored-by: Andrey F Co-authored-by: Anonymous Co-authored-by: Antonin Del Fabbro Co-authored-by: Christian Eichert Co-authored-by: Drugi Sapog Co-authored-by: Eduardo Calixto Co-authored-by: Emin Tufan Çetin Co-authored-by: Fjuro Co-authored-by: Ghost of Sparta Co-authored-by: Hosted Weblate Co-authored-by: Igor Rückert Co-authored-by: Ihor Hordiichuk Co-authored-by: Jan Layola Co-authored-by: Kevin Wang Co-authored-by: Linerly Co-authored-by: Massimo Pissarello Co-authored-by: Milo Ivir Co-authored-by: Petr Kadlec Co-authored-by: Priit Jõerüüt Co-authored-by: Rex_sa Co-authored-by: Sergio Marques Co-authored-by: Sveinn í Felli Co-authored-by: XxVictoriaxX Co-authored-by: Yaron Shahrabani Co-authored-by: bittin1ddc447d824349b2 Co-authored-by: trunars Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com> Co-authored-by: Максим Горпиніч Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/el/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/ Translation: NewPipe/Metadata --- app/src/main/res/values-ar/strings.xml | 4 +- app/src/main/res/values-be/strings.xml | 75 +++++++++---------- app/src/main/res/values-bg/strings.xml | 8 +- app/src/main/res/values-ca/strings.xml | 7 ++ app/src/main/res/values-cs/strings.xml | 4 +- app/src/main/res/values-de/strings.xml | 4 +- app/src/main/res/values-et/strings.xml | 4 +- app/src/main/res/values-fr/strings.xml | 8 +- app/src/main/res/values-he/strings.xml | 1 + app/src/main/res/values-hi/strings.xml | 4 +- app/src/main/res/values-hr/strings.xml | 9 ++- app/src/main/res/values-hu/strings.xml | 6 +- app/src/main/res/values-in/strings.xml | 4 +- app/src/main/res/values-is/strings.xml | 4 +- app/src/main/res/values-pa/strings.xml | 18 ++--- app/src/main/res/values-pt-rBR/strings.xml | 4 +- app/src/main/res/values-pt/strings.xml | 8 +- app/src/main/res/values-ru/strings.xml | 8 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 6 +- app/src/main/res/values-uk/strings.xml | 6 +- .../metadata/android/ar/changelogs/1003.txt | 10 ++- .../metadata/android/cs/changelogs/1002.txt | 5 +- .../metadata/android/cs/changelogs/1003.txt | 7 +- .../metadata/android/el/changelogs/1000.txt | 13 ++++ .../metadata/android/el/changelogs/999.txt | 1 + .../metadata/android/fr/changelogs/1003.txt | 10 +-- .../metadata/android/hi/changelogs/1001.txt | 6 ++ .../metadata/android/hi/changelogs/1002.txt | 6 +- .../metadata/android/hi/changelogs/1003.txt | 7 +- .../metadata/android/hi/changelogs/65.txt | 46 ++++++------ .../metadata/android/it/changelogs/1002.txt | 5 +- .../metadata/android/it/changelogs/1003.txt | 7 +- .../metadata/android/pa/changelogs/1001.txt | 6 ++ .../metadata/android/pa/changelogs/1002.txt | 5 +- .../metadata/android/pa/changelogs/1003.txt | 7 +- .../android/zh-Hans/changelogs/1000.txt | 13 ++++ 37 files changed, 216 insertions(+), 132 deletions(-) create mode 100644 fastlane/metadata/android/el/changelogs/1000.txt create mode 100644 fastlane/metadata/android/el/changelogs/999.txt create mode 100644 fastlane/metadata/android/hi/changelogs/1001.txt create mode 100644 fastlane/metadata/android/pa/changelogs/1001.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/1000.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 550ff38a0..50ddcd8d3 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -83,7 +83,7 @@ استئناف التشغيل متابعة التشغيل بعد المقاطعات (مثل المكالمات الهاتفية) إظهار تلميح \"اضغط للفتح\" - إظهار التلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\\ + إظهار التلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\" المشغل السلوك تشغيل في وضع منبثق @@ -558,7 +558,7 @@ إزالة ما تمت مشاهدته ستكون النصوص الأصلية من الخدمات مرئية في عناصر البث عرض الوقت الأصلي على العناصر - قم بتشغيل \"وضع تقييد المحتوى\" في يوتيوب\\ + قم بتشغيل \"وضع تقييد المحتوى\" في يوتيوب بواسطة %s أنشأها %s الصورة الرمزية للقناة diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 132bfc954..19f3c8b26 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -19,7 +19,7 @@ Прыбірае гук пры пэўнай раздзяляльнасці Знешні аўдыяплэер Падпісацца - Вы падпісаныя + Вы падпісаны Падпіска адменена Не ўдалося змяніць падпіску Не ўдалося абнавіць падпіску @@ -165,8 +165,9 @@ Няма падпісчыкаў %s падпісчык - %s падпісчыка + %s падпісчыкі %s падпісчыкаў + %s падпісчыкаў Няма праглядаў @@ -177,7 +178,7 @@ Няма відэа - %s Відэа + %s відэа %s відэа %s відэа %s відэа @@ -207,7 +208,7 @@ Недапушчальныя сімвалы замяняюцца на гэты Сімвал для замены Літары і лічбы - Большасць спецзнакаў + Большасць спецсімвалаў Аб NewPipe Іншыя ліцэнзіі © %1$s %2$s пад ліцэнзіяй %3$s @@ -221,7 +222,7 @@ NewPipe распрацаваны добраахвотнікамі, якія праводзяць свой вольны час, забяспечваючы лепшы карыстацкі досвед. Дапамажыце распрацоўшчыкам зрабіць NewPipe яшчэ лепшым, пакуль яны атрымліваюць асалоду ад кавы. Ахвяраваць грошы Вэб-сайт - Дзеля атрымання больш падрабязнай інфармацыі і апошніх навін аб NewPipe наведайце наш вэб-сайт. + Наведайце вэб-сайт, каб атрымаць больш інфармацыі і паглядзець апошнія навіны NewPipe. Палітыка прыватнасці NewPipe Праект NewPipe вельмі адказна ставіцца да вашай прыватнасці. Таму праграма не збірае ніякіх даных без вашай згоды. \nПалітыка прыватнасці NewPipe падрабязна тлумачыць, якія даныя адпраўляюцца і захоўваюцца пры адпраўцы справаздачы пра збой. Прачытаць палітыку прыватнасці @@ -231,9 +232,9 @@ Гісторыя Гісторыя Выдаліць гэты элемент з гісторыі пошуку? - Нядаўна прайграныя - Найбольш прайграваныя - Кантэнт галоўнай старонкі + Прайгравалася нядаўна + Прайгравалася найбольш + Змесціва галоўнай старонкі Пустая старонка Старонка кіёска Старонка канала @@ -254,13 +255,13 @@ Налады аўдыя Зацісніце, каб дадаць у чаргу Пачаць прайграванне ў фоне - Пачаць прайграванне у акне + Пачаць прайграванне ў акне Адкрыць бакавую панэль Закрыць бакавую панэль Пры адкрыцці кантэнту Пры адкрыцці спасылкі на кантэнт — %s Відэаплэер - Фонавы плэер + Фонавы прайгравальнік Аконны прайгравальнік Заўсёды пытаць Атрыманне звестак… @@ -269,7 +270,7 @@ Перайменаваць Імя Дадаць у плэйліст - Усталяваць як мініяцюру плэйліста + Зрабіць мініяцюрай плэйліста Дадаць плэйліст у закладкі Выдаліць закладку Выдаліць плэйліст\? @@ -308,7 +309,7 @@ Прапускаць цішыню Крок Скід - У адпаведнасці з Агульным рэгламентам па абароне даных ЕС (GDPR), звяртаем вашу ўвагу на палітыку прыватнасці NewPipe. Уважліва азнаёмцеся з ёй. \nВы павінны прыняць ўмовы, каб адправіць нам справаздачу пра памылку. + У адпаведнасці з Агульным рэгламентам па абароне даных ЕС (GDPR), звяртаем вашу ўвагу на палітыку прыватнасці NewPipe. Уважліва азнаёмцеся з ёй. \nВы павінны прыняць умовы, каб адправіць нам справаздачу пра памылку. Прыняць Адмовіцца Без абмежаванняў @@ -316,7 +317,7 @@ Згортванне пры пераключэнні праграмы Дзеянне пры пераключэнні з асноўнага відэаплэера на іншую праграму — %s Нічога не рабіць - Згортванне у фон + Згортванне ў фон Згортванне ў акно Адпісацца Выберыце ўкладку @@ -362,7 +363,7 @@ Спыніць Максімум спроб Колькасць спроб спампаваць да адмены - Перапыніць у платных сетках + Прыпыняць у сетках з тарыфікацыяй Карысна пры пераключэнні на мабільную сетку, хоць некаторыя спампоўванні немагчыма прыпыніць Падзеі Канферэнцыі @@ -386,7 +387,7 @@ Праграма NewPipe была закрыта падчас працы з файлам На прыладзе скончылася вольнае месца Прагрэс страчаны, бо файл быў выдалены - Час злучэння выйшла + Скончыўся час злучэння Вы хочаце ачысціць гісторыю спампоўвання ці выдаліць спампаваныя файлы? Абмежаваць чаргу спампоўвання Толькі адно адначасовае спампоўванне @@ -420,7 +421,7 @@ Не ўдалося праверыць сервер Увядзіце URL-адрас сервера Выберыце ўлюбёныя серверы PeerTube - Актыўны плэер быў зменены + Чарга актыўнага прайгравальніка будзе заменена Пераключэнне з аднаго плэера на другі можа прывесці да замены вашай чаргі Запытваць пацвярджэнне перад ачысткай чаргі Ніколі @@ -452,7 +453,7 @@ Альбомы Песні Відэа - Аўтаматычная чарга + Аўтапрайграванне Крок перамотвання Каляровыя апавяшчэнні Нічога @@ -491,8 +492,8 @@ Адключыць Няма аўдыяпатокаў даступных для знешніх плэераў Апавяшчаць - Няма даступных відэатрансляцый для знешніх плэераў - Выбраная трансляцыя не падтрымліваецца знешнімі плэерамі + Няма відэапатокаў даступных для знешніх плэераў + Выбраны паток не падтрымліваецца знешнімі плэерамі Выберыце якасць для знешніх плэераў Невядомая якасць Невядомы фармат @@ -503,7 +504,7 @@ Адкрыць праз Начная тэма Адкрыць вэб-сайт - Цяпер Вы можаце вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мігацець, а спасылкі могуць быць недаступныя для націскання. + Цяпер можна вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мільгаць, а спасылкі не націскацца. Запускаць галоўны прайгравальнік у поўнаэкранным рэжыме Паказаць дэталі канала Нізкая якасць (менш) @@ -562,8 +563,8 @@ %d хвіліна %d хвіліны - %d хвілінаў - %d хвілінаў + %d хвілін + %d хвілін Змяніць памер інтэрвалу загрузкі прагрэсіўнага змесціва (у цяперашні час %s). Меншае значэнне можа паскорыць іх першапачатковую загрузку Выключыце, каб схаваць апісанне відэа і дадатковую інфармацыю @@ -603,8 +604,8 @@ У чаргу далей У чарзе наступны Загрузка звестак аб стрыме… - Апрацоўка... Можа заняць некаторы час - Дублікат дададзены %d раз + Ідзе апрацоўка… Крыху пачакайце + Дублікат дададзены %d раз(ы) LeakCanary недаступны Паказаць уцечкі памяці Адключыце мультымедыйнае тунэляванне, калі ў вас з\'яўляецца чорны экран або заіканне падчас прайгравання відэа. @@ -613,7 +614,7 @@ Частыя пытанні Перайсці на вэб-сайт Правядзіце пальцам па элементах, каб выдаліць іх - Адмяніць пастаянную мініяцюру + Прыбраць пастаянную мініяцюру Паказваць індыкатары выяў Паказваць каляровыя стужкі Пікаса на выявах, якія пазначаюць іх крыніцу: чырвоная для сеткі, сіняя для дыска і зялёная для памяці Апрацоўка стужкі… @@ -624,7 +625,7 @@ Працэнт Відэа, якія прагледжаны перад дадаваннем і пасля дадавання ў спіс прайгравання, будуць выдалены. \nВы ўпэўнены? Гэта дзеянне немагчыма скасаваць! Паказвае варыянт збою пры выкарыстанні плэера - Выдаліць прагледжанае + Выдаліць прагледжаныя Паказаць панэль памылак Паўтон Любая сетка @@ -645,12 +646,12 @@ Выдаліць гэту групу? Новая Паказаць толькі разгрупаваныя падпіскі - Маючыя адбыцца + Запланаваныя Паказваць «Збой плэера» Запусціце праверку новых патокаў Збой праграмы - Апавяшчэнні аб новых стрымах - Апавяшчаць аб новых стрымах з падпісак + Апавяшчэнні пра новыя відэа + Апавяшчаць пра новыя відэа з падпісак Частата праверкі Неабходны тып злучэння Праверыць наяўнасць абнаўленняў @@ -693,7 +694,7 @@ Вы падпісаліся на канал Апошнія Радыё - Паказваць запланаваныя трансляцыі + Паказваць наступныя патокі Паказаць/схаваць трансляцыі Гэты кантэнт яшчэ не падтрымліваецца NewPipe. \n @@ -710,14 +711,14 @@ %s дае наступную прычыну: Вартае ўвагі Унутраная - Цалкам прагледзеў + Прагледжаныя цалкам Гэты кантэнт даступны толькі для аплачаных карыстальнікаў, таму NewPipe не можа яго трансляваць або спампоўваць. Даступны ў некаторых службах, звычайна нашмат хутчэй, але можа вяртаць абмежаваную колькасць элементаў і часта няпоўную інфармацыю (напрыклад, без працягласці, тыпу элемента, без актыўнага стану) Узроставае абмежаванне Для гэтага дзеяння не знойдзены прыдатны файлавы менеджар. \nУсталюйце файлавы менеджар, сумяшчальны з Storage Access Framework Ніякая праграма на вашай прыладзе не можа адкрыць гэта Стандартнае значэнне ExoPlayer - Часткова прагледжана + Прагледжаныя часткова Лічыце, што загрузка каналаў адбываецца занадта павольна? Калі так, паспрабуйце ўключыць хуткую загрузку (можна змяніць у наладах або націснуўшы кнопку ніжэй). \n \nNewPipe прапануе два спосабы загрузкі каналаў: \n• Атрыманне ўсяго канала падпіскі. Павольны, але інфармацыя поўная). \n• Выкарыстанне спецыяльнай канчатковай кропкі абслугоўвання. Хуткі, але звычайна інфармацыя няпоўная). \n \nРозніца паміж імі ў тым, што ў хуткім звычайна адсутнічае частка інфармацыі, напрыклад, працягласць або тып (немагчыма адрозніць трансляцыі ад звычайных відэа), і ён можа вяртаць менш элементаў. \n \nYouTube з\'яўляецца прыкладам сэрвісу, які прапануе гэты хуткі метад праз RSS-канал. \n \nТакім чынам, выбар залежыць ад таго, чаму вы аддаяце перавагу: хуткасці або дакладнасці інфармацыя. Прыватнасць Мова @@ -747,9 +748,7 @@ У гэтым патоку ўжо павінна быць гукавая дарожка Уключыце гэту опцыю, калі ў вас ёсць праблемы з ініцыялізацыяй дэкодэра, якая вяртаецца да дэкодэраў з больш нізкім прыярытэтам, калі ініцыялізацыя асноўных дэкодэраў не ўдаецца. Гэта можа прывесці да нізкай прадукцыйнасці прайгравання, чым пры выкарыстанні асноўных дэкодэраў Кіраванне некаторымі наладамі ExoPlayer. Каб гэтыя змены ўступілі ў сілу, патрабуецца перазапуск прайгравальніка - Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб усталёўваць паверхню непасрэдна для кодэка. ExoPlayer ужо выкарыстоўваецца на некаторых прыладах з гэтай праблемай, гэты параметр мае ўплыў толькі на прыладах з Android 6 і вышэй -\n -\nУключэнне гэтай опцыі можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым + Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб зажаваць паверхню непасрэдна для кодэка. Ужо выкарыстоўваецца ExoPlayer на некаторых прыладах з такой праблемай, гэты параметр ужываецца толькі на прыладах з Android 6 і вышэй\n\nУключэнне параметра можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым Якасць выяў Відэа \? @@ -771,7 +770,7 @@ Наступны паток Прадвызначана на вашай прыладзе адключана медыятунэляванне, бо гэтая мадэль прылады яго не падтрымлівае. Аватары падканалаў - Адкрыйце чаргу прайгравання + Адкрыць чаргу прайгравання Не загружаць выявы Высокая якасць Аб канале @@ -786,7 +785,7 @@ Банеры Плэйлісты - %1$s: %2$s - Перамясціць панэль укладак ўніз + Перамясціць панэль укладак уніз Няма жывых трансляцый Выберыце якасць выяў і ці трэба спампоўваць выявы ўвогуле, каб паменшыць выкарыстанне даных і памяці. Змены ачышчаюць кэш выяў як у памяці, так і на дыску - %s Прайграць @@ -812,7 +811,7 @@ NewPipe можа аўтаматычна правяраць наяўнасць абнаўленняў і паведаміць вам, калі яны будуць даступны. \nУключыць гэту функцыю? Налады ў імпартаваным экспарце выкарыстоўваюць уразлівы фармат, які састарэў з версіі NewPipe 0.27.0. Пераканайцеся, што імпартаваны экспарт атрыманы з надзейнай крыніцы, і ў будучыні пераважней выкарыстоўваць толькі экспарт, атрыманы з NewPipe 0.27.0 ці навей. Падтрымка імпарту налад у гэтым уразлівым фармаце хутка будзе цалкам выдаленая, і тады старыя версіі NewPipe больш не змогуць імпартаваць наладкі з экспарту з новых версій. Не - Рэзервовае капіраванне і аднаўленне + Рэзервовае капіяванне і аднаўленне Скінуць налады Скінуць усе налады на іх прадвызначаныя значэнні Пры скіданні ўсіх налад будуць адхілены ўсе вашы змены налад і праграма перазапусціцца. \n \nСапраўды хочаце працягнуць? diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index c91d00327..c9b752d05 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -192,7 +192,7 @@ © %1$s от %2$s под лиценза %3$s Съдействайте За всичко, което се сетите: превод, промени по дизайна, изчистване на кода или много сериозни промени по кода – помощта е винаги добре дошла. Колкото повече развитие, толкова по-добре! - Направете дарение + Дарение NewPipe се разработва от доброволци, които отделят от своето време, за да предоставят най-доброто потребителско изживяване. Включете се в разработката като почерпите разработчиците с една чашка кафе, които да изпият, докато правят NewPipe още по-добро приложение. Дари Уебсайт @@ -203,7 +203,7 @@ Прочетете нашата политика за поверителност Лицензът на NewPipe Липсва стрийм плейър (можете да изтеглите VLC, за да пуснете стрийма). - Покажи съвет при натискане на фона или изскачащия бутон във видеоклипа „Подробности:“ + Покажи съвет при натискане на фона или изскачащия бутон във видеоклипа \"Подробности:“ Изтрива историята на възпроизвежданите стриймове и позицията на възпроизвеждането Не са намерени видео стриймове Не са намерени аудио стриймове @@ -519,7 +519,7 @@ Полезно при превключване към мобилни данни, въпреки че някои изтегляния не поддържат възобновяване и ще започнат отначало Срив на приложението Цветът на известието да се избира според главния цвят в миниатюрата на видеото (може да не работи на всички устройства) - Използване на ограничения режим на YouTube + Включване на \"Ограничен режим“ в YouTube YouTube предлага „ограничен режим“, чрез който можете да филтрирате потенциално съдържание за възрастни Това видео е с възрастова граница. \n @@ -558,7 +558,7 @@ Покажи индикатори за позиция на възпроизвеждане в списъци Редактирайте всяко действие за известяване по-долу, като щракнете върху него. Първите три действия (възпроизвеждане/пауза, предишно и следващо) се задават от системата и не могат да бъдат конфигурирани. Изберете жест за дясната половина на екрана на плейъра - Действие с жест на дясно + Действие с жест надясно Стартирайте основния плейър на цял екран Известия за нови видеоклипове в абонаментите Известявайте за нови видеоклипове в абонаментите diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 561be3df5..9fbc05566 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -728,4 +728,11 @@ Pista d\'àudio No Cap emissió + Notifica sobre les noves retransmissions de les subscripcions + Noves notificacions de retransmissions + Les llistes de reproducció que estan en gris ja contenen aquest element. + Desestableix la miniatura permanent + Duplicat afegit/s %d vegada/es + El túnel multimèdia s\'ha desactivat de manera predeterminada al dispositiu perquè se sap que el vostre model de dispositiu no ho permet. + Semiton diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index c9d2e1e94..f2f8da16c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1,6 +1,6 @@ - Publikováno na %1$s + Publikováno %1$s Nenalezen žádný přehrávač. Nainstalovat VLC? Instalovat Zrušit @@ -785,7 +785,7 @@ \? Odběratelé Které karty mají být zobrazeny na stránkách kanálů - Sdílet URL seznamu + Sdílet seznam adres Sdílet s názvy %1$s \n%2$s diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a7c3f044d..f2d3b5698 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -63,7 +63,7 @@ Entschuldigung, etwas ist schiefgelaufen. Dein Kommentar (auf englisch): Live - Tippe auf die Lupe, um zu beginnen. + Tippe auf die Lupe, um zu suchen. Downloads Downloads Fehlerbericht @@ -425,7 +425,7 @@ Niemand schaut zu %s Zuschauer - %s Zuschauende + %s Zuschauer Niemand hört zu diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index f4f375035..f7563fa04 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -451,7 +451,7 @@ %s kuulajat Teavitused video räsimise edenemise kohta - Võta kasutusele YouTube\'i „Piiratud režiim“\\ + Võta kasutusele YouTube\'i „Piiratud režiim“ Faili asukoht on muutunud või on ta kustutatud Taasesituste asukohad on kustutatud Kas kustutame kõik taasesituste asukohad\? @@ -642,7 +642,7 @@ Kontrolli uuendusi Kontrolli uuendusi käsitsi Uued andmevoo kirjed - Näita „Jooksuta meediamängija kokku“ nupukest\\ + Näita „Jooksuta meediamängija kokku“ nupukest Näitab valikut meediamängija kokkujooksutamiseks NewPipe\'i töös tekkis viga, sellest teavitamiseks toksa Jooksuta meediamängija kokku diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9c8a36557..63d6fb263 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -178,7 +178,7 @@ Détails Paramètres audios Afficher l\'astuce « Maintenir pour ajouter à la file » - Affiche l’astuce lors de l’appui des boutons « Arrière-plan » ou « Flottant » sur la page de détails d’une vidéo + Affiche l’astuce lors de l’appui des boutons « Arrière-plan » ou « Flottant » sur la page de détails d’une vidéo [Inconnu] Récupération depuis l’erreur du lecteur Kiosque @@ -534,7 +534,7 @@ Créé par %s Les textes originaux des services vont être visibles dans les items Afficher la date originelle sur les items - Activer le « Mode restreint » de YouTube + Activer le « Mode restreint » de YouTube Afficher uniquement les abonnements non groupés Page des listes de lecture Aucune liste de lecture encore enregistrée @@ -671,7 +671,7 @@ Vérifier les mises à jour Nouveaux éléments du flux Faire planter le lecteur - Afficher « Faire planter le lecteur » + Afficher « Faire planter le lecteur » Montrer une option de plantage lors de l\'utilisation du lecteur Notification de rapport d\'erreur Notifications pour signaler les erreurs @@ -841,4 +841,4 @@ Pas assez d\'espace disponible sur l\'appareil Les paramètres de l\'export en cours d\'importation utilisent un format vulnérable qui a été déprécié depuis NewPipe 0.27.0. Assurez-vous que l\'export en cours d\'importation provient d\'une source fiable. Privilégiez les exports obtenues à partir de NewPipe 0.27.0 ou des versions plus récentes à l\'avenir. Le support pour l\'importation des paramètres dans ce format vulnérable sera bientôt complètement supprimé et les anciennes versions de NewPipe ne pourront plus importer les paramètres des exports des nouvelles versions. secondaire - \ No newline at end of file + diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index def09a450..47eea5ee4 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -851,4 +851,5 @@ \nלהמשיך? אין מספיק מקום פנוי במכשיר ההגדרות בייצוא המיובא משתמשות בתסדיר פגיע שהוצא משימוש מאז NewPipe 0.27.0. יש לוודא שהייצוא המיובא הוא ממקור מהימן, ועדיף להשתמש רק בייצוא שהושג מ־NewPipe 0.27.0 ומעלה בעתיד. תמיכה בייבוא הגדרות בתסדיר פגיע זה תוסר בקרוב לחלוטין, ואז גרסאות ישנות של NewPipe לא יוכלו לייבא עוד הגדרות של ייצוא מגרסאות חדשות. + משני diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 4640629a4..086d200d5 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,7 +1,7 @@ %1$s पे प्रकाशित हुआ - स्ट्रीमिंग के लिए प्लेयर नहीं मिला। क्या आप वीएलसी इंस्टॉल करना चाहेंगे\? + स्ट्रीमिंग के लिए प्लेयर नहीं मिला। क्या आप VLC इंस्टॉल करना चाहेंगे? इंस्टॉल करें ब्राउज़र में खोलें पॉपअप मोड में खोलें @@ -183,7 +183,7 @@ कतार में जोड़ने के लिए दबाकर रखें बैकग्राउंड में चलाना शुरू करें पॉपअप में चलाना शुरू करें - स्ट्रीमिंग करने के लिए प्लेयर नहीं मिला (आप इसे चलाने के लिए वीएलसी प्लेयर इंस्टॉल कर सकते हैं)। + स्ट्रीमिंग करने के लिए प्लेयर नहीं मिला (आप इसे चलाने के लिए VLC प्लेयर इंस्टॉल कर सकते हैं)। स्ट्रीम फाइल डाउनलोड करें जानकारी दिखाएं बुकमार्क की गई प्लेलिस्टें diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 582421659..3c09a8aca 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -424,8 +424,8 @@ Želiš li izbrisati ovu grupu\? Nova Uvijek aktualiziraj - Uključi brzi način - Isključi brzi način + Uključi brzi modus + Isključi brzi modus Memorija uređaja je popunjena Najomiljeniji Pritisni „Gotovo” kad je riješeno @@ -639,7 +639,7 @@ Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje Komentari su isključeni Označi kao pogledano - Način rada brzog feeda ne pruža više informacija o ovome. + Brzi modus feeda ne pruža više informacija o ovome. Interno Privatnost Sada možeš odabrati tekst u opisu. Napomena: stranica će možda treperiti i možda nećeš moći kliknuti poveznice u načinu rada za odabir teksta. @@ -832,4 +832,7 @@ Obnavljanje svih postavki odbacit će sve tvoje postavljene postavke i aplikacija će se ponovo pokrenuti. \n \nStvarno želiš nastaviti? + Uvijek koristi ExoPlayer postavku zaobilaženja videa za izlaznu površinu + Kartice za dohvaćanje prilikom aktualiziranja feeda. Ova opcija nema učinka ako se kanal aktualizira pomoću brzog modusa. + sekundarno diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 216e6d733..90da929bf 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -324,7 +324,7 @@ Alkalmazásfrissítés értesítése Fájl törölve Frissítések - Tipp megjelenítése a háttér vagy a felugró gomb megnyomásakor a videó „Részletek:\\” lehetőségnél + Tipp megjelenítése a háttér vagy a felugró gomb megnyomásakor a videó „Részletek:” lehetőségnél Automatikus lejátszás Adatok törlése Lejátszási pozíciók megjelenítése a listákban @@ -534,7 +534,7 @@ Értesítések a videók ujjlenyomatkészítési folyamatához Videó ujjlenyomat-készítési értesítése A YouTube biztosít egy „Korlátozott módot”, amely elrejti a lehetséges felnőtteknek szóló tartalmat - A YouTube „Korlátozott mód\\” bekapcsolása + A YouTube „Korlátozott mód” bekapcsolása A példány már létezik A példány érvényesítése nem sikerült Adja meg a példány webcímét @@ -658,7 +658,7 @@ A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken Lejátszó összeomlasztása Képjelölők megjelenítése - A „lejátszó összeomlasztása\\” lehetőség megjelenítése + A „Lejátszó összeomlasztása” lehetőség megjelenítése Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor Hangmagasság megtartása (torzítást okozhat) Frissítések keresése diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 5120a20de..fa7e82be7 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -138,7 +138,7 @@ Melanjutkan akhir dari antrean pemutaran (tak berulang) dengan menambahkan video terkait Simpan daftar video yang telah ditonton Tip \"Tahan untuk menambahkan\" - Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam video \"Detail:\\ + Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam video \"Detail:\" Lokasi Konten Pemutar Perilaku @@ -508,7 +508,7 @@ \nJadi pilihlah yang sesuai yang Anda inginkan: kecepatan atau kelengkapan informasi. Teks asli dari layanan akan ditampilkan di dalam video Tampilkan waktu yang lalu sebenarnya pada item - Aktifkan \"Mode Terbatas\\ + Aktifkan \"Mode Terbatas\" Oleh %s Dibuat oleh %s Thumbnail avatar channel diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 148181861..34939c478 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -607,7 +607,7 @@ Spilunarstöður í listum Sýna spilunarstöður í listum Sýna ábendinguna „Haltu niðri til að bæta við spilunarröð“ - Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn á myndskeiðinu í „Nánar:\\ + Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn á myndskeiðinu í „Nánar:\" Óþekkt slóð. Opna með öðru forriti\? Veldu uppáhalds PeerTube tilvik þín Þú mátt finna tilviki á %s @@ -647,7 +647,7 @@ Slökktu á margmiðlunargöngum (media tunneling) ef vart verður við svartan skjá eða hökt við spilun myndskeiða. Sýna myndvísa Sýna Picasso litaða borða ofan á myndum sem gefa til kynna uppruna þeirra: rauðan fyrir netið, bláan fyrir disk og grænan fyrir minni - Sýna „Láta spilara hrynja\\ + Sýna „Láta spilara hrynja\" Sýna valkost til að hrynja spilara Hrynja forrit Búа til villutilkynningu diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 0da6cdd09..23ab6e013 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -1,7 +1,7 @@ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਵੱਡਦਰਸ਼ੀ ਸ਼ੀਸ਼ੇ \'ਤੇ ਟੈਪ ਕਰੋ। - %1$s ਨੂੰ ਪ੍ਰਕਾਸ਼ਿਤ ਕੀਤੀ ਗਈ + %1$s ਨੂੰ ਪ੍ਰਕਾਸ਼ਿਤ ਹੋਇਆ ਸਟ੍ਰੀਮਿੰਗ ਲਈ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ। ਕੀ ਤੁਸੀਂ VLC ਸਥਾਪਤ ਕਰਨਾ ਚਾਹੋਗੇ? ਸਟ੍ਰੀਮਿੰਗ ਲਈ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ (ਤੁਸੀਂ ਇਸਨੂੰ ਚਲਾਉਣ ਲਈ VLC ਪਲੇਅਰ ਇੰਸਟਾਲ ਕਰ ਸਕਦੇ ਹੋ)। ਇੰਸਟਾਲ ਕਰੋ @@ -15,9 +15,9 @@ ਸੈਟਿੰਗਾਂ ਕੀ ਤੁਹਾਡਾ ਮਤਲਬ ਸੀ \"%1$s\"\? ਦੇ ਨਾਲ ਸਾਂਝਾ ਕਰੋ - ਬਾਹਰੀ ਵੀਡੀਓ ਪਲੇਅਰ ਵਰਤੋ + ਬਾਹਰੀ ਵੀਡੀਓ ਪਲੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਕੁਝ ਰੈਜ਼ੋਲਿਊਸ਼ਨਾਂ \'ਤੇ ਆਵਾਜ਼ ਹਟ ਸਕਦੀ ਹੈ - ਬਾਹਰੀ ਆਡੀਓ ਪਲੇਅਰ ਵਰਤੋ + ਬਾਹਰੀ ਆਡੀਓ ਪਲੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਸਬਸਕ੍ਰਾਈਬ ਕਰੋ ਸਬਸਕ੍ਰਾਈਬ ਹੈ ਚੈਨਲ ਅਨ-ਸਬਸਕ੍ਰਾਈਬ ਹੋਇਆ @@ -27,7 +27,7 @@ ਸਬਸਕ੍ਰਿਪਸ਼ਨਾਂ ਬੁੱਕਮਾਰਕ ਕੀਤੀਆਂ ਪਲੇਲਿਸਟਾਂ ਨਵਾਂ ਕੀ ਹੈ - ਬੈਕਗ੍ਰਾਊਂਡ ਆਡੀਓ + ਬੈਕਗ੍ਰਾਊਂਡ ਪੌਪ-ਅਪ ਵਿੱਚ ਸ਼ਾਮਿਲ ਕਰੋ ਵੀਡੀਓ ਲਈ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ @@ -52,7 +52,7 @@ ਗੂੜ੍ਹਾ ਕਾਲ਼ਾ ਪੌਪ-ਅਪ ਦਾ ਆਕਾਰ ਅਤੇ ਸਥਿਤੀ ਯਾਦ ਰੱਖੋ - ਪੌਪ-ਅਪ ਦਾ ਆਖਰੀ ਅਕਾਰ ਅਤੇ ਸਥਿਤੀ ਯਾਦ ਰੱਖੋ + ਪੌਪ-ਅਪ ਦਾ ਆਖਰੀ ਆਕਾਰ ਅਤੇ ਸਥਿਤੀ ਯਾਦ ਰੱਖੋ ਤੇਜ਼ ਤੇ ਅਣਸਟੀਕ ਭਾਲ ਦੀ ਵਰਤੋਂ ਕਰੋ ਅਣਸਟੀਕ ਭਾਲ ਨਾਲ ਪਲੇਅਰ ਘੱਟ ਸਟੀਕਤਾ ਦੇ ਪਰ ਅਧਿਕ ਤੇਜ਼ੀ ਨਾਲ ਵੀਡੀਓ ਸਥਿੱਤੀਆਂ ਦੀ ਤਲਾਸ਼ ਕਰ ਸਕਦਾ ਹੈ । ਇਸ ਨਾਲ ਅੱਗੇ-ਪਿੱਛੇ 5, 15 ਜਾਂ 25 ਸਕਿੰਟ ਲਿਜਾਣਾ ਕੰਮ ਨਹੀਂ ਕਰਦਾ ਹੈ ਚਿੱਤਰ ਕੈਸ਼ ਮਿਟਾਇਆ ਗਿਆ @@ -101,8 +101,8 @@ ਨਿਊਪਾਈਪ ਨੋਟੀਫਿਕੇਸ਼ਨ ਨਿਊਪਾਈਪ ਦੇ ਪਲੇਅਰ ਦੇ ਲਈ ਨੋਟੀਫਿਕੇਸ਼ਨ [ਅਣਜਾਣ] - ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚਲਾਓ - ਪੌਪ-ਅਪ ਵਿੱਚ ਚਲਾਓ + ਬੈਕਗ੍ਰਾਊਂਡ ਮੋਡ ਵਿੱਚ ਚਲਾਓ + ਪੌਪ-ਅਪ ਮੋਡ ਵਿੱਚ ਚਲਾਓ ਮੇਨ ਤੇ ਚਲਾਓ ਡਾਟਾਬੇਸ ਆਯਾਤ ਕਰੋ ਡਾਟਾਬੇਸ ਨਿਰਯਾਤ ਕਰੋ @@ -316,7 +316,7 @@ ਕੋਈ ਸੀਮਾ ਨਹੀਂ ਮੋਬਾਈਲ ਡਾਟਾ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਨੂੰ ਸੀਮਿਤ ਕਰੋ ਐਪ ਬਦਲਦੇ ਸਮੇਂ ਉਸਨੂੰ ਮਿਨੀਮਾਈਜ਼ ਕਰੋ - ਮੇਨ ਵੀਡੀਓ ਪਲੇਅਰ ਤੋਂ ਦੂਜੇ ਐਪ \'ਤੇ ਜਾਣ ਵੇਲ਼ੇ ਕਾਰਵਾਈ — %s + ਮੇਨ ਵੀਡੀਓ ਪਲੇਅਰ ਤੋਂ ਦੂਜੇ ਐਪ \'ਤੇ ਜਾਣ ਵੇਲੇ ਕਾਰਵਾਈ — %s ਕੋਈ ਨਹੀਂ ਬੈਕਗ੍ਰਾਊਂਡ ਪਲੇਅਰ ਵਿੱਚ ਬਦਲੋ ਪੌਪ-ਅਪ ਪਲੇਅਰ ਵਿੱਚ ਬਦਲੋ @@ -466,7 +466,7 @@ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਦੀ ਕੋਈ ਵੀ ਐਪ ਇਸ ਨੂੰ ਖੋਲ੍ਹ ਨਹੀਂ ਸਕਦੀ ਚੈਪਟਰ ਹਾਲੀਆ - ਥੰਮਨੇਲ ਨੂੰ ਤਾਲਾਬੱਧ ਸਕਰੀਨ ਦੇ ਪਿਛੋਕੜ ਅਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਦੋਵਾਂ ਲਈ ਵਰਤੋ + ਥੰਮਨੇਲ ਨੂੰ ਲਾਕ ਸਕਰੀਨ ਦੇ ਬੈਕਗ੍ਰਾਊਂਡ ਅਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਦੋਵਾਂ ਲਈ ਵਰਤੋ ਥੰਮਨੇਲ ਵਿਖਾਓ ਪਲੇਲਿਸਟ ਪੰਨਾ %s ਦੁਆਰਾ diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index e0b58da6a..cde34a2a6 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -174,7 +174,7 @@ Top 50 Novos e tendências Mostrar dica \"Segure para pôr na fila\" - Mostra dica ao tocar no botão segundo plano ou Popup em \"Detalhes:\\ do vídeo + Mostra dica ao tocar no botão segundo plano ou popup em \"Detalhes:\" do vídeo Reproduzir tudo Não é possível reproduzir este vídeo Ocorreu um erro irrecuperável na reprodução @@ -528,7 +528,7 @@ Remover assistidos Textos originais dos serviços serão visíveis nos itens de transmissão Mostrar tempo original nos itens - Ativar o \"Modo Restrito\\ do YouTube + Ativar o \"Modo Restrito\" do YouTube Por %s Criado por %s Foto de perfil do canal diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 9a6d6d405..340552371 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -25,7 +25,7 @@ Formato padrão de áudio Descarregar Mostrar vídeos \'Seguintes\' e \'Semelhantes\' - URL não suportado + URL não suportada Idioma padrão para conteúdo Vídeo e áudio Reproduzir vídeo, duração: @@ -281,7 +281,7 @@ Limpar histórico de visualizações Continuar (sem repetição) a fila de reprodução anexando um vídeo relacionado Mostrar dica \"Toque longo para colocar na fila\" - Mostrar dica ao premir em segundo plano ou no botão \"Detalhes\" da janela popup:\\ + Mostrar dica ao premir em segundo plano ou no botão \"Detalhes\" da janela popup: Canais Listas de reprodução Faixas @@ -528,7 +528,7 @@ Remover visualizados Os textos originais dos serviços serão visíveis nos itens do vídeo Mostrar antiguidade nos itens - Ativar \"Modo restrito\\ do YouTube + Ativar \"Modo restrito\" do YouTube Por %s Criado por %s Miniatura do avatar do canal @@ -551,7 +551,7 @@ Nunca A carregar A fila de reprodução atual será substituída - URL não reconhecido. Abrir com outra aplicação\? + URL não reconhecida. Abrir com outra aplicação? Colocar na fila automaticamente Baralhar Apenas em Wi-Fi diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 71dcdaea4..f04fef82f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -190,7 +190,7 @@ Вы подписаны Подписка отменена Показать подсказку «Зажмите, чтобы добавить» - Показывать подсказку при нажатии на фон или всплывающую кнопку в видео \"Подробнее:\\ + Показывать подсказку при нажатии \"В фоне\" или на всплывающую кнопку \"Подробнее:\" в видео [Неизвестно] Восстановление после ошибки плеера Зажмите, чтобы добавить в очередь @@ -535,7 +535,7 @@ Удалить просмотренные видео\? Отображать сообщённое сервисом время с момента публикации Исходное время публикации - Включите на YouTube \"Ограниченный режим\\ + Включить \"Ограниченный режим\" YouTube От %s Создано %s Миниатюра значка канала @@ -690,7 +690,7 @@ Уведомлять Вы подписались на канал Переключить все - Шоу \"Разбей игрока\\ + Показывать \"Сбой плеера\" Показать функцию вызова сбоя при работе плеера Вызвать сбой плеера Уведомление отчёта об ошибке @@ -720,7 +720,7 @@ Нет аудиопотоков, доступных внешним плеерам Выберите качество для внешних плееров Неизвестное качество - Размер предварительной загрузки + Размер интервала загрузки при воспроизведении Ответы на частые вопросы Если у вас возникли проблемы с использованием приложения, обязательно ознакомьтесь с ответами на распространённые вопросы! Посмотреть на веб-сайте diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index c85d17220..5fa064124 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -91,7 +91,7 @@ Återuppta uppspelning Fortsätt uppspelning efter avbrott (t.ex. telefonsamtal) Visa \"Håll för att köa\"-tips - Visa tips när bakgrunds- eller popup-knappen trycks på sidan för video \"Detaljer:\\ + Visa tips när bakgrunds eller popup-knappen trycks på sidan för video \"Detaljer:\" Spelare Beteende Historik och cacheminne diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 2d9c282b5..8e3bf19ef 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -157,7 +157,7 @@ Geçmiş Bu ögeyi arama geçmişinden silmek istiyor musunuz\? \"Basılı tutarak kuyruğa ekle\" ipucunu göster - Şurada arka plan ya da açılır pencere düğmesine basıldığında ipucu göster: Video \"Ayrıntılar:\\ + Video \"Ayrıntılar:\" sayfasında arka plan ya da açılır pencere düğmesine basıldığında ipucu göster Tümünü Oynat [Bilinmeyen] Bu akış oynatılamadı @@ -517,7 +517,7 @@ İzleneni kaldır Akış ögelerinde hizmetlerden alınan özgün metinler görünecektir Ögelerde özgün \'… önce\'yi göster - Aç: YouTube \"Kısıtlı Kip\\ + YouTube\'un \"Kısıtlı Kip\"ini aç %s tarafından %s tarafından oluşturuldu Kanalın avatar küçük resmi @@ -658,7 +658,7 @@ Hata raporlama bildirimi NewPipe hatayla karşılaştı, bildirmek için dokun Hata oluştu, bildirime bakın - Göster: \"Oynatıcıyı çöktür\\ + \"Oynatıcıyı çöktür\"ü göster Hata bildirimi oluştur Hata balonu göster Bu eyleme uygun dosya yönetici yok. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 22d9eb478..1ed9625e8 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -170,7 +170,7 @@ Показати інформацію Закладки відтворення Додати до - Показувати підказку під час натискання фонової або спливної кнопки у відео \"Деталі:\\ + Показувати підказку під час натискання фонової або спливної кнопки у відео \"Деталі:\" Сталася невиправна помилка програвача Зовнішні програвачі не підтримують такі види посилань Що:\\nЗапит:\\nМова вмісту:\\nКраїна вмісту:\\nМова застосунку:\\nСлужба:\\nЧас GMT:\\nПакунок:\\nВерсія:\\nВерсія ОС: @@ -529,7 +529,7 @@ \nЩоб побачити його ввімкніть «%1$s» в налаштуваннях. Ескіз аватара каналу Оригінальні тексти сервісів будуть видимі в потокових елементах - Увімкніть \"Обмежений режим\" YouTube + Увімкнути \"Обмежений режим\" YouTube Результати для: %s Створено %s Показати лише незгруповані підписки @@ -672,7 +672,7 @@ Перевірка нових версій вручну Перевірка оновлень… Нові записи стрічки - Показати \"Збій програвача\\ + Показати \"Збій програвача\" Показує параметр збою під час використання програвача Збій програвача Сповіщення про звіт про помилку diff --git a/fastlane/metadata/android/ar/changelogs/1003.txt b/fastlane/metadata/android/ar/changelogs/1003.txt index fef3a6cc3..84395a0b2 100644 --- a/fastlane/metadata/android/ar/changelogs/1003.txt +++ b/fastlane/metadata/android/ar/changelogs/1003.txt @@ -1,4 +1,6 @@ -تم إصلاح مشكلة عدم تشغيل YouTube لأي بث. - -يعالج هذا الإصدار فقط الخطأ الأكثر إلحاحًا الذي يمنع تحميل تفاصيل فيديو YouTube. -نحن ندرك وجود مشاكل أخرى، وسنقوم قريباً بإصدار إصدار منفصل لحلها. +هذا إصدار إصلاح عاجل يعمل على إصلاح أخطاء YouTube: +• [يوتيوب] إصلاح عدم تحميل أي معلومات فيديو ، وإصلاح أخطاء HTTP 403 أثناء تشغيل مقاطع الفيديو واستعادة تشغيل بعض مقاطع الفيديو المقيدة بالفئة العمرية +• إصلاح أحجام التسميات التوضيحية التي لا يتم تغييرها +• إصلاح معلومات التنزيل مرتين عند فتح البث +• [Soundcloud] قم بإزالة التدفقات المحمية بإدارة الحقوق الرقمية غير القابلة للتشغيل +• ترجمات محدثة diff --git a/fastlane/metadata/android/cs/changelogs/1002.txt b/fastlane/metadata/android/cs/changelogs/1002.txt index 7035a1112..e77bb552f 100644 --- a/fastlane/metadata/android/cs/changelogs/1002.txt +++ b/fastlane/metadata/android/cs/changelogs/1002.txt @@ -1 +1,4 @@ -Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube +Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube. + +Tato verze řeší pouze nejpalčivější chybu, která brání načtení detailů videa na YouTube. +Jsme si vědomi, že existují i další problémy, a brzy připravíme samostatné vydání, které je vyřeší. diff --git a/fastlane/metadata/android/cs/changelogs/1003.txt b/fastlane/metadata/android/cs/changelogs/1003.txt index 7035a1112..f9c301d33 100644 --- a/fastlane/metadata/android/cs/changelogs/1003.txt +++ b/fastlane/metadata/android/cs/changelogs/1003.txt @@ -1 +1,6 @@ -Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube +Opravná verze, která opravuje chyby YouTube: +- [YouTube] Oprava nenačítání informací o videu, oprava chyb HTTP 403 při přehrávání videí a obnovení přehrávání některých videí s věkovým omezením. +- Oprava nezměněných velikostí titulků +- Oprava dvojího stahování informací při otevření streamu +- [Soundcloud] Odstranění nepřehratelných streamů chráněných DRM +- Aktualizovány překlady diff --git a/fastlane/metadata/android/el/changelogs/1000.txt b/fastlane/metadata/android/el/changelogs/1000.txt new file mode 100644 index 000000000..858bae60b --- /dev/null +++ b/fastlane/metadata/android/el/changelogs/1000.txt @@ -0,0 +1,13 @@ +Βελτιώσεις +• Κάντε κλικ στην περιγραφή της λίστας αναπαραγωγής για να εμφανίζεται περισσότερο / λιγότερο περιεχόμενο +• [PeerTube] Χειριστείτε αυτόματα συνδέσμους παρουσίας «subscribeto.me». +• Ξεκινήστε την αναπαραγωγή μόνο ενός στοιχείου στην οθόνη ιστορικού + +Διορθώσεις +• Διορθώστε την ορατότητα του κουμπιού RSS +• Διορθώστε τα σφάλματα προεπισκόπησης της γραμμής αναζήτησης +• Διορθώστε την εισχώρηση στη λίστα αναπαραγωγής ενός στοιχείου χωρίς μικρογραφίες +• Διορθώστε την έξοδο από το παράθυρο διαλόγου λήψης προτού εμφανιστεί +• Διορθώστε το αναδυόμενο παράθυρο ουράς λίστας σχετικών στοιχείων +• Διορθώστε τη σειρά στο παράθυρο διαλόγου προσθήκης στη λίστα αναπαραγωγής +• Προσαρμόστε τη διάταξη του σελιδοδείκτη της λίστας αναπαραγωγής diff --git a/fastlane/metadata/android/el/changelogs/999.txt b/fastlane/metadata/android/el/changelogs/999.txt new file mode 100644 index 000000000..c0782663d --- /dev/null +++ b/fastlane/metadata/android/el/changelogs/999.txt @@ -0,0 +1 @@ +Αυτή η έκδοση επείγουσας επιδιόρθωσης διορθώνει σφάλματα HTTP 403 στη μέση των βίντεο του YouTube. Νέος • [SoundCloud] Προσθέστε υποστήριξη για διευθύνσεις URL on.soundcloud.com Βελτιωμένο • [Bandcamp] Εμφάνιση πρόσθετων πληροφοριών στο ραδιοφωνικό κιόσκι διορθώσεις • [YouTube] Διόρθωση περιστασιακών σφαλμάτων HTTP 403 στην αρχή ή στη μέση των βίντεο • [YouTube] Εξαγωγή avatar και banner από περισσότερους τύπους κεφαλίδων καναλιού • [Bandcamp] Διορθώστε διάφορα σφάλματα και χρησιμοποιείτε πάντα HTTPS diff --git a/fastlane/metadata/android/fr/changelogs/1003.txt b/fastlane/metadata/android/fr/changelogs/1003.txt index 6b969c81a..13c76e921 100644 --- a/fastlane/metadata/android/fr/changelogs/1003.txt +++ b/fastlane/metadata/android/fr/changelogs/1003.txt @@ -1,6 +1,6 @@ -Il s'agit d'une version de correction qui résout les erreurs de YouTube : -• [YouTube] Correction du non-chargement des informations des vidéos, correction des erreurs HTTP 403 lors de la lecture des vidéos et restauration de la lecture de certaines vidéos restreintes par l'âge -• Correction des tailles de sous-titres qui ne changent pas -• Correction du téléchargement des informations deux fois lors de l'ouverture d'un flux -• [Soundcloud] Suppression des flux protégés par DRM non lisibles +Il s'agit d'une version de correction qui résout les erreurs de YouTube : +• [YouTube] Correction du non-chargement des informations des vidéos, correction des erreurs HTTP 403 lors de la lecture des vidéos et restauration de la lecture de certaines vidéos restreintes par l'âge +• Correction des tailles de sous-titres qui ne changent pas +• Correction du téléchargement des informations deux fois lors de l'ouverture d'un flux +• [Soundcloud] Suppression des flux protégés par DRM non lisibles • Traductions mises à jour diff --git a/fastlane/metadata/android/hi/changelogs/1001.txt b/fastlane/metadata/android/hi/changelogs/1001.txt new file mode 100644 index 000000000..bc92ea2f1 --- /dev/null +++ b/fastlane/metadata/android/hi/changelogs/1001.txt @@ -0,0 +1,6 @@ +सुधार +• Android 13+ पर हमेशा प्लेयर नोटिफिकेशन प्राथमिकताएँ बदलने की अनुमति दें + +ठीक किया गया +• डेटाबेस/सदस्यता निर्यात करने से पहले से मौजूद फ़ाइल को छोटा नहीं किया जा सकता था, जिससे संभवतः दूषित निर्यात हो सकता था +• टाइमस्टैम्प पर क्लिक करने पर प्लेयर फिर से शुरू होने की समस्या को ठीक किया गया diff --git a/fastlane/metadata/android/hi/changelogs/1002.txt b/fastlane/metadata/android/hi/changelogs/1002.txt index 071ab64e3..d780f47a6 100644 --- a/fastlane/metadata/android/hi/changelogs/1002.txt +++ b/fastlane/metadata/android/hi/changelogs/1002.txt @@ -1 +1,5 @@ -फिक्स्ड YouTube कोई स्ट्रीम नहीं चला रहा है +YouTube द्वारा कोई भी स्ट्रीम न चलाए जाने की समस्या को ठीक किया गया। + +यह रिलीज़ केवल सबसे ज़्यादा दबाव वाली त्रुटि को संबोधित करती है जो YouTube वीडियो विवरण को लोड होने से रोकती है। + +हम जानते हैं कि अन्य समस्याएँ भी हैं, और हम जल्द ही उन्हें हल करने के लिए एक अलग रिलीज़ जारी करेंगे। diff --git a/fastlane/metadata/android/hi/changelogs/1003.txt b/fastlane/metadata/android/hi/changelogs/1003.txt index 071ab64e3..210a2566b 100644 --- a/fastlane/metadata/android/hi/changelogs/1003.txt +++ b/fastlane/metadata/android/hi/changelogs/1003.txt @@ -1 +1,6 @@ -फिक्स्ड YouTube कोई स्ट्रीम नहीं चला रहा है +यह एक हॉटफ़िक्स रिलीज़ है जो YouTube त्रुटियों को ठीक करता है: +• [YouTube] कोई भी वीडियो जानकारी लोड न होने की समस्या को ठीक करें, वीडियो चलाते समय HTTP 403 त्रुटियाँ ठीक करें और कुछ आयु-प्रतिबंधित वीडियो के प्लेबैक को पुनर्स्थापित करें +• कैप्शन का आकार न बदलने की समस्या को ठीक करें +• स्ट्रीम खोलते समय जानकारी दो बार डाउनलोड होने की समस्या को ठीक करें +• [साउंडक्लाउड] न चलाए जा सकने वाले DRM-संरक्षित स्ट्रीम हटाएँ +• अपडेट किए गए अनुवाद diff --git a/fastlane/metadata/android/hi/changelogs/65.txt b/fastlane/metadata/android/hi/changelogs/65.txt index 8570a056a..d2c2b8c71 100644 --- a/fastlane/metadata/android/hi/changelogs/65.txt +++ b/fastlane/metadata/android/hi/changelogs/65.txt @@ -1,26 +1,26 @@ -### Improvements +### सुधार -- Disable burgermenu icon animation #1486 -- undo delete of downloads #1472 -- Download option in share menu #1498 -- Added share option to long tap menu #1454 -- Minimize main player on exit #1354 -- Library version update and database backup fix #1510 -- ExoPlayer 2.8.2 Update #1392 - - Reworked the playback speed control dialog to support different step sizes for faster speed change. - - Added a toggle to fast-forward during silences in playback speed control. This should be helpful for audiobooks and certain music genres, and can bring a true seamless experience (and can break a song with lots of silences =\\). - - Refactored media source resolution to allow passing metadata alongside media internally in the player, rather than doing so manually. Now we have a single source of metadata and is directly available when playback starts. - - Fixed remote playlist metadata not updating when new metadata is available when playlist fragment is opened. - - Various UI fixes: #1383, background player notification controls now always white, easier to shutdown popup player through flinging -- Use new extractor with refactored architecture for multiservice +- बर्गरमेनू आइकन एनीमेशन को अक्षम करें #1486 +- डाउनलोड को पूर्ववत करें #1472 +- शेयर मेनू में डाउनलोड विकल्प #1498 +- लॉन्ग टैप मेनू में शेयर विकल्प जोड़ा गया #1454 +- बाहर निकलने पर मुख्य प्लेयर को छोटा करें #1354 +- लाइब्रेरी संस्करण अपडेट और डेटाबेस बैकअप फिक्स #1510 +- एक्सोप्लेयर 2.8.2 अपडेट #1392 +- तेज गति परिवर्तन के लिए विभिन्न चरण आकारों का समर्थन करने के लिए प्लेबैक गति नियंत्रण संवाद को फिर से तैयार किया गया। +- प्लेबैक गति नियंत्रण में मौन के दौरान तेजी से आगे बढ़ने के लिए एक टॉगल जोड़ा गया। यह ऑडियोबुक और कुछ संगीत शैलियों के लिए मददगार होना चाहिए, और एक सच्चा सहज अनुभव ला सकता है (और बहुत सारे मौन वाले गीत को तोड़ सकता है =\\)। +- मीडिया स्रोत रिज़ॉल्यूशन को फिर से तैयार किया गया ताकि प्लेयर में आंतरिक रूप से मीडिया के साथ मेटाडेटा को पास किया जा सके, बजाय मैन्युअल रूप से ऐसा करने के। अब हमारे पास मेटाडेटा का एक ही स्रोत है और प्लेबैक शुरू होने पर सीधे उपलब्ध है। +- जब प्लेलिस्ट का टुकड़ा खोला जाता है तो नया मेटाडेटा उपलब्ध होने पर रिमोट प्लेलिस्ट मेटाडेटा अपडेट नहीं होता है। +- विभिन्न UI फ़िक्सेस: #1383, बैकग्राउंड प्लेयर नोटिफिकेशन कंट्रोल अब हमेशा सफ़ेद रहता है, फ़्लिंगिंग के ज़रिए पॉपअप प्लेयर को बंद करना आसान है +- मल्टीसर्विस के लिए रीफ़ैक्टर्ड आर्किटेक्चर के साथ नए एक्सट्रैक्टर का उपयोग करें -### Fixes +### फ़िक्सेस -- Fix #1440 Broken Video Info Layout #1491 -- View history fix #1497 - - #1495, by updating the metadata (thumbnail, title and video count) as soon as the user access the playlist. - - #1475, by registering a view in the database when the user starts a video on external player on detail fragment. -- Fix creen timeout in case of popup mode. #1463 (Fixed #640) -- Main video player fix #1509 - - [#1412] Fixed repeat mode causing player NPE when new intent is received while player activity is in background. - - Fixed minimizing player to popup does not destroy player when popup permission is not granted. +- फ़िक्स #1440 टूटी हुई वीडियो जानकारी लेआउट #1491 +- व्यू हिस्ट्री फ़िक्स #1497 +- #1495, जैसे ही उपयोगकर्ता प्लेलिस्ट एक्सेस करता है मेटाडेटा (थंबनेल, शीर्षक और वीडियो काउंट) को अपडेट करके। +- #1475, जब उपयोगकर्ता डिटेल फ़्रैगमेंट पर बाहरी प्लेयर पर वीडियो शुरू करता है तो डेटाबेस में व्यू रजिस्टर करके। +- पॉपअप मोड के मामले में स्क्रीन टाइमआउट को ठीक करें। #1463 (फ़िक्स #640) +- मुख्य वीडियो प्लेयर फ़िक्स #1509 +- [#1412] प्लेयर गतिविधि के बैकग्राउंड में होने पर नया इंटेंट प्राप्त होने पर प्लेयर NPE का कारण बनने वाले रिपीट मोड को ठीक किया गया। +- पॉपअप के लिए प्लेयर को छोटा करने की सुविधा को ठीक किया गया, जब पॉपअप की अनुमति नहीं दी जाती है तो प्लेयर नष्ट नहीं होता है। diff --git a/fastlane/metadata/android/it/changelogs/1002.txt b/fastlane/metadata/android/it/changelogs/1002.txt index 951ffee5e..7028421cb 100644 --- a/fastlane/metadata/android/it/changelogs/1002.txt +++ b/fastlane/metadata/android/it/changelogs/1002.txt @@ -1 +1,4 @@ -Corretto problema di riproduzione di YouTube +Risolto il problema per cui YouTube non riproduceva alcun flusso + +Questa versione risolve solo l'errore più urgente che impedisce il caricamento dei dettagli dei video di YouTube. +Siamo consapevoli che ci sono altri problemi e presto pubblicheremo una versione separata per risolverli. diff --git a/fastlane/metadata/android/it/changelogs/1003.txt b/fastlane/metadata/android/it/changelogs/1003.txt index 951ffee5e..c8fa124b9 100644 --- a/fastlane/metadata/android/it/changelogs/1003.txt +++ b/fastlane/metadata/android/it/changelogs/1003.txt @@ -1 +1,6 @@ -Corretto problema di riproduzione di YouTube +Questa versione corregge gli errori di YouTube: +• [YouTube] Corretto il problema del mancato caricamento delle informazioni video, degli errori HTTP 403 durante la riproduzione dei video e ripristinata la riproduzione di alcuni video con limiti di età +• Corrette le dimensioni delle didascalie che non vengono modificate +• Corretto il download delle informazioni due volte durante l'apertura di un flusso +• [Soundcloud] Rimossi i flussi protetti da DRM non riproducibili +• Traduzioni aggiornate diff --git a/fastlane/metadata/android/pa/changelogs/1001.txt b/fastlane/metadata/android/pa/changelogs/1001.txt new file mode 100644 index 000000000..83f91c82b --- /dev/null +++ b/fastlane/metadata/android/pa/changelogs/1001.txt @@ -0,0 +1,6 @@ +ਸੁਧਾਰਿਆ ਗਿਆ +• ਐਂਡਰਾਇਡ 13+ 'ਤੇ ਪਲੇਅਰ ਸੂਚਨਾ ਤਰਜੀਹਾਂ ਨੂੰ ਹਮੇਸ਼ਾ ਬਦਲਣ ਦੀ ਆਗਿਆ ਦਿਓ + +ਠੀਕ ਕੀਤਾ ਗਿਆ +• ਡੇਟਾਬੇਸ/ਸਬਸਕ੍ਰਿਪਸ਼ਨ ਨਿਰਯਾਤ ਕਰਨ ਨਾਲ ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਫਾਈਲ ਨੂੰ ਕੱਟਿਆ ਨਹੀਂ ਜਾਵੇਗਾ, ਜਿਸ ਨਾਲ ਸੰਭਾਵਤ ਤੌਰ 'ਤੇ ਖਰਾਬ ਨਿਰਯਾਤ ਹੋ ਸਕਦਾ ਹੈ +• ਟਾਈਮਸਟੈਂਪ 'ਤੇ ਕਲਿੱਕ ਕਰਨ 'ਤੇ ਪਲੇਅਰ ਨੂੰ ਸ਼ੁਰੂ ਤੋਂ ਮੁੜ ਸ਼ੁਰੂ ਕਰਨ ਨੂੰ ਠੀਕ ਕਰੋ diff --git a/fastlane/metadata/android/pa/changelogs/1002.txt b/fastlane/metadata/android/pa/changelogs/1002.txt index fe62a1330..3cfce7b21 100644 --- a/fastlane/metadata/android/pa/changelogs/1002.txt +++ b/fastlane/metadata/android/pa/changelogs/1002.txt @@ -1 +1,4 @@ -ਸਥਿਰ YouTube ਕੋਈ ਸਟ੍ਰੀਮ ਨਹੀਂ ਚਲਾ ਰਿਹਾ +YouTube ਵੱਲੋਂ ਕੋਈ ਵੀ ਸਟ੍ਰੀਮ ਨਾ ਚਲਾਉਣ ਨੂੰ ਠੀਕ ਕੀਤਾ ਗਿਆ ਹੈ। + +ਇਹ ਰੀਲੀਜ਼ ਸਿਰਫ਼ ਉਸ ਸਭ ਤੋਂ ਵੱਡੀ ਗਲਤੀ ਨੂੰ ਹੱਲ ਕਰਦੀ ਹੈ ਜੋ YouTube ਵੀਡੀਓ ਵੇਰਵਿਆਂ ਨੂੰ ਲੋਡ ਹੋਣ ਤੋਂ ਰੋਕਦੀ ਹੈ। +ਅਸੀਂ ਜਾਣਦੇ ਹਾਂ ਕਿ ਹੋਰ ਸਮੱਸਿਆਵਾਂ ਵੀ ਹਨ, ਅਤੇ ਅਸੀਂ ਜਲਦੀ ਹੀ ਉਨ੍ਹਾਂ ਨੂੰ ਹੱਲ ਕਰਨ ਲਈ ਇੱਕ ਵੱਖਰੀ ਰੀਲੀਜ਼ ਕਰਾਂਗੇ। diff --git a/fastlane/metadata/android/pa/changelogs/1003.txt b/fastlane/metadata/android/pa/changelogs/1003.txt index fe62a1330..0c8a5a1a0 100644 --- a/fastlane/metadata/android/pa/changelogs/1003.txt +++ b/fastlane/metadata/android/pa/changelogs/1003.txt @@ -1 +1,6 @@ -ਸਥਿਰ YouTube ਕੋਈ ਸਟ੍ਰੀਮ ਨਹੀਂ ਚਲਾ ਰਿਹਾ +ਇਹ ਇੱਕ ਹੌਟਫਿਕਸ ਰੀਲੀਜ਼ ਹੈ ਜੋ YouTube ਗਲਤੀਆਂ ਨੂੰ ਠੀਕ ਕਰਦੀ ਹੈ: +• [YouTube] ਵੀਡੀਓ ਚਲਾਉਣ ਦੌਰਾਨ ਕਿਸੇ ਵੀ ਵੀਡੀਓ ਜਾਣਕਾਰੀ ਨੂੰ ਲੋਡ ਨਾ ਹੋਣ ਨੂੰ ਠੀਕ ਕਰੋ, ਵੀਡੀਓ ਚਲਾਉਂਦੇ ਸਮੇਂ HTTP 403 ਗਲਤੀਆਂ ਨੂੰ ਠੀਕ ਕਰੋ ਅਤੇ ਕੁਝ ਉਮਰ-ਪ੍ਰਤੀਬੰਧਿਤ ਵੀਡੀਓਜ਼ ਦੇ ਪਲੇਬੈਕ ਨੂੰ ਬਹਾਲ ਕਰੋ +• ਕੈਪਸ਼ਨ ਆਕਾਰਾਂ ਨੂੰ ਨਾ ਬਦਲਣ ਨੂੰ ਠੀਕ ਕਰੋ +• ਸਟ੍ਰੀਮ ਖੋਲ੍ਹਣ ਵੇਲੇ ਦੋ ਵਾਰ ਡਾਊਨਲੋਡਿੰਗ ਜਾਣਕਾਰੀ ਨੂੰ ਠੀਕ ਕਰੋ +• [Soundcloud] ਨਾ ਚਲਾਏ ਜਾ ਸਕਣ ਵਾਲੇ DRM-ਸੁਰੱਖਿਅਤ ਸਟ੍ਰੀਮਾਂ ਨੂੰ ਹਟਾਓ +• ਅੱਪਡੇਟ ਕੀਤੇ ਅਨੁਵਾਦ diff --git a/fastlane/metadata/android/zh-Hans/changelogs/1000.txt b/fastlane/metadata/android/zh-Hans/changelogs/1000.txt new file mode 100644 index 000000000..69ceb255e --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/1000.txt @@ -0,0 +1,13 @@ +改进 +• 使播放列表简介可点击以显示更多或更少的内容 +• [PeerTube] 自动接管 `subscribeto.me` 链接 +• 在历史记录页面中仅开始播放单一项目 + +修复 +• 修复 RSS 按钮的可见度 +• 修复进度预览可能引起的崩溃 +• 修复播放列表中缺少缩略图的项目 +• 修复在下载弹窗出现之前退出的问题 +• 修复相关项目列表排序弹出 +• 修复新增至播放列表的菜单项顺序 +• 调整播放列表书签项目的布局 From 54bf7f0ced98641e101e539569a9c65f2262e497 Mon Sep 17 00:00:00 2001 From: Harshita Date: Sat, 8 Mar 2025 22:05:28 +0530 Subject: [PATCH 47/50] BF-11894 : Fix the Duplicate menu options in ChannelFragment --- .../newpipe/fragments/list/channel/ChannelFragment.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 d67cd11f1..adaa3bebd 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 @@ -232,6 +232,14 @@ public class ChannelFragment extends BaseStateFragment binding.subChannelTitleView.setOnClickListener(openSubChannel); } + @Override + public void onDestroyView() { + super.onDestroyView(); + if (menuProvider != null) { + activity.removeMenuProvider(menuProvider); + } + } + @Override public void onDestroy() { super.onDestroy(); @@ -240,7 +248,6 @@ public class ChannelFragment extends BaseStateFragment } disposables.clear(); binding = null; - activity.removeMenuProvider(menuProvider); menuProvider = null; } From 48b200868a455722690881f739bcd4cb96e52c74 Mon Sep 17 00:00:00 2001 From: Harshita Date: Thu, 13 Mar 2025 00:10:59 +0530 Subject: [PATCH 48/50] BF-11894 : Fix the menu disappearing on performing backGesture --- .../list/channel/ChannelFragment.java | 117 +++++++++--------- 1 file changed, 61 insertions(+), 56 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 adaa3bebd..33532d567 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 @@ -123,62 +123,6 @@ public class ChannelFragment extends BaseStateFragment @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - menuProvider = new MenuProvider() { - @Override - public void onCreateMenu(@NonNull final Menu menu, - @NonNull final MenuInflater inflater) { - inflater.inflate(R.menu.menu_channel, menu); - - if (DEBUG) { - Log.d(TAG, "onCreateOptionsMenu() called with: " - + "menu = [" + menu + "], inflater = [" + inflater + "]"); - } - - } - - @Override - public void onPrepareMenu(@NonNull final Menu menu) { - menuRssButton = menu.findItem(R.id.menu_item_rss); - menuNotifyButton = menu.findItem(R.id.menu_item_notify); - updateRssButton(); - updateNotifyButton(channelSubscription); - } - - @Override - public boolean onMenuItemSelected(@NonNull final MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_item_notify: - final boolean value = !item.isChecked(); - item.setEnabled(false); - setNotify(value); - break; - case R.id.action_settings: - NavigationHelper.openSettings(requireContext()); - break; - case R.id.menu_item_rss: - if (currentInfo != null) { - ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl()); - } - break; - case R.id.menu_item_openInBrowser: - if (currentInfo != null) { - ShareUtils.openUrlInBrowser(requireContext(), - currentInfo.getOriginalUrl()); - } - break; - case R.id.menu_item_share: - if (currentInfo != null) { - ShareUtils.shareText(requireContext(), name, - currentInfo.getOriginalUrl(), currentInfo.getAvatars()); - } - break; - default: - return false; - } - return true; - } - }; - activity.addMenuProvider(menuProvider); } @Override @@ -195,6 +139,67 @@ public class ChannelFragment extends BaseStateFragment return binding.getRoot(); } + @Override + public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { + super.onViewCreated(rootView, savedInstanceState); + menuProvider = new MenuProvider() { + @Override + public void onCreateMenu(@NonNull final Menu menu, + @NonNull final MenuInflater inflater) { + inflater.inflate(R.menu.menu_channel, menu); + + if (DEBUG) { + Log.d(TAG, "onCreateOptionsMenu() called with: " + + "menu = [" + menu + "], inflater = [" + inflater + "]"); + } + + } + + @Override + public void onPrepareMenu(@NonNull final Menu menu) { + menuRssButton = menu.findItem(R.id.menu_item_rss); + menuNotifyButton = menu.findItem(R.id.menu_item_notify); + updateRssButton(); + updateNotifyButton(channelSubscription); + } + + @Override + public boolean onMenuItemSelected(@NonNull final MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_item_notify: + final boolean value = !item.isChecked(); + item.setEnabled(false); + setNotify(value); + break; + case R.id.action_settings: + NavigationHelper.openSettings(requireContext()); + break; + case R.id.menu_item_rss: + if (currentInfo != null) { + ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl()); + } + break; + case R.id.menu_item_openInBrowser: + if (currentInfo != null) { + ShareUtils.openUrlInBrowser(requireContext(), + currentInfo.getOriginalUrl()); + } + break; + case R.id.menu_item_share: + if (currentInfo != null) { + ShareUtils.shareText(requireContext(), name, + currentInfo.getOriginalUrl(), currentInfo.getAvatars()); + } + break; + default: + return false; + } + return true; + } + }; + activity.addMenuProvider(menuProvider); + } + @Override // called from onViewCreated in BaseFragment.onViewCreated protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); From f289bea6b3adbb6bfa1f5b3defe4bb471a92b59a Mon Sep 17 00:00:00 2001 From: Siddhesh Naik Date: Sun, 16 Mar 2025 12:44:01 +0530 Subject: [PATCH 49/50] Fix sonar warning --- .../newpipe/fragments/list/channel/ChannelFragment.java | 5 ----- 1 file changed, 5 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 33532d567..764f03e4b 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 @@ -120,11 +120,6 @@ public class ChannelFragment extends BaseStateFragment // LifeCycle //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - @Override public void onAttach(@NonNull final Context context) { super.onAttach(context); From a0b76c3385c0629f37745d1791a23f5337bf2dab Mon Sep 17 00:00:00 2001 From: TobiGr Date: Sun, 16 Mar 2025 21:39:55 +0100 Subject: [PATCH 50/50] Update NewPipe Extractor and add new proguard rules New rules are required since Rhino and Rhino Engine 1.8.0 --- app/build.gradle | 2 +- app/proguard-rules.pro | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d03bd64e3..f9b2e9bd9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -208,7 +208,7 @@ dependencies { implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' // WORKAROUND: if you get errors with the NewPipeExtractor dependency, replace `v0.24.3` with // the corresponding commit hash, since JitPack is sometimes buggy - implementation 'com.github.TeamNewPipe:NewPipeExtractor:9f83b385a' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:0b99100db' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 215df0da5..0cdffbe2e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -5,10 +5,17 @@ ## Rules for NewPipeExtractor -keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; } +## Rules for Rhino and Rhino Engine +-keep class org.mozilla.javascript.* { *; } -keep class org.mozilla.javascript.** { *; } +-keep class org.mozilla.javascript.engine.** { *; } -keep class org.mozilla.classfile.ClassFileWriter -dontwarn org.mozilla.javascript.JavaToJSONConverters -dontwarn org.mozilla.javascript.tools.** +-keep class javax.script.** { *; } +-dontwarn javax.script.** +-keep class jdk.dynalink.** { *; } +-dontwarn jdk.dynalink.** ## Rules for ExoPlayer -keep class com.google.android.exoplayer2.** { *; }